[
  {
    "path": ".ci/Dockerfile.cypress",
    "content": "FROM cypress/browsers:node-24.14.0-chrome-145.0.7632.116-1-ff-148.0-edge-145.0.3800.70-1\n\nENV APP /usr/src/app\nWORKDIR $APP\n\nCOPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc $APP/\nCOPY viz-lib $APP/viz-lib\nRUN npm install -g pnpm@10.30.3 && pnpm install --frozen-lockfile > /dev/null\n\nCOPY . $APP\n\nRUN ./node_modules/.bin/cypress verify\n"
  },
  {
    "path": ".ci/compose.ci.yaml",
    "content": "services:\n  redash:\n    build: ../\n    command: manage version\n    depends_on:\n      - postgres\n      - redis\n    ports:\n      - \"5000:5000\"\n    environment:\n      PYTHONUNBUFFERED: 0\n      REDASH_LOG_LEVEL: \"INFO\"\n      REDASH_REDIS_URL: \"redis://redis:6379/0\"\n      POSTGRES_PASSWORD: \"FmTKs5vX52ufKR1rd8tn4MoSP7zvCJwb\"\n      REDASH_DATABASE_URL: \"postgresql://postgres:FmTKs5vX52ufKR1rd8tn4MoSP7zvCJwb@postgres/postgres\"\n      REDASH_COOKIE_SECRET: \"2H9gNG9obnAQ9qnR9BDTQUph6CbXKCzF\"\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n  postgres:\n    image: postgres:18-alpine\n    command: \"postgres -c fsync=off -c full_page_writes=off -c synchronous_commit=OFF\"\n    restart: unless-stopped\n    environment:\n      POSTGRES_HOST_AUTH_METHOD: \"trust\"\n"
  },
  {
    "path": ".ci/compose.cypress.yaml",
    "content": "x-redash-service: &redash-service\n  build:\n    context: ../\n    args:\n      install_groups: \"main\"\n      code_coverage: ${CODE_COVERAGE}\nx-redash-environment: &redash-environment\n  REDASH_LOG_LEVEL: \"INFO\"\n  REDASH_REDIS_URL: \"redis://redis:6379/0\"\n  POSTGRES_PASSWORD: \"FmTKs5vX52ufKR1rd8tn4MoSP7zvCJwb\"\n  REDASH_DATABASE_URL: \"postgresql://postgres:FmTKs5vX52ufKR1rd8tn4MoSP7zvCJwb@postgres/postgres\"\n  REDASH_RATELIMIT_ENABLED: \"false\"\n  REDASH_ENFORCE_CSRF: \"true\"\n  REDASH_COOKIE_SECRET: \"2H9gNG9obnAQ9qnR9BDTQUph6CbXKCzF\"\nservices:\n  server:\n    <<: *redash-service\n    command: server\n    depends_on:\n      - postgres\n      - redis\n    ports:\n      - \"5000:5000\"\n    environment:\n      <<: *redash-environment\n      PYTHONUNBUFFERED: 0\n  scheduler:\n    <<: *redash-service\n    command: scheduler\n    depends_on:\n      - server\n    environment:\n      <<: *redash-environment\n  worker:\n    <<: *redash-service\n    command: worker\n    depends_on:\n      - server\n    environment:\n      <<: *redash-environment\n      PYTHONUNBUFFERED: 0\n  cypress:\n    ipc: host\n    build:\n      context: ../\n      dockerfile: .ci/Dockerfile.cypress\n    depends_on:\n      - server\n      - worker\n      - scheduler\n    environment:\n      CYPRESS_baseUrl: \"http://server:5000\"\n      CYPRESS_coverage: ${CODE_COVERAGE}\n      PERCY_TOKEN: ${PERCY_TOKEN}\n      PERCY_BRANCH: ${CIRCLE_BRANCH}\n      PERCY_COMMIT: ${CIRCLE_SHA1}\n      PERCY_PULL_REQUEST: ${CIRCLE_PR_NUMBER}\n      COMMIT_INFO_BRANCH: ${CIRCLE_BRANCH}\n      COMMIT_INFO_MESSAGE: ${COMMIT_INFO_MESSAGE}\n      COMMIT_INFO_AUTHOR: ${CIRCLE_USERNAME}\n      COMMIT_INFO_SHA: ${CIRCLE_SHA1}\n      COMMIT_INFO_REMOTE: ${CIRCLE_REPOSITORY_URL}\n      CYPRESS_PROJECT_ID: ${CYPRESS_PROJECT_ID}\n      CYPRESS_RECORD_KEY: ${CYPRESS_RECORD_KEY}\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n  postgres:\n    image: postgres:18-alpine\n    command: \"postgres -c fsync=off -c full_page_writes=off -c synchronous_commit=OFF\"\n    restart: unless-stopped\n    environment:\n      POSTGRES_HOST_AUTH_METHOD: \"trust\"\n"
  },
  {
    "path": ".ci/docker_build",
    "content": "#!/bin/bash\n\n# This script only needs to run on the main Redash repo\n\nif [ \"${GITHUB_REPOSITORY}\" != \"getredash/redash\" ]; then\n\techo \"Skipping image build for Docker Hub, as this isn't the main Redash repository\"\n\texit 0\nfi\n\nif [ \"${GITHUB_REF_NAME}\" != \"master\" ] && [ \"${GITHUB_REF_NAME}\" != \"preview-image\" ]; then\n\techo \"Skipping image build for Docker Hub, as this isn't the 'master' nor 'preview-image' branch\"\n\texit 0\nfi\n\nif [ \"x${DOCKER_USER}\" = \"x\" ] || [ \"x${DOCKER_PASS}\" = \"x\" ]; then\n\techo \"Skipping image build for Docker Hub, as the login details aren't available\"\n\texit 0\nfi\n\nset -e\nVERSION=$(jq -r .version package.json)\nVERSION_TAG=\"$VERSION.b${GITHUB_RUN_ID}.${GITHUB_RUN_NUMBER}\"\n\nexport DOCKER_BUILDKIT=1\nexport COMPOSE_DOCKER_CLI_BUILD=1\n\ndocker login -u \"${DOCKER_USER}\" -p \"${DOCKER_PASS}\"\n\nDOCKERHUB_REPO=\"redash/redash\"\nDOCKER_TAGS=\"-t redash/redash:preview -t redash/preview:${VERSION_TAG}\"\n\n# Build the docker container\ndocker build --build-arg install_groups=\"main,all_ds,dev\" ${DOCKER_TAGS} .\n\n# Push the container to the preview build locations\ndocker push \"${DOCKERHUB_REPO}:preview\"\ndocker push \"redash/preview:${VERSION_TAG}\"\n\necho \"Built: ${VERSION_TAG}\"\n"
  },
  {
    "path": ".ci/pack",
    "content": "#!/bin/bash\nNAME=redash\nVERSION=$(jq -r .version package.json)\nFULL_VERSION=$VERSION+b$CIRCLE_BUILD_NUM\nFILENAME=$NAME.$FULL_VERSION.tar.gz\n\nmkdir -p /tmp/artifacts/\n\ntar -zcv -f /tmp/artifacts/$FILENAME --exclude=\".git\" --exclude=\"optipng*\" --exclude=\"cypress\" --exclude=\"*.pyc\" --exclude=\"*.pyo\" --exclude=\"venv\" *\n"
  },
  {
    "path": ".ci/update_version",
    "content": "#!/bin/bash\nVERSION=$(jq -r .version package.json)\nFULL_VERSION=${VERSION}+b${GITHUB_RUN_ID}.${GITHUB_RUN_NUMBER}\n\nsed -ri \"s/^__version__ = '([A-Za-z0-9.-]*)'/__version__ = '${FULL_VERSION}'/\" redash/__init__.py\nsed -i \"s/dev/${GITHUB_SHA}/\" client/app/version.json\n"
  },
  {
    "path": ".coveragerc",
    "content": "[run]\nbranch = True\nsource = redash\n\n[report]\nomit =\n    */settings.py\n    */python?.?/*\nshow_missing = True\n"
  },
  {
    "path": ".dockerignore",
    "content": "client/.tmp/\nnode_modules/\nviz-lib/node_modules/\n.tmp/\n.venv/\nvenv/\n.git/\n/.codeclimate.yml\n/.coverage\n/coverage.xml\n/.circleci/\n/.github/\n/netlify.toml\n/setup/\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.py]\nindent_style = space\nindent_size = 4\n\n[*.{js,jsx,css,less,html}]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/---bug_report.md",
    "content": "---\nname: \"\\U0001F41B Bug report\"\nabout: Report reproducible software issues so we can improve\n---\n\n<!--\n\nWe use GitHub only for bug reports 🐛\n\nAnything else should be a discussion: https://github.com/getredash/redash/discussions/ 👫\n\n🚨For support, help & questions use https://github.com/getredash/redash/discussions/categories/q-a\n💡For feature requests & ideas use https://github.com/getredash/redash/discussions/categories/ideas\n\n**Found a security vulnerability?** Please email security@redash.io to report any security vulnerabilities. We will acknowledge receipt of your vulnerability and strive to send you regular updates about our progress. If you're curious about the status of your disclosure please feel free to email us again. If you want to encrypt your disclosure email, you can use this PGP key.\n\n-->\n\n### Issue Summary\n\nA summary of the issue and the browser/OS environment in which it occurs.\n\n### Steps to Reproduce\n\n1. This is the first step\n2. This is the second step, etc.\n\nAny other info e.g. Why do you consider this to be a bug? What did you expect to happen instead?\n\n### Technical details:\n\n* Redash Version:\n* Browser/OS:\n* How did you install Redash:\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/--anything_else.md",
    "content": "---\nname: \"\\U0001F4A1Anything else\"\nabout: \"For help, support, features & ideas - please use Discussions \\U0001F46B \"\nlabels: \"Support Question\"\n---\n\nWe use GitHub only for bug reports 🐛\n\nAnything else should be a discussion: https://github.com/getredash/redash/discussions/ 👫\n\n🚨For support, help & questions use https://github.com/getredash/redash/discussions/categories/q-a\n💡For feature requests & ideas use https://github.com/getredash/redash/discussions/categories/ideas\n\nAlternatively, check out these resources below. Thanks! 😁.\n\n- [Discussions](https://github.com/getredash/redash/discussions/)\n- [Knowledge Base](https://redash.io/help)\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "## What type of PR is this? \n<!-- Check all that apply, delete what doesn't apply. -->\n\n- [ ] Refactor\n- [ ] Feature\n- [ ] Bug Fix\n- [ ] New Query Runner (Data Source) \n- [ ] New Alert Destination\n- [ ] Other\n\n## Description\n<!-- In case of adding / modifying a query runner, please specify which version(s) you expect are compatible. -->\n\n## How is this tested?\n\n- [ ] Unit tests (pytest, jest)\n- [ ] E2E Tests (Cypress)\n- [ ] Manually\n- [ ] N/A\n\n<!-- If Manually, please describe. -->\n\n## Related Tickets & Documents\n<!-- If applicable, please include a link to your documentation PR against getredash/website -->\n\n## Mobile & Desktop Screenshots/Recordings (if there are UI changes)\n"
  },
  {
    "path": ".github/config.yml",
    "content": "# https://github.com/behaviorbot/request-info?installation_id=189571\nrequestInfoLabelToAdd: needs-more-info\nrequestInfoReplyComment: >\n  We would appreciate it if you could provide us with more info about this issue/pr!\n\n"
  },
  {
    "path": ".github/weekly-digest.yml",
    "content": "# Configuration for weekly-digest - https://github.com/apps/weekly-digest\npublishDay: mon\ncanPublishIssues: true\ncanPublishPullRequests: true\ncanPublishContributors: true\ncanPublishStargazers: true\ncanPublishCommits: true\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Tests\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\nenv:\n  NODE_VERSION: 24\n  PNPM_VERSION: 10.30.3\njobs:\n  backend-lint:\n    runs-on: ubuntu-22.04\n    steps:\n      - if: github.event.pull_request.mergeable == 'false'\n        name: Exit if PR is not mergeable\n        run: exit 1\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 1\n          ref: ${{ github.event.pull_request.head.sha }}\n      - uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - run: python -m pip install black==23.1.0 ruff==0.0.287\n      - run: ruff check .\n      - run: black --check .\n\n  backend-unit-tests:\n    runs-on: ubuntu-22.04\n    needs: backend-lint\n    env:\n      COMPOSE_FILE: .ci/compose.ci.yaml\n      COMPOSE_PROJECT_NAME: redash\n      COMPOSE_DOCKER_CLI_BUILD: 1\n      DOCKER_BUILDKIT: 1\n    steps:\n      - if: github.event.pull_request.mergeable == 'false'\n        name: Exit if PR is not mergeable\n        run: exit 1\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 1\n          ref: ${{ github.event.pull_request.head.sha }}\n      - name: Build Docker Images\n        run: |\n          set -x\n          docker compose build --build-arg install_groups=\"main,all_ds,dev\" --build-arg skip_frontend_build=true\n          docker compose up -d\n          sleep 10\n      - name: Create Test Database\n        run: docker compose -p redash run --rm postgres psql -h postgres -U postgres -c \"create database tests;\"\n      - name: List Enabled Query Runners\n        run: docker compose -p redash run --rm redash manage ds list_types\n      - name: Run Tests\n        run: docker compose -p redash run --name tests redash tests --junitxml=junit.xml --cov-report=xml --cov=redash --cov-config=.coveragerc tests/\n      - name: Copy Test Results\n        run: |\n          mkdir -p /tmp/test-results/unit-tests\n          docker cp tests:/app/coverage.xml ./coverage.xml\n          docker cp tests:/app/junit.xml /tmp/test-results/unit-tests/results.xml\n      # - name: Upload coverage reports to Codecov\n      #   uses: codecov/codecov-action@v3\n      #   with:\n      #     token: ${{ secrets.CODECOV_TOKEN }}\n      - name: Store Test Results\n        uses: actions/upload-artifact@v4\n        with:\n          name: backend-test-results\n          path: /tmp/test-results\n      - name: Store Coverage Results\n        uses: actions/upload-artifact@v4\n        with:\n          name: coverage\n          path: coverage.xml\n\n  frontend-lint:\n    runs-on: ubuntu-22.04\n    steps:\n      - if: github.event.pull_request.mergeable == 'false'\n        name: Exit if PR is not mergeable\n        run: exit 1\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 1\n          ref: ${{ github.event.pull_request.head.sha }}\n      - uses: pnpm/action-setup@v4\n        with:\n          version: ${{ env.PNPM_VERSION }}\n      - uses: actions/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'pnpm'\n      - name: Install Dependencies\n        run: pnpm install --frozen-lockfile\n      - name: Run Lint\n        run: pnpm run lint:ci\n      - name: Store Test Results\n        uses: actions/upload-artifact@v4\n        with:\n          name: frontend-test-results\n          path: /tmp/test-results\n\n  frontend-unit-tests:\n    runs-on: ubuntu-22.04\n    needs: frontend-lint\n    steps:\n      - if: github.event.pull_request.mergeable == 'false'\n        name: Exit if PR is not mergeable\n        run: exit 1\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 1\n          ref: ${{ github.event.pull_request.head.sha }}\n      - uses: pnpm/action-setup@v4\n        with:\n          version: ${{ env.PNPM_VERSION }}\n      - uses: actions/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'pnpm'\n      - name: Install Dependencies\n        run: pnpm install --frozen-lockfile\n      - name: Run App Tests\n        run: pnpm run test\n      - name: Run Visualizations Tests\n        run: pnpm --filter @redash/viz test\n      - run: pnpm run lint\n\n  frontend-e2e-tests:\n    runs-on: ubuntu-22.04\n    needs: frontend-lint\n    env:\n      COMPOSE_FILE: .ci/compose.cypress.yaml\n      COMPOSE_PROJECT_NAME: cypress\n      CYPRESS_INSTALL_BINARY: 0\n      PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1\n      # PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}\n      # CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }}\n      # CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}\n    steps:\n      - if: github.event.pull_request.mergeable == 'false'\n        name: Exit if PR is not mergeable\n        run: exit 1\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 1\n          ref: ${{ github.event.pull_request.head.sha }}\n      - uses: pnpm/action-setup@v4\n        with:\n          version: ${{ env.PNPM_VERSION }}\n      - uses: actions/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'pnpm'\n      - name: Enable Code Coverage Report For Master Branch\n        if: endsWith(github.ref, '/master')\n        run: |\n          echo \"CODE_COVERAGE=true\" >> \"$GITHUB_ENV\"\n      - name: Install Dependencies\n        run: pnpm install --frozen-lockfile\n      - name: Setup Redash Server\n        run: |\n          set -x\n          pnpm run cypress build\n          pnpm run cypress start -- --skip-db-seed\n          docker compose run cypress pnpm run cypress db-seed\n      - name: Execute Cypress Tests\n        run: pnpm run cypress run-ci\n      - name: \"Failure: output container logs to console\"\n        if: failure()\n        run: docker compose logs\n      - name: Copy Code Coverage Results\n        run: docker cp cypress:/usr/src/app/coverage ./coverage || true\n      - name: Store Coverage Results\n        uses: actions/upload-artifact@v4\n        with:\n          name: coverage\n          path: coverage\n"
  },
  {
    "path": ".github/workflows/periodic-snapshot.yml",
    "content": "name: Periodic Snapshot\n\non:\n  schedule:\n    - cron: '10 0 1 * *'  # 10 minutes after midnight on the first day of every month\n  workflow_dispatch:\n    inputs:\n      bump:\n        description: 'Bump the last digit of the version'\n        required: false\n        type: boolean\n      version:\n        description: 'Specific version to set'\n        required: false\n        default: ''\n\nenv:\n  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\npermissions:\n  actions: write\n  contents: write\n\njobs:\n  bump-version-and-tag:\n    runs-on: ubuntu-latest\n    if: github.ref_name == github.event.repository.default_branch\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          ssh-key: ${{ secrets.ACTION_PUSH_KEY }}\n\n      - run: |\n          git config user.name 'github-actions[bot]'\n          git config user.email '41898282+github-actions[bot]@users.noreply.github.com'\n\n          # Function to bump the version\n          bump_version() {\n            local version=\"$1\"\n            local IFS=.\n            read -r major minor patch <<< \"$version\"\n            patch=$((patch + 1))\n            echo \"$major.$minor.$patch-dev\"\n          }\n\n          # Determine the new version tag\n          if [ \"${{ github.event_name }}\" = \"workflow_dispatch\" ]; then\n            BUMP_INPUT=\"${{ github.event.inputs.bump }}\"\n            SPECIFIC_VERSION=\"${{ github.event.inputs.version }}\"\n\n            # Check if both bump and specific version are provided\n            if [ \"$BUMP_INPUT\" = \"true\" ] && [ -n \"$SPECIFIC_VERSION\" ]; then\n              echo \"::error::Error: Cannot specify both bump and specific version.\"\n              exit 1\n            fi\n\n            if [ -n \"$SPECIFIC_VERSION\" ]; then\n              TAG_NAME=\"$SPECIFIC_VERSION-dev\"\n            elif [ \"$BUMP_INPUT\" = \"true\" ]; then\n              CURRENT_VERSION=$(grep '\"version\":' package.json | awk -F\\\" '{print $4}')\n              TAG_NAME=$(bump_version \"$CURRENT_VERSION\")\n            else\n              echo \"No version bump or specific version provided for manual dispatch.\"\n              exit 1\n            fi\n          else\n            TAG_NAME=\"$(date +%y.%m).0-dev\"\n          fi\n\n          echo \"New version tag: $TAG_NAME\"\n\n          # Update version in files\n          gawk -i inplace -F: -v q=\\\" -v tag=${TAG_NAME} '/^  \"version\": / { print $1 FS, q tag q \",\"; next} { print }' package.json\n          gawk -i inplace -F= -v q=\\\" -v tag=${TAG_NAME} '/^__version__ =/ { print $1 FS, q tag q; next} { print }' redash/__init__.py\n          gawk -i inplace -F= -v q=\\\" -v tag=${TAG_NAME} '/^version =/ { print $1 FS, q tag q; next} { print }' pyproject.toml\n\n          git add package.json redash/__init__.py pyproject.toml\n          git commit -m \"Snapshot: ${TAG_NAME}\"\n          git tag ${TAG_NAME}\n          git push --atomic origin master refs/tags/${TAG_NAME}\n\n          # Run the 'preview-image' workflow if run this workflow manually\n          # For more information, please see the: https://docs.github.com/en/actions/security-guides/automatic-token-authentication\n          if [ \"$BUMP_INPUT\" = \"true\" ] || [ -n \"$SPECIFIC_VERSION\" ]; then\n            gh workflow run preview-image.yml --ref $TAG_NAME\n          fi\n"
  },
  {
    "path": ".github/workflows/preview-image.yml",
    "content": "name: Preview Image\non:\n  push:\n    tags:\n      - '*-dev'\n  workflow_dispatch:\n    inputs:\n      dockerRepository:\n        description: 'Docker repository'\n        required: true\n        default: 'preview'\n        type: choice\n        options:\n          - preview\n          - redash\n\nenv:\n  NODE_VERSION: 24\n\njobs:\n  build-skip-check:\n    runs-on: ubuntu-22.04\n    outputs:\n      skip: ${{ steps.skip-check.outputs.skip }}\n    steps:\n      - name: Skip?\n        id: skip-check\n        run: |\n          if [[ \"${{ vars.DOCKER_USER }}\" == '' ]]; then\n            echo 'Docker user is empty. Skipping build+push'\n            echo skip=true >> \"$GITHUB_OUTPUT\"\n          elif [[ \"${{ secrets.DOCKER_PASS }}\" == '' ]]; then\n            echo 'Docker password is empty. Skipping build+push'\n            echo skip=true >> \"$GITHUB_OUTPUT\"\n          elif [[ \"${{ vars.DOCKER_REPOSITORY }}\" == '' ]]; then\n            echo 'Docker repository is empty. Skipping build+push'\n            echo skip=true >> \"$GITHUB_OUTPUT\"\n          else\n            echo 'Docker user and password are set and branch is `master`.'\n            echo 'Building + pushing `preview` image.'\n            echo skip=false >> \"$GITHUB_OUTPUT\"\n          fi\n\n  build-docker-image:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        arch:\n          - amd64\n          - arm64\n        include:\n          - arch: amd64\n            os: ubuntu-22.04\n          - arch: arm64\n            os: ubuntu-22.04-arm\n    outputs:\n      VERSION_TAG: ${{ steps.version.outputs.VERSION_TAG }}\n    needs:\n      - build-skip-check\n    if: needs.build-skip-check.outputs.skip == 'false'\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 1\n          ref: ${{ github.event.push.after }}\n\n      - uses: pnpm/action-setup@v4\n        with:\n          version: 10.30.3\n      - uses: actions/setup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'pnpm'\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Login to DockerHub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ vars.DOCKER_USER }}\n          password: ${{ secrets.DOCKER_PASS }}\n\n      - name: Install Dependencies\n        env:\n          PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true\n        run: pnpm install --frozen-lockfile\n\n      - name: Set version\n        id: version\n        run: |\n          set -x\n          .ci/update_version\n          VERSION_TAG=$(jq -r .version package.json)\n          echo \"VERSION_TAG=$VERSION_TAG\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Build and push preview image to Docker Hub\n        id: build-preview\n        uses: docker/build-push-action@v4\n        if: ${{ github.event.inputs.dockerRepository == 'preview' || !github.event.workflow_run }}\n        with:\n          tags: |\n            ${{ vars.DOCKER_REPOSITORY }}/redash\n            ${{ vars.DOCKER_REPOSITORY }}/preview\n          context: .\n          build-args: |\n            test_all_deps=true\n          outputs: type=image,push-by-digest=true,push=true\n          cache-from: type=gha,scope=${{ matrix.arch }}\n          cache-to: type=gha,mode=max,scope=${{ matrix.arch }}\n        env:\n          DOCKER_CONTENT_TRUST: true\n\n      - name: Build and push release image to Docker Hub\n        id: build-release\n        uses: docker/build-push-action@v4\n        if: ${{ github.event.inputs.dockerRepository == 'redash' }}\n        with:\n          tags: |\n            ${{ vars.DOCKER_REPOSITORY }}/redash:${{ steps.version.outputs.VERSION_TAG }}\n          context: .\n          build-args: |\n            test_all_deps=true\n          outputs: type=image,push-by-digest=false,push=true\n          cache-from: type=gha,scope=${{ matrix.arch }}\n          cache-to: type=gha,mode=max,scope=${{ matrix.arch }}\n        env:\n          DOCKER_CONTENT_TRUST: true\n\n      - name: \"Failure: output container logs to console\"\n        if: failure()\n        run: docker compose logs\n\n      - name: Export digest\n        run: |\n          mkdir -p ${{ runner.temp }}/digests\n          if [[ \"${{ github.event.inputs.dockerRepository }}\" == 'preview' || !github.event.workflow_run ]]; then\n            digest=\"${{ steps.build-preview.outputs.digest}}\"\n          else\n            digest=\"${{ steps.build-release.outputs.digest}}\"\n          fi\n          touch \"${{ runner.temp }}/digests/${digest#sha256:}\"\n\n      - name: Upload digest\n        uses: actions/upload-artifact@v4\n        with:\n          name: digests-${{ matrix.arch }}\n          path: ${{ runner.temp }}/digests/*\n          if-no-files-found: error\n\n  merge-docker-image:\n    runs-on: ubuntu-22.04\n    needs: build-docker-image\n    steps:\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Login to DockerHub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ vars.DOCKER_USER }}\n          password: ${{ secrets.DOCKER_PASS }}\n\n      - name: Download digests\n        uses: actions/download-artifact@v4\n        with:\n          path: ${{ runner.temp }}/digests\n          pattern: digests-*\n          merge-multiple: true\n\n      - name: Create and push manifest for the preview image\n        if: ${{ github.event.inputs.dockerRepository == 'preview' || !github.event.workflow_run }}\n        working-directory: ${{ runner.temp }}/digests\n        run: |\n          docker buildx imagetools create -t ${{ vars.DOCKER_REPOSITORY }}/redash:preview \\\n            $(printf '${{ vars.DOCKER_REPOSITORY }}/redash:preview@sha256:%s ' *)\n          docker buildx imagetools create -t ${{ vars.DOCKER_REPOSITORY }}/preview:${{ needs.build-docker-image.outputs.VERSION_TAG }} \\\n            $(printf '${{ vars.DOCKER_REPOSITORY }}/preview:${{ needs.build-docker-image.outputs.VERSION_TAG }}@sha256:%s ' *)\n\n      - name: Create and push manifest for the release image\n        if: ${{ github.event.inputs.dockerRepository == 'redash' }}\n        working-directory: ${{ runner.temp }}/digests\n        run: |\n          docker buildx imagetools create -t ${{ vars.DOCKER_REPOSITORY }}/redash:${{ needs.build-docker-image.outputs.VERSION_TAG }} \\\n            $(printf '${{ vars.DOCKER_REPOSITORY }}/redash:${{ needs.build-docker-image.outputs.VERSION_TAG }}@sha256:%s ' *)\n"
  },
  {
    "path": ".github/workflows/restyled.yml",
    "content": "name: Restyled\n\non:\n  pull_request:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  restyled:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n\n      - uses: restyled-io/actions/setup@v4\n      - id: restyler\n        uses: restyled-io/actions/run@v4\n        with:\n          fail-on-differences: true\n\n      - if: |\n          !cancelled() &&\n          steps.restyler.outputs.success == 'true' &&\n          github.event.pull_request.head.repo.full_name == github.repository\n        uses: peter-evans/create-pull-request@v6\n        with:\n          base: ${{ steps.restyler.outputs.restyled-base }}\n          branch: ${{ steps.restyler.outputs.restyled-head }}\n          title: ${{ steps.restyler.outputs.restyled-title }}\n          body: ${{ steps.restyler.outputs.restyled-body }}\n          labels: \"restyled\"\n          reviewers: ${{ github.event.pull_request.user.login }}\n          delete-branch: true\n"
  },
  {
    "path": ".gitignore",
    "content": ".venv\nvenv/\n.cache\n.coverage.*\n.coveralls.yml\n.idea\n*.pyc\n.nyc_output\ncoverage\n.coverage\ncoverage.xml\nclient/dist\n.DS_Store\n.#*\n\\#*#\n*~\n_build\n.vscode\n.env\n.tool-versions\n\ndump.rdb\n\nnode_modules\n.tmp\n.sass-cache\nnpm-debug.log\n\nclient/cypress/screenshots\nclient/cypress/videos\n"
  },
  {
    "path": ".npmrc",
    "content": "engine-strict=true\nauto-install-peers=true\nshamefully-hoist=true\n"
  },
  {
    "path": ".nvmrc",
    "content": "v24\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/psf/black\n    rev: 23.1.0\n    hooks:\n      - id: black\n        language_version: python3\n  - repo: https://github.com/charliermarsh/ruff-pre-commit\n    rev: \"v0.0.287\"\n    hooks:\n      - id: ruff\n"
  },
  {
    "path": ".restyled.yaml",
    "content": "enabled: true\n\nauto: false\n\n# Open Restyle PRs?\npull_requests: true\n\n# Leave comments on the original PR linking to the Restyle PR?\ncomments: true\n\n# Set commit statuses on the original PR?\nstatuses:\n  # Red status in the case of differences found\n  differences: true\n  # Green status in the case of no differences found\n  no_differences: true\n  # Red status if we encounter errors restyling\n  error: true\n\n# Request review on the Restyle PR?\n#\n# Possible values:\n#\n#   author: From the author of the original PR\n#   owner: From the owner of the repository\n#   none: Don't\n#\n# One value will apply to both origin and forked PRs, but you can also specify\n# separate values.\n#\n#   request_review:\n#     origin: author\n#     forked: owner\n#\nrequest_review: author\n\n# Add labels to any created Restyle PRs\n#\n# These can be used to tell other automation to avoid our PRs.\n#\nlabels:\n  - restyled\n  - \"Skip CI\"\n\n# Labels to ignore\n#\n# PRs with any of these labels will be ignored by Restyled.\n#\n# ignore_labels:\n#   - restyled-ignore\n\n# Restylers to run, and how\nrestylers:\n  - name: black\n    image: restyled/restyler-black:v24.4.2\n    include:\n      - redash\n      - tests\n      - migrations/versions\n  - name: prettier\n    image: restyled/restyler-prettier:v3.3.2-2\n    command:\n      - prettier\n      - --write\n    include:\n      - client/app/**/*.js\n      - client/app/**/*.jsx\n      - client/cypress/**/*.js\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\n## 26.03.0\n\n* Fix regular expression warning ([#7650](https://github.com/getredash/redash/pull/7650))\n* Update Python version to 3.13 ([#7636](https://github.com/getredash/redash/pull/7636))\n* Add last 2 years, last 3 years to the Date Range list ([#7635](https://github.com/getredash/redash/pull/7635))\n* Update plotly.js to 3.3.1, react-pivottable to 0.11.0 ([#7634](https://github.com/getredash/redash/pull/7634))\n* Add pivot chart ([#7632](https://github.com/getredash/redash/pull/7632))\n* Aggregate y value for same x ([#7631](https://github.com/getredash/redash/pull/7631))\n* cli: add --json option to users list command ([#7624](https://github.com/getredash/redash/pull/7624))\n* Update packages for compatibility with setuptools 82 ([#7622](https://github.com/getredash/redash/pull/7622))\n* Fix Elasticsearch connector configuration key mismatch ([#7607](https://github.com/getredash/redash/pull/7607))\n* Support ipv6 for server in ipv6-only kubernetes cluster ([#7596](https://github.com/getredash/redash/pull/7596))\n* fix(destinations): Handle unicode characters in webhook notifications ([#7586](https://github.com/getredash/redash/pull/7586))\n* Add lineShape option for Line and Area charts ([#7582](https://github.com/getredash/redash/pull/7582))\n* Feature/catch notsupported exception ([#7573](https://github.com/getredash/redash/pull/7573))\n* Enhance dashboard parameter handling: persist updated values and apply saved parameters ([#7570](https://github.com/getredash/redash/pull/7570))\n* Update queries.latest_query_data on save ([#7560](https://github.com/getredash/redash/pull/7560))\n* Correct custom chart help text: use newPlot() ([#7557](https://github.com/getredash/redash/pull/7557))\n* SchemaBrowser: on column comment tooltip, show newlines correctly ([#7552](https://github.com/getredash/redash/pull/7552))\n* Query Serach: avoid concurrent search API request ([#7551](https://github.com/getredash/redash/pull/7551))\n* Advanced query search syntax for multi byte search ([#7546](https://github.com/getredash/redash/pull/7546))\n* Make details visualization configurable ([#7535](https://github.com/getredash/redash/pull/7535))\n* Update ace-builds/react-ace to the latest versions ([#7532](https://github.com/getredash/redash/pull/7532))\n* Fix/too many history replace state ([#7530](https://github.com/getredash/redash/pull/7530))\n* Add range slider to the chart ([#7525](https://github.com/getredash/redash/pull/7525))\n* Add \"Missing and NULL values\" option to scatter chart ([#7523](https://github.com/getredash/redash/pull/7523))\n* fix: webpack missing source-map warning for @plotly/msgbox-gl ([#7522](https://github.com/getredash/redash/pull/7522))\n* keep ordering on search ([#7520](https://github.com/getredash/redash/pull/7520))\n* Fix: null is not shown for text with \"Allow HTML content\" ([#7519](https://github.com/getredash/redash/pull/7519))\n* Fix stacking bar chart ([#7516](https://github.com/getredash/redash/pull/7516))\n* Update plotly.js to 3.1.0 ([#7514](https://github.com/getredash/redash/pull/7514))\n* Update Poetry to 2.1.4 ([#7509](https://github.com/getredash/redash/pull/7509))\n* Update from webpack4 to webpack5 ([#7507](https://github.com/getredash/redash/pull/7507))\n* Allow HTTP request line more than 4096 bytes ([#7506](https://github.com/getredash/redash/pull/7506))\n* Add \"Last 10 years\" option for dynamic date range ([#7422](https://github.com/getredash/redash/pull/7422))\n* Make favorite queries/dashboard order by starred at(favorited at) ([#7351](https://github.com/getredash/redash/pull/7351))\n* Fix height for mobile safari not to overlap URL bar ([#7334](https://github.com/getredash/redash/pull/7334))\n* Multi-org: format base path, not including protocol ([#7260](https://github.com/getredash/redash/pull/7260))\n* PostgreSQL\n  * PostgreSQL: Rely on information_schema.columns for views and foreign tables ([#7521](https://github.com/getredash/redash/pull/7521))\n  * PostgreSQL: allow connection parameters to be specified ([#7579](https://github.com/getredash/redash/pull/7579))\n  * changed pg.py has_privileges function to include special characters a… ([#7574](https://github.com/getredash/redash/pull/7574))\n  * Use standard PostgreSQL image and drop clean-all target ([#7555](https://github.com/getredash/redash/pull/7555))\n* MySQL\n  * MySQL: add column type, comment, and table comment on Schema Browser ([#7544](https://github.com/getredash/redash/pull/7544))\n  * Add charset option to RDS MySQL datasource ([#7616](https://github.com/getredash/redash/pull/7616))\n  * fix(mysql): Change default charset to utf8mb4 ([#7615](https://github.com/getredash/redash/pull/7615))\n* BigQuery\n  * BigQuery: show table description tooltip in Schema Browser ([#7543](https://github.com/getredash/redash/pull/7543))\n  * BigQuery: Remove \"Job ID\" metadata on annotaton to avoid cache misses ([#7541](https://github.com/getredash/redash/pull/7541))\n  * BigQuery: support multiple dataset locations ([#7540](https://github.com/getredash/redash/pull/7540))\n  * BigQuery: show column description(comment) on Schema Browser ([#7538](https://github.com/getredash/redash/pull/7538))\n* DuckDB\n  * Add duckdb support ([#7548](https://github.com/getredash/redash/pull/7548))\n  * duckdb: Show catalog (database) where applicable (e.g. Motherduck) ([#7599](https://github.com/getredash/redash/pull/7599))\n* Trino\n  * Serialize Trino ROW types as JSON objects with field names ([#7644](https://github.com/getredash/redash/pull/7644))\n  * added passing client_tags option to Trino plugin ([#7633](https://github.com/getredash/redash/pull/7633))\n  * Add impersonation option in trino datasource ([#7605](https://github.com/getredash/redash/pull/7605))\n* DB2\n  * Add ibm-db package to enable DB2 as datasource: ([#7581](https://github.com/getredash/redash/pull/7581))\n* Jira\n  * Update jql.py (jira datasource) to use jira api v3 updated. ([#7527](https://github.com/getredash/redash/pull/7527))\n* Snowflake\n  * Add private_key auth method to snowflake query runner ([#7371](https://github.com/getredash/redash/pull/7371))\n\n\n## 25.08.0\n\n* MongoDB: fix for empty username/password (Issue #7486) ([#7487](https://github.com/getredash/redash/pull/7487))\n* clickhouse: display data types ([#7490](https://github.com/getredash/redash/pull/7490))\n* Clickhouse: do not display INFORMATION_SCHEMA tables ([#7489](https://github.com/getredash/redash/pull/7489))\n* Sort Dashboard and Query tags by name([#7484](https://github.com/getredash/redash/pull/7484))\n* Add migration to set default alert selector([#7475](https://github.com/getredash/redash/pull/7475))\n* Make refresh cookie name configurable([#7473](https://github.com/getredash/redash/pull/7473))\n* Make NULL values visible([#7439](https://github.com/getredash/redash/pull/7439))\n* Fix: saving empty query with auto limit crashes([#7430](https://github.com/getredash/redash/pull/7430))\n* Push image using DOCKER_REPOSITORY([#7428](https://github.com/getredash/redash/pull/7428))\n* Update assertion method in JSON dumps test([#7424](https://github.com/getredash/redash/pull/7424))\n* Add translate=\"no\" to html tag to prevent redash from translating and crashing([#7421](https://github.com/getredash/redash/pull/7421))\n* Update Azure Data Explorer query runner to latest version([#7411](https://github.com/getredash/redash/pull/7411))\n* Use 12-column layout for dashboard grid([#7396](https://github.com/getredash/redash/pull/7396))\n* Change BigQuery super class from BaseQueryRunner to BaseSQLQueryRunner([#7378](https://github.com/getredash/redash/pull/7378))\n* Upgrade prettier version to the same version that CI is using([#7367](https://github.com/getredash/redash/pull/7367))\n* Fix table item list ordering([#7366](https://github.com/getredash/redash/pull/7366))\n* Fix to make \"show data labels\" works([#7363](https://github.com/getredash/redash/pull/7363))\n* Upgrade plotly.js to version 2 to fix the UI crashing issue([#7359](https://github.com/getredash/redash/pull/7359))\n* TiDB: Exclude INFORMATION_SCHEMA([#7352](https://github.com/getredash/redash/pull/7352))\n* Sanitize NaN, Infinite, -Infinite causing error when saving as PostgreSQL JSON([#7348](https://github.com/getredash/redash/pull/7348))\n* Fix UnboundLocalError when checking alerts for query([#7346](https://github.com/getredash/redash/pull/7346))\n* BigQuery: Avoid too long(10 seconds) interval for bigquery api to get results([#7342](https://github.com/getredash/redash/pull/7342))\n* Add NULLS LAST option for query list ordering([#7341](https://github.com/getredash/redash/pull/7341))\n* TypeScript sourcemap for viz-lib([#7336](https://github.com/getredash/redash/pull/7336))\n* Fix the issue that chart(scatter, bubble, line...) having data with same x-value have wrong y-value([#7330](https://github.com/getredash/redash/pull/7330))\n* Partiallly Revert \"Remove workaround from check_csrf() (#6919)\" ([#7327](https://github.com/getredash/redash/pull/7327))\n* Make autocomplete always available([#7326](https://github.com/getredash/redash/pull/7326))\n* Include Plotly.js localization([#7323](https://github.com/getredash/redash/pull/7323))\n* Use absolute path for image resources([#7322](https://github.com/getredash/redash/pull/7322))\n* Build/Test\n  * Require vars.DOCKER_REPOSITORY to publish image([#7400](https://github.com/getredash/redash/pull/7400))\n  * ci: snapshot only on default branch([#7355](https://github.com/getredash/redash/pull/7355))\n  * Push by tag name for Docker repository \"redash\"([#7321](https://github.com/getredash/redash/pull/7321))\n* Dependencies\n  * Bump tar-fs from 2.1.1 to 2.1.2([#7385](https://github.com/getredash/redash/pull/7385))\n\n## 25.01.0\n\n* Support result reuse in Athena data sources ([\\#7202](https://github.com/getredash/redash/pull/7202))\n* Handle the case when query runner configuration is an empty dict ([\\#7258](https://github.com/getredash/redash/pull/7258))\n* BigQuery: add date, datetime type mapping ([\\#7252](https://github.com/getredash/redash/pull/7252))\n* Build/Test\n  * Create workflow trigger for publishing release image ([\\#7259](https://github.com/getredash/redash/pull/7259))\n* Dependencies\n  * Bump actions/upload-artifact from v3 to v4 ([\\#7266](https://github.com/getredash/redash/pull/7266))\n  * Bump jinja2 from 3.1.4 to 3.1.5 ([\\#7262](https://github.com/getredash/redash/pull/7262))\n  * Bump nanoid from 3.3.6 to 3.3.8 ([\\#7249](https://github.com/getredash/redash/pull/7249))\n  * Bump to paramiko-3.4.1 ([\\#7240](https://github.com/getredash/redash/pull/7240))\n\n## 24.12.0\n\n* Replace ptvsd with debugpy to match modern VS Code ([\\#7234](https://github.com/getredash/redash/pull/7234))\n* Alerts: Only evaluate the next state if there's a value ([\\#7222](https://github.com/getredash/redash/pull/7222))\n* Bring back version check & beacon reporting ([\\#7211](https://github.com/getredash/redash/pull/7211))\n\n## 24.11.0\n\n* Alerts: don't crash when there is no data ([\\#7208](https://github.com/getredash/redash/pull/7208))\n* Fix issue with scheduled queries ([\\#7111](https://github.com/getredash/redash/pull/7111))\n* Correctly rehash queries in a migration ([\\#7184](https://github.com/getredash/redash/pull/7184))\n* Use correct redis connection ([\\#7077](https://github.com/getredash/redash/pull/7077))\n* Fix RQ wrongly moving jobs to FailedJobRegistry ([\\#7186](https://github.com/getredash/redash/pull/7186))\n* Build/Test\n  * Move restyled to a github action ([\\#7191](https://github.com/getredash/redash/pull/7191))\n  * Docker build: use heredoc for multi-line actions ([\\#7210](https://github.com/getredash/redash/pull/7210))\n* Dependencies\n  * Bump cryptography from 42.0.8 to 43.0.1 ([\\#7205](https://github.com/getredash/redash/pull/7205))\n  * Bump http-proxy-middleware from 2.0.6 to 2.0.7 ([\\#7204](https://github.com/getredash/redash/pull/7204))\n  * Bump snowflake-connector-python from 3.12.0 to 3.12.3 ([\\#7203](https://github.com/getredash/redash/pull/7203))\n  * Bump restrictedpython from 6.2 to 7.3 ([\\#7181](https://github.com/getredash/redash/pull/7181))\n  * Bump elliptic from 6.5.7 to 6.6.0 ([\\#7214](https://github.com/getredash/redash/pull/7214))\n\n## 24.10.0\n\n* Get rid of the strange looking 0 following \"Running...\" and \"runtime\" ([\\#7099](https://github.com/getredash/redash/pull/7099))\n* Automatically remove orphans when running make up ([\\#7164](https://github.com/getredash/redash/pull/7164))\n* Update `make up` to automatically initialize the db ([\\#7161](https://github.com/getredash/redash/pull/7161))\n* Better error msg for token validation ([\\#7159](https://github.com/getredash/redash/pull/7159))\n* Add REDASH_HOST to the docker compose file ([\\#7157](https://github.com/getredash/redash/pull/7157))\n* Make schema refresh timeout configurable via env var ([\\#7114](https://github.com/getredash/redash/pull/7114))\n* Dependencies \n  * Update pymssql to fix some problems with macOS ARM64 (`2.3.1`) ([\\#7140](https://github.com/getredash/redash/pull/7140))\n  * Bump body-parser from 1.20.1 to 1.20.3 ([\\#7156](https://github.com/getredash/redash/pull/7156))\n  * Bump express from 4.19.2 to 4.21.0 ([\\#7155](https://github.com/getredash/redash/pull/7155))\n  * Bump path-to-regexp from 3.2.0 to 3.3.0 ([\\#7154](https://github.com/getredash/redash/pull/7154))\n  * Bump dompurify from 2.0.17 to 2.5.4 in /viz-lib ([\\#7163](https://github.com/getredash/redash/pull/7163))\n\n## 24.09.0\n\n* Add option to choose color scheme for charts ([\\#7062](https://github.com/getredash/redash/pull/7062))\n* Add data type to Athena query runner ([\\#7112](https://github.com/getredash/redash/pull/7112))\n* Add data type to Redshift query runner ([\\#7109](https://github.com/getredash/redash/pull/7109))\n* Fix a display order bug in MongoDB Query Runner ([\\#7106](https://github.com/getredash/redash/pull/7106))\n* Add the option to take new custom version for Snapshot ([\\#7096](https://github.com/getredash/redash/pull/7096))\n* Update certificates for RDS ([\\#7100](https://github.com/getredash/redash/pull/7100))\n* Fix columns duplication on MongoDB Query Runner [\\#6640](https://github.com/getredash/redash/pull/6640) ([\\#6641](https://github.com/getredash/redash/pull/6641))\n* Get data size in memory for better logs ([\\#7090](https://github.com/getredash/redash/pull/7090))\n* Adding Evaluate button for alerts to test them ([\\#7032](https://github.com/getredash/redash/pull/7032))\n* Add min/max/first selector for alerts ([\\#7076](https://github.com/getredash/redash/pull/7076))([\\#7103](https://github.com/getredash/redash/pull/7103))\n* Add support for 'linux/arm64' platforms ([\\#7094](https://github.com/getredash/redash/pull/7094))\n* Regressions\n  * Revert \"Removed unused configuration class ([\\#6682](https://github.com/getredash/redash/pull/6682))\" ([\\#7071](https://github.com/getredash/redash/pull/7071))\n  * Revert \"Adding ability to fix table columns in place ([\\#7019](https://github.com/getredash/redash/pull/7019))\" ([\\#7131](https://github.com/getredash/redash/pull/7131))\n* Dependencies\n  * Fix mismatched poetry version ([\\#7122](https://github.com/getredash/redash/pull/7122))\n  * Bump cryptography to 42.0.x & snowflake-connector-python to 3.12.0 ([\\#7097](https://github.com/getredash/redash/pull/7097))\n  * Bump webpack from 5.88.2 to 5.94.0 in /viz-lib ([\\#7135](https://github.com/getredash/redash/pull/7135))\n  * Bump bootstrap to 3.4.1\n  * Bump sentry-sdk from 1.28.1 to 2.8.0 ([\\#7069](https://github.com/getredash/redash/pull/7069))\n  * Bump python-rapidjson to 1.20 ([\\#7126](https://github.com/getredash/redash/pull/7126))\n  * Bump elliptic to version 6.5.7 to fix a Dependabot warning ([\\#7120](https://github.com/getredash/redash/pull/7120))\n\n## 24.08.0\n\n* Remove defaults set during schema upgrade/downgrade [\\#7068](https://github.com/getredash/redash/pull/7068)  \n* Athena: Support Arbitrary Catalog ID [\\#7059](https://github.com/getredash/redash/pull/7059)  \n* Add option to toggle sort on pie charts [\\#7055](https://github.com/getredash/redash/pull/7055)  \n* Conditionally render tooltip for Edit alert button [\\#7054](https://github.com/getredash/redash/pull/7054)  \n* Make Redash FIPS compatible [\\#7049](https://github.com/getredash/redash/pull/7049)  \n* Add a label for Restyler's PR and Bump component version [\\#7037](https://github.com/getredash/redash/pull/7037)  \n* Add new text pattern parameter input type [\\#7025](https://github.com/getredash/redash/pull/7025)  \n* Adding ability to fix table columns in place [\\#7019](https://github.com/getredash/redash/pull/7019) (_reverted in [#7131](https://github.com/getredash/redash/pull/7131)_)\n* Fixed frontend test deprecation warnings [\\#7013](https://github.com/getredash/redash/pull/7013)  \n* Dependencies  \n  * Bump setuptools from 69.0.3 to 70.0.0 [\\#7060](https://github.com/getredash/redash/pull/7060)  \n  * Bump requests to 2.32.3 [\\#7057](https://github.com/getredash/redash/pull/7057)  \n  * Bump zipp from 3.17.0 to 3.19.1 [\\#7051](https://github.com/getredash/redash/pull/7051)\n  * Bump certifi from 2023.11.17 to 2024.7.4 [\\#7047](https://github.com/getredash/redash/pull/7047)\n  * Bump ws from 5.2.3 to 5.2.4 in /viz-lib [\\#7040](https://github.com/getredash/redash/pull/7040)\n\n## 24.07.0\n\n* Map() implementation fix for chart labels [\\#7022](https://github.com/getredash/redash/pull/7022)  \n* PostgreSQL: Only list tables where schema has USAGE permission [\\#7000](https://github.com/getredash/redash/pull/7000)  \n* Update to Python 3.10 / Debian 12 [\\#6991](https://github.com/getredash/redash/pull/6991)  \n* Dependencies  \n  * Bump ws from 5.2.3 to 5.2.4 [\\#7021](https://github.com/getredash/redash/pull/7021)  \n  * Bump urllib3 from 1.26.18 to 1.26.19 [\\#7020](https://github.com/getredash/redash/pull/7020)\n\n## 24.06.0 [broken]\n\n* Sync .nvmrc with workflow [\\#6958](https://github.com/getredash/redash/pull/6958)\n* Fix 'str' object has no attribute 'pop' error when parsing query [\\#6941](https://github.com/getredash/redash/pull/6941)\n* pgautoupgrade now does multi-arch builds [\\#6939](https://github.com/getredash/redash/pull/6939)\n* Fix error serialization [\\#6937](https://github.com/getredash/redash/pull/6937)\n* Dependencies  \n  * Bump requests from 2.31.0 to 2.32.0 [\\#6981](https://github.com/getredash/redash/pull/6981)\n  * Bump pyodbc from 4.0.28 to 5.1.0 [\\#6962](https://github.com/getredash/redash/pull/6962)\n  * Bump jinja2 from 3.1.3 to 3.1.4 [\\#6951](https://github.com/getredash/redash/pull/6951)\n\n\n## 24.05.0 [broken]\n\n* Downgrade 'codecov-action' version from v4 to v3 [\\#6930](https://github.com/getredash/redash/pull/6930)\n* Update widgets.py [\\#6926](https://github.com/getredash/redash/pull/6926)\n* Use staticPath var to fetch unsupportedRedirect.js [\\#6923](https://github.com/getredash/redash/pull/6923)\n* Remove workaround from check\\_csrf() [\\#6919](https://github.com/getredash/redash/pull/6919)\n* Bugfix: unable to parse elasticsearch index mappings [\\#6918](https://github.com/getredash/redash/pull/6918)\n* Consistent rq status naming and handling [\\#6913](https://github.com/getredash/redash/pull/6913)\n* Fix: aws elasticsearch typo [\\#6899](https://github.com/getredash/redash/pull/6899)\n* Add pydeps Makefile target for installing Python dependencies [\\#6890](https://github.com/getredash/redash/pull/6890)\n* Improve the text displayed when using the command line [\\#6884](https://github.com/getredash/redash/pull/6884)\n* Fixed local setup to run on ARM64 [\\#6877](https://github.com/getredash/redash/pull/6877)\n* Fix for coverage [\\#6872](https://github.com/getredash/redash/pull/6872)\n* Use default docker repo name if variable is not defined [\\#6870](https://github.com/getredash/redash/pull/6870)\n* Fix percy for a branch [\\#6868](https://github.com/getredash/redash/pull/6868)\n* Update yarn to current latest in 1.22.x series [\\#6858](https://github.com/getredash/redash/pull/6858)\n* Extend \\`make up\\` to automatically initialise the database [\\#6855](https://github.com/getredash/redash/pull/6855)\n* Remove version check and all of the data sharing [\\#6852](https://github.com/getredash/redash/pull/6852)\n* Automatically use the latest version of PostgreSQL [\\#6851](https://github.com/getredash/redash/pull/6851)\n* Remove Qubole query runner [\\#6848](https://github.com/getredash/redash/pull/6848)\n* Update \"make clean\" to remove Redash dev Docker images [\\#6847](https://github.com/getredash/redash/pull/6847)\n* Handle timedelta in query results [\\#6846](https://github.com/getredash/redash/pull/6846)\n* Autoformat hyperlinks in Slack alerts [\\#6845](https://github.com/getredash/redash/pull/6845)\n* Flatten all level for MongoDB data source [\\#6844](https://github.com/getredash/redash/pull/6844)\n* Filter widget results to fix tests during repeatable execution [\\#6693](https://github.com/getredash/redash/pull/6693)\n* Reuse built frontend in ci, merge compose files [\\#6674](https://github.com/getredash/redash/pull/6674)\n* Show pg and athena column comments and table descriptions as antd tooltip if they are defined [\\#6582](https://github.com/getredash/redash/pull/6582)\n* Dependencies  \n  * Bump gunicorn to 22.0.0 [\\#6900](https://github.com/getredash/redash/pull/6900)\n  * Bump sqlparse from 0.4.4 to 0.5.0 [\\#6895](https://github.com/getredash/redash/pull/6895)\n  * Bump dnspython from 2.4.2 to 2.6.1 [\\#6886](https://github.com/getredash/redash/pull/6886)\n  * Bump python-oracledb from 2.0.1 to 2.1.2 [\\#6881](https://github.com/getredash/redash/pull/6881)\n  * Bump idna from 3.6 to 3.7 [\\#6878](https://github.com/getredash/redash/pull/6878)\n  * Bump tar from 6.1.15 to 6.2.1 [\\#6866](https://github.com/getredash/redash/pull/6866)\n  * Bump pymongo from 4.3.3 to 4.6.3 [\\#6863](https://github.com/getredash/redash/pull/6863)\n  * Bump Rq from 1.9.0 to 1.16.1 [\\#6902](https://github.com/getredash/redash/pull/6902)\n\n## 24.04.0 [broken]\n\n* Handle decimal types in query results [\\#6837](https://github.com/getredash/redash/pull/6837)\n* BigQuery: use default for useQueryAnnotation option [\\#6824](https://github.com/getredash/redash/pull/6824) \n* Uncaught rejection promise error in Edit Visualization Dialog Modal [\\#6794](https://github.com/getredash/redash/pull/6794)\n* Add RisingWave support [\\#6776](https://github.com/getredash/redash/pull/6776)\n* Schedule may not contain an until key [\\#6771](https://github.com/getredash/redash/pull/6771)\n* ClickHouse query runner: fixed error message [\\#6764](https://github.com/getredash/redash/pull/6764)\n* Dependencies  \n  * Bump express from 4.18.2 to 4.19.2 [\\#6838](https://github.com/getredash/redash/pull/6838)\n  * Bump webpack-dev-middleware from 5.3.3 to 5.3.4 [\\#6829](https://github.com/getredash/redash/pull/6829)\n  * Bump jwcrypto from 1.5.1 to 1.5.6 [\\#6816](https://github.com/getredash/redash/pull/6816)\n  * Bump follow-redirects from 1.15.5 to 1.15.6 in /viz-lib [\\#6813](https://github.com/getredash/redash/pull/6813)\n  * Bump es5-ext from 0.10.53 to 0.10.63 in /viz-lib [\\#6782](https://github.com/getredash/redash/pull/6782)\n\n## 24.03.0\n\n- Publish preview Docker image when release candidate is tagged [#6787](https://github.com/getredash/redash/pull/6787) [#6789](https://github.com/getredash/redash/pull/6789)\n\n## 24.02.0\n\n- Converted `text` columns to `jsonb` [#6687](https://github.com/getredash/redash/pull/6687) [#6713](https://github.com/getredash/redash/pull/6713)\n- Update query hash with parameters applied [#6683](https://github.com/getredash/redash/pull/6683)\n\n## 24.01.0\n\n- Prometheus:\n  - Add ssl support [#6657](https://github.com/getredash/redash/pull/6657)\n- Influxdb\n  - Add v2 query runner [#6646](https://github.com/getredash/redash/pull/6646)\n\n## 23.12.0\n\n- Add unarchive button to dashboard\n- Add fork dashboard function\n- Default to last used datasource\n- Regression fix\n  - Revert \"Switch from numeral to numbro)\" [#6595](https://github.com/getredash/redash/pull/6595)\n\n## 23.11.0\n\n- Add query Runner for Yandex Disk\n- New connection options for MySQL [#6538](https://github.com/getredash/redash/pull/6538)\n- Regression fix\n  - Revert \"Render counter widgets using relative font size\" [#6566](https://github.com/getredash/redash/pull/6566)\n\n## 23.10.0\n\n- Avoid updating query result for archived queries [#6488](https://github.com/getredash/redash/pull/6488)\n- Add Tinybird query runner [#5616](https://github.com/getredash/redash/pull/5616)\n\n## 23.09.0\n\n- Allow the X and Y tick format to be customized using a [D3 format string](https://redash.io/help/user-guide/visualizations/formatting-axis/) [#6370](https://github.com/getredash/redash/pull/6370)\n- Add support for a default alert template [#5996](https://github.com/getredash/redash/pull/5996)\n- New alert destination: Asana [#5753](https://github.com/getredash/redash/pull/5753)\n- Fix query refresh if name contain control characters [#5602](https://github.com/getredash/redash/pull/5602)\n- Allow RSA key used for JWT to be specified as a file path [#6271](https://github.com/getredash/redash/pull/6271)\n- Add 'click' functionality for Chart visualization\n\n\n\n## V10.1.0 - 2021-11-23\n\nThis release includes patches for three security vulnerabilities:\n\n- Insecure default configuration affects installations where REDASH_COOKIE_SECRET is not set explicitly (CVE-2021-41192)\n- SSRF vulnerability affects installations that enabled URL-loading data sources (CVE-2021-43780)\n- Incorrect usage of state parameter in OAuth client code affects installations where Google Login is enabled (CVE-2021-43777)\n\nAnd a couple features that didn't merge in time for 10.0.0\n\n- Big Query: Speed up schema loading (#5632)\n- Add support for Firebolt data source (#5606)\n- Fix: Loading schema for Sqlite DB with \"Order\" column name fails (#5623)\n\n## v10.0.0 - 2021-10-01\n\nA few changes were merged during the V10 beta period.\n\n- New Data Source: CSV/Excel Files\n- Fix: Edit Source button disappeared for users without CanEdit permissions\n- We pinned our docker base image to Python3.7-slim-buster to avoid build issues\n- Fix: dashboard list pagination didn't work\n\n## v10.0.0-beta - 2021-06-16\n\nJust over a year since our last release, the V10 beta is ready. Since we never made a non-beta release of V9, we expect many users will upgrade directly from V8 -> V10. This will bring a lot of exciting features. Please check out the V9 beta release notes below to learn more.\n\nThis V10 beta incorporates fixes for the feedback we received on the V9 beta along with a few long-requested features (horizontal bar charts!) and other changes to improve UX and reliability.\n\nThis release was made possible by contributions from 35+ people (the Github API didn't let us pull handles this time around): Alex Kovar, Alexander Rusanov, Arik Fraimovich, Ben Amor, Christopher Grant, Đặng Minh Dũng, Daniel Lang, deecay, Elad Ossadon, Gabriel Dutra, iwakiriK, Jannis Leidel, Jerry, Jesse Whitehouse, Jiajie Zhong, Jim Sparkman, Jonathan Hult, Josh Bohde, Justin Talbot, koooge, Lei Ni, Levko Kravets, Lingkai Kong, max-voronov, Mike Nason, Nolan Nichols, Omer Lachish, Patrick Yang, peterlee, Rafael Wendel, Sebastian Tramp, simonschneider-db, Tim Gates, Tobias Macey, Vipul Mathur, and Vladislav Denisov\n\nOur special thanks to [Sohail Ahmed](https://pk.linkedin.com/in/sohail-ahmed-755776184) for reporting a vulnerability in our \"forgot password\" page (#5425)\n\n### Upgrading\n\n(This section is duplicated from the previous release - since many users will upgrade directly from V8 -> V10)\n\nTypically, if you are running your own instance of Redash and wish to upgrade, you would simply modify the Docker tag in your `docker-compose.yml` file. Since RQ has replaced Celery in this version, there are a couple extra modifications that need to be done in your `docker-compose.yml`:\n\n1. Under `services/scheduler/environment`, omit `QUEUES` and `WORKERS_COUNT` (and omit `environment` altogether if it is empty).\n2. Under `services`, add a new service for general RQ jobs:\n\n```yaml\nworker:\n  <<: *redash-service\n  command: worker\n  environment:\n    QUEUES: \"periodic emails default\"\n    WORKERS_COUNT: 1\n```\n\nFollowing that, force a recreation of your containers with `docker-compose up --force-recreate --build` and you should be good to go.\n### UX\n- Redash now uses a vertical navbar\n- Dashboard list now includes “My Dashboards” filter\n- Dashboard parameters can now be re-ordered\n- Queries can now be executed with Shift + Enter on all platforms.\n- Added New Dashboard/Query/Alert buttons to corresponding list pages\n- Dashboard text widgets now prompt to confirm before closing the text editor\n- A plus sign is now shown between tags used for search\n- On the queries list view “My Queries” has moved above “Archived”\n- Improved behavior for filtering by tags in list views\n- When a user’s session expires for inactivity, they are prompted to log-in with a pop-up so they don’t lose their place in the app\n- Numerous accessibility changes towards the a11y standard\n- Hide the “Create” menu button if current user doesn’t have permission to any data sources\n\n### Visualizations\n- Feature: Added support for horizontal box plots\n- Feature: Added support for horizontal bar charts\n- Feature: Added “Reverse” option for Chart visualization legend\n- Feature: Added option to align Chart Y-axes at zero\n- Feature: The table visualization header is now fixed when scrolling\n- Feature: Added USA map to choropleth visualization\n- Fix: Selected filters were reset when switching visualizations\n- Fix: Stacked bar chart showed the wrong Y-axis range in some cases\n- Fix: Bar chart with second y axis overlapped data series\n- Fix: Y-axis autoscale failed when min or max was set\n- Fix: Custom JS visualization was broken because of a typo\n- Fix: Too large visualization caused filters block to collapse\n- Fix: Sankey visualization looked inconsistent if the data source returned VARCHAR instead of numeric types\n\n### Structural Updates\n- Redash now prevents CSRF attacks\n- Migration to TypeScript\n- Upgrade to Antd version 4\n### Data Sources\n- New Data Sources: SPARQL Endpoint, Eccenca Corporate Memory, TrinoDB\n- Databricks\n  - Custom Schema Browser that allows switching between databases\n  - Option added to truncate large results\n  - Support for multiple-statement queries\n  - Schema browser can now use eventlet instead of RQ\n- MongoDB:\n  - Moved Username and Password out of the connection string so that password can be stored secretly\n- Oracle:\n  - Fix: Annotated queries always failed. Annotation is now disabled\n- Postgres/CockroachDB:\n  - SSL certfile/keyfile fields are now handled as secret\n- Python:\n  - Feature: Custom built-ins are now supported\n  - Fix: Query runner was not compatible with Python 3\n- Snowflake:\n  - Data source now accepts a custom host address (for use with proxies)\n- TreasureData:\n  - API key field is now handled as secret\n- Yandex:\n  - OAuth token field is now handled as secret\n\n### Alerts\n- Feature: Added ability to mute alerts without deleting them\n- Change: Non-email alert destination details are now obfuscated to avoid leaking sensitive information (webhook URLs, tokens etc.)\n- Fix: numerical comparisons failed if value from query was a string\n\n### Parameters\n- Added “Last 12 months” option for dynamic date ranges\n\n### Bug Fixes\n- Fix: Private addresses were not allowed even when enforcing was disabled \n- Fix: Python query runner wasn’t updated for Python 3\n- Fix: Sorting queries by schedule returned the wrong order\n- Fix: Counter visualization was enormous in some cases\n- Fix: Dashboard URL will now change when the dashboard title changes\n- Fix: URL parameters were removed when forking a query\n- Fix: Create link on data sources page was broken\n- Fix: Queries could be reassigned to read-only data sources\n- Fix: Multi-select dropdown was very slow if there were 1k+ options\n- Fix: Search Input couldn’t be focused or updated while editing a dashboard\n- Fix: The CLI command for “status” did not work\n- Fix: The dashboard list screen displayed too few items under certain pagination configurations\n\n### Other\n- Added an environment variable to disable public sharing links for queries and dashboards\n- Alert destinations are now encrypted at the database\n- The base query runner now has stubs to implement result truncating for other data sources\n- Static SAML configuration and assertion encryption are now supported\n- Adds new component for adding extra actions to the query and dashboard pages\n- Non-admins with at least view_only permission on a dashboard can now make GET requests to the data source resource\n- Added a BLOCKED_DOMAINS setting to prevent sign-ups from emails at specific domains\n- Added a rate limit to the “forgot password” page\n- RQ workers will now shutdown gracefully for known error codes\n- Scheduled execution failure counter now resets following a successful ad hoc execution\n- Redash now deletes locks for cancelled queries\n- Upgraded Ace Editor from v6 to v9\n- Added a periodic job to remove ghost locks\n- Removed content width limit on all pages\n- Introduce a <Link> React component\n\n## v9.0.0-beta - 2020-06-11\n\nThis release was long time in the making and has several major changes:\n\n- Our backend code was updated to support Python 3 and we no longer support Python 2. If you're using our Docker images, this should be a transparent change for you.\n- We replaced Celery with RQ for background jobs processing. This will require some setup updates -- see instructions below.\n- The frontend code is now 100% React and we removed all the Angular dependencies.\n\nThis release was made possible by contributions from over 50 people: @ari-e, @ariarijp, @arihantsurana, @arikfr, @atharvai, @cemremengu, @chulucninh09, @citrin, @daniellangnet, @DavidHernandez, @deecay, @dmudro, @erans, @erels, @ezkl, @gabrieldutra, @gstaykov, @ialeinikov, @ikenji, @Jakdaw, @jezdez, @juanvasquezreyes, @koooge, @kravets-levko, @kykrueger, @leibowitz, @leosunmo, @lihan, @loganprice, @mickeey2525, @mnoorenberghe, @monicagangwar, @NicolasLM, @p-yang, @Ralnoc, @ranbena, @randyzwitch, @rauchy, @rxin, @saravananselvamohan, @satyamkrishna, @shinsuke-nara, @stefan-mees, @stevebuckingham, @susodapop, @taminif, @thewarpaint, @tsuyoshizawa, @uncletimmy3, @wengkham.\n\n### Upgrading\n\nTypically, if you are running your own instance of Redash and wish to upgrade, you would simply modify the Docker tag in your `docker-compose.yml` file. Since RQ has replaced Celery in this version, there are a couple extra modifications that need to be done in your `docker-compose.yml`:\n\n1. Under `services/scheduler/environment`, omit `QUEUES` and `WORKERS_COUNT` (and omit `environment` altogether if it is empty).\n2. Under `services`, add a new service for general RQ jobs:\n\n```yaml\nworker:\n  <<: *redash-service\n  command: worker\n  environment:\n    QUEUES: \"periodic emails default\"\n    WORKERS_COUNT: 1\n```\n\nFollowing that, force a recreation of your containers with `docker-compose up --force-recreate --build` and you should be good to go.\n\n### UX\n\n- Redesigned Query Results page:\n  - Completely new layout is easier to read for non-technical Redash users.\n  - Empty query results are clearly displayed. User is now prompted to edit or execute the query.\n- Mobile Experience Improvements:\n  - UI element spacing has been redesigned for clarity\n  - Admin pages now honor max-width. Tables scroll independent of the top menu.\n  - Large legends no longer shrink the visualization on small screens.\n  - Fix: it was sometimes impossible to scroll pages with dashboards because the visualizations captured every touch event.\n  - Fix: Visualizations on small screens would not always show horizontal scroll bars.\n- Dashboards can now be un-archived using the API.\n- Dashboard UI performance was improved.\n- List pages were changed to show a user's name instead of avatar.\n- Search-enabled tables now show a prompt for which columns will be searched.\n- In the visualization editor, the settings pane now scrolls independent of the visualization preview.\n- Tokens in the schema viewer now sort alphabetically.\n- Links to settings panes that require Admin privileges are now hidden from non-Admins.\n- The Admin page now remembers which tab you were viewing after a page reload.\n\n### Visualizations\n\n- Feature: Allow bubble size control with either coefficient or sizemode.\n- Feature: Table visualization now treats Unix timestamps in query results as timestamps.\n- Feature: It's now possible to provide a description to each Table column, appearing in UI as a tooltip.\n- Feature: Added tooltip and popover templating to the map with markers visualization.\n- Feature: Added an organization setting to hide the Plotly mode bar on all visualizations.\n- Feature: Cohort visualization now has appearance settings.\n- Feature: Add option to explicitly set Chart legend position.\n- Change: Deprecated visualizations are now hidden.\n- Change: Table settings editor now extends vertically instead of horizontally.\n- Change: The maximum table pagination is now 500.\n- Change: Pie chart labels maintain contrast against lighter slices.\n- Fix: Chart series switched places when picking Y axis.\n- Fix: Third column was not selectable for Bubble and Heatmap charts.\n- Fix: On the counter visualizations, the “count rows” option showed an empty string instead of 0.\n- Fix: Table visualization with column named \"children\" rendered +/- buttons.\n- Fix: Sankey visualization now correctly occupies all available area even with fewer stages.\n- Fix: Pie chart ignores series labels.\n\n### Data Sources\n\n- New Data Sources: Amazon Cloudwatch, Amazon CloudWatch Logs Insights, Azure Kusto, Exasol.\n- Athena:\n  - Added the option to specify a base cost in settings, displaying a price for each query when executed.\n- BigQuery:\n  - Fix: large jobs continued running after the user clicked “Cancel” query execution.\n- Cassandra:\n  - Updated driver to 3.21.0 which dramatically reduces Docker build times.\n  - SSL options are now available.\n- Clickhouse:\n  - You can now choose whether to verify the SSL certificate.\n- Databricks:\n  - Databricks now use an ODBC-based connector.\n  - Fix: Date column was coerced to DateTime in the front-end.\n- Druid:\n  - Added username and password authentication option.\n- Microsoft SQL Server\n  - Added support for ODBC connections via pyodbc. There are now two MSSQL data source types. One using TDS. The other is using ODBC.\n- MongoDB:\n  - Added support for running queries on secondary in replicaset mode.\n  - Fix: Connection test always succeeded.\n- Oracle:\n  - Fix: Connection would fail if username or password contained special characters.\n  - Fix: Comparisons would fail if scale was None.\n- RDS:\n  - Updated rds-combined-ca-bundle.pem to the latest CA.\n- Redshift:\n  - Added the ability to use IAM Roles and Users.\n  - Fix: Redshift was unable to have its schema refreshed.\n- Rockset:\n  - Fix: Allow Redash to load collections in all workspaces.\n- Snowflake:\n  - You can now refresh the snowflake schema without waking the cluster.\n  - Added support for all of Snowflake’s datetime types. Otherwise certain timestamps would only appear as strings in the front-end.\n- TreasureData:\n  - Fix: API calls would fail when setting a non-default region.\n\n### Alerts\n\n- Feature: Added ability to mute alerts without deleting them.\n- Fix: numerical comparisons failed if value from query was a string.\n\n### Parameters\n\n- Added Last x Days options for date range parameters.\n- Fix: Parameters added in empty queries were always added as text parameters\n\n### Bug Fixes\n\n- Fix: Alembic migration schema was preventing v4 users from upgrading. In v5 we started encrypting data source credentials in the database.\n- Fix: System admin dashboard would not show correct database size if non-default name was used.\n- Fix: refresh_queries job would break if any query had a bad schedule object.\n- Fix: Orgs with LDAP enabled couldn’t disable password login.\n- Fix: SSL mode was sometimes sent as an empty string to the database instead of omitted entirely.\n- Fix: When creating new Map visualization with clustering disabled, map would crash on save.\n- Fix: It was possible on the New Query page to click “Save” multiple times, causing multiple new query records to be created.\n- Fix: Visualization render errors on a dashboard would crash the entire page.\n- Fix: A scheduled execution failure would modify the query’s “updated_at” timestamp.\n- Fix: Parameter UI would wrap awkwardly during some drag operations.\n- Fix: In dashboard edit mode, users couldn’t modify widgets.\n- Fix: Frontend error when parsing a NaN float.\n\n### Other\n\n- Added TSV as a download format (in addition to CSV and Excel).\n- Added maildev settings (helps with automated settings).\n- Refine permissions usage in Redash to allow for guest users\n- The query results API now explicitly handles 404 errors.\n- Forked queries now retain the tags of the original query.\n- We now allow setting custom Sentry environments.\n- Started using Black linter for our Python source code\n- Added CLI command to re-encrypt data source details with new secret key.\n- Favorites list is now loaded on menu click instead of on page load.\n- Administrators can now allow connections to private IP addresses.\n\n## v8.0.0 - 2019-10-27\n\nThere were no changes in this release since `v8.0.0-beta.2`. This is just to mark a stable release.\n\n## v8.0.0-beta.2 - 2019-09-16\n\nThis is an update to the previous beta release, which includes:\n\n- Add options for users to share anonymous usage information with us (see [docs](https://redash.io/help/open-source/admin-guide/usage-data) for details).\n- Visualizations:\n  - Allow the user to decide how to handle null values in charts.\n- Upgrade Sentry-SDK to latest version.\n- Make horizontal table scroll visible in dashboard widgets without scrolling.\n- Data Sources:\n  - Add support for Azure Data Explorer (Kusto).\n  - MySQL: fix connections without SSL configuration failing.\n  - Amazon Redshift: option to set query group for adhoc/scheduled queries.\n  - Hive: make error message more friendly.\n  - Qubole: add support to run Quantum queries.\n- Display data source icon in query editor.\n- Fix: allow users with view only acces to use the queries in Query Results\n- Dashboard: when updating parameters refersh only widgets that use those parameters.\n\nThis release had contributions from 12 people: @arikfr, @cclauss, @gabrieldutra, @justinclift, @kravets-levko, @ranbena, @rauchy, @sandeepV2, @shinsuke-nara, @spacentropy, @sphenlee, @swfz.\n\n## v8.0.0-beta - 2019-08-18\n\nAfter months of being heads down with hard work, it's finally time to wrap up the V8 release 🤩 This release includes many long awaited improvements to parameters, UX improvements, further React migration and other changes, fixes and improvements.\n\nWhile this version is already running on the hosted platform to make sure it's stable, we're excited to put this in the hands of our Open Source users.\n\nStarting from this release we will no longer build a tarball distribution of the codebase and recommend everyone to switch over to using our Docker images. We're planning on dropping Python 2 support towards its EOL this year and switching over to the Docker image will make this transition much simpler.\n\nThis release was made possible by contributions from over 40 people: @aidarbek, @AntonZarutsky, @ariarijp, @arikfr, @combineads, @deecay, @fmy, @gabrieldutra, @guwenqing, @guyco33, @ialeinikov, @Jakdaw, @jezdez, @justinclift, @k-tomoyasu, @katty0324, @koooge, @kravets-levko, @ktmud, @KumanoTanaka, @kyoshidajp, @nason, @oldPadavan, @openjck, @osule, @otsaloma, @ranbena, @rauchy, @rueian, @sekiyama58, @shinsuke-nara, @taminif, @The-Alchemist, @vv-p, @washort, @wudi-ayuan, @ygrishaev, @yoavbls, @yoshiken, @yusukegoto and the support of over 500 organizations who subscribed to our hosted version and by that sponsor the team's work.\n\n### Parameters\n\n- Parameter UI improvements:\n  - Support for multi-select in dropdown (and query dropdown) parameters.\n  - Support for dynamic values in date and date-range parameters.\n  - Search dropdown parameter values.\n  - New UX for applying parameter changes in queries and dashboards.\n- Allow using Safe Parameters in visualization embeds and public dashboards. Safe Parameters are any parameter type except for the a text parameter (dropdowns are safe).\n\n### Data Sources\n\n- New Data Sources: Couchbase, Phoenix and Dgraph.\n- New JSON data source (and deprecated old URL data source).\n- Snowflake: update connector to latest version.\n- PostgreSQL: show only accessible tables in schema.\n- BigQuery:\n  - Correctly handle NaN values.\n  - Treat repeated fields as rrays.\n  - [BigQuery] Fix: in some queries there is no mode field\n- DynamoDB:\n  - Support for Unicode in queries.\n  - Safe loading of schema.\n- Rockset: better handling of query errors.\n- Google Sheets:\n  - Support for Team Drive.\n  - Friendlier error message in case of an API error and more reliable test connection.\n- MySQL:\n  - Support for calling Stored Procedures and better handling of query cancellation.\n  - Switch to using `mysqlclient` (a maintained fork of `Python-MySQL`).\n- MongoDB: Support serializing Decimal128 values.\n- Presto: support for passwords in connection settings.\n- Amazon Athena: allow to specify custom work group.\n- Query Results: querying a column with a dictionary or array fails\n- Clickhouse: make sure we don't show password in error messages.\n- Enable Cassandra support by default.\n\n### Visualizations\n\n- Charts:\n  - Fix: legend overlapping chart on small screens.\n  - Fix: Pie chart not rendering when series doesn't exist in options.\n  - Pie Chart: add option to set direction of slices.\n- WordCloud: rewritten to support new options (provide frequency in query, limits), scale when resizing, handle long words and more.\n- Pivot Table: support hiding totals.\n- Counters: apply formatting to target value.\n- Maps:\n  - Ability to customize marker icon and color.\n  - Customization options for Choropleth maps.\n- New Visualization: Details View.\n\n### **UX**\n\n- Replace blank screen with a loading indicator when the application is doing its first load.\n- Multiple improvements to dashboards editing: auto-save, grid markings and better refresh indicator.\n- Admin can now edit user's groups from the user page.\n- Add keyboard shortcut (Ctrl/Cmd+Shift+F) to trigger query formatting.\n\n### API\n\n- Query Result API response minimized to only required fields when called with a non user API key.\n- Prefer API key over cookies in authentication.\n- User can now regenerate Query API Key.\n\n### Other Changes\n\n- Sends CSP headers to prevent various kinds of security attacks via the browser. Might break unusual usages and embeds of Redash.\n- New Failed Scheduled Queries email report (can be enabled from organization settings screen).\n- Deprecated HipChat Alert Destination.\n- Add options to hide different parts of a Visualization embed UI (parameters, title, link to query).\n- Support multi-byte search for query names and descriptions (needs to be enabled in Organization settings screen).\n- CSV query results download: correctly serialize booleans and date values.\n- Dashboard filters now collect values from all widgets with the same filter.\n- Support for custom message and description in alert notifications (currently disabled behind a feature flag until we improve the alert UX).\n\n### Bug Fixes\n\n- Fix: adding widget to dashboard from a query page is broken.\n- Fix: default time format option was wrong.\n- Fix: when too many errors of a scheduled queries occur it causes an OverflowError.\n- Fix: when forking a query maintain the same visualizations order.\n\n## v7.0.0 - 2019-03-17\n\nWe're trying a new format for the CHANGELOG in this release. Focusing on the bigger changes, but for whoever interested, you can see all the changes [here](https://github.com/getredash/redash/compare/v6.0.0...master).\n\nBesides all the features, bug fixes and improvements listed below we managed to convert a large portion of Redash's frontend code from Angular.js to React. You can see status in [#3071](https://github.com/getredash/redash/issues/3071).\n\nThis release was made possible with the help of 34 contributors. 🙇‍♂️\n\n### Data Sources\n\n- **All data source options are now encrypted in the database.** By default the encryption uses the `REDASH_COOKIE_SECRET` value (`redash.settings.COOKIE_SECRET`), but you can specify a different value by setting the `REDASH_SECRET_KEY` environment variable value. Note that you need to set this _before_ doing the upgrade.\n- New Data Sources: Uptycs and Apache Drill.\n- Snowplow: is now enabled by default & supports region setting.\n- Elasticsearch: add support for Amazon Elasticsearch IAM authentication (with IAM profile or key/secret pair).\n- PostgreSQL: add support for serializing range values.\n- Redshift: remove duplicate column information for late-binding views.\n- Athena: load all databases (using pagination).\n- BigQuery: correctly handle temp tables with no schema field.\n- Jira (JQL): support for fetching all records with pagination.\n- Prometheus: fix schema loading and add support for query range.\n\n### In-app Help\n\nYou can now open the [Knowledge Base](https://redash.io/help) inside the application. We also added a few \"help triggers\" in the app, that will open the Knowledge Base in context of what you're currently doing.\n\n### Parameters\n\n- **Dashboard Parameters**: We improved the flow of adding queries with parameters to dashboards and now give you full control over how parameters are mapped. You no longer have to make sure all parameters have the same name or use the `Global` checkbox. We also added new options, like keeping the parameter local to the widget or setting a static value. [Read more in our Knowledge Base →](https://redash.io/help/user-guide/querying/query-parameters#Parameter-Mapping-on-Dashboards)\n- We added server side validation of parameter values for all parameter types, except for parameters of `text` type. All validated parameter types are considered safe. When a query is using safe parameters (or no parameters at all), View Only users can refresh it.\n- Refreshing safe queries is done using the new results API endpoint, which takes only a query ID (and optionally parameter values) and does not need the query text.\n\n### Query Editor Improvements\n\n- Run only the highlighted query text: hit Execute after highlighting a portion of your query and only the selected portion will be sent to the database. This is useful for testing sub-SELECT statements and CTE's.\n- Improved auto complete: add a dot . after a table name in the query editor and auto complete will only suggest columns on that table.\n- Autosave parameter configuration changes.\n- YAML syntax support (for data sources like Yandex Metrica).\n\n### Improved Query Scheduler\n\nThe Query Scheduler got a face lift and some new options: you can pick a day for a weekly schedule to run on and also set an end date after which the query will no longer execute on schedule.\n\n### Data Sources\n\nWe added Apache Drill, Uptycs and a new JSON data source. Also fixed a few bugs in Athena's query runner and others.\n\n### User Management\n\nThe users page got revamped with a new look and feel and few new features:\n\n- An indication when a user was last active.\n- Show if an invited user hasn't finished the setup process yet (Pending Invitations section).\n- You can now generate a new API key for users, if there's a concern it was compromised.\n\n### Admin\n\n- New Celery queues status screens, replacing the old Queries Status and better reflecting the status of running queries.\n- Make the queue name for schema refresh job configurable. The default used to be hard coded `schemas`, which is not available on all setups. Now it's `celery`.\n- The `gevent` library is installed by default, and you can now setup gunicorn to use `gevent` based workers.\n- New Docker entrypoint command to do a health check for a worker process.\n- Flask-Admin is no longer setup or supported.\n\n### Other Changes\n\n- New Alert destination: Google Hangouts Chat.\n- When downloading results from the results API it will set a user friendly filename for the downloaded file.\n- Archived Queries section added to the queries list.\n\n### Bug Fixes\n\n- Fixed: fork query does not fork tables but instead adds default table.\n- Fixed: when deleting a visualization, any widget using it was left empty on the dashboard.\n- Fixed: issues with Query Editor resizing on new versions of Chrome.\n- Fixed: issues with exporting dictionaries to Excel.\n- Fixed: Cohort visualization gets stuck when passing string values.\n- Fixed: use series name for Pie chart label.\n- Make sure Flask app created in Celery's worker process (could cause some query runners to get stuck while running queries).\n\n## v6.0.0 - 2018-12-16\n\nv6.0.0 release version. Mainly includes fixes for regressions from the beta version.\n\nThis release had contributions from 5 people: @rauchy, @denisov-vlad, @arikfr, @ariarijp, and @gabrieldutra. Thank you, everyone 🙏\n\n### Changed\n\n- #3183 Make refresh_queries less noisey in logs. @arikfr\n\n### Fixed\n\n- #3163 Include correct version in production builds. @rauchy\n- #3161 Clickhouse: fix int() conversion error. @denisov-vlad\n- #3166 Directly using record_event task requires timestamp. @arikfr\n- #3167 Alert.evaluate failing when the column is missing. @arikfr\n- ##3162 Remove API permissions for users who have been disabled. @rauchy\n- #3171 Reject empty query name. @ariarijp\n- #3175, #3186 Fix disable error message. @rauchy, @gabrieldutra\n- #3182 [Redshift] support for schema names with dots. @arikfr\n- #3187 Safely create_app in Celery code (try to fetch current_app first). @arikfr\n\n### Other\n\n- #3155 Add DB Seed to Cypress and setup Percy. @gabrieldutra\n- #3180 Remove coverage from pytest terminal output. @rauchy\n\n## v6.0.0-beta - 2018-12-03\n\nThis release was 2 months in the making and it is full with good stuff!\n\n- We have 5 new data sources: Databricks, IBM DB2, Kylin, Druid and Rockset. ⌗\n- There are fixes and improvements to 11 existing data sources (MySQL, Redshift, Postgres, MongoDB, Google BigQuery, Vertica, TreasureData, Presto, ClickHouse, Google Sheets and Google Analytics).\n- The Query Results data source can now load cached results, just use the `cached_query_` prefix instead of `query_`.\n- On the visualizations front we added a Heatmap visualization and did updated the table and counter visualizations.\n- Alerts got some fixes and a new destination: PagerDuty.\n- If the live autocomplete in the code editor annoys you, you can disable it now (although we're working to make it better, see #3092).\n- Fast queries will now load faster. 🏃‍♂️\n- We improved the layout of visualizations and content on smaller screen sizes. 📱\n- For those of you who like sharing, you can now enable the ability to share ownership of queries and dashboards and let others to edit them. Check the Settings page to enable this feature.\n\nThere were also important changes to the code and infrastructure:\n\n- More components moved to React.\n- We switched to Webpack 4 with the help of @dmonego.\n- We upgraded to Celery 4 with the help of @emtwo, @jezdez, @mashrikt and @atharvai.\n- We started moving towards Python 3 for our backend. The first step was to make sure our code pass basic sanity tests with Flake 8, which was implemented by @cclauss.\n- We improved our testing on the frontend by adding setup for Jest tests and E2E testing using Cypress (@gabrieldutra).\n- Each pull request now gets a deploy preview using Netlify to easily test frontend changes.\n\nThis is just a summary, you're welcome to review the full list below. ⬇\n\nThis release had contributions from 38 people: @arikfr, @kravets-levko, @jezdez, @kyoshidajp, @kocsmy, @alison985, @gabrieldutra, @washort, @GitSumito, @emtwo, @rauchy, @alexanderlz, @denisov-vlad, @ariarijp, @yoavbls, @zhujunsan, @sjakthol, @koooge, @SakuradaJun, @dmonego, @Udomomo, @cclauss, @combineads, @zaimy, @Trigl, @ralphilius, @jodevsa, @deecay, @igorcanadi, @pashaxp, @hoangphuoc25, @toph, @burnash, @wankdanker, @Yossi-a, @Rovel, @kadrach, and @nicof38. Thank you, everyone 🙏\n\n### Added\n\n- #2747, #3143 Add a new Databricks query runner. @alison985, @jezdez, @arikfr\n- #2767 Add ability to add viz to dashboard from query edit page. @alison985, @jezdez\n- #2780 Add a query autocomplete toggle. @alison985, @jezdez, @arikfr\n- #2768 Add authentication via JWT providers. @SakuradaJun\n- #2790 Add the ability to sort favorited queries, paginate the dashboard list and improve UI inconsistencies. @jezdez\n- #2681 Add ability to search table column names in schema browser. @alison985\n- #2855 Add option to query cached results. @yoavbls\n- #2740 Add ability for extensions to add periodic tasks. @emtwo\n- #2924 Google Spreadsheets: Add support for opening by URL. @alexanderlz\n- #2903 Add PagerDuty as an Alert Destination. @alexanderlz\n- #2824 Add support for expanding dashboard visualizations. @sjakthol\n- #2900 Add ability to specify a counter label. @ralphilius\n- #2565 Add frontend extension capabilities. @emtwo\n- #2848 Add IBM Db2 as a data source using the ibm-db Python package. @nicof38\n- #2959 Add option to auto reload widget data in shared dashboards. @arikfr\n- #2993 Add page size settings. @kyoshidajp\n- #2080 New Heatmap chart visualization with Plotly. @deecay\n- #2991 Show users in CLI group list. @GitSumito\n- #2342 New SQLPARSE_FORMAT_OPTIONS setting to configure query formatter. @ariarijp\n- #3031 Add some tests for Query Results. @ariarijp\n- #2936 Add Kylin data source. @Trigl\n- #3047 Add Druid data source. @rauchy\n- #3077 New user interface for the feature flag of the share edit permissions feature. @arikfr\n- #3007 Add permissions to the result of \"manage.py groups list\" command. @Udomomo\n- #3088 Add get_current_user() fuction for the Python query runner. @kyoshidajp\n- #3114 Add event tracking to autocomplete toggle. @arikfr\n- #3068 Add Rockset query runner. @igorcanadi, @arikfr\n- #3105 Display frontend version. @rauchy\n\n### Changed\n\n- #2636 Rewrite query editor with React. @washort, @arikfr\n- #2637 Convert edit-in-place component to React. @washort, @arikfr\n- #2766 Suitable events are now being recorded server side instead of in the frontend. @alison985, @jezdez\n- #2796 Change placement (right/bottom) of chart legend depending on chart width. @kravets-levko\n- #2833 Uses server side sort order for tag list and show count of tagged items. @jezdez\n- #2318 Support authentication for the URL data source. @jezdez\n- #2884 Rename Yandex Metrika to Metrica. @jezdez\n- #2909 MySQL: hide sys tables. @arikfr\n- #2817 Consistently use simplejson for loading and dumping JSON. @jezdez\n- #2872 Use Plotly's function to clean y-values (x may be category or date/time). @kravets-levko\n- #2938 Auto focus tag input. @kyoshidajp\n- #2927 Design refinements for queries pages. @kocsmy\n- #2950 Show activity status in CLI user list. @GitSumito\n- #2968 Presto data source: setting protocol (http/https), safe loading of error messages. @arikfr\n- #2967 Show groups in CLI user list. @GitSumito\n- #2603 MongoDB: Update requirements to support srv. @arikfr\n- #2961 MongoDB: Skip system collections when loading schema. @arikfr\n- #2960 Add timeout to various HTTP requests. @arikfr\n- #2983 Databricks: New logo, updated name and enabled by default. @arikfr\n- #2982 Table visualization: change default size to 25 and add more size options. @arikfr\n- #2866 Redshift: Hide tables the configured user cannot access. @sjakthol\n- #3058 Mustache: don't html-escape query parameters values. @kravets-levko\n- #3079 Always use basic autocomplete, as well as the live autocomplete. @arikfr\n- #3084 Support tel://, sms://, mailto:// links in query results. @zhujunsan\n- #3083 Clickhouse: Add WITH TOTALS option support. @denisov-vlad\n- #3063 Allow setting colors for bubble charts. @toph\n- #3085 BigQuery: Switch to Standard SQL as the default. @kyoshidajp\n- #3094 Tags autocomplete: Show note when creating a new label. @kravets-levko\n- #2984 Autocomplete toggle improvements. @arikfr\n- #3089 Open new tab when forking a query. @kyoshidajp\n- #3126 MongoDB: add support for sorting columns. @arikfr\n- #3128 Improve backoff algorithm of query results polling to speed it up. @arikfr\n- #3125 Vertica: update driver & add support for connection timeout. @arikfr\n- #3124 Support unicode in Postgres/Redshift schema. @arikfr\n- #3138 Migrate all tags components to React. @kravets-levko\n- #3139 Better manage permissions modal. @kocsmy\n- #3149 Improve tag link colors and fix group tags on Users page. @kocsmy\n- #3146 Update, replace and fix new alert destination logos so it fits better. @kocsmy\n- #3147 Add and improve recent db logos that didn't fit in size properly. @kocsmy\n- #3148 Fix label positioning on no found screen. @kocsmy\n- #3156 json_dumps: add support for serializing buffer objects. @arikfr\n\n### Fixed\n\n- #2849 Fix invalid reference to alert.to_dict() in webhook. @wankdanker\n- #2840 Improve counter visualization text scaling. @kravets-levko\n- #2854 Widget titles are no longer rendered wrong on public dashboards. @kravets-levko\n- #2318 Removed redundant exception handling in data sources since that's handled in the query backend. @jezdez\n- #2886 Fix Javascript build that broke because registerAll tried to run EditInPlace component. @arikfr\n- #2911 Don’t show “Add to dashboard” in dropdown to unsaved queries. @jezdez\n- #2916 Fix export query results output file name. @gabrieldutra\n- #2917 Fix output file name not changing after rename query. @gabrieldutra\n- #2868 Address edge case when retrieving Glue schemas for Athena data source. @kadrach\n- #2929 Fix: date value in a filter is duplicated. @combineads\n- #2875 Unbreak charts with long legend break in horizontal mode. Update plotly.js. @kravets-levko\n- #2937 Fix event recording in admin API backend. @kyoshidajp\n- #2953 Minor fixes for the Clickhouse data source. @denisov-vlad\n- #2941 Bring back fix to Box plot hover. @arikfr\n- #2957 Apply missing CSS classes to EditInPlace component. @arikfr\n- #2897 Show \"Add description\" only after saving the query. @arikfr\n- #2922 Query page layout improvements for small screens. @kravets-levko\n- #2956 Clickhouse: move timeout to params. @denisov-vlad\n- #2964 Fix no tags shown when having empty set. @gabrieldutra\n- #2757 Use full text search ranking when searching in list views. @jezdez\n- #2969 Query Results data source: improved errors, quoted column names. @arikfr\n- #2906 Preventing open redirection in loging process. @kyoshidajp\n- #2867 TreasureData: Deduplicate column names. @zaimy\n- #2994 Fix scheme of various URLs from http to https. @kyoshidajp\n- #2992 Fix an invalid prop type warning in new version notifier. @kyoshidajp\n- #3022 Fix Toolbox covering part of a chart. @kravets-levko\n- #2998 Fix charts losing responsive features after refreshing the dashboard. @kravets-levko\n- #3034 Postgres: handle NaN/Infinity values. @kravets-levko\n- #2745 Sort columns with undefined values. @Yossi-a\n- #3041 Sort CLI output of lists. @GitSumito\n- #2803, #3006 Address various tag display issues on query list page. @kocsmy, @alison985\n- #3049 Fix edit-in-place component which ignored isEditable flag and didn't work on Groups page. @kravets-levko\n- #2965 Google Analytics: Fix crash when no results are returned. @alexanderlz\n- #3061 Fix table visualization so that the horizontal scrollbar is not be always visible. @kravets-levko\n- #3076 Add white-space padding to separators in the footer. @burnash\n- #2919 Fix URL data source to not require a URL. @arikfr\n- #3098 Force AngularJS to update query editor properly. @washort\n- #3100 Delete redundant regex segment in query result frontend. @zhujunsan\n- #2978 Prevent the query update timestamp from changing when it is linked to new query results. @rauchy\n- #3046 Fix query page header. @kravets-levko\n- #3097 Mongo: Fix collection fields retreival bug when Views are present. @jodevsa\n- #3107 Keep query text in local state for now. @washort\n- #3111 Fix mobile padding issues on Query results. @kocsmy\n- #3122 Show menu divider only if query is archived. @jezdez\n- #3120 Fix tag counts for dashboards and queries. @jezdez\n- #3141 Fix schema refresh to work on MySQL 8. @hoangphuoc25\n- #3142 Fix: editing dashboard title results in the visualizations being replaced by the loading markers. @kravets-levko\n\n### Other\n\n- #2850 The setup scripts are now based on Ubuntu 18.04 LTS and Docker. @pashaxp, @arikfr\n- #2985 Add Jest based tests to our stack. @arikfr\n- #2999 Add netlify configuration. @arikfr\n- #3000 Initial Cypress based E2E test infrastructure. @gabrieldutra\n- #2898 Move Ant styles into a central location. @arikfr\n- #2910 Fix webpack build error about BigMessage. @jezdez\n- #2928 Speed up builds by skipping installing requirements_all_ds.txt in CI unit tests. @arikfr\n- #2963 Fix tarball build failure. @emtwo\n- #2996 Fix setup.sh failures when run as root. @arikfr\n- #2989 Rearrange make targets. @koooge\n- #3036 Update Flask-Admin to 1.5.2. @yoavbls\n- #2901 Fix documentation links. @kravets-levko\n- #3073 Remove only Redash containers in clean Make task. @ariarijp\n- #3048 Remove pytest-watch dependency to workaround an issue with watchdog. @rauchy\n- #2905 Update development docker-compose.yml file to use latest Redis and Postgres servers and specify working volume explictly. @Rovel\n- #3032 Makefile: Add make targets for test. @koooge\n- #2933 Switch to Webpack 4. @dmonego\n- #2908 Update setup files. @arikfr\n- #2946 Update snowflake_connector_python version. @arikfr\n- #2773 Upgrade to Celery 4.2.1. @emtwo, @jezdez\n- #2881 CircleCI: Make flake8 tests pass on Legacy Python and Python 3. @cclauss\n- #2907 Remove unused dependencies (honcho, wsgiref). @arikfr\n- #3039 Build docker image on master branch. @arikfr\n- #3106 Fix registerAll failures after minification. @arikfr\n\n## v5.0.2 - 2018-10-18\n\n### Security\n\n- Fix: prevent Open Redirect vulnerability.\n\n## v5.0.1 - 2018-09-27\n\n### Added\n\n- Added support for JWT authentication (for services like Cloudflare Access or Google IAP).\n\n### Changed\n\n- Upgraded Celery version to 3.1.26 to make upgrade to Celery 4 easier.\n\n## v5.0.0 - 2018-09-21\n\nFinal release for V5. Most of the changes were already in the beta release of V5, but this includes several fixes along\nwith UI improvements.\n\n🙏 Thanks to @arikfr, @jezdez, @kravets-levko, @alison985, @kocsmy, @yossi-a, @tdsmith, @nasmithan, @jrbenny35, @sjakthol, @ariarijp and @combineads who contributed to this release.\n\n### Security\n\n- Fix: don't expose Google OAuth client secret. @arikfr\n\n### Changed\n\n- Improve mobile rendering of dashboards and queries. @kocsmy\n- UI improvements for favorites and empty state. @arikfr\n- Remove unnecessary X at the end of the query search. @kocsmy\n- Add server-side sorting to dashboard list. @jezdez\n- Sort queries in descending order. @jezdez\n- Throw error when non-owner tries to add a user to dashboard permissions. @alison985\n- Propagate query execution errors from Celery tasks properly. @alison985\n- Reload the route when using the app header search input. @jezdez\n\n### Fixed\n\n- Fix: BigQuery default location is null and not US. @arikfr\n- Fix: query embeds are broken. @arikfr\n- Fix: typo in Celery log foramt. @ariarijp\n- Use QuerySerializer in outdated queries list. @jezdez\n- Fix: sometimes widgets are getting zero height. @kravets-levko\n- Athena: Switch to simple_json to serialize NaN/Infinity values as nulls. @kravets-levko, @jezdez\n- Fix: queries with parameters with no value breaking the scheduler. @arikfr\n- Fix: MongoDB query results parser didn't support unicode keys. @arikfr\n- Fix: Google Analytics schema wasn't loading in some cases. @arikfr\n- Fix: date/time parameters not working as global param @kravets-levko.\n- Fix: Widgets crumble when trying to move / resize a widget. @kravets-levko\n- Fix: handling rows with \"length\" field with forOwn method. @yossi-a\n- Fix: query selection not working on alert page. @sjakthol\n- Fix: query_results for Embedded Parameters (removed deprecated to_dict function). @nasmithan\n- Fix: unicode not supported in dashboard search. @combineads\n- Fix: unicode not supported in users search. @arikfr\n\n### Other\n\n- Add test for using saved parameters in scheduled queries. @alison985\n- Minor code smell cleanup. @jezdez\n- Update QueryResultListResource docstring. @tdsmith\n- Switch to CirlceCI 2.0 @jrbenny35, @arikfr\n- Remove unnecessary init methods. @jezdez\n\n## v5.0.0-Beta - 2018-08-06\n\nThis is the first beta of the V5 release (and hopefully the last one). This version includes a lot of exciting new additions along with bug fixes and other changes.\n\nSome notable changes:\n\n- Extensive work on parameters UI:\n  - New Date Range parameter type.\n  - UI for creating new parameters.\n  - Support for Now/Today as default value of date/time parameter.\n- Tagging and favorites ⭐️ support for queries and dashboards.\n- Users list page was improved (search, additional information) and you can now disable users.\n- Query editor improvements: additional keyboard shortcuts and support for searching in query text.\n- Visualizations improvements: option to select colors of pie chart sectors, X Axis type auto detect and option to format values, labels and tooltips.\n- Data Sources:\n  - Support for Yandex Metrika and AppMetrika.\n  - BigQuery: location property support and schema will load all tables now.\n  - Elasticsearch: stop sending source_content_type parameter which wasn't supported in older versions.\n- Started migrating the frontend codebase to React.\n\nAnd much more!\n\n🙏 Thanks to @kravets-levko, @arikfr, @ariarijp, @alison985, @kyoshidajp, @kocsmy, @denisov-vlad, @deecay, @yuua, @emtwo, @Pablohn26, @sieben, @atharvai, @matsumo, @tdawber, @innovia, @gabrieldutra, @coreyhuinker, @maxv, @sjakthol, @mtrbean and @washort who contributed to this release!\n\n### Added\n\n- #2712: Date/Time Range parameter type (@kravets-levko)\n- #2482: Add support for ChatWork Alert Destination. (@matsumo)\n- #2678: Explicit \"Add Parameter\" Button in Query Editor. (@kravets-levko)\n- #2513: Add location property to BigQuery data source settings. (@kyoshidajp)\n- #2616: Pie chart: support setting pie chart sector colors. (@kravets-levko)\n- #2697: Date/Time parameters: support for \"Now\" as default value. (@kravets-levko)\n- #2693: Enable search function in Query Editor. (@arikfr)\n- #2573: Tagging and favorites for Queries and Dashboards (@arikfr)\n- #2640: Keyboard shortcut to collapse query editor/schema browser (@kravets-levko)\n- #2674: Add support for the Chrome Logger extension (@arikfr)\n- #2653: Add redash db size to status page (@alison985)\n- #2669: Store Athena query id with result metadata (@tdawber)\n- #2546: Configuration for incorporating React components (@washort)\n- #2533: New datasource: Yandex Metrika & AppMetrika (@denisov-vlad)\n- #2536: Chart: formats for values, labels and tooltips (@kravets-levko)\n- #2560: Introduce Policy object (@arikfr)\n- #2380: Admin should be able to disable a user (@kravets-levko)\n- #2509: Show custom date format on settings page (@kyoshidajp)\n\n### Changed\n\n- #2715: Improve users list page (@arikfr)\n- #2710: Update Ant variables to fit Redash's style (@kocsmy)\n- #2709: Move format button next Add New Param button. (@arikfr)\n- #2664: Dashboard shows a spinner when query failed to load (@kravets-levko)\n- #2626: Show real status when loading cached query result (@kravets-levko)\n- #2663: Set column name implicitly when column name is blank (@ariarijp)\n- #2695: Improve Date/DateTime type parameters (@kravets-levko)\n- #2694: Block users with disposable email addresses (@arikfr)\n- #2687: YAML: changed load to safe_load (@denisov-vlad)\n- #2514: Update value parsing for google spreadsheets source (@atharvai)\n- #2570: fixes query pagination alignment (@alison985)\n- #2584: keep query result pagination out of scroll (@alison985)\n- #2647: Improve Script Query Runner (@ariarijp)\n- #2583: Query header improvements on widgets (@kocsmy)\n- #2671: Save some space (@kocsmy)\n- #2658: delaying schema filtering to improve responsiveness (@alison985)\n- #2648: Update datasource documentation links (@Pablohn26)\n- #2613: Improve Script Query Runner (@ariarijp)\n- #2619: data source sort case insensitive (@alison985)\n- #2604: Improve Google Spreadsheets Query Runner (@ariarijp)\n- #2542: Closes #2541: x-axis improvements. (@emtwo)\n- #2590: Remove redundant variables (@ariarijp)\n- #2585: Show data only mode: allow to add and delete visualizations (@kravets-levko)\n- #2549: Allow get_tables to see views and v10-style partitioned tables (@coreyhuinker)\n- #2568: sort datasources alphabetically (@alison985)\n- #2444: feat: show error if saml response cannot be parsed (@sjakthol)\n- #2554: Display name to be delete (@kyoshidajp)\n- #2510: Display confirmation dialog when deleting a item (@kyoshidajp)\n- #2518: Design improvements (@kocsmy)\n- #2520: Filter data sources in a data source input area (@kyoshidajp)\n\n### Fixed\n\n- #2722: Elasticsearch: Don't send source_content_type parameter. (@arikfr)\n- #2719: Remove closing input tags (@maxv)\n- #2458: Get all tables in the BigQuery (@kyoshidajp)\n- #2698: Make sure we return distinct data source values (@arikfr)\n- #2315: Fix: pyHive type matches (@yuua)\n- #2638: Dashboard stops rendering when adding widget with empty query (@kravets-levko)\n- #2610: Fix export query results output file name (@gabrieldutra)\n- #2574: commit query result to db before evaluating alerts (@mtrbean)\n- #2580: add break-word wrap to add/edit text box on dashboard (@alison985)\n- #2578: Fix connection error when you run \"create_tables\" (@ariarijp)\n- #2572: remove extra menu line if query is archived (@alison985)\n- #2526: Fix pivot hide control in dashboards (@deecay)\n- #2511: Fixing signed_out.html template (@kocsmy)\n- #2523: Frontend: fix boolean field with null value display as null. (@innovia)\n\n### Other\n\n- #2682: Add Zeit's now support to have preview builds for every PR (@arikfr)\n- #2668: Upgrade bootstrap script to Redash 4.0.1 (@ariarijp)\n- #2639: Add tests for SpreadSheets (@ariarijp)\n- #2635: Add tests for Query Results (@ariarijp)\n- #2537: Remove trailing semicolon (@sieben)\n\n## v4.0.1 - 2018-05-02\n\n### Added\n\n- Log user's screen resolution. @arikfr\n\n### Changed\n\n- [Redshift] fix the order of columns in the schema browser. @akiray03\n- Improve dashboard refresh UX: show previous data while refreshing. @arikfr\n\n### Fixed\n\n- Disable fork button to view_only users. @tonyjiangh\n- Hide overflowing data source and alert destination names. @kocsmy\n- Login pages were broken on mobile. @kocsmy\n- Cohort visualization wasn't rendering if value wasn't properly detected as date. @kravets-levko\n- Dashboard filters setting wasn't persisting. @arikfr\n- Display nulls and empty values as blank in table numeric fields. @chriszs\n- Date column on alerts page is labeled \"Created By\". @dbravender\n- Bootstrap script was breaking due to incompatability with pip 10. @ariarijp\n\n### Other\n\n- Updated README. @kocsmy\n\n## v4.0.0 - 2018-04-16\n\n### Added\n\n- MatterMost alert destination. @alon710\n- Full screen view on map visualizations. @deecay\n- Choropleth map visualization 🗺. @kravets-levko\n- Report Celery queue size. @arikfr\n- Load dashboard refresh rate from URL. @arikfr\n- Configuration for query refresh intervals. @arikfr\n\n### Changed\n\n- TreasureData: improve query failure message. @toru-takahashi\n- Update botocore version (fixes an issue with loading Athena tables). @arikfr\n- Changed Map visualization name to \"Map (Markers)\" to distinguish from the Choropleth one. @arikfr\n- Use MongoClient for ReplicaSet connections. @fmy\n- Update pymongo version to support newer MongoDB versions. @arikfr\n- Changed \"his\" to \"their\" in user creation form success message. @tnetennba3\n- Show friendly names in dynamic forms labels. @arikfr\n- Render safe HTML by default in tables to remain backward compatible. @arikfr\n- Apply time limit to alert status checking task. @arikfr\n- Plotly: increase Y value accuracy. @arikfr\n- close metadata database connection early in the execute query Celery task. @arikfr\n\n### Fixed\n\n- Query page layout gets messed up when clicking on \"cancel\" in \"Do you want to leave this page?\" dialog. @kravets-levko\n- docker-entrypoint broke for other database names than \"postgres\". @valentin2105\n- (BigQuery) UDF URI was used even if empty. @arikfr\n- Show correct Box Plot chart hover data. @deeccay\n- Fork button shows in data only view, but not working. @arikfr\n- Saving widget sends too much data to the server, sometimes making dashboard save fail. @arikfr\n- DynamoDB: always return counter as a number rather than string. @arikfr\n- MSSQL: UUID fields were detected as booleans. @arikfr\n- The whole dashboard page reloads when clicking on refresh. @arikfr\n- Line chart with category x-axis: when some values missing, wrong hints displayed on hover. @kravets-levko\n- Second Y-axis not displayed when stacking enabled. @kravets-levko\n- Widget with empty contents had extra 40px of white space (paddings of container). @kravets-levko\n- Add scrollbars to pivot table widgets. @kravets-levko\n- Multiple performance, usability and auto-height related fixes to the dashboard rendering engine (also switched to GridStack). @kravets-levko\n- Login form missing on LDAP logging page. @idalin\n- Empty state: show connect data source link only to admins. @arikfr\n- Dashboard \"dancing\" widgets (when auto-height enabled). @kravets-levko\n\n### Other\n\n- Webpack: ignore vim swap files. @deecay\n\n## v4.0.0-rc.1 - 2018-03-05\n\n### Added\n\n- Configuration for query refresh intervals.\n- [Prometheus] Support for range queries. @jubel-han\n- Extensions system based on Python entrypoints. @jezdez\n- Funnel visualization. @tonyjiangh\n- UI to edit allowed Google OAuth domains. @arikfr\n- Empty state for homepage, alerts, queries and dashboards pages. @kocsmy, @arikfr\n\n### Changed\n\n- Maintain widget's auto-height state until it's been resized by the user. @kravets-levko\n- Change default table viz width from 4 to 3 columns. @kravets-levko\n- When saving dashboard adding or removing widgets, save only modified widgets (with changed size and/or position). @kravets-levko\n- Don't allow disabling Password based login if no SSO is enabled. @arikfr\n- Always show login page, even if password based login disabled. @arikfr\n- Upgrade `sqlparse` to 0.2.4. @ariarijp\n- Make sure datetime/number columns in table visualization don't wrap. @kravets-levko\n- Explicitly set order of tabs in settings page. @kravets-levko\n- User can no longer change the type of a saved visualization. @kravets-levko\n- Update docker-compose.yml to restart postgres/redis containers `unless-stopped`. @benmanns\n- New default colors for chart visualizations. @kocsmy\n- Updated design of all the authentication pages (login, forgot password, etc). @kravets-levko\n\n### Fixed\n\n- Glue schemas with more than 100 tables were showing only first 100 tables. @jezdez\n- Long visualizations dind't render scrollbars on some browsers. @kravets-levko\n- When the dataset was returning some columns name as non strings, table couldn't be rendered. @kravets-levko\n- Missing logos for Prometheus and Snowflake. @kocsmy\n- Render correct link to LDAP login on login page. @arikfr\n- Sort widgets by column/row to make sure they are placed correctly. @arikfr\n- Public dashboards were not rendered due to Javascript error. @kravets-levko\n\n## v4.0.0-beta - 2018-02-14\n\n### Added\n\n- Massive update to the UI/UX of the whole application. @kocsmy, @kravets-levko, @arikfr\n- Flexible dashboard layout: resize widgets both vertically and horizonally. @kravets-levko\n- Configuration and new options for the table visualization. @kravets-levko\n- API to return internal usage events. @arikfr\n- Add an option to set a common prefix to the backend logs. @arikfr\n- [MongoDB] support nested fields in results. @arikfr\n- Cohort visualization: add options and fix rendering logic. @kravets-levko\n- Table visualization: `URL` column type. @kravets-levko\n- Table visualization: `Image` column type. @kravets-levko\n- [BigQuery] show amount of data scanned. @arikfr\n- Make dashboard refresh intervals configurable. @arikfr\n- Button to insert table/column name from schema into the query text. @kravets-levko\n- [Athena] show amount of data scanned. @washort\n- [Salesforce] Add setting to set the API version. @mayconbordin\n- UI for configuration options (auth, date format, etc). @arikfr\n- CLI command to create the root user. @kyoshidajp\n- [Redshift] support for loading late binding views in schema browser. @tonyjiangh\n- Show user's profile picture and load it from Google when using Google OAuth. @kyoshidajp\n- CockroachDB query runner. @yershalom\n- MAPD query runner. @cdessanti\n- Pie chart: show subplot titles. @deecay\n\n### Changed\n\n- Make trusted header authentication compatible with multiorg mode. @sjakthol\n- Update AWS RDS certificate bundle. @arikfr\n- Add Prometheus to the default query runners list. @arikfr\n- [Athena] update botocore version to support Glue. @arikfr\n- Support for quotes passwords in the Redis and Postgres connection URLs. @javier-sanz\n- Change the way static assets are served. @arikfr\n- [BigQuery] Properly handle RECORD fields in schema (show the nested fields). @arikfr\n- Upgrade to Celery 3.1.25 in preparation to Celery 4. @jezdez\n- Remove loading indicator when updating query parameter value (before executing). @kravets-levko\n- Improvements to the chart visualization (see #2156 for details). @kravets-levko\n- Start searching for queries immediately instead of waiting for 3 characters. @kyoshidajp\n- Make all references to Elasticsearch be properly capitalized. @kakakakakku\n- Use PostgreSQL's FTS/tsvector type for query searches. @jezdez\n- [Redshift] Make sslmode configurable. @sjakthol\n- Allow passing options to tests Docker command. @arikfr\n- Improve error handling mechanism and make error pages friendlier. @kravets-levko, @kocsmy, @arikfr\n- Make LDAP settings names more consistent. @gramakri\n- [Oracle] support for non SELECT queries. @doddjc21\n- Admin can no longer remove themselves from the built-in groups. @negibouze\n- Update pie charts font style. @deecay\n- Upgrade psycopg2 for support PostgreSQL 10.0. @kyoshidajp\n- Convert all stylesheets to LESS. @kravets-levko\n- [Elasticsearch] Collect doc_count field from aggregation. @arjan\n- Switch to pytest. @jezdez\n- Ensure email is case-insensitive. @miketheman\n- [Redshift] change default SSL mode to prefer. @arikfr\n- Return Redis memory usage in bytes for easier monitoring. @kakakakakku\n- create_db command in docker-entrypoint waits for Postgres to become available first. @ariarijp\n- [Elasticsearch] set source_content_type on ES queries to support Elasticsearch 6.0. @alexdrans\n- Show `-` instead of `Invalid Date` for null values given to `dateTime` filter. @kyoshidajp\n\n### Fixed\n\n- Parameters list was resetting when adding a new parameter. @arikfr\n- Don't escape values in non-html columns. @kravets-levko\n- Commit SAML user group assignment to the database. @sjakthol\n- Update correct settings in SAML settings form. @sjakthol\n- Fix Google OAuth login in MULTIORG mode. @shinji19\n- Strip annotation from query when path is specified in Script query runner. @ariarijp\n- Fix filter headers when there are multiple rows of filters. @kocsmy\n- Update query version when changing query data source. @washort\n- Fix upgrade script to support changes in CircleCI. @rgjodekerken\n- Don't show error indicators after submitting the user form. @bamboo-yujiro\n- [Query Results] support unicode column names. @tonyjiangh\n- Issue with Google OAuth caused by old pyOpenSSL version. @crooy\n- Fix layout of outdated queries admin view. @bamboo-yujiro\n- User can't download query results of a new query. @arikfr\n- Typo in celery logs format. @ariarijp\n- Handling whitespace characters in Query Results data source. @ariarijp\n- [MySQL] Close cursor when cancellig the query. @jasonsmithj\n\n## v3.0.0 - 2017-11-13\n\n### Added\n\n- Query Result data source (run queries on query results).\n- Athena: option to load schema from Glue catalog. @myouju\n- Allow running any command inside the container via the Docker entrypoint script. @jezdez\n- Make invitation token max age configurable. @hhamalai\n- Redshift: add support for the new ACM root CA.\n- Redshift: support for Spectrum (external) tables. @atharvai\n- MongoDB: option to set allowDiskUse in queries.\n- Option to disable SQLAlchemy connection pool.\n- Option to set a time limit on adhoc queries.\n- Option to disable sending an invite to a new user.\n- Azure SQL Data Warehouse query runner. @kitsuyui\n- Prometheus query runner. @yershalom\n- Option to set the Flask-Limiter storage engine.\n- Option to set UnicodeWriter's error handling method. @fan-t-endo\n- PostgreSQL: SSL configuration option. @TylerBrock\n- Counter visualization: additional formatting options. @deecay\n- Query based drop down parameter. @rohithmenon\n- MySQL: multiple queries support & connection timeout.\n- Ability to select all in multi-filter. @Posnet\n- LDAP (Active Directory) support. @amarjayr\n\n### Changed\n\n- Copy parameters when forking a query. @kyoshidajp\n- Prevent using Query API Key with refresh API (previously it was just failing).\n- Reduce boilerplate in frontend code.\n- Set auto focus in first input items. @kyoshidajp\n- Update gunicorn to latest version.\n- Make log format configurable.\n- Sort series by name.\n- Allow setting test file with Docker test run. @meinac\n- Use outdated queries count stored already in Redis.\n- Show links based on permissions the user have.\n- Cassandra: update driver version. @yershalom\n- Docker-Compose: update configuration to always restart services. @muddydixon\n- Modernize Python 2 code to get ready for Python 3. @cclauss\n- Cohort visualization: make it friendlier to use by better handle gaps in data, so it's easier to generate the data needed.\n- Use a different markdown library. @alexmuller\n- Salesforce: improve error messages we receive from the API. @akiray03\n- Custom JS code visualization improvements. @deecay\n- DQL: Update version to 0.5.24. @aterreno\n- Cassandra: get_schema support for both C\\* 2.x and 3.x, support for SortedSet type serialization. (@mfouilleul))\n- Replace deprecated ng-annotate with babel plugin. @44px\n- Update Python dependencies to recent versions. @alison985\n- Bootstrap script: create /opt/redash directory only if it doesn't exist. @isomura\n- Bootstrap script: make use of REDASH_BASE_PATH variable in setup script. @sylvain\n\n### Fixed\n\n- Require full data source access to fork a query.\n- API key of one query could be used to get results of another one.\n- Delete group id from user object when deleting the group. @kyoshidajp\n- Sorting of X axis wasn't working for Box plot type visualizations. @deecay\n- Exporting query results as excel was failing when one of the columns had array data. @kyoshidajp\n- Show query editor's Archive/Publish Query drop-down only on saved queries. @cyriac\n- Move misplaced configuration in docker-compose.production.yml. @yutannihilation\n- MySQL: support UTF8 schema.\n- TreasureData queries were failing when returning 0 rows.\n- Use series color for Boxplot. @deecay\n- Revoke permission should respect to given grantee and access type. @meinac\n- Fixed eslint \"Cannot read property 'length' of undefined\" error. @kravets-levko\n- Don't crash query editor when there are unclosed curly brackets.\n- Error value in charts wasn't displayed if it was 0.\n- Prevent line breaks in EditInPlace description when using Firefox. @alexmuller\n- Queries#all_queries was sometimes returning wrong number of queries.\n- record_event fails for API events.\n- Cancel button on tasks admin page was broken.\n- Remove deprecated cx_Oracle types. @queeno\n- Textbox widgets were updating their value even when editor was cancelled. @alison985\n- Collaborators couldn't edit visualizations or schedule.\n- Use series color for error bar. @deecay\n- Upgrade script was using the wrong restart command on new AMIs.\n\n## v2.0.1 - 2017-10-22\n\nThis is a patch release, that adds support for Redshift ACM certificates (see #2044 for details).\n\n## v2.0.0 - 2017-08-08\n\n### Added\n\n- [Cassandra] Support for UUID serializing and setting protocol version. @mfouilleul\n- [BigQuery] Add maximumBillingTier to BigQuery configuration. @dotneet\n- Add the propertyOrder field to specify order of data source settings. @rmakulov\n- Add Plotly based Boxplot visualization. @deecay\n- [Presto] Add: query cancellation support. @fbertsch\n- [MongoDB] add \\$oids JSON extension.\n- [PostgreSQL] support for loading materialized views in schema.\n- [MySQL] Add option to hide SSL settings.\n- [MySQL] support for RDS MySQL and SSL.\n- [Google Analytics] support for mcf queries & better errors.\n- Add: static enum parameter type. @rockwotj\n- Add: option to hide pivot table controls. @deecay\n- Retry reload of query results if it had an error.\n- [Data Sources] Add: MemSQL query runner. @alexanderlz\n- \"Dumb\" recents option (see #1779 for details)\n- Athena: direct query runner using the instead of JDBC proxy. @laughingman7743\n- Optionally support parameters in embeds. @ziahamza\n- Sorting ability in alerts view.\n- Option to change default encoding of CSV writer. @yamamanx\n- Ability to set dashboard level filters from UI.\n- CLI command to open IPython shell.\n- Add link to query page from admin view. @miketheman\n- Add the option to write logs to STDOUT instead of STDERR. @eyalzek\n- Add limit parameter to tasks API. @alexpekurovsky\n- Add SQLAlchemy pool settings.\n- Support for category type y axis.\n- Add 12 & 24 hours refresh rate option to dashboards.\n\n### Changed\n\n- Upgrade Google API client library for all Google data sources. @ahamino\n- [JIRA JQL] change default max results limit from 50 to 1000. @jvanegmond\n- Upgrade to newer Plotly version. @deecay\n- [Athena] Configuration flag to disable query annotations for Athena. @suemoc\n- Ignore extra columns in CSV output. @alexanderlz\n- [TreasureData] improve error handling and upgrade client.\n- [InfluxDB] simpler test connection query (show databases requires admin).\n- [MSSQL] Mark integers as decimals as well, as sometimes decimal columns being returned\n  with integer column type.\n- [Google Spreadsheets] add timeout to requests.\n- Sort dashboards list by name. @deecay\n- Include Celery task name in statsd metrics.\n- Don't include paused datasource's queries in outdated queries count.\n- Cohort: handle the case where the value/total might be strings.\n- Query results: better type guessing on the client side.\n- Counter: support negative indexes to iterate from the end of the results.\n- Data sources and destinations configuration: change order of name and type (type first now).\n- Show API Key in a modal dialog instead of alert.\n- Sentry: upgrade client version.\n- Sentry: don't install logging hook.\n- Split refresh schemas into separate tasks and add a timeout.\n- Execute scheduled queries with parameters using their default value.\n- Keep track of last query execution (including failed ones) for scheduling purposes.\n- Same view for input on search result page as in header. @44px\n- Metrics: report endpoints without dots for metrics.\n- Redirect to / when org not found.\n- Improve parameters label placement. @44px\n- Auto-publish queries when they are named (with option to disable; #1830).\n- Show friendly error message in case of duplicate data source name.\n- Don't allow saving dashboard with empty name.\n- Enable strict checking for Angular DI.\n- Disable Angular debug info (should improve performance).\n- Update to Webpack 2. @44px\n- Remove /forgot endpoint if REDASH_PASSWORD_LOGIN_ENABLED is false. @amarjayr\n- Docker: make Gunicorn worker count configurable. @unixwitch\n- Snowflake support is no longer enabled by default.\n- Enable memory optimization for Excel exporter.\n\n### Fixed\n\n- Fix: set default values in options to enable 'default: True' for checkbox. @rmakulov\n- Support MULTI_ORG again.\n- [Google Spreadsheets] handle distant future dates.\n- [SQLite] better handle utf-8 error messages.\n- Fix: don't remove locks for queries with task status of PENDING.\n- Only split columns with \\_\\_/:: that end with filter/MultiFilter.\n- Alert notifications fail (sometime) with a SQLAlchemy error.\n- Safeguard against empty query results when checking alert status. @danielerapati\n- Delete data source doesn't work when query results referenced by queries.\n- Fix redirect to /setup on the last setup step. @44px\n- Cassandra: use port setting in connection options. @yershalom\n- Metrics: table name wasn't found for count queries.\n- BigQuery wasn't loading due to bad import.\n- DynamicForm component was inserting empty values.\n- Clear null values from data source options dictionary.\n- /api/session API call wasn't working when multi tenancy enabled\n- If column had no type it would use previous column's type.\n- Alert destination details were not updating.\n- When setting rearm on a new alert, it wasn't persisted.\n- Salesforce: sandbox parameter should be optional. @msnider\n- Alert page wasn't properly linked from alerts list. @alison985\n- PostgreSQL passwords with spaces were not supported. (#1056)\n- PivotTable wasn't updating after first save.\n\n## v1.0.3 - 2017-04-18\n\n### Fixed\n\n- Fix: sort by column no longer working.\n\n## v1.0.2 - 2017-04-18\n\n### Fixed\n\n- Fix: favicon wasn't showing up.\n- Fix: support for unicode in dashboard tags. @deecay\n- Fix: page freezes when rendering large result set.\n- Fix: chart embeds were not rendering in PhantomJS.\n\n## v1.0.1 - 2017-04-02\n\n### Added\n\n- Add: bubble charts support.\n- Add \"Refresh Schema\" button to the datasource @44px\n- [Data Sources] Add: ATSD query runner @rmakulov\n- [Data Sources] Add: SalesForce query runner @msnider\n- Add: scheduled query backoff in case of errors @washort\n- Add: use results row count as the value for the counter visualization. @deecay\n\n### Changed\n\n- Moved CSV/Excel query results generation code to models. @akiray03\n- Add support for filtered data in Pivot table visualization @deecay\n- Friendlier labels for archived state of dashboard/query\n\n### Fixed\n\n- Fix: optimize queries to avoid N+1 queries.\n- Fix: percent stacking math was wrong. @spasovski\n- Fix: set query filter to match value from URL query string. @benmargo\n- [Clickhouse] Fix: detection of various data types. @denisov-vlad\n- Fix: user can't edit their own alert.\n- Fix: angular minification issue in textbox editor and schema browser.\n- Fixes to better support IE11 (add polyfill for Object.assign and show vertical scrollbar). @deecay\n- Fix: datetime parameters were not using a date picker.\n- Fix: Impala schema wasn't loading.\n- Fix: query embed dialog close button wasn't working @r0fls\n- Fix: make errors from Presto runner JSON-serializable @washort\n- Fix: race condition in query task status reporting @washort\n- Fix: remove \\$\\$hashKey from Pivot table\n- Fix: map visualization had severe performance issue.\n- Fix: pemrission dialog wasn't rendering.\n- Fix: word cloud visualization didn't show column names.\n- Fix: wrong timestamps in admin tasks page.\n- Fix: page header wasn't updating on dashboards page @MichaelJAndy\n- Fix: keyboard shortcuts didn't work in parameter inputs\n\n### Other\n\n- Change default job expiry times to: job lock expire after 12 hours (previously: 6 hours) and Celery task result object expire after 4 hours (previously: 1 hour). @shimpeko\n\n## v1.0.0-rc.2 - 2017-02-22\n\n### Changed\n\n- [#1563](https://github.com/getredash/redash/pull/1563) Send events to webhook as JSON with a schema.\n- [#1601][presto] friendlier error messages. (@aslotnick)\n- Move the query runner unavailable log message to be DEBUG level instead of WARNING, as it was mainly confusing people.\n- Remove \"Send to Cloud\" button from Plotly based visualizations.\n- Change Plotly's default hover mode to \"Compare\".\n- [#1612] Change: Improvements to the dashboards list page.\n\n### Fixed\n\n- [#1564] Fix: map visualization column picker wasn't populated. (@janusd)\n- [#1597][sql server] Fix: schema wasn't loading on case sensitive servers. (@deecay)\n- Fix: dashbonard owner couldn't edit his dashboard.\n- Fix: toggle_publish event wasn't logged properly.\n- Fix: events with API keys were not logged.\n- Fix: share dashboard dialog was broken after code minification.\n- Fix: public dashboard endpoint was broken.\n- Fix: public dashboard page was broken after code minification.\n- Fix: visualization embed page was broken after code minification.\n- Fix: schema browser has dark background.\n- Fix: Google button missing on invite page.\n- Fix: global parameters don't render on dashboards with text boxes.\n- Fix: sunburst / Sankey visualizations have bad data.\n- Fix: extra whitespace created by the filters component.\n- Fix: query results cleanup task was trying to delete query objects.\n- Fix: alert subscriptions were not triggered.\n- [DynamoDB] Fix: count(\\*) queries were broken. (@kopanitsa))\n- Fix: Redash is using too many database connections.\n- Fix: download links were not working in dashboards.\n- Fix: the first selection in multi filters was broken in dashboards.\n\n### Other\n\n- [#1555] Change sourcemaps to generate a sourcemap per module. (@44px)\n- [#1570] Fix Docker Compose configuration for nginx. (@btmc)\n- [#1582] Update Dockerfile to build frontend assets and update the folder ownership.\n- Dockerfile: change the uid of the redash user to match host user uid.\n- Update npm-shrinkwrap.json file to use http proctocol instead of git. (@deecay)\n\n## v1.0.0-rc.1 - 2017-01-31\n\nThis version has two big changes behind the scenes:\n\n- Refactor the frontend to use latest (at the time) Angular version (1.5) along with better frontend pipeline based on)\n  WebPack.\n- Refactor the backend code to use SQLAlchemy and Alembic, for easier migrations/upgrades.)\n\nAlong with that we have many fixes, additions, new data sources (Google Analytics, ClickHouse, Amazon Athena, Snowflake)\nand fixes to the existing ones (mainly ElasticSearch and Cassandra).\n\nWhen upgrading make sure to upgrade from version 0.12.0 and update your .env file:\n\n1. If you have local PostreSQL database, you will need to update the URL from `postgresql://redash` to `postgresql:///redash`.\n2. Remove the `REDASH_STATIC_ASSETS_PATH` definition.\n\nMake sure to make these changes before running upgrade as otherwise it will fail.\n\nWe're releasing a new upgrade script -- see [here](https://redash.io/help-onpremise/maintenance/how-to-upgrade-redash.html) for details.\n\n### Added\n\n- [#1546](https://github.com/getredash/redash/pull/1546) Add: API docstrings (@washort)\n- [#1504](https://github.com/getredash/redash/pull/1504) Add: global parameters for dashboards (Tyler Rockwood)\n- [#1508](https://github.com/getredash/redash/pull/1508) [Jira JQL] Add: support custom JIRA fields and enhance value mapping (@sseifert)\n- [#1530](https://github.com/getredash/redash/pull/1530) Add: Docker based developer workflow (Arik Fraimovich)\n- [#1515](https://github.com/getredash/redash/pull/1515) [Python] Add: get_source_schema method (Vladislav Denisov)\n- [#1512](https://github.com/getredash/redash/pull/1512) [Python] Add: define more safe_builtins (Vladislav Denisov)\n- [#1513](https://github.com/getredash/redash/pull/1513) Add: get_by_id & get_by_name methods for Query and DataSource classes (Vladislav Denisov)\n- [#1482](https://github.com/getredash/redash/pull/1482) [Cassandra] Add: schema browser support & explicit protocol version (@yershalom)\n- [#1488](https://github.com/getredash/redash/pull/1488) [Data Sources] Add: Snowflake query runner (@arikfr)\n- [#1479](https://github.com/getredash/redash/pull/1479) [ElasticSearch] Add: enable schema browser (@adamlwgriffiths)\n- [#1475](https://github.com/getredash/redash/pull/1475) [Cassnadra] Added set_keyspace for easier query cassandra (@yershalom)\n- [#1468](https://github.com/getredash/redash/pull/1468) [Datasources] Add: Amazon Athena query runner (@arikfr)\n- [#1433](https://github.com/getredash/redash/pull/1433) [Charts] Add: errors bands in graphs (@luke14free)\n- [#1405](https://github.com/getredash/redash/pull/1405) [Datasources] Add: simple Google Analytics query runner (@denisov-vlad)\n- [#1409](https://github.com/getredash/redash/pull/1409) [Datasources] Add: Add query runner for Yandex ClickHouse (@denisov-vlad)\n- [#1373](https://github.com/getredash/redash/pull/1373) Add: rate limit the login page (@AntoineAugusti)\n\n### Changed\n\n- [#1549](https://github.com/getredash/redash/pull/1549) Change: disable version counter for queries: (Arik Fraimovich)\n- [#1548](https://github.com/getredash/redash/pull/1548) Change: improve UI in small resolution: (Arik Fraimovich)\n- [#1547](https://github.com/getredash/redash/pull/1547) Change: Improve drafts UX (Arik Fraimovich)\n- [#1540](https://github.com/getredash/redash/pull/1540) [MySQL] Change: faster retrieval of schema (Yaning Zhu)\n- [#1517](https://github.com/getredash/redash/pull/1517) [ClickHouse] Change: convert UInt64 columns to integer type (Vladislav Denisov)\n- [#1528](https://github.com/getredash/redash/pull/1528) [Vertica] Change: set longer read_timeout (lab79)\n- [#1522](https://github.com/getredash/redash/pull/1522) Change: move package.json/webpack.config to root directory (Arik Fraimovich)\n- [#1514](https://github.com/getredash/redash/pull/1514) [Athena] Change: enable query annotations (Gaurav Awadhwal)\n- [#1525](https://github.com/getredash/redash/pull/1525) Change: update amazon linux bootstrap.sh (Karri Niemelä)\n- [#1509](https://github.com/getredash/redash/pull/1509) [Presto/Athena] Change: remove special rule around public schema (@GAwadhwalAtlassian)\n- [#1485](https://github.com/getredash/redash/pull/1485) Close #1453: more minimal notification of draft status for query/dashboard (@arikfr)\n- [#1474](https://github.com/getredash/redash/pull/1474) [Cassandra] Change: test connection query (@yershalom)\n- [#1464](https://github.com/getredash/redash/pull/1464) [Clickhouse] Change: use UTF-8 encoding for POST data (@jaykelin)\n- [#1417](https://github.com/getredash/redash/pull/1417) Change: Replace Peewee with SQLAlchemy/Alembic (@arikfr, @washort)\n- [#1458](https://github.com/getredash/redash/pull/1458) Change: switch from flask_script to click, add CLI unit tests and upgrade Flask version (@washort)\n- [#1438](https://github.com/getredash/redash/pull/1438) [ElasticSearch] Change: use simplejson for better error descriptions (@adamlwgriffiths)\n- [#1435](https://github.com/getredash/redash/pull/1435) Whitelisting more builtin primitives (@mattrobenolt)\n- [#1376](https://github.com/getredash/redash/pull/1376) Change: upgrade the frontend stack (@arikfr, @luke14free)\n- [#1429](https://github.com/getredash/redash/pull/1429) Add missing error check from #1402 (@adamlwgriffiths)\n- [#1256](https://github.com/getredash/redash/pull/1256) Change: when forking a query, copy all visualizations (@ninneko)\n- [#1421](https://github.com/getredash/redash/pull/1421) Change: [BigQuery] only specify useLegacySQL is it's True (@arikfr)\n- [#1353](https://github.com/getredash/redash/pull/1353) Change: make draft status for queries and dashboards toggleable (@washort)\n- [#1419](https://github.com/getredash/redash/pull/1419) Change: use redash.utils.json_dumps instead of json.dumps in Python query runner (@ehfeng)\n- [#1402](https://github.com/getredash/redash/pull/1402) Change: correctly propagate ElasticSearch errors to the UI (@adamlwgriffiths)\n- [#1371](https://github.com/getredash/redash/pull/1371) Change: display user's password reset link to the admin when mail server disabled (@vitorbaptista)\n\n### Fixed\n\n- [#1551](https://github.com/getredash/redash/pull/1551) Fix: flask-admin - exclude created_at/updated_at so models can be saved (Arik Fraimovich)\n- [#1545](https://github.com/getredash/redash/pull/1545) [ElasticSearch] Fix: query fails when properties key is missing (hgs847825)\n- [#1526](https://github.com/getredash/redash/pull/1526) [ElasticSearch] Fix for #1521 (Adam Griffiths)\n- [#1521](https://github.com/getredash/redash/pull/1521) [ElasticSearch] Fix: wrong variable name. (Arik Fraimovich)\n- [#1497](https://github.com/getredash/redash/pull/1497) Fix #16: when updating dashboard name refresh dashboards dropdown (@arikfr)\n- [#1491](https://github.com/getredash/redash/pull/1491) Fix: DynamoDB test connection was broken (@arikfr)\n- [#1487](https://github.com/getredash/redash/pull/1487) Fix #1432: delete visualization sends full visualization body instead‚Ä¶ (@arikfr)\n- [#1484](https://github.com/getredash/redash/pull/1484) Fix #1457: sort was using the string value (@arikfr)\n- [#1478](https://github.com/getredash/redash/pull/1478) [ElasticSearch] Fix: connection test was always succesfful (@adamlwgriffiths)\n- [#1440](https://github.com/getredash/redash/pull/1440) Fix: API errors for dashboards with invalid layout data (@whummer)\n- [#1427](https://github.com/getredash/redash/pull/1427) [Cassandra] Fix: remove reference to non existing Error class (@arikfr)\n- [#1423](https://github.com/getredash/redash/pull/1423) [Cassandra] Fix: cassandra.cluster.Error wasn't imported (@arikfr)\n- Fix #1001: queries with a column named \"length\" were not rendered.\n- Fix #578: dashboard list not scrollable.\n- Fix #137: add direction indicators when sorting query results.\n\n## v0.12.0 - 2016-11-20\n\n### Added\n\n- 61fe16e #1374: Add: allow '\\*' in REDASH_CORS_ACCESS_CONTROL_ALLOW_ORIGIN (Allen Short)\n- 2f09043 #1113: Add: share modify/access permissions for queries and dashboard (whummer)\n- 3db0eea #1341: Add: support for specifying SAML nameid-format (zoetrope)\n- b0ecd0e #1343: Add: support for local SAML metadata file (zoetrope)\n- 0235d37 #1335: Add: allow changing alert email subject. (Arik Fraimovich)\n- 2135dfd #1333: Add: control over y axis min/max values (Arik Fraimovich)\n- 49e788a #1328: Add: support for snapshot generation service (Arik Fraimovich)\n- 229ca6c #1323: Add: collect runtime metrics for Celery tasks (Arik Fraimovich)\n- 931a1f3 #1315: Add: support for loading BigQuery schema (Arik Fraimovich)\n- 39b4f9a #1314: Add: support MongoDB SSL connections (Arik Fraimovich)\n- ca1ca9b #1312: Add: additional configuration for Celery jobs (Arik Fraimovich)\n- fc00e61 #1310: Add: support for date/time with seconds parameters (Arik Fraimovich)\n- d72a198 #1307: Add: API to force refresh data source schema (Arik Fraimovich)\n- beb89ec #1305: Add: UI to edit dashboard text box widget (Kazuhito Hokamura)\n- 808fdd4 #1298: Add: JIRA (JQL) query runner (Arik Fraimovich)\n- ff9e844 #1280: Add: configuration flag to disable scheduled queries (Hirotaka Suzuki)\n- ef4699a #1269: Add: Google Drive federated tables support in BigQuery query runner (Kurt Gooden)\n- 2eeb947 #1236: Add: query runner for Cassandra and ScyllaDB (syerushalmy)\n- 10b398e #1249: Add: override slack webhook parameters (mystelynx)\n- 2b5e340 #1252: Add: Schema loading support for Presto query runner (using information_schema) (Rohan Dhupelia)\n- 2aaf5dd #1250: Add: query snippets feature (Arik Fraimovich)\n- 8d8af73 #1226: Add: Sankey visualization (Arik Fraimovich)\n- a02edda #1222: Add: additional results format for sunburst visualization (Arik Fraimovich)\n- 0e70188 #1213: Add: new sunburst sequence visualization (Arik Fraimovich)\n- 9a6d2d7 #1204: Add: show views in schema browser for Vertica data sources (Matthew Carter)\n- 600afa5 #1138: Add: ability to register user defined function (UDF) resources for BigQuery DataSource/Query (fabito)\n- b410410 #1166: Add: \"every 14 days\" refresh option (Arik Fraimovich)\n- 906365f #967: Add: extend ElasticSearch query_runner to support aggregations (lloydw)\n\n### Changed\n\n- 2de4aa2 #1395: Change: switch to requests in URL query runner (Arik Fraimovich)\n- db1a941 #1392: Change: Update documentation links to point at the new location. (Arik Fraimovich)\n- 002f794 #1368: Change: added ability to disable auto update in admin views (Arik Fraimovich)\n- aa5d14e #1366: Change: improve error message for exception in the Python query runner (deecay)\n- 880627c #1355: Change: pass the user object to the run_query method (Arik Fraimovich)\n- 23c605b #1342: SAML: specify entity id (zoetrope)\n- 015b1dc #1334: Change: allow specifying recipient address when sending email test message (Arik Fraimovich)\n- 39aaa2f #1292: Change: improvements to map visualization (Arik Fraimovich)\n- b22191b #1332: Change: upgrade Python packages (Arik Fraimovich)\n- 23ba98b #1331: Celery: Upgrade Celery to more recent version. (Arik Fraimovich)\n- 3283116 #1330: Change: upgrade Requests to latest version. (Arik Fraimovich)\n- 39091e0 #1324: Change: add more logging and information for refresh schemas task (Arik Fraimovich)\n- 462faea #1316: Change: remove deprecated settings (Arik Fraimovich)\n- 73e1837 #1313: Change: more flexible column width calculation (Arik Fraimovich)\n- e8eb840 #1279: Change: update bootstrap.sh to support Ubuntu 16.04 (IllusiveMilkman)\n- 8cf0252 #1262: Change: upgrade Plot.ly version and switch to smaller build (Arik Fraimovich)\n- 0b79fb8 #1306: Change: paginate queries page & add explicit urls. (Arik Fraimovich)\n- 41f99f5 #1299: Change: send Content-Type header (application/json) in query results responses (Tsuyoshi Tatsukawa)\n- dfb1a20 #1297: Change: update Slack configuration titles. (Arik Fraimovich)\n- 8c1056c #1294: Change: don't annotate BigQuery queries (Arik Fraimovich)\n- a3cf92e #1289: Change: use key_as_string when available (ElasticSearch query runner) (Arik Fraimovich)\n- e155191 #1285: Change: do not display Oracle tablespace name in schema browser (Matthew Carter)\n- 6cbc39c #1282: Change: deduplicate Google Spreadsheet columns (Arik Fraimovich)\n- 4caf2e3 #1277: Set specific version of cryptography lib (Arik Fraimovich)\n- d22f0d4 #1216: Change: bootstrap.sh - use non interactive dist-upgrade (Atsushi Sasaki)\n- 19530f4 #1245: Change: switch from CodeMirror to Ace editor (Arik Fraimovich)\n- dfb92db #1234: Change: MongoDB query runner set DB name as mandatory (Arik Fraimovich)\n- b750843 #1230: Change: annotate Presto queries with metadata (Noriaki Katayama)\n- 5b20fe2 #1217: Change: install libffi-dev for Cryptography (Ubuntu setup script) (Atsushi Sasaki)\n- a9fac34 #1206: Change: update pymssql version to 2.1.3 (kitsuyui)\n- 5d43cbe #1198: Change: add support for Standard SQL in BigQuery query runner (mystelynx)\n- 84d0c22 #1193: Change: modify the argument order of moment.add function call (Kenya Yamaguchi)\n\n### Fixed\n\n- d6febb0 #1375: Fix: Download Dataset does not work when not logged in (Joshua Dechant)\n- 96553ad #1369: Fix: missing format call in Elasticsearch test method (Adam Griffiths)\n- c57c765 #1365: Fix: compare retrieval times in UTC timezone (Allen Short)\n- 37dff5f #1360: Fix: connection test was broken for MySQL (ichihara)\n- 360028c #1359: Fix: schema loading query for Hive was wrong for non default schema (laughingman7743)\n- 7ee41d4 #1358: Fix: make sure all calls to run_query updated with new parameter (Arik Fraimovich)\n- 0d94479 #1329: Fix: Redis memory leak. (Arik Fraimovich)\n- 7145aa2 #1325: Fix: queries API was doing N+1 queries in most cases (Arik Fraimovich)\n- cd2e927 #1311: Fix: BoxPlot visualization wasn't rendering on a dashboard (Arik Fraimovich)\n- a562ce7 #1309: Fix: properly render checkboxes in dynamic forms (Arik Fraimovich)\n- d48192c #1308: Fix: support for Unicode columns name in Google Spreadsheets (Arik Fraimovich)\n- e42f93f #1283: Fix: schema browser was unstable after opening a table (Arik Fraimovich)\n- 170bd65 #1272: Fix: TreasureData get_schema method was returning array instead of string as column name (ariarijp)\n- 4710c41 #1265: Fix: refresh modal not working for unsaved query (Arik Fraimovich)\n- bc3a5ab #1264: Fix: dashboard refresh not working (Arik Fraimovich)\n- 6202d09 #1240: Fix: when shared dashboard token not found, return 404 (Wesley Batista)\n- 93aac14 #1251: Fix: autocomplete went crazy when database has no autocomplete. (Arik Fraimovich)\n- b8eca28 #1246: Fix: support large schemas in schema browser (Arik Fraimovich)\n- b781003 #1223: Fix: Alert: when hipchat Alert.name is multibyte character, occur error. (toyama0919)\n- 0b928e6 #1227: Fix: Bower install fails in vagrant (Kazuhito Hokamura)\n- a411af2 #1232: Fix: don't show warning when query string (parameters value) changes (Kazuhito Hokamura)\n- 3dbb5a6 #1221: Fix: sunburst didn't handle all cases of path lengths (Arik Fraimovich)\n- a7cc1ee #1218: Fix: updated result not being saved when changing query text. (Arik Fraimovich)\n- 0617833 #1215: Fix: email alerts not working (Arik Fraimovich)\n- 78f65b1 #1187: Fix: read only users receive the permission error modal in query view (Arik Fraimovich)\n- bba801f #1167: Fix the version of setuptools on bootstrap script for Ubuntu (Takuya Arita)\n- ce81d69 #1160: Fix indentation in docker-compose-example.yml (Hirofumi Wakasugi)\n- dd759fe #1155: Fix: make all configuration values of Oracle required (Arik Fraimovich)\n\n### Docs\n\n- a69ee0c #1225: Fix: RST formatting of the Vagrant documentation (Kazuhito Hokamura)\n- 03837c0 #1242: Docs: add warning re. quotes on column names and BigQuery (Ereli)\n- 9a98075 #1255: Docs: add documentation for InfluxDB (vishesh92)\n- e0485de #1195: Docs: fix typo in maintenance page title (Antoine Augusti)\n- 7681d3e #1164: Docs: update permission documentation (Daniel Darabos)\n- bcd3670 #1156: Docs: add SSL parameters to nginx configuration (Josh Cox)\n\n## v0.11.1.b2095 - 2016-08-02\n\nThis is a hotfix release, which fixes an issue with email alerts in v0.11.0.\n\n## v0.11.0.b2016 - 2016-07-03\n\nThe main features of this release are:\n\n- Alert Destinations: ability to define multiple destinations for alert notifications (currently implemented: HipChat, Slack, Webhook and email).\n- The long-awaited UI for query parameters (see example in #1069).\n\nAlso, this release includes numerous smaller features, improvements, and bug fixes.\n\nA big thank you goes to all who contributed code and documentation in this release: @AntoineAugusti, @James226, @adamlwgriffiths, @alexdebrie, @anthony-coble, @ariarijp, @dheerajrav, @edwardsharp, @machira, @nabilblk, @ninneko, @ordd, @tomerben, @toru-takahashi, @vishesh92, @vorakumar and @whummer.\n\n### Added\n\n- d5e5b24 #1136: Feature: add --org option to all relevant CLI commands. (@adamlwgriffiths)\n- 87e25f2 #1129: Feature: support for JSON query formatting (Mongo, ElasticSearch) (@arikfr)\n- 6bb2716 #1121: Show error when failing to communicate with server (@arikfr)\n- f21276e #1119: Feature: add UI to delete alerts (@arikfr)\n- 8656540 #1069: Feature: UI for query parameters (@arikfr)\n- 790128c #1067: Feature: word cloud visualization (@anthony-coble)\n- 8b73a2b #1098: Feature: UI for alert destinations & new destination types (@alexdebrie)\n- 1fbeb5d #1092: Add Heroku support (@adamlwgriffiths)\n- f64622d #1089: Add support for serialising UUID type within MSSQL #961 (@James226)\n- 857caab #1085: Feature: API to pause a data source (@arikfr)\n- 214aa3b #1060: Feature: support configuring user's groups with SAML (@vorakumar)\n- e20a005 #1007: Issue#1006: Make bottom margin editable for Chart visualization (@vorakumar)\n- 6e0dd2b #1063: Add support for date/time Y axis (@tomerben)\n- b5a4a6b #979: Feature: Add CLI to edit group permissions (@ninneko)\n- 6d495d2 #1014: Add server-side parameter handling for embeds (@whummer)\n- 5255804 #1091: Add caching for queries used in embeds (@whummer)\n\n### Changed\n\n- 0314313 #1149: Presto QueryRunner supports tinyint and smallint (@toru-takahashi)\n- 8fa6fdb #1030: Make sure data sources list ordered by id (@arikfr)\n- 8df822e #1141: Make create data source button more prominent (@arikfr)\n- 96dd811 #1127: Mark basic_auth_password as secret (@adamlwgriffiths)\n- ad65391 #1130: Improve Slack notification style (@AntoineAugusti)\n- df637e3 #1116: Return meaningful error when there is no cached result. (@arikfr)\n- 65635ec #1102: Switch to HipChat V2 API (@arikfr)\n- 14fcf01 #1072: Remove counter from the tasks Done tab (as it always shows 50). #1047 (@arikfr)\n- 1a1160e #1062: DynamoDB: Better exception handling (@arikfr)\n- ed45dcb #1044: Improve vagrant flow (@staritza)\n- 8b5dc8e #1036: Add optional block for more scripts in template (@arikfr)\n\n### Fixed\n\n- dbd48e1 #1143: Fix: use the email input type where needed (@ariarijp)\n- 7445972 #1142: Fix: dates in filters might be duplicated (@arikfr)\n- 5d0ed02 #1140: Fix: Hive should use the enabled variable (@arikfr)\n- 392627d #1139: Fix: Impala data source referencing wrong variable (@arikfr)\n- c5bfbba #1133: Fix: query scrolling issues (@vishesh92)\n- c01d266 #1128: Fix: visualization options not updating after changing type (@arikfr)\n- 6bc0e7a #1126: Fix #669: save fails when doing partial save of new query (@arikfr)\n- 3ce27b9 #1118: Fix: remove alerts for archived queries (@arikfr)\n- 4fabaae #1117: Fix #1052: filter not working for date/time values (@arikfr)\n- c107c94 #1077: Fix: install needed dependencies to use Hive in Docker image (@nabilblk)\n- abc790c #1115: Fix: allow non integers in alert reference value (@arikfr)\n- 4ec473c #1110: Fix #1109: mixed group permissions resulting in wrong permission (@arikfr)\n- 1ca5262 #1099: Fix RST syntax for links (@adamlwgriffiths)\n- daa6c1c #1096: Fix typo in env variable VERSION_CHECK (@AntoineAugusti)\n- cd06d27 #1095: Fix: use create_query permission for new query button. (@ordd)\n- 2bc0b27 #1061: Fix: area chart stacking doesn't work (@machira)\n- 8c21e91 #1108: Remove potnetially concurrency not safe code form enqueue_query (@arikfr)\n- e831218 #1084: Fix #1049: duplicate alerts when data source belongs to multiple groups (@arikfr)\n- 6edb0ca #1080: Fix typo (@jeffwidman)\n- 64d7538 #1074: Fix: ElasticSearch wasn't using correct type names (@toyama0919)\n- 3f90dd9 #1064: Fix: old task trackers were not really removed (@arikfr)\n- e10ecd2 #1058: Bring back filters if dashboard filters are enabled (@AntoineAugusti)\n- 701035f #1059: Fix: DynamoDB having issues when setting host (@arikfr)\n- 2924d4f #1040: Small fixes to visualizations view (@arikfr)\n- fec0d5f #1037: Fix: multi filter wasn't working with \\_\\_ syntax (@dheerajrav)\n- b066ce4 #1033: Fix: only ask for notification permissions if wasn't denied (@arikfr)\n- 960c416 #1032: Fix: make sure we return dashboards only for current org only (@arikfr)\n- b3844d3 #1029: Hive: close connection only if it exists (@arikfr)\n\n### Docs\n\n- 6bb09d8 #1146: Docs: add a link to settings documentation. (@adamlwgriffiths)\n- 095e759 #1103: Docs: add section about monitoring (@AntoineAugusti)\n- e942486 #1090: Contributing Guide (@arikfr)\n- 3037c4f #1066: Docs: command type-o fix. (@edwardsharp)\n- 2ee0065 #1038: Add an ISSUE_TEMPLATE.md to direct people at the forum (@arikfr)\n- f7322a4 #1021: Vagrant docs: add purging the cache step (@ariarijp)\n\n---\n\nFor older releases check the GitHub releases page:\nhttps://github.com/getredash/redash/releases\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guide\n\nThank you for taking the time to contribute! :tada::+1:\n\nThe following is a set of guidelines for contributing to Redash. These are guidelines, not rules, please use your best judgement and feel free to propose changes to this document in a pull request.\n\n:star: If you're already here and love the project, please make sure to press the Star button. :star:\n## Table of Contents\n\n[How can I contribute?](#how-can-i-contribute)\n\n- [Reporting Bugs](#reporting-bugs)\n- [Suggesting Enhancements / Feature Requests](#suggesting-enhancements--feature-requests)\n- [Pull Requests](#pull-requests)\n- [Documentation](#documentation)\n- Design?\n\n[Additional Notes](#additional-notes)\n\n- [Release Method](#release-method)\n- [Code of Conduct](#code-of-conduct)\n\n## Quick Links:\n\n- [User Forum](https://github.com/getredash/redash/discussions)\n- [Documentation](https://redash.io/help/)\n\n\n---\n## How can I contribute?\n\n### Reporting Bugs\n\nWhen creating a new bug report, please make sure to:\n\n- Search for existing issues first. If you find a previous report of your issue, please update the existing issue with additional information instead of creating a new one.\n- If you are not sure if your issue is really a bug or just some configuration/setup problem, please start a [Q&A discussion](https://github.com/getredash/redash/discussions/new?category=q-a) first. Unless you can provide clear steps to reproduce, it's probably better to start with a discussion and later to open an issue.\n- If you still decide to open an issue, please review the template and guidelines and include as much details as possible.\n\n### Suggesting Enhancements / Feature Requests\n\nIf you would like to suggest an enhancement or ask for a new feature:\n\n- Please check [the Ideas discussions](https://github.com/getredash/redash/discussions/categories/ideas) for existing threads about what you want to suggest/ask. If there is, feel free to upvote it to signal interest or add your comments.\n- If there is no open thread, you're welcome to start one to have a discussion about what you want to suggest. Try to provide as much details and context as possible and include information about *the problem you want to solve* rather only *your proposed solution*.\n\n### Pull Requests\n\n**Code contributions are welcomed**. For big changes or significant features, it's usually better to reach out first and discuss what you want to implement and how (we recommend reading: [Pull Request First](https://medium.com/practical-blend/pull-request-first-f6bb667a9b6#.ozlqxvj36)). This is to make sure that what you want to implement is aligned with our goals for the project and that no one else is already working on it.\n\n#### Criteria for Review / Merging\n\nWhen you open your pull request, please follow this repository’s PR template carefully:\n\n- Indicate the type of change\n  - If you implement multiple unrelated features, bug fixes, or refactors please split them into individual pull requests.\n- Describe the change \n- If fixing a bug, please describe the bug or link to an existing github issue / forum discussion\n- Include UI screenshots / GIFs whenever possible\n- Please add [documentation](#documentation) for new features or changes in functionality along with the code.\n- Please follow existing code style:\n  - Python: we use [Black](https://github.com/psf/black) to auto format the code.\n  - Javascript: we use [Prettier](https://github.com/prettier/prettier) to auto-format the code.\n\n#### Initial Review (1 week)\n\nDuring this phase, a team member will apply the “Team Review” label if a pull request meets our criteria or a “Needs More Information” label if not. If more information is required, the team member will comment which criteria have not been met.\n\nIf your pull request receives the “Needs More Information” label, please make the requested changes and then remove the label. This resets the 1 week timer for an initial review.\n\nStale pull requests that remain untouched in “Needs More Information” for more than 4 weeks will be closed.\n\nIf a team member closes your pull request, you may reopen it after you have made the changes requested during initial review. After you make these changes, remove the “Needs More Information” label. This again resets the timer for another initial review.\n\n#### Full Review (2 weeks)\n\nAfter the “Team Review” label is applied, a member of the core team will review the PR within 2 weeks. \n\nReviews will approve, request changes, or ask questions to discuss areas of uncertainty. After you’ve responded, a member of the team will re-review within one week.\n\n#### Merging (1 week)\n\nAfter your pull request has been approved, a member of the core team will merge the pull request within a week.\n\n### Documentation\n\nThe project's documentation can be found at [https://redash.io/help/](https://redash.io/help/). The [documentation sources](https://github.com/getredash/website/tree/master/src/pages/kb) are hosted on GitHub. To contribute edits / new pages, you can use GitHub's interface. Click the \"Edit on GitHub\" link on the documentation page to quickly open the edit interface.\n\n## Additional Notes\n\n### Release Method\n\nWe publish a stable release every ~3-4 months, although the goal is to get to a stable release every month. \n\nEvery build of the master branch updates the *redash/redash:preview* Docker Image. These releases are usually stable, but might contain regressions and therefore recommended for \"advanced users\" only.\n\nWhen we release a new stable release, we also update the *latest* Docker image tag, the EC2 AMIs and GCE images.\n\n## Code of Conduct\n\nThis project adheres to the Contributor Covenant [code of conduct](https://redash.io/community/code_of_conduct). By participating, you are expected to uphold this code. Please report unacceptable behavior to team@redash.io.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:24-bookworm AS frontend-builder\n\nRUN npm install --global pnpm@10.30.3\n\n# Controls whether to build the frontend assets\nARG skip_frontend_build\n\nENV CYPRESS_INSTALL_BINARY=0\nENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1\n\nRUN useradd -m -d /frontend redash\nUSER redash\n\nWORKDIR /frontend\nCOPY --chown=redash package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc /frontend/\nCOPY --chown=redash viz-lib /frontend/viz-lib\nCOPY --chown=redash scripts /frontend/scripts\n\n# Controls whether to instrument code for coverage information\nARG code_coverage\nENV BABEL_ENV=${code_coverage:+test}\n\n# Use BuildKit cache mount for pnpm store to speed rebuilds\nRUN --mount=type=cache,id=pnpm-store,target=/frontend/.cache/pnpm,uid=1001,gid=1001 \\\n  pnpm config set store-dir /frontend/.cache/pnpm && \\\n  if [ \"x$skip_frontend_build\" = \"x\" ] ; then pnpm install --frozen-lockfile; fi\n\nCOPY --chown=redash client /frontend/client\nCOPY --chown=redash webpack.config.js /frontend/\n\n# Use the same cache mount for the build step\nRUN --mount=type=cache,id=pnpm-store,target=/frontend/.cache/pnpm,uid=1001,gid=1001 <<EOF\n  if [ \"x$skip_frontend_build\" = \"x\" ]; then\n    pnpm run build\n  else\n    mkdir -p /frontend/client/dist\n    touch /frontend/client/dist/multi_org.html\n    touch /frontend/client/dist/index.html\n  fi\nEOF\n\nFROM python:3.13-slim-bookworm\n\nEXPOSE 5000\n\nRUN useradd --create-home redash\n\n# Ubuntu packages\nRUN apt-get update && \\\n  apt-get install -y --no-install-recommends \\\n  pkg-config \\\n  curl \\\n  gnupg \\\n  build-essential \\\n  pwgen \\\n  libffi-dev \\\n  sudo \\\n  git-core \\\n  # Kerberos, needed for MS SQL Python driver to compile on arm64\n  libkrb5-dev \\\n  # Postgres client\n  libpq-dev \\\n  # ODBC support:\n  g++ unixodbc-dev \\\n  # for SAML\n  xmlsec1 \\\n  # Additional packages required for data sources:\n  libssl-dev \\\n  default-libmysqlclient-dev \\\n  freetds-dev \\\n  libsasl2-dev \\\n  unzip \\\n  libsasl2-modules-gssapi-mit && \\\n  apt-get clean && \\\n  rm -rf /var/lib/apt/lists/*\n\n\nARG TARGETPLATFORM\nARG databricks_odbc_driver_url=https://databricks-bi-artifacts.s3.us-east-2.amazonaws.com/simbaspark-drivers/odbc/2.6.26/SimbaSparkODBC-2.6.26.1045-Debian-64bit.zip\nRUN <<EOF\n  if [ \"$TARGETPLATFORM\" = \"linux/amd64\" ]; then\n    curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg\n    curl https://packages.microsoft.com/config/debian/12/prod.list > /etc/apt/sources.list.d/mssql-release.list\n    apt-get update\n    ACCEPT_EULA=Y apt-get install  -y --no-install-recommends msodbcsql18\n    apt-get clean\n    rm -rf /var/lib/apt/lists/*\n    curl \"$databricks_odbc_driver_url\" --location --output /tmp/simba_odbc.zip\n    chmod 600 /tmp/simba_odbc.zip\n    unzip /tmp/simba_odbc.zip -d /tmp/simba\n    dpkg -i /tmp/simba/*.deb\n    printf \"[Simba]\\nDriver = /opt/simba/spark/lib/64/libsparkodbc_sb64.so\" >> /etc/odbcinst.ini\n    rm /tmp/simba_odbc.zip\n    rm -rf /tmp/simba\n  fi\nEOF\n\nWORKDIR /app\n\nENV POETRY_VERSION=2.1.4\nENV POETRY_HOME=/etc/poetry\nENV POETRY_VIRTUALENVS_CREATE=false\nRUN curl -sSL --retry 3 --retry-delay 5 https://install.python-poetry.org | python3 -\n\n# Avoid crashes, including corrupted cache artifacts, when building multi-platform images with GitHub Actions.\nRUN /etc/poetry/bin/poetry cache clear pypi --all\n\nCOPY pyproject.toml poetry.lock ./\n\nARG POETRY_OPTIONS=\"--no-root --no-interaction --no-ansi\"\n# for LDAP authentication, install with `ldap3` group\n# disabled by default due to GPL license conflict\nARG install_groups=\"main,all_ds,dev\"\nRUN /etc/poetry/bin/poetry install --only $install_groups $POETRY_OPTIONS\n\nCOPY --chown=redash . /app\nCOPY --from=frontend-builder --chown=redash /frontend/client/dist /app/client/dist\nRUN chown redash /app\nUSER redash\n\nENTRYPOINT [\"/app/bin/docker-entrypoint\"]\nCMD [\"server\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2013-2020, Arik Fraimovich.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, \nare permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, \n   this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation and/or\n   other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" \nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, \nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR \nPURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS \nBE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL \nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; \nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY \nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING \nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, \nEVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "LICENSE.borders",
    "content": "The Bahrain map data used in Redash was downloaded from\nhttps://cartographyvectors.com/map/857-bahrain-detailed-boundary in PR #6192.\n* Free for personal and commercial purpose with attribution.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: compose_build up test_db create_database clean down tests lint backend-unit-tests frontend-unit-tests test build watch start redis-cli bash\n\ncompose_build: .env\n\tCOMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose build\n\nup:\n\tdocker compose up -d redis postgres --remove-orphans\n\tdocker compose exec -u postgres postgres psql postgres --csv \\\n\t\t-1tqc \"SELECT table_name FROM information_schema.tables WHERE table_name = 'organizations'\" 2> /dev/null \\\n\t\t| grep -q \"organizations\" || make create_database\n\tCOMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose up -d --build --remove-orphans\n\ntest_db:\n\t@for i in `seq 1 5`; do \\\n\t\tif (docker compose exec postgres sh -c 'psql -U postgres -c \"select 1;\"' 2>&1 > /dev/null) then break; \\\n\t\telse echo \"postgres initializing...\"; sleep 5; fi \\\n\tdone\n\tdocker compose exec postgres sh -c 'psql -U postgres -c \"drop database if exists tests;\" && psql -U postgres -c \"create database tests;\"'\n\ncreate_database: .env\n\tdocker compose run server create_db\n\nclean:\n\tdocker compose down\n\tdocker compose --project-name cypress down\n\tdocker compose rm --stop --force\n\tdocker compose --project-name cypress rm --stop --force\n\tdocker image rm --force \\\n\t\tcypress-server:latest cypress-worker:latest cypress-scheduler:latest \\\n\t\tredash-server:latest redash-worker:latest redash-scheduler:latest\n\tdocker container prune --force\n\tdocker image prune --force\n\tdocker volume prune --force\n\ndown:\n\tdocker compose down\n\n.env:\n\tprintf \"REDASH_COOKIE_SECRET=`pwgen -1s 32`\\nREDASH_SECRET_KEY=`pwgen -1s 32`\\n\" >> .env\n\nenv: .env\n\nformat:\n\tpre-commit run --all-files\n\ntests:\n\tdocker compose run server tests\n\nlint:\n\truff check .\n\tblack --check . --diff\n\nbackend-unit-tests: up test_db\n\tdocker compose run --rm --name tests server tests\n\nfrontend-unit-tests:\n\tCYPRESS_INSTALL_BINARY=0 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 pnpm install --frozen-lockfile\n\tpnpm test\n\ntest: backend-unit-tests frontend-unit-tests lint\n\nbuild:\n\tpnpm run build\n\nwatch:\n\tpnpm run watch\n\nstart:\n\tpnpm start\n\nredis-cli:\n\tdocker compose run --rm redis redis-cli -h redis\n\nbash:\n\tdocker compose run --rm server bash\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img title=\"Redash\" src='https://redash.io/assets/images/logo.png' width=\"200px\"/>\n</p>\n\n[![Documentation](https://img.shields.io/badge/docs-redash.io/help-brightgreen.svg)](https://redash.io/help/)\n[![GitHub Build](https://github.com/getredash/redash/actions/workflows/ci.yml/badge.svg)](https://github.com/getredash/redash/actions)\n\nRedash is designed to enable anyone, regardless of the level of technical sophistication, to harness the power of data big and small. SQL users leverage Redash to explore, query, visualize, and share data from any data source. Their work, in turn, enables anybody in their organization to use the data. Every day, millions of users at thousands of organizations around the world use Redash to develop insights and make data-driven decisions.\n\nRedash features:\n\n1. **Browser-based**: Everything in your browser, with a shareable URL.\n2. **Ease-of-use**: Become immediately productive with data without the need to master complex software.\n3. **Query editor**: Quickly compose SQL and NoSQL queries with a schema browser and auto-complete.\n4. **Visualization and dashboards**: Create [beautiful visualizations](https://redash.io/help/user-guide/visualizations/visualization-types) with drag and drop, and combine them into a single dashboard.\n5. **Sharing**: Collaborate easily by sharing visualizations and their associated queries, enabling peer review of reports and queries.\n6. **Schedule refreshes**: Automatically update your charts and dashboards at regular intervals you define.\n7. **Alerts**: Define conditions and be alerted instantly when your data changes.\n8. **REST API**: Everything that can be done in the UI is also available through the REST API.\n9. **Broad support for data sources**: An extensible data source API with native support for a long list of common databases and platforms.\n\n<img src=\"https://raw.githubusercontent.com/getredash/website/8e820cd02c73a8ddf4f946a9d293c54fd3fb08b9/website/_assets/images/redash-anim.gif\" width=\"80%\"/>\n\n## Getting Started\n\n* [Setting up a Redash instance](https://redash.io/help/open-source/setup) (includes links to ready-made AWS/GCE images).\n* [Documentation](https://redash.io/help/).\n\n## Supported Data Sources\n\nRedash supports more than 35 SQL and NoSQL [data sources](https://redash.io/help/data-sources/supported-data-sources). It can also be extended to support more. Below is a list of built-in sources:\n\n- Amazon Athena\n- Amazon CloudWatch / Insights\n- Amazon DynamoDB\n- Amazon Redshift\n- ArangoDB\n- Axibase Time Series Database\n- Apache Cassandra\n- ClickHouse\n- CockroachDB\n- Couchbase\n- CSV\n- Databricks\n- DB2 by IBM\n- Dgraph\n- Apache Drill\n- Apache Druid\n- e6data\n- Eccenca Corporate Memory\n- Elasticsearch\n- Exasol\n- Microsoft Excel\n- Firebolt\n- Databend\n- Google Analytics\n- Google BigQuery\n- Google Spreadsheets\n- Graphite\n- Greenplum\n- Apache Hive\n- Apache Impala\n- InfluxDB\n- InfluxDBv2\n- IBM Netezza Performance Server\n- JIRA (JQL)\n- JSON\n- Apache Kylin\n- OmniSciDB (Formerly MapD)\n- MariaDB\n- MemSQL\n- Microsoft Azure Data Warehouse / Synapse\n- Microsoft Azure SQL Database\n- Microsoft Azure Data Explorer / Kusto\n- Microsoft SQL Server\n- MongoDB\n- MySQL\n- Oracle\n- Apache Phoenix\n- Apache Pinot\n- PostgreSQL\n- Presto\n- Prometheus\n- Python\n- Qubole\n- Rockset\n- RisingWave\n- Salesforce\n- ScyllaDB\n- Shell Scripts\n- Snowflake\n- SPARQL\n- SQLite\n- TiDB\n- Tinybird\n- TreasureData\n- Trino\n- Uptycs\n- Vertica\n- Yandex AppMetrica\n- Yandex Metrica\n\n## Getting Help\n\n* Issues: https://github.com/getredash/redash/issues\n* Discussion Forum: https://github.com/getredash/redash/discussions/\n* Development Discussion: https://discord.gg/tN5MdmfGBp\n\n## Reporting Bugs and Contributing Code\n\n* Want to report a bug or request a feature? Please open [an issue](https://github.com/getredash/redash/issues/new).\n* Want to help us build **_Redash_**? Fork the project, edit in a [dev environment](https://github.com/getredash/redash/wiki/Local-development-setup) and make a pull request. We need all the help we can get!\n\n## Security\n\nPlease email security@redash.io to report any security vulnerabilities. We will acknowledge receipt of your vulnerability and strive to send you regular updates about our progress. If you're curious about the status of your disclosure please feel free to email us again. If you want to encrypt your disclosure email, you can use [this PGP key](https://keybase.io/arikfr/key.asc).\n\n## License\n\nBSD-2-Clause.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nPlease email security@redash.io to report any security vulnerabilities. We will acknowledge receipt of your vulnerability and strive to send you regular updates about our progress. If you're curious about the status of your disclosure please feel free to email us again. If you want to encrypt your disclosure email, you can use [this PGP key](https://keybase.io/arikfr/key.asc).\n"
  },
  {
    "path": "bin/docker-entrypoint",
    "content": "#!/bin/bash\nset -e\n\nscheduler() {\n  echo \"Starting RQ scheduler...\"\n\n  exec /app/manage.py rq scheduler\n}\n\ndev_scheduler() {\n  echo \"Starting dev RQ scheduler...\"\n\n  exec watchmedo auto-restart --directory=./redash/ --pattern=*.py --recursive -- ./manage.py rq scheduler\n}\n\nworker() {\n  echo \"Starting RQ worker...\"\n\n  export WORKERS_COUNT=${WORKERS_COUNT:-2}\n  export QUEUES=${QUEUES:-}\n\n  exec supervisord -c worker.conf\n}\n\nworkers_healthcheck() {\n  WORKERS_COUNT=${WORKERS_COUNT}\n  echo \"Checking active workers count against $WORKERS_COUNT...\"\n  ACTIVE_WORKERS_COUNT=`echo $(rq info --url $REDASH_REDIS_URL -R | grep workers | grep -oP ^[0-9]+)`\n  if [ \"$ACTIVE_WORKERS_COUNT\" -lt \"$WORKERS_COUNT\"  ]; then\n    echo \"$ACTIVE_WORKERS_COUNT workers are active, Exiting\"\n    exit 1\n  else\n    echo \"$ACTIVE_WORKERS_COUNT workers are active\"\n    exit 0\n  fi\n}\n\ndev_worker() {\n  echo \"Starting dev RQ worker...\"\n\n  exec watchmedo auto-restart --directory=./redash/ --pattern=*.py --recursive -- ./manage.py rq worker $QUEUES\n}\n\nserver() {\n  # Recycle gunicorn workers every n-th request. See http://docs.gunicorn.org/en/stable/settings.html#max-requests for more details.\n  MAX_REQUESTS=${MAX_REQUESTS:-1000}\n  MAX_REQUESTS_JITTER=${MAX_REQUESTS_JITTER:-100}\n  TIMEOUT=${REDASH_GUNICORN_TIMEOUT:-60}\n  BIND_ADDRESS=${REDASH_GUNICORN_BIND:-[::]:5000}\n  exec /usr/local/bin/gunicorn -b \"$BIND_ADDRESS\" --name redash -w${REDASH_WEB_WORKERS:-4} redash.wsgi:app --max-requests $MAX_REQUESTS --max-requests-jitter $MAX_REQUESTS_JITTER --timeout $TIMEOUT --limit-request-line ${REDASH_GUNICORN_LIMIT_REQUEST_LINE:-0}\n}\n\ncreate_db() {\n  exec /app/manage.py database create_tables\n}\n\nhelp() {\n  echo \"Redash Docker.\"\n  echo \"\"\n  echo \"Usage:\"\n  echo \"\"\n\n  echo \"server -- start Redash server (with gunicorn)\"\n  echo \"worker -- start a single RQ worker\"\n  echo \"dev_worker -- start a single RQ worker with code reloading\"\n  echo \"scheduler -- start an rq-scheduler instance\"\n  echo \"dev_scheduler -- start an rq-scheduler instance with code reloading\"\n  echo \"\"\n  echo \"shell -- open shell\"\n  echo \"dev_server -- start Flask development server with debugger and auto reload\"\n  echo \"debug -- start Flask development server with remote debugger via debugpy\"\n  echo \"create_db -- create database tables\"\n  echo \"manage -- CLI to manage redash\"\n  echo \"tests -- run tests\"\n}\n\ntests() {\n  export REDASH_DATABASE_URL=\"postgresql://postgres@postgres/tests\"\n\n  if [ $# -eq 0 ]; then\n    TEST_ARGS=tests/\n  else\n    TEST_ARGS=$@\n  fi\n  exec pytest $TEST_ARGS\n}\n\ncase \"$1\" in\n  worker)\n    shift\n    worker\n    ;;\n  workers_healthcheck)\n    shift\n    workers_healthcheck\n    ;;\n  server)\n    shift\n    server\n    ;;\n  scheduler)\n    shift\n    scheduler\n    ;;\n  dev_scheduler)\n    shift\n    dev_scheduler\n    ;;\n  dev_worker)\n    shift\n    dev_worker\n    ;;\n  celery_healthcheck)\n    shift\n    echo \"DEPRECATED: Celery has been replaced with RQ and now performs healthchecks autonomously as part of the 'worker' entrypoint.\"\n    ;;\n  dev_server)\n    export FLASK_DEBUG=1\n    exec /app/manage.py runserver --debugger --reload -h 0.0.0.0\n    ;;\n  debug)\n    export FLASK_DEBUG=1\n    export REMOTE_DEBUG=1\n    exec /app/manage.py runserver --debugger --no-reload -h 0.0.0.0\n    ;;\n  shell)\n    exec /app/manage.py shell\n    ;;\n  create_db)\n    create_db\n    ;;\n  manage)\n    shift\n    exec /app/manage.py $*\n    ;;\n  tests)\n    shift\n    tests $@\n    ;;\n  help)\n    shift\n    help\n    ;;\n  *)\n    exec \"$@\"\n    ;;\nesac\n"
  },
  {
    "path": "bin/get_changes.py",
    "content": "#!/bin/env python3\n\nimport re\nimport subprocess\nimport sys\n\n\ndef get_change_log(previous_sha):\n    args = [\n        \"git\",\n        \"--no-pager\",\n        \"log\",\n        \"--merges\",\n        \"--grep\",\n        \"Merge pull request\",\n        '--pretty=format:\"%h|%s|%b|%p\"',\n        \"master...{}\".format(previous_sha),\n    ]\n    log = subprocess.check_output(args)\n    changes = []\n\n    for line in log.split(\"\\n\"):\n        try:\n            sha, subject, body, parents = line[1:-1].split(\"|\")\n        except ValueError:\n            continue\n\n        try:\n            pull_request = re.match(r\"Merge pull request #(\\d+)\", subject).groups()[0]\n            pull_request = \" #{}\".format(pull_request)\n        except Exception:\n            pull_request = \"\"\n\n        author = subprocess.check_output([\"git\", \"log\", \"-1\", '--pretty=format:\"%an\"', parents.split(\" \")[-1]])[1:-1]\n\n        changes.append(\"{}{}: {} ({})\".format(sha, pull_request, body.strip(), author))\n\n    return changes\n\n\nif __name__ == \"__main__\":\n    previous_sha = sys.argv[1]\n    changes = get_change_log(previous_sha)\n\n    for change in changes:\n        print(change)\n"
  },
  {
    "path": "bin/release_manager.py",
    "content": "#!/usr/bin/env python3\nimport os\nimport re\nimport subprocess\nimport sys\nfrom urllib.parse import urlparse\n\nimport requests\nimport simplejson\n\ngithub_token = os.environ[\"GITHUB_TOKEN\"]\nauth = (github_token, \"x-oauth-basic\")\nrepo = \"getredash/redash\"\n\n\ndef _github_request(method, path, params=None, headers={}):\n    if urlparse(path).hostname != \"api.github.com\":\n        url = \"https://api.github.com/{}\".format(path)\n    else:\n        url = path\n\n    if params is not None:\n        params = simplejson.dumps(params)\n\n    response = requests.request(method, url, data=params, auth=auth)\n    return response\n\n\ndef exception_from_error(message, response):\n    return Exception(\"({}) {}: {}\".format(response.status_code, message, response.json().get(\"message\", \"?\")))\n\n\ndef rc_tag_name(version):\n    return \"v{}-rc\".format(version)\n\n\ndef get_rc_release(version):\n    tag = rc_tag_name(version)\n    response = _github_request(\"get\", \"repos/{}/releases/tags/{}\".format(repo, tag))\n\n    if response.status_code == 404:\n        return None\n    elif response.status_code == 200:\n        return response.json()\n\n    raise exception_from_error(\"Unknown error while looking RC release: \", response)\n\n\ndef create_release(version, commit_sha):\n    tag = rc_tag_name(version)\n\n    params = {\n        \"tag_name\": tag,\n        \"name\": \"{} - RC\".format(version),\n        \"target_commitish\": commit_sha,\n        \"prerelease\": True,\n    }\n\n    response = _github_request(\"post\", \"repos/{}/releases\".format(repo), params)\n\n    if response.status_code != 201:\n        raise exception_from_error(\"Failed creating new release\", response)\n\n    return response.json()\n\n\ndef upload_asset(release, filepath):\n    upload_url = release[\"upload_url\"].replace(\"{?name,label}\", \"\")\n    filename = filepath.split(\"/\")[-1]\n\n    with open(filepath) as file_content:\n        headers = {\"Content-Type\": \"application/gzip\"}\n        response = requests.post(\n            upload_url, file_content, params={\"name\": filename}, headers=headers, auth=auth, verify=False\n        )\n\n    if response.status_code != 201:  # not 200/201/...\n        raise exception_from_error(\"Failed uploading asset\", response)\n\n    return response\n\n\ndef remove_previous_builds(release):\n    for asset in release[\"assets\"]:\n        response = _github_request(\"delete\", asset[\"url\"])\n        if response.status_code != 204:\n            raise exception_from_error(\"Failed deleting asset\", response)\n\n\ndef get_changelog(commit_sha):\n    latest_release = _github_request(\"get\", \"repos/{}/releases/latest\".format(repo))\n    if latest_release.status_code != 200:\n        raise exception_from_error(\"Failed getting latest release\", latest_release)\n\n    latest_release = latest_release.json()\n    previous_sha = latest_release[\"target_commitish\"]\n\n    args = [\n        \"git\",\n        \"--no-pager\",\n        \"log\",\n        \"--merges\",\n        \"--grep\",\n        \"Merge pull request\",\n        '--pretty=format:\"%h|%s|%b|%p\"',\n        \"{}...{}\".format(previous_sha, commit_sha),\n    ]\n    log = subprocess.check_output(args)\n    changes = [\"Changes since {}:\".format(latest_release[\"name\"])]\n\n    for line in log.split(\"\\n\"):\n        try:\n            sha, subject, body, parents = line[1:-1].split(\"|\")\n        except ValueError:\n            continue\n\n        try:\n            pull_request = re.match(r\"Merge pull request #(\\d+)\", subject).groups()[0]\n            pull_request = \" #{}\".format(pull_request)\n        except Exception:\n            pull_request = \"\"\n\n        author = subprocess.check_output([\"git\", \"log\", \"-1\", '--pretty=format:\"%an\"', parents.split(\" \")[-1]])[1:-1]\n\n        changes.append(\"{}{}: {} ({})\".format(sha, pull_request, body.strip(), author))\n\n    return \"\\n\".join(changes)\n\n\ndef update_release_commit_sha(release, commit_sha):\n    params = {\n        \"target_commitish\": commit_sha,\n    }\n\n    response = _github_request(\"patch\", \"repos/{}/releases/{}\".format(repo, release[\"id\"]), params)\n\n    if response.status_code != 200:\n        raise exception_from_error(\"Failed updating commit sha for existing release\", response)\n\n    return response.json()\n\n\ndef update_release(version, build_filepath, commit_sha):\n    try:\n        release = get_rc_release(version)\n        if release:\n            release = update_release_commit_sha(release, commit_sha)\n        else:\n            release = create_release(version, commit_sha)\n\n        print(\"Using release id: {}\".format(release[\"id\"]))\n\n        remove_previous_builds(release)\n        response = upload_asset(release, build_filepath)\n\n        changelog = get_changelog(commit_sha)\n\n        response = _github_request(\"patch\", release[\"url\"], {\"body\": changelog})\n        if response.status_code != 200:\n            raise exception_from_error(\"Failed updating release description\", response)\n\n    except Exception as ex:\n        print(ex)\n\n\nif __name__ == \"__main__\":\n    commit_sha = sys.argv[1]\n    version = sys.argv[2]\n    filepath = sys.argv[3]\n\n    # TODO: make sure running from git directory & remote = repo\n    update_release(version, filepath, commit_sha)\n"
  },
  {
    "path": "bin/run",
    "content": "#!/usr/bin/env bash\n\n# Ideally I would use stdin with source, but in older bash versions this\n# wasn't supported properly.\nTEMP_ENV_FILE=`mktemp /tmp/redash_env.XXXXXX`\nsed 's/^REDASH/export REDASH/' .env > $TEMP_ENV_FILE\nsource $TEMP_ENV_FILE\nrm $TEMP_ENV_FILE\n\nexec \"$@\"\n"
  },
  {
    "path": "client/.babelrc",
    "content": "{\n  \"presets\": [\n    [\n      \"@babel/preset-env\",\n      {\n        \"exclude\": [\"@babel/plugin-transform-async-to-generator\", \"@babel/plugin-transform-arrow-functions\"],\n        \"corejs\": \"2\",\n        \"useBuiltIns\": \"usage\"\n      }\n    ],\n    \"@babel/preset-react\",\n    \"@babel/preset-typescript\"\n  ],\n  \"plugins\": [\n    \"@babel/plugin-transform-class-properties\",\n    \"@babel/plugin-transform-object-assign\",\n    [\n      \"babel-plugin-transform-builtin-extend\",\n      {\n        \"globals\": [\"Error\"]\n      }\n    ]\n  ],\n  \"env\": {\n    \"test\": {\n      \"plugins\": [\"istanbul\"]\n    }\n  }\n}\n"
  },
  {
    "path": "client/.eslintignore",
    "content": "build/*.js\ndist\nconfig/*.js\nclient/dist\n"
  },
  {
    "path": "client/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  parser: \"@typescript-eslint/parser\",\n  extends: [\n    \"react-app\",\n    \"plugin:compat/recommended\",\n    \"prettier\",\n    \"plugin:jsx-a11y/recommended\",\n  ],\n  plugins: [\"jest\", \"compat\", \"no-only-tests\", \"@typescript-eslint\", \"jsx-a11y\"],\n  settings: {\n    \"import/resolver\": \"webpack\",\n    polyfills: [\n      \"document.body\",\n      \"Notification\",\n    ],\n  },\n  env: {\n    browser: true,\n    node: true,\n  },\n  rules: {\n    // allow debugger during development\n    \"no-debugger\": process.env.NODE_ENV === \"production\" ? 2 : 0,\n    // Pre-existing patterns - anonymous default exports are used throughout\n    \"import/no-anonymous-default-export\": \"off\",\n    // Some tests verify no-throw behavior without explicit assertions\n    \"jest/expect-expect\": \"off\",\n    \"jsx-a11y/anchor-is-valid\": [\n      // TMP\n      \"off\",\n      {\n        components: [\"Link\"],\n        aspects: [\"noHref\", \"invalidHref\", \"preferButton\"],\n      },\n    ],\n    \"jsx-a11y/no-redundant-roles\": \"error\",\n    \"jsx-a11y/no-autofocus\": \"off\",\n    \"jsx-a11y/click-events-have-key-events\": \"off\", // TMP\n    \"jsx-a11y/no-static-element-interactions\": \"off\", // TMP\n    \"jsx-a11y/no-noninteractive-element-interactions\": \"off\", // TMP\n    \"no-console\": [\"warn\", { allow: [\"warn\", \"error\"] }],\n    \"no-restricted-imports\": [\n      \"error\",\n      {\n        paths: [\n          {\n            name: \"antd\",\n            message: \"Please use 'import XXX from antd/lib/XXX' import instead.\",\n          },\n          {\n            name: \"antd/lib\",\n            message: \"Please use 'import XXX from antd/lib/XXX' import instead.\",\n          },\n        ],\n      },\n    ],\n  },\n  overrides: [\n    {\n      // Only run typescript-eslint on TS files\n      files: [\"*.ts\", \"*.tsx\", \".*.ts\", \".*.tsx\"],\n      extends: [\"plugin:@typescript-eslint/recommended\"],\n      rules: {\n        // Do not require functions (especially react components) to have explicit returns\n        \"@typescript-eslint/explicit-function-return-type\": \"off\",\n        // Do not require to type every import from a JS file to speed up development\n        \"@typescript-eslint/no-explicit-any\": \"off\",\n        // Do not complain about useless contructors in declaration files\n        \"no-useless-constructor\": \"off\",\n        \"@typescript-eslint/no-useless-constructor\": \"error\",\n        // Many API fields and generated types use camelcase\n        camelcase: \"off\",\n        // Allow {} type - used extensively in existing codebase\n        \"@typescript-eslint/ban-types\": [\"error\", { types: { \"{}\": false } }],\n      },\n    },\n    {\n      // Cypress test files\n      files: [\"**/cypress/**/*.js\"],\n      extends: [\"plugin:cypress/recommended\"],\n      plugins: [\"cypress\", \"chai-friendly\"],\n      env: {\n        \"cypress/globals\": true,\n      },\n      rules: {\n        \"no-redeclare\": \"off\",\n        \"cypress/unsafe-to-chain-command\": \"off\",\n        \"func-names\": [\"error\", \"never\"],\n        \"no-unused-expressions\": \"off\",\n        \"chai-friendly/no-unused-expressions\": \"error\",\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "client/.gitignore",
    "content": "dist\n"
  },
  {
    "path": "client/app/.eslintrc.js",
    "content": "module.exports = {\n  extends: [\"plugin:jest/recommended\"],\n  plugins: [\"jest\"],\n  env: {\n    \"jest/globals\": true,\n  },\n  rules: {\n    \"jest/no-focused-tests\": \"off\",\n  },\n};\n"
  },
  {
    "path": "client/app/__tests__/enzyme_setup.js",
    "content": "import { configure } from \"enzyme\";\nimport Adapter from \"enzyme-adapter-react-16\";\n\nconfigure({ adapter: new Adapter() });\n"
  },
  {
    "path": "client/app/__tests__/mocks.js",
    "content": "import MockDate from \"mockdate\";\n\nconst date = new Date(\"2000-01-01T02:00:00.000\");\n\nMockDate.set(date);\n"
  },
  {
    "path": "client/app/assets/css/login.css",
    "content": "body {\n  padding-top: 0px !important;\n  background-color: #FFFFFF;\n}\n\n.logo-container {\n  background-color: #668899;\n  display: table;\n  width: 100%;\n  padding: 10px;\n}\n\n.content-container {\n  background-color: white;\n  display: table;\n  width: 100%;\n  padding: 10px;\n  height: calc(100% - 116px);\n}\n\n@media (min-width: 992px) {\n  .content-container {\n    height: 100%;\n    width: 60%;\n    float: left;\n  }\n\n  .logo-container {\n    height: 100%;\n    width: 40%;\n    float: right;\n  }\n}\n\n.login-or {\n  position: relative;\n  font-size: 18px;\n  color: #aaa;\n  margin-top: 20px;\n  margin-bottom: 20px;\n  padding-top: 10px;\n  padding-bottom: 10px;\n}\n\n.span-or {\n  display: block;\n  position: absolute;\n  left: 50%;\n  top: -2px;\n  margin-left: -25px;\n  background-color: #fff;\n  width: 50px;\n  text-align: center;\n}\n\n.hr-or {\n  background-color: #cdcdcd;\n  height: 1px;\n  margin-top: 0px !important;\n  margin-bottom: 0px !important;\n}\n\nimg.login-button {\n  width: 250px;\n  display: block;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n"
  },
  {
    "path": "client/app/assets/images/illustrations/readme.md",
    "content": "The illustrations shared in this folder are covered by the CC-BY-SA-NC-ND License v4.0 (or newer). You are allowed to use them when using unmodified versions of the Redash source code for non-commercial purposes (meaning for yourself), but you are not allowed to use for any other use or to distribute them as part of your software or fork of Redash.\n\nFor any questions, please contact us.\n"
  },
  {
    "path": "client/app/assets/less/STYLING-README.md",
    "content": "# Styling readme\n\nSome general rules before you add stuff.\n\n- Avoid using inline css\n- If possible, use classes that are already in place instead of adding new\n- Keep less/inc folder untouched, rewrite things in less/redash respectively \n- Try following BEM naming conventions: http://getbem.com/naming/\n"
  },
  {
    "path": "client/app/assets/less/ant.less",
    "content": "@import \"~antd/lib/style/core/iconfont\";\n@import \"~antd/lib/style/core/motion\";\n@import \"~antd/lib/alert/style/index\";\n@import \"~antd/lib/input/style/index\";\n@import \"~antd/lib/input-number/style/index\";\n@import \"~antd/lib/date-picker/style/index\";\n@import \"~antd/lib/modal/style/index\";\n@import \"~antd/lib/tooltip/style/index\";\n@import \"~antd/lib/select/style/index\";\n@import \"~antd/lib/checkbox/style/index\";\n@import \"~antd/lib/upload/style/index\";\n@import \"~antd/lib/form/style/index\";\n@import \"~antd/lib/button/style/index\";\n@import \"~antd/lib/radio/style/index\";\n@import \"~antd/lib/time-picker/style/index\";\n@import \"~antd/lib/pagination/style/index\";\n@import \"~antd/lib/table/style/index\";\n@import \"~antd/lib/popover/style/index\";\n@import \"~antd/lib/tag/style/index\";\n@import \"~antd/lib/grid/style/index\";\n@import \"~antd/lib/switch/style/index\";\n@import \"~antd/lib/empty/style/index\";\n@import \"~antd/lib/drawer/style/index\";\n@import \"~antd/lib/card/style/index\";\n@import \"~antd/lib/steps/style/index\";\n@import \"~antd/lib/divider/style/index\";\n@import \"~antd/lib/dropdown/style/index\";\n@import \"~antd/lib/menu/style/index\";\n@import \"~antd/lib/list/style/index\";\n@import \"~antd/lib/badge/style/index\";\n@import \"~antd/lib/card/style/index\";\n@import \"~antd/lib/spin/style/index\";\n@import \"~antd/lib/skeleton/style/index\";\n@import \"~antd/lib/tabs/style/index\";\n@import \"~antd/lib/notification/style/index\";\n@import \"~antd/lib/collapse/style/index\";\n@import \"~antd/lib/progress/style/index\";\n@import \"~antd/lib/typography/style/index\";\n@import \"~antd/lib/descriptions/style/index\";\n@import \"inc/ant-variables\";\n\n// Increase z-indexes to avoid conflicts with some other libraries (e.g. Plotly)\n@zindex-modal: 2000;\n@zindex-modal-mask: 2000;\n@zindex-message: 2010;\n@zindex-notification: 2010;\n@zindex-popover: 2030;\n@zindex-dropdown: 2050;\n@zindex-picker: 2050;\n@zindex-tooltip: 2060;\n@item-hover-bg: #e5f8ff;\n\n.@{drawer-prefix-cls} {\n  &.help-drawer {\n    z-index: @zindex-tooltip; // help drawer should be topmost\n  }\n}\n\n// Remove bold in labels for Ant checkboxes and radio buttons\n.ant-checkbox-wrapper,\n.ant-radio-wrapper {\n  font-weight: normal;\n}\n\n.ant-select-dropdown-menu-item em {\n  color: @input-color-placeholder;\n  font-size: 11px;\n}\n\n// Fix for disabled button styles inside Tooltip component.\n// Tooltip wraps disabled buttons with `<span>` and moves all styles\n// and classes to that `<span>`. This resets all button styles and\n// turns it into simple inline element (because now it's wrapper is a button)\n.btn {\n  button[disabled] {\n    -moz-appearance: none !important;\n    -webkit-appearance: none !important;\n    appearance: none !important;\n    border: 0 !important;\n    outline: none !important;\n    background: transparent !important;\n    margin: 0 !important;\n    padding: 0 !important;\n  }\n}\n\n// Button overrides\n.@{btn-prefix-cls} {\n  transition-duration: 150ms;\n\n  &.icon-button {\n    width: 32px;\n    padding: 0 10px;\n  }\n}\n\n// Fix ant input number showing duplicate arrows\n.ant-input-number-input::-webkit-outer-spin-button,\n.ant-input-number-input::-webkit-inner-spin-button {\n  -webkit-appearance: none;\n  margin: 0;\n}\n\n// Pagination overrides (based on existing Bootstrap overrides)\n.@{pagination-prefix-cls} {\n  display: inline-block;\n  margin-top: 18px;\n  margin-bottom: 18px;\n  vertical-align: top;\n\n  &-item {\n    background-color: @pagination-bg;\n    border-color: transparent;\n    color: @pagination-color;\n    font-size: 14px;\n    margin-right: 5px;\n\n    a {\n      color: inherit;\n    }\n\n    &:focus,\n    &:hover {\n      background-color: @pagination-hover-bg;\n      border-color: transparent;\n      color: @pagination-hover-color;\n      a {\n        color: inherit;\n      }\n    }\n\n    &-active {\n      &,\n      &:hover,\n      &:focus {\n        background-color: @pagination-active-bg;\n        color: @pagination-active-color;\n        border-color: transparent;\n        pointer-events: none;\n        cursor: default;\n\n        a {\n          color: inherit;\n        }\n      }\n    }\n  }\n\n  &-disabled {\n    &,\n    &:hover,\n    &:focus {\n      opacity: 0.5;\n      pointer-events: none;\n    }\n  }\n\n  &-prev,\n  &-next {\n    .@{pagination-prefix-cls}-item-link {\n      background-color: @pagination-bg;\n      border-color: transparent;\n      color: @pagination-color;\n      line-height: @pagination-item-size - 2px;\n\n      .@{pagination-prefix-cls}.mini & {\n        line-height: @pagination-item-size-sm - 2px;\n      }\n    }\n\n    &:focus .@{pagination-prefix-cls}-item-link,\n    &:hover .@{pagination-prefix-cls}-item-link {\n      background-color: @pagination-hover-bg;\n      border-color: transparent;\n      color: @pagination-hover-color;\n    }\n  }\n\n  &-prev,\n  &-jump-prev,\n  &-jump-next {\n    margin-right: 5px;\n  }\n\n  &-jump-prev,\n  &-jump-next {\n    .@{pagination-prefix-cls}-item-container {\n      .@{pagination-prefix-cls}-item-link-icon {\n        color: @pagination-color;\n      }\n    }\n  }\n}\n\n// Table\n\n.@{table-prefix-cls} {\n  color: inherit;\n\n  tr,\n  th,\n  td {\n    transition: none !important;\n  }\n\n  &-thead > tr > th {\n    padding: @table-padding-vertical * 2 @table-padding-horizontal;\n  }\n\n  .@{table-prefix-cls}-column-sorters {\n    &:before,\n    &:hover:before {\n      content: none;\n    }\n  }\n\n  &-thead > tr > th {\n    .@{table-prefix-cls}-column-sorter {\n      &-up,\n      &-down {\n        &.on {\n          color: @table-header-icon-active-color;\n        }\n      }\n    }\n  }\n\n  &-tbody > tr&-row {\n    &:hover,\n    &:focus,\n    &:focus-within {\n      & > td {\n        background: @table-row-hover-bg;\n      }\n    }\n  }\n\n  // Custom styles\n\n  &-headerless &-tbody > tr:first-child > td {\n    border-top: @border-width-base @border-style-base @border-color-split;\n  }\n}\n\n// List\n\n.@{list-prefix-cls} {\n  &-item {\n    // custom rule\n    &.selected {\n      background-color: #f6f8f9;\n    }\n\n    &.disabled {\n      background-color: fade(#f6f8f9, 40%);\n\n      & > * {\n        opacity: 0.4;\n      }\n    }\n  }\n}\n\n.@{dialog-prefix-cls} {\n  // styling for short modals (no lines)\n  &.shortModal {\n    .@{dialog-prefix-cls} {\n      &-header,\n      &-footer {\n        border: none;\n        padding: 16px;\n      }\n\n      &-body {\n        padding: 10px 16px;\n      }\n\n      &-close-x {\n        width: 46px;\n        height: 46px;\n        line-height: 46px;\n      }\n    }\n  }\n\n  // fullscreen modals\n  &-fullscreen {\n    .@{dialog-prefix-cls} {\n      position: absolute;\n      left: 15px;\n      top: 15px;\n      right: 15px;\n      bottom: 15px;\n      width: auto !important;\n      height: auto !important;\n      max-width: none;\n      max-height: none;\n      margin: 0;\n      padding: 0;\n\n      .@{dialog-prefix-cls}-content {\n        position: absolute;\n        left: 0;\n        top: 0;\n        right: 0;\n        bottom: 0;\n        width: auto;\n        height: auto;\n        margin: 0;\n        padding: 0;\n        display: flex;\n        flex-direction: column;\n      }\n\n      .@{dialog-prefix-cls}-body {\n        flex: 1 1 auto;\n        overflow: auto;\n      }\n    }\n  }\n}\n\n// description in modal header\n.modal-header-desc {\n  font-size: @font-size-base;\n  color: @text-color-secondary;\n  font-weight: normal;\n  margin-top: 4px;\n}\n\n// Notification overrides\n.@{notification-prefix-cls} {\n  // vertical centering\n  &-notice-close {\n    top: 20px;\n    right: 20px;\n  }\n\n  &-notice-description {\n    max-width: 484px;\n  }\n}\n\n.@{btn-prefix-cls} .@{iconfont-css-prefix}-ellipsis {\n  margin: 0 -7px 0 -8px;\n}\n\n// Collapse\n\n.@{collapse-prefix-cls} {\n  &&-headerless {\n    border: 0;\n    background: none;\n\n    .@{collapse-prefix-cls}-header {\n      display: none;\n    }\n\n    .@{collapse-prefix-cls}-item,\n    .@{collapse-prefix-cls}-content {\n      border: 0;\n    }\n\n    .@{collapse-prefix-cls}-content-box {\n      padding: 0;\n    }\n  }\n}\n\n// overrides for tall form components such as ace editor\n.@{form-prefix-cls}-item {\n  &-children {\n    display: block; // so feeback icon positions correctly\n  }\n\n  // no change for short components, sticks to body for tall ones\n  &-children-icon {\n    top: auto !important;\n    bottom: 8px;\n\n    // makes the icon white instead of see-through\n    & svg {\n      background: white;\n      border-radius: 50%;\n    }\n  }\n\n  // for form items that contain text\n  &.form-item-line-height-normal .@{form-prefix-cls}-item-control {\n    line-height: 20px;\n    margin-top: 9px;\n  }\n}\n\n.@{menu-prefix-cls} {\n  // invert stripe position with class .invert-stripe-position\n  &-inline.invert-stripe-position {\n    .@{menu-prefix-cls}-item {\n      &::after {\n        right: auto;\n        left: 0;\n      }\n    }\n\n    &:focus,\n    &:focus-within {\n      color: @menu-highlight-color;\n    }\n  }\n}\n\n.@{dropdown-prefix-cls}-menu-item {\n  &:focus,\n  &:focus-within {\n    background-color: @item-hover-bg;\n  }\n}\n\n// overrides for checkbox\n@checkbox-prefix-cls: ~\"@{ant-prefix}-checkbox\";\n\n.@{checkbox-prefix-cls}-wrapper + span,\n.@{checkbox-prefix-cls} + span {\n  padding-right: 0;\n}\n\n// make sure Multiple select has room for icons\n.@{select-prefix-cls}-multiple {\n  &.@{select-prefix-cls}-show-arrow,\n  &.@{select-prefix-cls}-show-search,\n  &.@{select-prefix-cls}-loading {\n    .@{select-prefix-cls}-selector {\n      padding-right: 30px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/404.less",
    "content": ".four-zero {\r\n\tbackground: @white;\r\n\tbox-shadow: 0 1px 11px rgba(0, 0, 0, 0.27);\r\n\tborder-radius: 2px;\r\n\tposition: absolute;\r\n\ttop: 50%;\r\n\tmargin-top: -150px;\r\n\ttext-align: center;\r\n\tpadding: 15px;\r\n\theight: 300px;\r\n\twidth: 500px;\r\n\tleft: 50%;\r\n\tcolor: #333;\r\n\tmargin-left: -250px;\r\n\t\r\n\th2 {\r\n\t\tfont-size: 130px;\r\n\t}\r\n\r\n\t\r\n\t@media (max-width: @screen-xs-max) {\r\n\t\twidth: ~\"calc(100% - 40px)\";\r\n\t\tleft: 20px;\r\n\t\tmargin-left: 0;\r\n\t\theight: 260px;\r\n\t\tmargin-top: -130px;\r\n\t\t\r\n\t\th2 {\r\n\t\t\tfont-size: 90px;\r\n\t\t}\r\n        }\r\n\t\r\n\th2 {\r\n\t\tline-height: 100%;\r\n\t\tfont-weight: 100;\r\n\t}\r\n\t\r\n\tsmall {\r\n\t\tdisplay: block;\r\n\t\tfont-size: 26px;\r\n\t\tmargin-top: -10px\r\n\t}\r\n\t\r\n\t\r\n\tfooter {\r\n\t\tbackground: @ace;\r\n\t\tposition: absolute;\r\n\t\tleft: 0;\r\n\t\tbottom: 0;\r\n\t\twidth: 100%;\r\n\t\tpadding: 10px;\r\n\t\t\r\n\t\t& > a {\r\n\t\t\tfont-size: 21px;\r\n\t\t\tdisplay: inline-block;\r\n\t\t\tcolor: #333;\r\n\t\t\tmargin: 0 1px;\r\n\t\t\tline-height: 40px;\r\n\t\t\twidth: 40px;\r\n\t\t\theight: 40px;\r\n\t\t\tbackground: rgba(0, 0, 0, 0.09);\r\n\t\t\tborder-radius: 50%;\r\n\t\t\ttext-align: center;\r\n\t\t\t\r\n\t\t\t&:hover {\r\n\t\t\t\tbackground: rgba(0, 0, 0, 0.2);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\r\n\r\n"
  },
  {
    "path": "client/app/assets/less/inc/ace-editor.less",
    "content": ".ace_editor {\n  border: 1px solid fade(@redash-gray, 15%);\n  height: 100%;\n  margin-bottom: 10px;\n\n  &.ace_autocomplete .ace_completion-highlight {\n    text-shadow: none !important;\n    background: #ffff005e;\n    font-weight: 600;\n  }\n\n  &.ace-tm {\n    .ace_gutter {\n      background: #fff !important;\n    }\n\n    .ace_gutter-active-line {\n      background-color: fade(@redash-gray, 20%) !important;\n    }\n\n    .ace_marker-layer .ace_active-line {\n      background: fade(@redash-gray, 9%) !important;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/alert.less",
    "content": ".alert-page h3 {\r\n  flex-grow: 1;\r\n\r\n  input {\r\n    margin: -0.2em 0;\r\n    width: 100%;\r\n    min-width: 170px;\r\n  }\r\n}\r\n\r\n.btn-create-alert[disabled] {\r\n  display: block;\r\n  margin-top: -20px;\r\n}\r\n\r\n.alert-state {\r\n  border-bottom: 1px solid @input-border;\r\n  padding-bottom: 30px;\r\n\r\n  .alert-state-indicator {\r\n    text-transform: uppercase;\r\n    font-size: 14px;\r\n    padding: 5px 8px;\r\n  }\r\n\r\n  .ant-form-item-explain {\r\n    margin-top: 10px;\r\n  }\r\n\r\n  .alert-last-triggered {\r\n    color: @headings-color;\r\n  }\r\n}\r\n\r\n.alert-query-selector {\r\n  min-width: 250px;\r\n  width: auto !important;\r\n}\r\n\r\n// allow form item labels to gracefully break line\r\n.alert-form-item label {\r\n  white-space: initial;\r\n  padding-right: 8px;\r\n  line-height: 21px;\r\n\r\n  &::after {\r\n    margin-right: 0 !important;\r\n  }\r\n}\r\n"
  },
  {
    "path": "client/app/assets/less/inc/ant-variables.less",
    "content": "/* --------------------------------------------------------\n    Colors\n-----------------------------------------------------------*/\n@lightblue: #03a9f4;\n@primary-color: #2196f3;\n\n@redash-gray: rgba(102, 136, 153, 1);\n@redash-orange: rgba(255, 120, 100, 1);\n@redash-black: rgba(0, 0, 0, 1);\n@redash-yellow: rgba(252, 252, 161, 0.75);\n\n/* --------------------------------------------------------\n    Font\n-----------------------------------------------------------*/\n@redash-font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\",\n  sans-serif;\n@font-family-no-number: @redash-font;\n@font-family: @redash-font;\n@code-family: @redash-font;\n@font-size-base: 13px;\n\n/* --------------------------------------------------------\n    Borders\n-----------------------------------------------------------*/\n@border-color-split: #f0f0f0;\n\n/* --------------------------------------------------------\n    Typograpgy\n-----------------------------------------------------------*/\n@text-color: #595959;\n\n/* --------------------------------------------------------\n    Form\n-----------------------------------------------------------*/\n@input-height-base: 35px;\n@input-color: #595959;\n@input-color-placeholder: #b4b4b4;\n@border-radius-base: 2px;\n@border-color-base: #e8e8e8;\n\n/* --------------------------------------------------------\n    Pagination\n-----------------------------------------------------------*/\n@pagination-item-size: 33px;\n@pagination-font-family: @redash-font;\n@pagination-font-weight-active: normal;\n\n@pagination-bg: fade(@redash-gray, 15%);\n@pagination-color: #7e7e7e;\n@pagination-active-bg: @lightblue;\n@pagination-active-color: #fff;\n@pagination-disabled-bg: fade(@redash-gray, 15%);\n@pagination-hover-color: #333;\n@pagination-hover-bg: fade(@redash-gray, 25%);\n\n/* --------------------------------------------------------\n    Table\n-----------------------------------------------------------*/\n@table-border-radius-base: 0;\n@table-header-color: #333;\n@table-header-bg: fade(@redash-gray, 3%);\n@table-header-icon-color: fade(@text-color, 20%);\n@table-header-icon-active-color: @text-color;\n@table-header-sort-bg: @table-header-bg;\n@table-header-sort-active-bg: @table-header-bg;\n@table-header-filter-active-bg: @table-header-bg;\n@table-body-sort-bg: transparent;\n@table-row-hover-bg: fade(@redash-gray, 5%);\n@table-padding-vertical: 7px;\n@table-padding-horizontal: 10px;\n\n/* --------------------------------------------------------\n    Notification\n-----------------------------------------------------------*/\n@notification-padding: @notification-padding-vertical 48px @notification-padding-vertical 17px;\n@notification-width: auto;\n"
  },
  {
    "path": "client/app/assets/less/inc/base.less",
    "content": "*,\nbutton,\ninput,\ni,\na {\n  -webkit-font-smoothing: antialiased;\n}\n\n*,\n*:active,\n*:hover {\n  outline: none !important;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important;\n}\n\nhtml {\n  overflow-x: ~\"hidden\\0/\";\n  -ms-overflow-style: auto;\n}\n\nhtml,\nbody {\n  height: 100%;\n}\n\nbody {\n  padding-top: 0;\n  background: #f6f8f9;\n  font-family: @redash-font;\n  position: relative;\n\n  #application-root {\n    padding-bottom: 15px;\n  }\n}\n\n#application-root {\n  height: 100%;\n}\n\n#application-root,\n#app-content {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n}\n\nstrong {\n  font-weight: 500;\n}\n\n#content {\n  position: relative;\n  padding-top: 30px;\n  padding-bottom: 30px;\n\n  @media (min-width: (@screen-sm-min + 1)) {\n    padding-right: 15px;\n    padding-left: 15px;\n  }\n\n  @media (min-width: (@screen-lg-min + 80px)) {\n    margin-left: @sidebar-left-width;\n  }\n\n  @media (min-width: @screen-sm-min) and (max-width: (@screen-md-max + 80px)) {\n    margin-left: @sidebar-left-mid-width;\n  }\n\n  @media (max-width: (@screen-sm-min)) {\n    margin-left: 0;\n  }\n}\n\n.container {\n  &.c-boxed {\n    max-width: @boxed-width;\n  }\n}\n\n.settings-screen,\n.home-page,\n.page-dashboard-list,\n.page-queries-list,\n.page-alerts-list,\n.alert-page,\n.admin-page-layout {\n  .container {\n    width: 100%;\n    max-width: none;\n  }\n}\n\n.scrollbox {\n  overflow: auto;\n  position: relative;\n}\n\n.clickable {\n  cursor: pointer;\n\n  button&:disabled {\n    cursor: not-allowed;\n  }\n}\n\n.resize-vertical {\n  resize: vertical !important;\n  transition: height 0s !important;\n}\n.resize-horizontal {\n  resize: horizontal !important;\n  transition: width 0s !important;\n}\n.resize-both,\n.resize-vertical.resize-horizontal {\n  resize: both !important;\n  transition: height 0s, width 0s !important;\n}\n\n.bg-ace {\n  background-color: fade(@redash-gray, 12%) !important;\n}\n\n// resizeable\n.rg-top span,\n.rg-bottom span {\n  height: 3px;\n  border-color: #b1c1ce; // TODO: variable\n}\n\n.rg-bottom {\n  bottom: 15px;\n\n  span {\n    margin: 1.5px 0 0 -10px;\n  }\n}\n\n// Plotly\ntext.slicetext {\n  text-shadow: 1px 1px 5px #333;\n}\n\n// markdown\n.markdown strong {\n  font-weight: bold;\n}\n\n.markdown img {\n  max-width: 100%;\n}\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n  background-color: fade(@redash-gray, 15%);\n  color: #111;\n}\n\n.profile__image--sidebar {\n  border-radius: 100%;\n  margin-right: 3px;\n  margin-top: -2px;\n}\n\n.profile__image--settings {\n  border-radius: 100%;\n}\n\n.profile__image_thumb {\n  border-radius: 100%;\n  margin-right: 3px;\n  margin-top: -2px;\n  width: 20px;\n  height: 20px;\n}\n\n// Error state\n.error-state {\n  display: flex;\n  flex-direction: column;\n  justify-content: flex-start;\n  text-align: center;\n  margin-top: 25vh;\n  padding: 35px;\n  font-size: 14px;\n  line-height: 21px;\n\n  .error-state__icon {\n    .zmdi {\n      font-size: 64px;\n      color: @redash-gray;\n    }\n  }\n\n  @media (max-width: 767px) {\n    margin-top: 10vh;\n  }\n}\n\n.warning-icon-danger {\n  color: @red !important;\n}\n\n// page\n.page-title {\n  display: flex;\n  align-items: center;\n\n  .label {\n    margin-top: 3px;\n    display: inline-block;\n  }\n\n  .favorites-control {\n    font-size: 19px;\n    margin-right: 10px;\n  }\n}\n\n.page-header--new {\n  h3 {\n    margin: 0.2em 0;\n    line-height: 1.3;\n    font-weight: 500;\n  }\n}\n\n.select-option-divider {\n  margin: 10px 0 !important;\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/bootstrap-overrides.less",
    "content": "/** Media - Overriding the Media object to 3.2 version in order to prevent issues like text overflow. **/\n.media {\n    margin-top: 0;\n    .clearfix();\n\n    & > .pull-left {\n        padding-right: 15px;\n    }\n\n    & > .pull-right {\n        padding-left: 15px;\n    }\n\n    overflow: visible;\n}\n\n.media-heading {\n    font-size: 14px;\n    margin-bottom: 10px;\n}\n\n.media-body {\n    zoom: 1;\n    display: block;\n    width: auto;\n}\n\n.media-object {\n    border-radius: 2px;\n}\n\n.collapsing,\n.collapse.in {\n    padding: 0;\n    transition: all 0.35s ease;\n}\n\n/** LIST **/\n.list-inline > li {\n    vertical-align: top;\n    margin-left: 0;\n}\n\n// Hide URLs next to links when printing (override `bootstrap` rules)\n@media print {\n    a[href]:after {\n        content: none !important;\n    }\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/breadcrumb.less",
    "content": ".breadcrumb {\r\n    border-bottom: 1px solid #E5E5E5;\r\n    border-radius: 0;\r\n    padding-top: 10px;\r\n    padding-right: 33px;\r\n    padding-bottom: 11px;\r\n    \r\n    @media (min-width: (@screen-lg-min + 80px)) { \r\n        padding-left: (@sidebar-left-width + @grid-gutter-width);\r\n    }\r\n    \r\n    @media (min-width: @screen-sm-min) and (max-width: (@screen-md-max + 80px)) {\r\n        padding-left: (@sidebar-left-mid-width + @grid-gutter-width);\r\n    }\r\n       \r\n    @media (max-width: (@screen-sm-min)) {\r\n        padding-left: @grid-gutter-width/2;\r\n    }\r\n    \r\n    & > li {\r\n        & > a {\r\n            color: #A9A9A9;\r\n            \r\n            &:hover {\r\n                color: @breadcrumb-active-color;\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "client/app/assets/less/inc/button.less",
    "content": ".btn {\r\n    &:not(.btn-alt) {\r\n        border: 0;\r\n    }\r\n\r\n    &[class*=\"bg-\"]:not(.bg-white) {\r\n        color: #fff;\r\n    }\r\n\r\n    .caret {\r\n        margin-top: -3px;\r\n    }\r\n\r\n    &:not(.btn-link) {\r\n        &:active,\r\n        &.active,\r\n        &:hover {\r\n\r\n        }\r\n    }\r\n}\r\n\r\n.btn-default {\r\n    .button-variant(#333, #eee, transparent);\r\n}\r\n\r\n.btn-inverse {\r\n    .button-variant(#fff, #454545, transparent);\r\n}\r\n\r\n.btn-link {\r\n    color: #333;\r\n}\r\n\r\n.btn-icon {\r\n    border-radius: 50%;\r\n    width: 40px;\r\n    height: 40px;\r\n    padding: 0;\r\n    text-align: center;\r\n\r\n    .zmdi {\r\n        font-size: 17px;\r\n    }\r\n}\r\n\r\n.btn-icon-text {\r\n    & > .zmdi {\r\n        font-size: 15px;\r\n        vertical-align: top;\r\n        display: inline-block;\r\n        margin-top: 2px;\r\n        line-height: 100%;\r\n        margin-right: 5px;\r\n    }\r\n}\r\n\r\n.open .btn {\r\n    outline: none !important;\r\n    -webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important;\r\n\r\n    &:focus, &:active {\r\n        outline: none !important;\r\n        -webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important;\r\n    }\r\n}\r\n\r\n/** ALTERNATIVE BUTTONS **/\r\n.btn-alt(@color) {\r\n    border-color: @color;\r\n    color: @color;\r\n\r\n    &:not(.btn-white) {\r\n        &:hover,\r\n        &:active,\r\n        &:focus {\r\n            color: #fff;\r\n            background: @color;\r\n        }\r\n    }\r\n\r\n    &.btn-white {\r\n        &:hover,\r\n        &:active,\r\n        &:focus {\r\n            color: #333;\r\n            background: @color;\r\n        }\r\n    }\r\n}\r\n\r\n.btn-alt {\r\n    background: transparent;\r\n\r\n    &.btn-default {\r\n        .btn-alt(darken(@brand-default, 30%));\r\n    }\r\n\r\n    &.btn-info {\r\n        .btn-alt(@brand-info);\r\n    }\r\n\r\n    &.btn-primary {\r\n        .btn-alt(@brand-primary);\r\n    }\r\n\r\n    &.btn-success {\r\n        .btn-alt(@brand-success);\r\n    }\r\n\r\n    &.btn-warning {\r\n        .btn-alt(@brand-warning);\r\n    }\r\n\r\n    &.btn-danger {\r\n        .btn-alt(@brand-danger);\r\n    }\r\n}\r\n\r\n.btn-xs > .fa {\r\n  font-size: 14px;\r\n  top: 1px;\r\n  position: relative;\r\n}\r\n\r\n\r\n.btn-default {\r\n    background-color: fade(@redash-gray, 15%);\r\n}\r\n\r\n.btn-transparent {\r\n    background-color: transparent !important;\r\n}\r\n\r\n.btn-default:hover, .btn-default:focus, .btn-default.focus, .btn-default:active, .btn-default.active, .open > .dropdown-toggle.btn-default {\r\n    background-color: fade(@redash-gray, 25%);\r\n}\r\n\r\n.btn-default:active:hover, .btn-default.active:hover, .open > .dropdown-toggle.btn-default:hover, .btn-default:active:focus, .btn-default.active:focus, .open > .dropdown-toggle.btn-default:focus, .btn-default:active.focus, .btn-default.active.focus, .open > .dropdown-toggle.btn-default.focus {\r\n    color: #333;\r\n    background-color: fade(@redash-gray, 45%);\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/carousel.less",
    "content": ".carousel-caption {\r\n    left: 0;\r\n    right: 0;\r\n    bottom: 0;\r\n    background: rgba(0,0,0,0.6);\r\n    \r\n    h3 {\r\n        margin-top: 0;\r\n        margin-bottom: 3px;\r\n        color: #fff;\r\n    }\r\n}\r\n\r\n.carousel-indicators {\r\n    bottom: 10px;\r\n    \r\n    & > li:not(.active) {\r\n        border: 0;\r\n        background: #000;\r\n    }\r\n}\r\n\r\n.carousel-control {\r\n    width: 50px;\r\n    background: none;\r\n    \r\n    .fa {\r\n        font-size: 50px;\r\n        height: 52px;\r\n        margin-top: -26px;\r\n        position: absolute;\r\n        top: 50%;\r\n        .margin-left(-9px);\r\n    }\r\n}\r\n\r\n@media @max-768 {\r\n    .carousel-indicators, .carousel-caption {\r\n        display: none;\r\n    }\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/chart.less",
    "content": "/* --------------------------------------------------------\r\n    Chart Helper Classes\r\n-----------------------------------------------------------*/\r\n.main-chart {\r\n    margin: 0px -8px 0 -10px;\r\n    overflow: hidden;\r\n    position: relative;\r\n    bottom: -10px;\r\n}\r\n\r\n.mc-item {\r\n    width: 100%;\r\n    height: 250px;\r\n}\r\n\r\n.mc-pie {\r\n    width: 100%;\r\n    height: 300px;\r\n}\r\n\r\n@media (min-width: @screen-sm-min) {\r\n    .mc-info {\r\n        position: absolute;\r\n        bottom: 10px;\r\n        z-index: 1;\r\n        padding: 10px 20px 15px;\r\n        left: 10px;\r\n        background-color: rgba(0, 150, 136, 0.55);\r\n        color: #fff;\r\n        max-width: 270px;\r\n        \r\n        span {\r\n            font-size: 33px;\r\n        }\r\n        \r\n        small {\r\n            display: block;\r\n            margin-top: -3px;\r\n            margin-left: 3px;\r\n            line-height: 130%;\r\n        }\r\n    }\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Overview Small Charts\r\n-----------------------------------------------------------*/\r\n.o-item {\r\n    padding: 0 20px 15px 20px;\r\n    color: #fff;\r\n    margin-bottom: @grid-gutter-width;\r\n    box-shadow: @tile-shadow;\r\n}\r\n\r\n.oi-number {\r\n    font-size: 23px;\r\n    display: block;\r\n    margin-top: 6px;\r\n    margin-bottom: 3px; \r\n    line-height: 100%;\r\n}\r\n\r\n.oi-title {\r\n    text-transform: uppercase;\r\n    .text-overflow();\r\n    line-height: 100%;\r\n    padding: 10px 15px;\r\n    width: auto;\r\n    margin: 0 -21px 20px;\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Count Box\r\n-----------------------------------------------------------*/\r\n.count-box {\r\n    padding: 20px 23px 0;\r\n    \r\n    [class*=\"col-\"] {\r\n        padding-left: 8px;\r\n        padding-right: 8px;\r\n    }\r\n}\r\n\r\n.cb-item {\r\n    background: rgba(255,255,255,0.22);\r\n    padding: 10px 0;\r\n    text-align: center;\r\n    margin-bottom: 16px;\r\n    \r\n    & > h3 {\r\n        margin: 0;\r\n        line-height: 100%;\r\n        color: #fff;\r\n        font-weight: normal;\r\n    }\r\n    \r\n    & > small {\r\n        line-height: 100%;\r\n        margin-top: 1px;\r\n        display: block;\r\n        font-size: 11px;\r\n        color: #fff;\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Flot Charts\r\n-----------------------------------------------------------*/\r\n.flot-legend {\r\n    text-align: center;\r\n    margin: 10px 0 5px;\r\n    \r\n    table {\r\n        display: inline-block;\r\n    }\r\n    \r\n    .legendColorBox {\r\n        & > div {\r\n            border: #fff !important;\r\n            \r\n            & > div {\r\n                border-radius: 50%;\r\n            }\r\n        }\r\n    }\r\n    \r\n    .legendLabel {\r\n        padding: 0 8px 0 3px;\r\n    }\r\n}\r\n\r\n[class*=\"flc-\"] {\r\n    text-align: center;\r\n    margin: 20px 0 5px;\r\n    \r\n    table {\r\n        display: inline-block;\r\n    }\r\n    \r\n    .legendColorBox {\r\n        & > div {\r\n            border: #fff !important;\r\n            \r\n            & > div {\r\n                border-radius: 50%;\r\n            }\r\n        }\r\n    }\r\n    \r\n    .legendLabel {\r\n        padding: 0 8px 0 3px;\r\n    }\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Easy Pie Charts\r\n-----------------------------------------------------------*/\r\n.pie-overviews {\r\n    margin-bottom: -15px;\r\n}\r\n\r\n.po-item {\r\n    display: inline-block;\r\n    position: relative;\r\n    margin: 0 5px 10px;\r\n    padding-bottom: 13px;\r\n    color: #fff;\r\n}\r\n\r\n.poi-percent {\r\n    position: absolute;\r\n    text-align: center;\r\n    width: 100%;\r\n    margin-top: 32px;\r\n    font-size: 27px;\r\n    text-shadow: none;\r\n    padding-left: 2px;\r\n    \r\n    &:after {\r\n        content: '%';\r\n        font-size: 11px;\r\n    }\r\n}\r\n\r\n.poi-title {\r\n    position: absolute;\r\n    bottom: 0;\r\n    width: 100%;\r\n    text-align: center;\r\n    font-size: 12px;\r\n    \r\n    i {\r\n        font-size: 15px;\r\n        font-weight: normal;\r\n        -webkit-font-smoothing: antialiased;\r\n        .opacity(0.5);\r\n        \r\n        &:hover {\r\n            .opacity(1);\r\n            cursor: pointer;\r\n        }\r\n    }\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Chart Tooltips\r\n-----------------------------------------------------------*/\r\n#jqstooltip,\r\n.chart-tooltip {\r\n    min-width: 21px;\r\n    min-height: 23px;\r\n    text-align: center;\r\n    border: 0;\r\n    background: #333;\r\n}\r\n\r\n#jqstooltip .jqsfield,\r\n.chart-tooltip {\r\n    font-size: 12px;\r\n    font-weight: 500;\r\n    font-family: inherit;\r\n    text-align: center;\r\n    color: #fff;\r\n}\r\n\r\n#jqstooltip .jqsfield {\r\n    & > span {\r\n        display: none;\r\n    }\r\n}\r\n\r\n.chart-tooltip {\r\n    position: absolute;\r\n    padding: 6px 10px 5px;\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/dropdown.less",
    "content": "\n.dropdown-menu {\n    z-index: 1000000000;\n    box-shadow: @dropdown-shadow;\n    margin-top: 1px;\n    border-width: 0;\n    .animated(fadeIn, 300ms);\n\n    > .disabled{\n        cursor: not-allowed;\n        // The real magic ;)\n        > a {\n            pointer-events: none;\n            color: @dropdown-link-disabled-color;\n        }\n    }\n\n    & > li > a {\n        padding: 8px 17px;\n    }\n\n    &.dm-icon {\n        & > li > a > .zmdi {\n            line-height: 100%;\n            vertical-align: top;\n            font-size: 18px;\n            width: 28px;\n        }\n    }\n\n    &:not([class*=\"bg-\"]) {\n        & > li > a {\n            &:hover {\n                color: #000;\n            }\n        }\n    }\n\n    &[class*=\"bg-\"] {\n        & > li > a {\n            font-weight: 300;\n            color: #fff;\n        }\n    }\n}\n\n.dropdown-header {\n    padding: 10px 15px 9px;\n    text-transform: uppercase;\n    font-weight: normal;\n    border-radius: 1px 1px 0 0;\n    line-height: 100%;\n    border-radius: 2px 2px 0 0;\n\n    &[class*=\"bg-\"] {\n        color: #fff;\n    }\n\n    .actions {\n        top: 0;\n        right: 0;\n\n        & > li > a {\n            display: block;\n            padding: 6px 0 5px;\n            width: 33px;\n            text-align: center;\n\n            &:hover {\n                background: rgba(0,0,0,0.08);\n            }\n        }\n    }\n}\n\n.dropdown-menu {\n  >span {\n    >li {\n      >a {\n        display: block;\n        padding: 3px 20px;\n        clear: both;\n        font-weight: normal;\n        line-height: 1.428571429;\n        color: #333333;\n        white-space: nowrap;\n        &:hover, &:focus {\n          color: #ffffff;\n          text-decoration: none;\n          background-color: #428bca;\n        }\n      }\n    }\n  }\n}\n\n"
  },
  {
    "path": "client/app/assets/less/inc/edit-in-place.less",
    "content": ".edit-in-place {\n  white-space: pre-line;\n  display: inline-block;\n\n  p {\n    margin-bottom: 0;\n  }\n\n  .editable {\n    display: inline-block;\n    cursor: pointer;\n\n    &:hover {\n      background: @redash-yellow;\n      border-radius: @redash-radius;\n    }\n  }\n\n  &.active input,\n  &.active textarea {\n    display: inline-block;\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/flex.less",
    "content": ".d-flex         { display: flex !important; }\n.d-inline-flex  { display: inline-flex !important; }\n\n.flex-row            { flex-direction: row !important; }\n.flex-column         { flex-direction: column !important; }\n.flex-row-reverse    { flex-direction: row-reverse !important; }\n.flex-column-reverse { flex-direction: column-reverse !important; }\n\n.flex-wrap         { flex-wrap: wrap !important; }\n.flex-nowrap       { flex-wrap: nowrap !important; }\n.flex-wrap-reverse { flex-wrap: wrap-reverse !important; }\n.flex-fill         { flex: 1 1 auto !important; }\n\n.justify-content-start   { justify-content: flex-start !important; }\n.justify-content-end     { justify-content: flex-end !important; }\n.justify-content-center  { justify-content: center !important; }\n.justify-content-between { justify-content: space-between !important; }\n.justify-content-around  { justify-content: space-around !important; }\n\n.align-items-start    { align-items: flex-start !important; }\n.align-items-end      { align-items: flex-end !important; }\n.align-items-center   { align-items: center !important; }\n.align-items-baseline { align-items: baseline !important; }\n.align-items-stretch  { align-items: stretch !important; }\n\n.align-content-start   { align-content: flex-start !important; }\n.align-content-end     { align-content: flex-end !important; }\n.align-content-center  { align-content: center !important; }\n.align-content-between { align-content: space-between !important; }\n.align-content-around  { align-content: space-around !important; }\n.align-content-stretch { align-content: stretch !important; }\n\n.align-self-auto     { align-self: auto !important; }\n.align-self-start    { align-self: flex-start !important; }\n.align-self-end      { align-self: flex-end !important; }\n.align-self-center   { align-self: center !important; }\n.align-self-baseline { align-self: baseline !important; }\n.align-self-stretch  { align-self: stretch !important; }\n"
  },
  {
    "path": "client/app/assets/less/inc/font.less",
    "content": "///* --------------------------------------------------------\n//    Roboto Light - 300\n//-----------------------------------------------------------*/\n//.font-face(roboto, 'Roboto-Light-webfont', 300, normal);\n//\n//\n///* --------------------------------------------------------\n//    Roboto Regular - 400\n//-----------------------------------------------------------*/\n//.font-face(roboto, 'Roboto-Regular-webfont', 400, normal);\n//\n//\n///* --------------------------------------------------------\n//    Roboto Medium - 500\n//-----------------------------------------------------------*/\n//.font-face(roboto, 'Roboto-Medium-webfont', 500, normal);\n//\n//\n///* --------------------------------------------------------\n//    Roboto Bold - 700\n//-----------------------------------------------------------*/\n//.font-face(roboto, 'Roboto-Bold-webfont', 700, normal);\n"
  },
  {
    "path": "client/app/assets/less/inc/form.less",
    "content": "label {\n  font-weight: 500;\n}\n\ntextarea.v-resizable {\n  resize: vertical;\n}\n\n.form-group {\n  &.required {\n    .control-label {\n      &:after {\n        content: \" *\";\n        color: inherit;\n      }\n    }\n  }\n  &.has-error {\n    .help-block {\n      &.error {\n        display: block;\n      }\n    }\n  }\n  .help-block {\n    &.error {\n      display: none;\n    }\n  }\n}\n\n/* --------------------------------------------------------\n    Input Fields\n-----------------------------------------------------------*/\n.form-control {\n  .transition(all);\n  .transition-duration(300ms);\n  resize: none;\n  box-shadow: 0 0 0 40px rgba(0, 0, 0, 0) !important;\n  border-radius: @redash-input-radius;\n\n  &:focus {\n    box-shadow: none !important;\n    border-color: @blue;\n  }\n  &:hover {\n    border-color: @blue;\n  }\n}\n\n/* --------------------------------------------------------\n    Custom Checkbox + Radio\n-----------------------------------------------------------*/\n.cra-validatation(@color) {\n  input[type=\"checkbox\"],\n  input[type=\"radio\"] {\n    & + .input-helper {\n      border-color: @color;\n    }\n\n    &:checked + .input-helper:before {\n      background: @color;\n    }\n  }\n}\n\n.cr-alt {\n  position: relative;\n  padding-top: 0;\n  margin: 0;\n\n  label {\n    position: relative;\n    padding-left: 28px;\n  }\n\n  &.has-success {\n    .cra-validatation(@green);\n  }\n\n  &.has-warning {\n    .cra-validatation(@orange);\n  }\n\n  &.has-error {\n    .cra-validatation(@red);\n  }\n\n  input[type=\"checkbox\"],\n  input[type=\"radio\"] {\n    .opacity(0);\n    width: 20px;\n    height: 20px;\n    position: absolute;\n    z-index: 10;\n    margin: 0;\n    top: 0;\n    left: 0;\n    cursor: pointer;\n\n    & + .input-helper {\n      border: 1px solid @input-border;\n      width: 19px;\n      height: 19px;\n      background: #fff;\n      position: absolute;\n      left: 0;\n      top: -1px;\n      cursor: pointer;\n    }\n\n    &:checked + .input-helper:before {\n      content: \"\";\n      width: 9px;\n      height: 9px;\n      background: #31acff;\n      position: absolute;\n      left: 4px;\n      top: 4px;\n    }\n  }\n\n  input[type=\"radio\"] {\n    & + i {\n      border-radius: 50%;\n    }\n\n    &:checked + i:before {\n      border-radius: 50%;\n    }\n  }\n\n  &.disabled {\n    .opacity(0.7);\n  }\n}\n\n.checkbox-inline,\n.radio-inline {\n  padding-left: 27px;\n}\n\n/* --------------------------------------------------------\n    Input Addon\n-----------------------------------------------------------*/\n.input-group {\n  .input-group-addon {\n    min-width: 40px;\n    color: #333;\n    padding: 0;\n  }\n\n  &:not([class*=\"input-group-\"]) {\n    .input-group-addon {\n      font-size: 15px;\n    }\n  }\n}\n\n/* --------------------------------------------------------\n    Toggle Switch\n-----------------------------------------------------------*/\n.ts-color(@color) {\n  input {\n    &:not(:disabled) {\n      &:checked {\n        & + .ts-helper {\n          background: fade(@color, 50%);\n\n          &:before {\n            background: @color;\n          }\n\n          &:active {\n            &:before {\n              box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28), 0 0 0 20px fade(@color, 20%);\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\n.toggle-switch {\n  display: inline-block;\n  vertical-align: top;\n  .user-select(none);\n\n  .ts-label {\n    display: inline-block;\n    margin: 0 20px 0 0;\n    vertical-align: top;\n    -webkit-transition: color 0.56s cubic-bezier(0.4, 0, 0.2, 1);\n    transition: color 0.56s cubic-bezier(0.4, 0, 0.2, 1);\n  }\n\n  .ts-helper {\n    display: inline-block;\n    position: relative;\n    width: 40px;\n    height: 16px;\n    border-radius: 8px;\n    background: rgba(0, 0, 0, 0.26);\n    -webkit-transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);\n    transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);\n    vertical-align: middle;\n    cursor: pointer;\n\n    &:before {\n      content: \"\";\n      position: absolute;\n      top: -4px;\n      left: -4px;\n      width: 24px;\n      height: 24px;\n      background: #fafafa;\n      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);\n      border-radius: 50%;\n      webkit-transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1), background 0.28s cubic-bezier(0.4, 0, 0.2, 1),\n        box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);\n      transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1), background 0.28s cubic-bezier(0.4, 0, 0.2, 1),\n        box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);\n    }\n  }\n\n  &:not(.disabled) {\n    .ts-helper {\n      &:active {\n        &:before {\n          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28), 0 0 0 20px rgba(128, 128, 128, 0.1);\n        }\n      }\n    }\n  }\n\n  input {\n    position: absolute;\n    z-index: 1;\n    width: 46px;\n    margin: 0 0 0 -4px;\n    height: 24px;\n    .opacity(0);\n    cursor: pointer;\n\n    &:checked {\n      & + .ts-helper {\n        &:before {\n          left: 20px;\n        }\n      }\n    }\n  }\n\n  &:not([data-ts-color]) {\n    .ts-color(@teal);\n  }\n\n  &.disabled {\n    .opacity(0.6);\n  }\n\n  &[data-ts-color=\"red\"] {\n    .ts-color(@red);\n  }\n\n  &[data-ts-color=\"blue\"] {\n    .ts-color(@blue);\n  }\n\n  &[data-ts-color=\"amber\"] {\n    .ts-color(@amber);\n  }\n\n  &[data-ts-color=\"purple\"] {\n    .ts-color(@purple);\n  }\n\n  &[data-ts-color=\"pink\"] {\n    .ts-color(@pink);\n  }\n\n  &[data-ts-color=\"lime\"] {\n    .ts-color(@lime);\n  }\n\n  &[data-ts-color=\"cyan\"] {\n    .ts-color(@cyan);\n  }\n\n  &[data-ts-color=\"green\"] {\n    .ts-color(@green);\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/generics.less",
    "content": "/* --------------------------------------------------------\r\n    Generate Margin Classes (0px - 25px)\r\n    margin, margin-top, margin-bottom, margin-left, margin-right\r\n-----------------------------------------------------------*/\r\n.margin (@label, @size: 1, @key:1) when (@size =< 30) {\r\n  .m-@{key} {\r\n    margin: @size !important;\r\n  }\r\n\r\n  .m-t-@{key} {\r\n    margin-top: @size !important;\r\n  }\r\n\r\n  .m-b-@{key} {\r\n    margin-bottom: @size !important;\r\n  }\r\n\r\n  .m-l-@{key} {\r\n    margin-left: @size !important;\r\n  }\r\n\r\n  .m-r-@{key} {\r\n    margin-right: @size !important;\r\n  }\r\n\r\n  .margin(@label - 5; @size + 5; @key + 5);\r\n}\r\n\r\n.margin(25, 0px, 0);\r\n\r\n.m-2 {\r\n  margin: 2px;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Generate Padding Classes (0px - 25px)\r\n    padding, padding-top, padding-bottom, padding-left, padding-right\r\n-----------------------------------------------------------*/\r\n.padding (@label, @size: 1, @key:1) when (@size =< 30) {\r\n  .p-@{key} {\r\n    padding: @size !important;\r\n  }\r\n\r\n  .p-t-@{key} {\r\n    padding-top: @size !important;\r\n  }\r\n\r\n  .p-b-@{key} {\r\n    padding-bottom: @size !important;\r\n  }\r\n\r\n  .p-l-@{key} {\r\n    padding-left: @size !important;\r\n  }\r\n\r\n  .p-r-@{key} {\r\n    padding-right: @size !important;\r\n  }\r\n\r\n  .padding(@label - 5; @size + 5; @key + 5);\r\n}\r\n\r\n.padding(25, 0px, 0);\r\n\r\n/* --------------------------------------------------------\r\n    Generate Font-Size Classes (8px - 20px)\r\n-----------------------------------------------------------*/\r\n.font-size (@label, @size: 8, @key:10) when (@size =< 20) {\r\n  .f-@{key} {\r\n    font-size: @size !important;\r\n  }\r\n\r\n  .font-size(@label - 1; @size + 1; @key + 1);\r\n}\r\n\r\n.font-size(20, 8px, 8);\r\n\r\n.f-inherit {\r\n  font-size: inherit !important;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Font Weight\r\n-----------------------------------------------------------*/\r\n.f-300 {\r\n  font-weight: 300 !important;\r\n}\r\n.f-400 {\r\n  font-weight: 400 !important;\r\n}\r\n.f-500 {\r\n  font-weight: 500 !important;\r\n}\r\n.f-700 {\r\n  font-weight: 700 !important;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Position\r\n-----------------------------------------------------------*/\r\n.p-relative {\r\n  position: relative !important;\r\n}\r\n.p-absolute {\r\n  position: absolute !important;\r\n}\r\n.p-fixed {\r\n  position: fixed !important;\r\n}\r\n.p-static {\r\n  position: static !important;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Overflow\r\n-----------------------------------------------------------*/\r\n.o-hidden {\r\n  overflow: hidden !important;\r\n}\r\n.o-visible {\r\n  overflow: visible !important;\r\n}\r\n.o-auto {\r\n  overflow: auto !important;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Display\r\n-----------------------------------------------------------*/\r\n.di-block {\r\n  display: inline-block !important;\r\n}\r\n.d-block {\r\n  display: block;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Background Colors and Colors\r\n-----------------------------------------------------------*/\r\n@array: c-white bg-white @white, c-ace bg-ace @ace, c-black bg-black @black, c-brown bg-brown @brown,\r\n  c-pink bg-pink @pink, c-red bg-red @red, c-blue bg-blue @blue, c-purple bg-purple @purple,\r\n  c-deeppurple bg-deeppurple @deeppurple, c-lightblue bg-lightblue @lightblue, c-cyan bg-cyan @cyan,\r\n  c-teal bg-teal @teal, c-green bg-green @green, c-lightgreen bg-lightgreen @lightgreen, c-lime bg-lime @lime,\r\n  c-yellow bg-yellow @yellow, c-amber bg-amber @amber, c-orange bg-orange @orange,\r\n  c-deeporange bg-deeporange @deeporange, c-gray bg-gray @gray, c-bluegray bg-bluegray @bluegray,\r\n  c-indigo bg-indigo @indigo;\r\n\r\n.for(@array);\r\n.-each(@value) {\r\n  @name: extract(@value, 1);\r\n  @name2: extract(@value, 2);\r\n  @color: extract(@value, 3);\r\n  &.@{name2} {\r\n    background-color: @color !important;\r\n  }\r\n\r\n  &.@{name} {\r\n    color: @color !important;\r\n  }\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Background Colors\r\n-----------------------------------------------------------*/\r\n.bg-brand {\r\n  background-color: @brand-bg;\r\n}\r\n.bg-black-trp {\r\n  background-color: rgba(0, 0, 0, 0.12) !important;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Borders\r\n-----------------------------------------------------------*/\r\n.b-0 {\r\n  border: 0 !important;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Width\r\n-----------------------------------------------------------*/\r\n.w-100 {\r\n  width: 100% !important;\r\n}\r\n.w-50 {\r\n  width: 50% !important;\r\n}\r\n.w-25 {\r\n  width: 25% !important;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Border Radius\r\n-----------------------------------------------------------*/\r\n.brd-2 {\r\n  border-radius: 2px;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Alignment\r\n-----------------------------------------------------------*/\r\n.va-top {\r\n  vertical-align: top;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Screen readers\r\n-----------------------------------------------------------*/\r\n.sr-only {\r\n  position: absolute;\r\n  width: 1px;\r\n  height: 1px;\r\n  padding: 0;\r\n  margin: -1px;\r\n  overflow: hidden;\r\n  clip: rect(0, 0, 0, 0);\r\n  white-space: nowrap;\r\n  border-width: 0;\r\n}\r\n"
  },
  {
    "path": "client/app/assets/less/inc/header.less",
    "content": "#header {\r\n    width: 100%;\r\n    z-index: 10;\r\n    top: 0;\r\n    left: 0;\r\n    background-color: #fff;\r\n    height: @header-height;\r\n    \r\n    &.affix {\r\n        box-shadow: 0 0 20px rgba(0, 0, 0, 0.23);\r\n    }\r\n    \r\n    &:not(.affix) {\r\n        box-shadow: @tile-shadow;\r\n        position: fixed;\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Top Menu\r\n-----------------------------------------------------------*/\r\n.header-inner {\r\n    padding: 0;\r\n    margin: 0;\r\n    width: 100%;\r\n    list-style: none;\r\n\r\n    & > li {\r\n        &:not(.pull-right) {\r\n            float: left;\r\n        }\r\n        \r\n        @media (max-width: @screen-sm-min) {\r\n            &:not(.top-search) {\r\n                position: static;\r\n            }\r\n            \r\n            .dropdown-menu {\r\n                width: ~\"calc(100% - 30px)\";\r\n                margin-left: 15px;\r\n            }\r\n        }\r\n\r\n        & > a {\r\n            height: @header-height;\r\n            color: #333;\r\n            min-width: 45px;\r\n            display: block;\r\n            position: relative;\r\n            \r\n            & > .zmdi {\r\n                font-size: 22px;\r\n                line-height: @header-height;\r\n            }\r\n        }\r\n        \r\n        &:not(.logo) {\r\n            text-align: center;\r\n        }\r\n        \r\n        &.open > a:not([class*=\"hi-\"]):before {\r\n            content: \"\";\r\n            width: 40px;\r\n            height: 40px;\r\n            position: absolute;\r\n            top: 50%;\r\n            left: 50%;\r\n            margin-top: -21px;\r\n            margin-left: -20px;\r\n            background: #eee;\r\n            border-radius: 50%;\r\n            z-index: -1;\r\n        }\r\n    }\r\n    \r\n    .dropdown-menu {\r\n        margin-top: -5px; \r\n    }\r\n    \r\n    .open {\r\n        & > .hi-messages { color: @green; }\r\n        & > .hi-notifications { color: @orange; }\r\n        & > .hi-projects { color: @green; }\r\n        & > .hi-events { color: @blue; }\r\n        \r\n        .hi-count {\r\n            display: none;\r\n        }\r\n    }\r\n}\r\n\r\n.hi-count {\r\n    position: absolute;\r\n    font-style: normal;\r\n    background-color: @red;\r\n    padding: 0 4px;\r\n    font-size: 10px;\r\n    color: #fff;\r\n    line-height: 17px;\r\n    height: 17px;\r\n    top: 11px;\r\n    right: 6px;\r\n    border-radius: 50%;\r\n    width: 17px;\r\n}\r\n\r\n.hi-dropdown {\r\n    padding: 0;\r\n    \r\n    @media (min-width: @screen-sm-min) {\r\n        width: 350px;\r\n    }\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Logo\r\n-----------------------------------------------------------*/\r\n.logo {\r\n    position: relative; \r\n    z-index: 2;\r\n    height: @logo-height;\r\n    \r\n    @media (min-width: (@screen-lg-min + 80px)) { \r\n        width: @logo-width;\r\n        background-color: #000;\r\n        margin-right: 15px;\r\n        \r\n        & > a {\r\n            padding: 15px 22px;\r\n        }\r\n    }\r\n    \r\n    @media (max-width: (@screen-md-max + 80px)) {\r\n        width: @sidebar-left-mid-width;\r\n        \r\n        & > a {\r\n            display: none !important;\r\n        }\r\n    }\r\n    \r\n    @media (max-width: (@screen-sm-min)) {\r\n        padding: 12px;\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Sidebar Trigger for mobile\r\n-----------------------------------------------------------*/\r\n#menu-trigger {\r\n    font-size: 21px;\r\n    text-align: center;\r\n    color: #fff;\r\n    cursor: pointer;\r\n    display: none;\r\n    background: #000;\r\n    height: 100%;\r\n    \r\n    &.toggled i:before {\r\n        content: '\\f2ea';\r\n    }\r\n    \r\n    @media (min-width: (@screen-sm-min + 1)) {\r\n        line-height: @header-height;\r\n    }\r\n    \r\n    @media (max-width: (@screen-md-max + 80px)) {\r\n        display: block;\r\n    }\r\n    \r\n    @media (max-width: (@screen-sm-min)) {\r\n        border-radius: 2px;\r\n        line-height: 39px;\r\n    }\r\n}\r\n        \r\n\r\n/* --------------------------------------------------------\r\n    Top Search\r\n-----------------------------------------------------------*/\r\n.top-search {\r\n    position: relative;\r\n    background: #fff;\r\n    height: @header-height;\r\n    \r\n    &:not(.toggled) {\r\n        width: 80px;\r\n        margin-left: 15px;\r\n        \r\n        &:before {\r\n            font-family: @font-icon;\r\n            content: \"\\f1c3\";\r\n            position: absolute;\r\n            left: 0;\r\n            top: 15px;\r\n            font-size: 22px;\r\n            z-index: 1;\r\n            color: #333;\r\n        }\r\n            \r\n        .ts-reset {\r\n            display: none;    \r\n        }\r\n        \r\n        .ts-input {\r\n            cursor: pointer;\r\n        }\r\n        \r\n        @media (max-width: (@screen-xs-min - 150px)) {\r\n            width: 20px;\r\n        }\r\n    }\r\n    \r\n    .ts-input {\r\n        height: @header-height - 2px;\r\n        padding-left: 25px;\r\n        width: 100%;\r\n        border: 0;\r\n        position: relative;\r\n        background: transparent;\r\n        z-index: 1;\r\n    }\r\n    \r\n    &.toggled {\r\n        position: absolute;\r\n        top: 0;\r\n        font-size: 20px;\r\n        font-weight: normal;\r\n        z-index: 1;\r\n        width: 100%;\r\n        left: 0;\r\n        \r\n        @media (min-width: (@screen-lg-min + 80px)) {\r\n            padding-left: @sidebar-left-width;\r\n        }\r\n        \r\n        @media (min-width: (@screen-sm-min + 1px)) and (max-width: (@screen-md-max + 80px)) { \r\n            padding-left: @sidebar-left-mid-width;\r\n        }\r\n        \r\n        .ts-input {\r\n            background: #fff;\r\n        }\r\n        \r\n        .ts-reset {\r\n            font-size: 11px; \r\n            color: #fff; \r\n            position: absolute;\r\n            top: 50%;\r\n            right: 15px;\r\n            z-index: 2;\r\n            width: 20px;\r\n            height: 20px;\r\n            background-color: #8E8E8E;\r\n            line-height: 20px;\r\n            text-align: center;\r\n            border-radius: 50%;\r\n            margin-top: -10px;\r\n            \r\n            &:hover {\r\n                cursor: pointer;\r\n                background: #333;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Events\r\n-----------------------------------------------------------*/\r\n.event-time {\r\n    width: 67px; \r\n    height: 50px;\r\n    text-align: center;\r\n    padding: 9px 0;\r\n    color: #fff;\r\n    border-radius: 2px;\r\n    margin-top: 2px;\r\n    \r\n    & > h2 {\r\n        margin: 0;\r\n        line-height: 100%;\r\n        font-size: 17px;\r\n        margin-bottom: -1px;\r\n        color: #fff;\r\n        font-weight: normal;\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Apps\r\n-----------------------------------------------------------*/\r\n@media (min-width: @screen-sm-min) {\r\n    #launch-apps {    \r\n        padding: 0;\r\n        text-align: center;\r\n        width: 300px;\r\n    }\r\n    \r\n    .la-body {\r\n        padding: 20px 10px;\r\n    }\r\n    \r\n    .lab-item {\r\n        width: 60px;\r\n        display: inline-block;\r\n        margin: 10px;\r\n        \r\n        &:hover {\r\n            & > a {\r\n                .opacity(0.8);\r\n            }\r\n            \r\n            & > small {\r\n                color: #333;\r\n            }\r\n        }\r\n        \r\n        & > a {\r\n            height: 60px;\r\n            display: block;\r\n            color: #fff;\r\n            line-height: 70px;\r\n            border-radius: 50%;\r\n            .transition(opacity);\r\n        \r\n            & > i {\r\n                font-size: 25px;\r\n            }\r\n        }\r\n        \r\n        & > small { \r\n            color: #969696;  \r\n            display: block;\r\n            margin-top: 5px; \r\n            .transition(color);\r\n        } \r\n  \r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Time\r\n-----------------------------------------------------------*/\r\n#time {\r\n    font-size: 18px;\r\n    font-weight: 400;\r\n    background-color: @sidebar;\r\n    color: #FBFBFB;\r\n    padding: 4px 11px;\r\n    border-radius: 2px;\r\n    margin: 14px;\r\n    \r\n    span {\r\n        &:not(:last-child):after {\r\n            content: \":\";\r\n            position: relative;\r\n            top: -1px;\r\n            right: -1px;\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "client/app/assets/less/inc/ie-warning.less",
    "content": ".ie-warning {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\tz-index: 9999;\n\tbackground: @black;\n\twidth: 100%;\n\theight: 100%;\n\ttext-align: center;\n\tcolor: #fff;\n\tfont-family: \"Courier New\", Courier, monospace;\n\tpadding: 50px 0;\n\n\tp {\n\t\tfont-size: 17px;\n\t}\n    \n    .iew-container {\n        min-width: 1024px;\n        width: 100%;\n        height: 200px;\n        background: #fff;\n        margin: 50px 0;\n    }\n\n\t.iew-download {\n\t\tlist-style: none;\n\t\tpadding: 30px 0;\n\t\tmargin: 0 auto;\n        width: 720px;\n\n\t\t& > li {\n\t\t\tfloat: left;\n\t\t\tvertical-align: top;\n\n\t\t\t& > a {\n\t\t\t\tdisplay: block;\n\t\t\t\tcolor: #000;\n\t\t\t\twidth: 140px;\n\t\t\t\tfont-size: 15px;\n\t\t\t\tpadding: 15px 0;\n\n\t\t\t\t& > div {\n\t\t\t\t\tmargin-top: 10px;\n\t\t\t\t}\n\n\t\t\t\t&:hover {\n\t\t\t\t\tbackground-color: #eee;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "client/app/assets/less/inc/jumbotron.less",
    "content": ".jumbotron {\r\n    padding-left: 60px;\r\n    padding-right: 60px;\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/label.less",
    "content": ".label {\r\n    border-radius: 2px;\r\n    padding: 3px 6px 4px;\r\n    font-weight: 500;\r\n    font-size: 11px;\r\n}\r\n\r\n.badge {\r\n    border-radius: 1px;\r\n}\r\n\r\n.label-default {\r\n    background: fade(@redash-gray, 85%);\r\n}\r\n\r\n.label-tag-unpublished {\r\n    background: fade(@redash-gray, 85%);\r\n}\r\n\r\n.label-tag-archived {\r\n    .label-warning();\r\n}\r\n\r\n.label-tag {\r\n    background: fade(@redash-gray, 10%);\r\n    color: fade(@redash-gray, 75%);\r\n}\r\n\r\n.label-tag-unpublished,\r\n.label-tag-archived,\r\n.label-tag {\r\n    margin-right: 3px;\r\n    display: inline;\r\n    margin-top: 2px;\r\n    max-width: 24ch;\r\n    .text-overflow();\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/less-plugins/for.less",
    "content": "\r\n.for(@i, @n) {.-each(@i)}\r\n.for(@n)     when (isnumber(@n)) {.for(1, @n)}\r\n.for(@i, @n) when not (@i = @n)  {\r\n    .for((@i + (@n - @i) / abs(@n - @i)), @n);\r\n}\r\n\r\n.for(@array)   when (default()) {.for-impl_(length(@array))}\r\n.for-impl_(@i) when (@i > 1)    {.for-impl_((@i - 1))}\r\n.for-impl_(@i) when (@i > 0)    {.-each(extract(@array, @i))}\r\n"
  },
  {
    "path": "client/app/assets/less/inc/list-group.less",
    "content": ".list-group {\n    margin-bottom: 0;\n\n    &.lg-alt .list-group-item {\n        border: 0;\n    }\n\n    &:not(.lg-alt) {\n        &.lg-listview .list-group-item {\n            border-left: 0;\n            border-right: 0;\n\n            &:last-child {\n                border-bottom: 0;\n            }\n        }\n    }\n}\n\n.max-character {\n  .text-overflow();\n}\n\n.list-group-item {\n &.active {\n   button {\n     color: white;\n   }\n }\n  .cr-alt {\n      line-height: 100%;\n      margin-top: 2px;\n  }\n\n  &.active, &.active:hover, &.active:focus {\n    background-color: #fff;\n    box-shadow: inset 3px 0px 0px @brand-primary;\n  }\n}\n\n.list-group-item-heading {\n    margin-bottom: 2px;\n    color: #333;\n\n    & > small {\n        font-size: 11px;\n        color: #C5C5C5;\n        margin-left: 10px;\n    }\n}\n\n.list-group-item-heading,\n.list-group-item-text {\n    .text-overflow();\n}\n\n.list-group-item-text {\n    display: block;\n\n    &:not(:last-child) {\n        margin-bottom: 4px;\n    }\n}\n\n.list-group-img {\n    width: 38px;\n    height: 38px;\n    border-radius: 2px;\n}\n\n.ui-select-choices-row.disabled > span {\n  background-color: inherit !important;\n}\n\n.list-group-item.inactive,\n.ui-select-choices-row.disabled {\n  background-color: #eee !important;\n  border-color: transparent;\n  opacity: 0.5;\n  box-shadow: none;\n  color: #333;\n  pointer-events: none;\n  cursor: not-allowed;\n}"
  },
  {
    "path": "client/app/assets/less/inc/list.less",
    "content": ".clist {\r\n    list-style: none;\r\n    \r\n    & > li {\r\n        &:before {\r\n            font-family: @font-icon;\r\n            margin: 0 10px 0 -20px;\r\n            vertical-align: middle;\r\n        }\r\n    }\r\n    \r\n    &.clist-angle > li:before {\r\n        content: \"\\f2fb\";\r\n    }\r\n    \r\n    &.clist-check > li:before {\r\n        content: \"\\f26b\";\r\n    }\r\n    \r\n    &.clist-star > li:before { \r\n        content: \"\\f27d\";\r\n    }\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/login.less",
    "content": ".login-content {\r\n    overflow: hidden;\r\n    height: 100%;\r\n    background: @brand-bg;\r\n    padding: 0;\r\n    text-align: center;\r\n    \r\n    &:after {\r\n        content: \"\";\r\n        vertical-align: middle; \r\n        display: inline-block;\r\n        width: 1px;\r\n        height: 100%;\r\n    }\r\n}\r\n\r\n.lc-block {\r\n    background: #fff;\r\n    box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27);\r\n    border-radius: 2px;\r\n    width: 300px;\r\n    display: inline-block;\r\n    vertical-align: middle;\r\n    position: relative;\r\n    padding: 45px 30px 30px;\r\n    \r\n    &:not(.toggled) {\r\n        display: none;\r\n    }\r\n    \r\n    &.toggled {\r\n        .animated(fadeInUp, 300ms);\r\n        z-index: 10;\r\n    }\r\n    \r\n    @media (max-width: @screen-xs-max) {\r\n        padding: 15px 35px 25px 20px;\r\n        width: ~\"calc(100% - 60px)\";\r\n    }\r\n    \r\n    .form-control {\r\n        text-align: center;\r\n    }\r\n}\r\n\r\n.lcb-float {\r\n    width: 60px;\r\n    height: 60px;\r\n    background: #ffffff;\r\n    border-radius: 50%;\r\n    box-shadow: 0 -10px 19px rgba(0, 0, 0, 0.38);\r\n    position: absolute;\r\n    top: -35px;\r\n    left: 50%;\r\n    margin-left: -30px;\r\n    \r\n    img {\r\n        width: 100%;\r\n        height: 100%;\r\n        border-radius: 50%;\r\n        padding: 4px;\r\n    }\r\n    \r\n    i {\r\n        color: #333;\r\n        font-size: 25px;\r\n        line-height: 60px;\r\n    }\r\n}\r\n\r\n.lcb-lockscreen {\r\n    position: relative;\r\n    \r\n    .form-control {\r\n        padding-right: 35px;\r\n    }\r\n    \r\n    .lcbl-btn {\r\n        background-color: #2196F3;\r\n        position: absolute;\r\n        top: 0;\r\n        right: 0;\r\n        width: 30px;\r\n        color: #fff;\r\n        font-size: 15px;\r\n        height: 27px;\r\n        margin: 4px;\r\n        line-height: 26px;\r\n        border-radius: 2px;\r\n    }\r\n}\r\n\r\n.login-navigation {     \r\n    list-style: none;\r\n    padding: 0;\r\n    margin: 0;\r\n    position: absolute;\r\n    width: 100%;\r\n    text-align: center;\r\n    left: 0%;\r\n    bottom: -45px;\r\n\r\n    & > li {\r\n        display: inline-block;\r\n        margin: 0 2px;\r\n        .transition(all);\r\n        .transition-duration(150ms);\r\n        cursor: pointer;\r\n        vertical-align: top;\r\n        color: #fff;\r\n        line-height: 16px;\r\n        min-width: 16px; \r\n        min-height: 16px;\r\n        text-transform: uppercase;\r\n        .backface-visibility(hidden); \r\n\r\n        & > span {\r\n            .opacity(0);\r\n        }\r\n\r\n        &:not(:hover) {\r\n            font-size: 0px;\r\n            border-radius: 100%;\r\n        }\r\n\r\n        &:hover {\r\n            border-radius: 10px;\r\n            padding: 0 5px;\r\n            font-size: 8px; \r\n\r\n            & > span {\r\n                .opacity(1);\r\n            } \r\n        }\r\n\r\n    }\r\n}\r\n\r\n"
  },
  {
    "path": "client/app/assets/less/inc/media.less",
    "content": "/* --------------------------------------------------------\r\n    Thumbnail\r\n-----------------------------------------------------------*/\r\n.thumbnail {\r\n\ta&:hover,\r\n\ta&:focus,\r\n\ta&.active {\r\n\t    border-color: @thumbnail-border;\r\n\t} \r\n} \r\n\r\n\r\n/* --------------------------------------------------------\r\n    Lightbox\r\n-----------------------------------------------------------*/\r\n.lightbox {\r\n    & > a {\r\n        position: relative;\r\n        .transition(opacity);\r\n        .transition-duration(300ms);\r\n        \r\n        & > img {\r\n            width: 100%;\r\n        }\r\n        \r\n        &:hover {\r\n            .opacity(0.8);\r\n        }\r\n    }\r\n    \r\n    & > a:not(.p-item) {   //Not for photo items\r\n        margin-bottom: 20px;\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Carousel\r\n-----------------------------------------------------------*/\r\n.carousel {\r\n\t.carousel-control {\r\n\t\t.transition(all);\r\n\t\t.transition-duration(250ms);\r\n\t\t.opacity(0);\r\n\t\t\r\n\t\t.zmdi {\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: 50%;\r\n\t\t\tleft: 50%;\r\n\t\t\tline-height: 100%;\r\n\t\t\t\r\n\t\t\t@media screen and (min-width: @screen-sm-min) {\r\n\t\t\t\tfont-size: 60px;\r\n\t\t\t\twidth: 60px;\r\n\t\t\t\theight: 60px;\r\n\t\t\t\tmargin-top: -30px;\r\n\t\t\t\tmargin-left: -30px;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t@media screen and (max-width: @screen-sm-max) {\r\n\t\t\t\twidth: 24px;\r\n\t\t\t\theight: 24px;\r\n\t\t\t\tmargin-top: -12px;\r\n\t\t\t\tmargin-left: -12px;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\t&:hover {\r\n\t\t.carousel-control {\r\n\t\t\t.opacity(1);\r\n\t\t}\r\n\t}\r\n\t\r\n\t.carousel-caption {\r\n\t\tbackground: rgba(0,0,0,0.6);\r\n\t\tleft: 0;\r\n\t\tright: 0;\r\n\t\tbottom: 0;\r\n\t\twidth: 100%;\r\n\t\tpadding-bottom: 50px;\r\n\t\t\r\n\t\t& > h3 {\r\n\t\t\tcolor: #fff;\r\n\t\t\tmargin: 0 0 5px;\r\n\t\t\tfont-weight: 300;\r\n\t\t}\r\n\t\t\r\n\t\t& > p {\r\n\t\t\tmargin: 0;\r\n\t\t}\r\n\t\t\r\n\t\t@media screen and (max-width: @screen-sm-max) {\r\n\t\t\tdisplay: none;\r\n\t\t}\r\n\t}\r\n\t\r\n\t.carousel-indicators {\r\n\t\tbottom: 10px;\r\n\t\tmargin: 0;\r\n\t\tleft: 0;\r\n\t\tbottom: 0;\r\n\t\twidth: 100%;\r\n\t\tpadding: 0 0 6px;\r\n\t\tbackground: rgba(0,0,0,0.6);\r\n\t\t\r\n\t\tli {\r\n\t\t\tborder-radius: 0;\r\n\t\t\twidth: 15px;\r\n\t\t\tborder: 0;\r\n\t\t\tbackground: #fff;\r\n\t\t\theight: 3px;\r\n\t\t\tmargin: 0;\r\n\t\t\t.transition(all);\r\n\t\t\t.transition-duration(250ms);\r\n\t\t\t\r\n\t\t\t&.active {\r\n\t\t\t\twidth: 25px;\r\n\t\t\t\theight: 3px;\r\n\t\t\t\tbackground: @orange;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/messages.less",
    "content": "#messages-main {\r\n    position: relative;\r\n    margin: 0 auto;\r\n    .clearfix();\r\n    \r\n    .ms-menu {\r\n        position: absolute;\r\n        left: 0;\r\n        top: 0;\r\n        border-right: 1px solid #eee;\r\n        padding-bottom: 50px;\r\n        height: 100%;\r\n        width: 240px;\r\n        background: #fff;\r\n\r\n        @media (max-width: @screen-xs-max) {\r\n            height: ~\"calc(100% - 58px)\";\r\n            display: none;\r\n            z-index: 1;\r\n            top: 58px;\r\n\r\n            &.toggled {\r\n                display: block;\r\n            }\r\n        }\r\n    }\r\n    \r\n    .ms-body {\r\n        @media (min-width: @screen-sm-min) {\r\n            padding-left: 240px;\r\n        }\r\n\r\n        @media (max-width: @screen-xs-max) {\r\n            overflow: hidden;\r\n        }\r\n    }\r\n    \r\n    .ms-user {\r\n        padding: 15px;\r\n        background: @ace;\r\n        \r\n        & > div {\r\n            overflow: hidden;\r\n            padding: 3px 5px 0px 15px;\r\n            font-size: 11px;\r\n        }\r\n    }\r\n    \r\n    #ms-compose {\r\n        position: fixed;\r\n        bottom: 120px;\r\n        z-index: 1;\r\n        right: 30px;\r\n        box-shadow: 0 0 4px rgba(0, 0, 0, 0.14),0 4px 8px rgba(0, 0, 0, 0.28);\r\n    }\r\n}\r\n\r\n#ms-menu-trigger {\r\n    .user-select(none);\r\n    position: absolute;\r\n    left: 0;\r\n    top: 0;\r\n    width: 50px;\r\n    height: 100%;\r\n    text-align: right;\r\n    padding-right: 10px;\r\n    padding-top: 19px;\r\n    \r\n    i {\r\n        font-size: 21px;\r\n    }\r\n\r\n    &.toggled {\r\n        i:before{\r\n            content: '\\f2ea';\r\n        }\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    For Message\r\n-----------------------------------------------------------*/\r\n.message-feed {\r\n    padding: 20px;\r\n    \r\n    &.right {\r\n        text-align: right;\r\n        \r\n        & > .pull-right {\r\n            margin-left: 15px;\r\n        }\r\n    }\r\n    \r\n    &:not(.right) {\r\n        .mf-content {\r\n            background: @amber;\r\n            color: #fff;\r\n        }\r\n        \r\n        \r\n    }\r\n    \r\n    &.right .mf-content {\r\n        background: #eee;\r\n    }\r\n}\r\n\r\n.mf-content {\r\n    padding: 12px 17px 13px;\r\n    border-radius: 2px;\r\n    display: inline-block;\r\n    max-width: 80%;\r\n}\r\n\r\n.mf-date {\r\n    display: block;\r\n    color: #B3B3B3;\r\n    margin-top: 7px;\r\n    \r\n    & > i {\r\n        font-size: 14px;\r\n        line-height: 100%;\r\n        position: relative;\r\n        top: 1px;\r\n    }\r\n}\r\n\r\n.msb-reply {\r\n    box-shadow: 0 -20px 20px -5px #fff;\r\n    position: relative;\r\n    margin-top: 30px;\r\n    border-top: 1px solid #eee;\r\n    background: @ace;\r\n  \r\n    textarea {\r\n        width: 100%;\r\n        font-size: 13px;\r\n        border: 0;\r\n        padding: 10px 15px;\r\n        resize: none;\r\n        height: 60px;\r\n        background: transparent;\r\n    }\r\n    \r\n    button {\r\n        position: absolute;\r\n        top: 0;\r\n        right: 0;\r\n        border: 0;\r\n        height: 100%;\r\n        width: 60px;\r\n        font-size: 25px;\r\n        color: @blue;\r\n        background: transparent;\r\n        \r\n        &:hover {\r\n            background: #f2f2f2;\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/misc.less",
    "content": "/* --------------------------------------------------------\r\n    Actions\r\n-----------------------------------------------------------*/\r\n.actions {\r\n    position: absolute;\r\n    list-style: none;\r\n    padding: 0;\r\n    margin: 0;\r\n    \r\n    & > li {\r\n        display: inline-block;\r\n    \r\n        & > a {\r\n            display: block;\r\n            padding: 0 10px;\r\n            \r\n            & > i {\r\n                font-size: 20px;\r\n            }\r\n        }\r\n    }\r\n\r\n    .dropdown-menu {\r\n        min-width: 140px;\r\n        margin-top: -8px;\r\n        margin-right: -1px;\r\n    }\r\n    \r\n    &:not(.a-alt) {\r\n        & > li > a > i {\r\n            color: #939393;\r\n        }\r\n        \r\n        & > li.open > a > i,\r\n        & > li > a:hover > i {\r\n            color: #000;\r\n        }\r\n    }\r\n    \r\n    &.a-alt {\r\n        & > li > a > i {\r\n            color: #fff;\r\n        }\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    View More\r\n-----------------------------------------------------------*/\r\n.view-more {\r\n    display: block;\r\n    padding: 5px 10px;\r\n    text-align: center;\r\n    border-top: 1px solid darken(@light-gray, 3%);\r\n    font-size: 12px;\r\n    margin-top: 15px;\r\n    color: #777777;\r\n    \r\n    &:hover {\r\n        color: #333;\r\n        background-color: @light-gray;\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Page Header\r\n-----------------------------------------------------------*/\r\n.page-header {\r\n    padding: 0 22px;\r\n    font-weight: normal;\r\n    font-size: 19px;\r\n    margin: 0 0 20px 0;\r\n    \r\n    small {\r\n        text-transform: none;\r\n        display: block;\r\n        font-size: 12px;\r\n        color: #9C9C9C;\r\n        margin-top: 7px;\r\n        line-height: 140%;\r\n    }\r\n    \r\n    h3 {\r\n        margin: 0;\r\n        font-weight: normal;\r\n        font-size: 15px;\r\n        color: #333;\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Close\r\n-----------------------------------------------------------*/\r\n.close {\r\n    font-weight: normal;\r\n    text-shadow: none;\r\n    .opacity(0.5);\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Action Header\r\n-----------------------------------------------------------*/\r\n.action-header {\r\n    position: relative;\r\n    background: @ace;\r\n    padding: 15px 13px 15px 17px;\r\n}\r\n\r\n.ah-actions {\r\n    z-index: 3;\r\n    float: right;\r\n    margin-top: 7px;\r\n    position: relative;\r\n}\r\n\r\n.ah-label {\r\n    color: #818181;\r\n    display: inline-block;\r\n    margin: 0;\r\n    font-size: 14px;\r\n    font-weight: normal;\r\n    padding: 0 6px;\r\n    line-height: 33px;\r\n    vertical-align: middle;\r\n    float: left;\r\n}\r\n\r\n.ah-search {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    height: 100%;\r\n    width: 100%;\r\n    z-index: 4;\r\n    background: #fff;\r\n    display: none;\r\n    \r\n    &:before {\r\n        content: \"\\f1c3\";\r\n        font-family: 'Material-Design-Iconic-Font';\r\n        position: absolute;\r\n        left: 24px;\r\n        top: 17px;\r\n        font-size: 22px;\r\n    }\r\n}\r\n\r\n.ahs-input {\r\n    border: 0;\r\n    padding: 0 26px 0 55px;\r\n    height: 63px;\r\n    font-size: 18px;\r\n    width: 100%;\r\n    font-weight: 100;\r\n    background: #fff;\r\n    border-bottom: 1px solid #EEE;\r\n}\r\n\r\n.ahs-close {\r\n    font-style: normal;\r\n    position: absolute;\r\n    top: 23px;\r\n    right: 22px;\r\n    font-size: 17px;\r\n    width: 18px;\r\n    height: 18px;\r\n    background-color: #ADADAD;\r\n    line-height: 100%;\r\n    color: #fff;\r\n    text-align: center;\r\n    cursor: pointer;\r\n    border-radius: 50%;\r\n    \r\n    &:hover {\r\n        background: #333;\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Load More\r\n-----------------------------------------------------------*/\r\n.load-more {\r\n    text-align: center;\r\n    margin-top: 30px;\r\n    \r\n    a {\r\n        padding: 5px 10px 3px;\r\n        display: inline-block;\r\n        background-color: @red;\r\n        color: #FFF;\r\n        border-radius: 2px;\r\n        white-space: nowrap;\r\n        \r\n        i {\r\n            font-size: 20px;\r\n            vertical-align: middle;\r\n            position: relative;\r\n            margin-top: -2px;\r\n        }\r\n        \r\n        &:hover {\r\n            background-color: darken(@red, 10%);\r\n        }\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Data List\r\n-----------------------------------------------------------*/\r\n.dl-horizontal dt {\r\n    text-align: left;\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    User Avatar\r\n-----------------------------------------------------------*/\r\n.img-avatar {\r\n    height: 37px;\r\n    border-radius: 2px;\r\n    width: 37px;\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Percy\r\n-----------------------------------------------------------*/\r\n@media only percy {\r\n    .hide-in-percy, .pace {\r\n        visibility: hidden;\r\n    }\r\n\r\n    // hide tooltips in Percy\r\n    .ant-tooltip {\r\n        display: none !important;\r\n    }\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/mixins.less",
    "content": "/* --------------------------------------------------------\r\n    Font Face\r\n-----------------------------------------------------------*/\r\n.font-face(@family, @name, @weight: 300, @style){\r\n    @font-face{\r\n        font-family: @family;\r\n        src:url('../fonts/@{family}/@{name}.eot');\r\n        src:url('../fonts/@{family}/@{name}.eot?#iefix') format('embedded-opentype'),\r\n            url('../fonts/@{family}/@{name}.woff') format('woff'),\r\n            url('../fonts/@{family}/@{name}.ttf') format('truetype'),\r\n            url('../fonts/@{family}/@{name}.svg#icon') format('svg');\r\n        font-weight: @weight;\r\n        font-style: @style;\r\n    }\r\n}\r\n\r\n/* --------------------------------------------------------\r\n    Button Varients\r\n-----------------------------------------------------------*/\r\n.button-variant(@color; @background; @border) {\r\n    color: @color;\r\n    background-color: @background;\r\n    border-color: @border;\r\n  \r\n    &:hover,\r\n    &:focus,\r\n    &.focus,\r\n    &:active,\r\n    &.active,\r\n    .open > .dropdown-toggle& {\r\n        color: @color;\r\n        background-color: darken(@background, 2%);\r\n        border-color: darken(@border, 1%);\r\n    }\r\n    &:active,\r\n    &.active,\r\n    .open > .dropdown-toggle& {\r\n      background-image: none;\r\n    }\r\n    &.disabled,\r\n    &[disabled],\r\n    fieldset[disabled] & {\r\n        &,\r\n        &:hover,\r\n        &:focus,\r\n        &.focus,\r\n        &:active,\r\n        &.active {\r\n            background-color: @background;\r\n            border-color: @border;\r\n        }\r\n    }\r\n  \r\n    .badge {\r\n        color: @background;\r\n        background-color: @color;\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    CSS Transform - Scale and Rotate\r\n-----------------------------------------------------------*/\r\n.scale-rotate(@scale, @rotate) {\r\n    -webkit-transform: scale(@scale) rotate(@rotate);\r\n    -ms-transform: scale(@scale) rotate(@rotate);\r\n    -o-transform: scale(@scale) rotate(@rotate);\r\n    transform: scale(@scale) rotate(@rotate);\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    CSS Animations based on animate.css\r\n-----------------------------------------------------------*/\r\n.animated(@name, @duration) {\r\n    -webkit-animation-name: @name;\r\n    animation-name: @name;\r\n    -webkit-animation-duration: @duration;\r\n    animation-duration: @duration;\r\n    -webkit-animation-fill-mode: both;\r\n    animation-fill-mode: both;\r\n}\r\n"
  },
  {
    "path": "client/app/assets/less/inc/modal.less",
    "content": ".modal-header {\n    padding: 23px 26px;\n}\n\n.modal-body {\n    padding: 0 26px 10px;\n}\n\n.modal-content {\n    box-shadow: 0 5px 20px rgba(0, 0, 0, 0.31);\n}\n\n.modal-footer {\n    padding: 20px 26px;\n}\n\n.modal-xl {\n  position: fixed;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  overflow: hidden;\n  .modal-dialog {\n    position: fixed;\n    margin: 0;\n    width: 100%;\n    height: 100%;\n    padding: 0;\n  }\n  .modal-content {\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    border: 2px solid #3c7dcf;\n    border-radius: 0;\n    box-shadow: none;\n  }\n  .modal-header {\n    position: absolute;\n    top: 0;\n    right: 0;\n    left: 0;\n    height: 50px;\n    padding: 10px;\n    border: 0;\n  }\n  .modal-body {\n    position: absolute;\n    top: 50px;\n    bottom: 60px;\n    width: 100%;\n    overflow: auto;\n  }\n  .modal-footer {\n    position: absolute;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    height: 60px;\n    padding: 10px;\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/panel.less",
    "content": ".panel {\r\n    box-shadow: none;\r\n    border: 0;\r\n}\r\n\r\n.panel-heading {\r\n    padding: 0;\r\n    >p {\r\n      &:last-child {\r\n        margin-bottom: 0px;\r\n      }\r\n    }\r\n    >a, .query-link {\r\n      color: inherit;\r\n    }\r\n    .query-link {\r\n      &:hover {\r\n        text-decoration: underline;\r\n      }\r\n    }\r\n}\r\n\r\n.panel-title {\r\n    & > a {\r\n        padding: 10px 15px;\r\n        display: block;\r\n        font-size: 13px;\r\n    }\r\n}\r\n\r\n.panel-collapse {\r\n    .panel-heading {\r\n        position: relative;\r\n\r\n        .panel-title {\r\n            & > a {\r\n                padding: 8px 5px 16px 30px;\r\n                color: #000;\r\n                position: relative;\r\n                border-bottom: 2px solid #eee;\r\n            }\r\n        }\r\n\r\n        &:before {\r\n            font-family: @font-icon;\r\n            font-size: 17px;\r\n            position: absolute;\r\n            left: 0;\r\n            top: 4px;\r\n            content: \"\\f278\";\r\n        }\r\n\r\n        &.active {\r\n            &:before {\r\n                content: \"\\f273\";\r\n            }\r\n        }\r\n    }\r\n\r\n\r\n    .panel-body {\r\n        border-top: 0 !important;\r\n        padding-left: 5px;\r\n        padding-right: 5px;\r\n    }\r\n}\r\n\r\n.panel-collapse-color(@color) {\r\n    .panel-collapse {\r\n        .panel-heading {\r\n            &.active .panel-title > a {\r\n                border-bottom-color: @color;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n.panel-group {\r\n    &:not([data-collapse-color]) {\r\n        .panel-collapse-color(@blue);\r\n    }\r\n\r\n    &[data-collapse-color=\"red\"] {\r\n        .panel-collapse-color(@red);\r\n    }\r\n\r\n    &[data-collapse-color=\"green\"] {\r\n        .panel-collapse-color(@green);\r\n    }\r\n\r\n    &[data-collapse-color=\"amber\"] {\r\n        .panel-collapse-color(@amber);\r\n    }\r\n\r\n    &[data-collapse-color=\"teal\"] {\r\n        .panel-collapse-color(@teal);\r\n    }\r\n\r\n    &[data-collapse-color=\"black\"] {\r\n        .panel-collapse-color(@black);\r\n    }\r\n\r\n    &[data-collapse-color=\"cyan\"] {\r\n        .panel-collapse-color(@cyan);\r\n    }\r\n}\r\n"
  },
  {
    "path": "client/app/assets/less/inc/photos.less",
    "content": ".photos {\r\n    &:not(.pmb-block) {\r\n        margin: 10px 5px 0;\r\n    }\r\n    \r\n    .p-item {\r\n        padding: 0 3px;\r\n        margin-bottom: 6px;\r\n    }\r\n}\r\n\r\n.p-item {\r\n    & > img {\r\n        border-radius: 2px;\r\n    }\r\n}\r\n    \r\n.p-timeline {\r\n    position: relative;\r\n    padding-left: 80px;\r\n    margin-bottom: 75px;\r\n    \r\n    .p-item {\r\n        float: left;\r\n        width: 70px;\r\n        height: 70px;\r\n        margin: 0 3px 3px 0;\r\n    }\r\n    \r\n            \r\n    &:last-child .pt-line:before {\r\n        height: 100%;\r\n    }\r\n}\r\n\r\n.ptb-title { \r\n    font-size: 15px;\r\n    font-weight: 400; \r\n    margin-bottom: 20px; \r\n}\r\n\r\n.pt-line {\r\n    position: absolute;\r\n    left: 0;\r\n    top: 0;\r\n    height: 100%;\r\n    line-height: 14px;\r\n    \r\n    &:before,\r\n    &:after {\r\n        content: \"\";\r\n        position: absolute;\r\n    }\r\n    \r\n    &:before {\r\n        width: 1px;\r\n        height: ~\"calc(100% + 63px)\";\r\n        background: #E2E2E2;\r\n        top: 14px;\r\n        right: -20px;\r\n    }\r\n    \r\n    &:after {\r\n        top: 2px;\r\n        right: -26px;\r\n        width: 13px;\r\n        height: 13px;\r\n        border: 1px solid #C1C1C1;\r\n        border-radius: 50%;\r\n    }\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/popover.less",
    "content": ".popover {\r\n    box-shadow: fade(@redash-gray, 25%) 0px 0px 15px 0px;\r\n}\r\n\r\n.popover-title {\r\n    border-bottom: 0;\r\n    padding: 15px; \r\n    font-size: 12px;\r\n    text-transform: uppercase;\r\n    \r\n    & + .popover-content { \r\n        padding-top: 0;\r\n    }\r\n}   \r\n\r\n.popover-content {\r\n    padding: 15px; \r\n    \r\n    p {\r\n        margin-bottom: 0; \r\n    }\r\n} "
  },
  {
    "path": "client/app/assets/less/inc/pricing-table.less",
    "content": ".pricing-table {\r\n    margin-top: 50px;\r\n}\r\n\r\n.pt-inner {\r\n    text-align: center;\r\n    \r\n    .pti-header {\r\n          padding: 45px 10px 70px;\r\n          color: #fff;\r\n          position: relative;\r\n            margin-bottom: 15px;\r\n\r\n        \r\n        & > h2 {\r\n              margin: 0;\r\n              line-height: 100%;\r\n              color: #fff;\r\n              font-weight: 100;\r\n              font-size: 50px;\r\n\r\n            small {\r\n                  color: #fff;\r\n                  letter-spacing: 0;\r\n                  vertical-align: top;\r\n                  font-size: 16px;\r\n                  font-weight: 100;\r\n            }\r\n        }\r\n        \r\n        .ptih-title {\r\n              background-color: rgba(0, 0, 0, 0.1);\r\n              padding: 8px 10px 9px;\r\n              text-transform: uppercase;\r\n              margin: 0 -10px;\r\n              position: absolute;\r\n              width: 100%;\r\n              bottom: 0;\r\n        }\r\n    }\r\n    \r\n    .pti-body {\r\n        padding: 0 23px;\r\n        \r\n        .ptib-item {\r\n            padding: 15px 0;\r\n            font-weight: 400;\r\n\r\n            &:not(:last-child) {\r\n                border-bottom: 1px solid #eee;\r\n            }\r\n        }\r\n    }\r\n    \r\n    .pti-footer {\r\n        padding: 10px 20px 30px;\r\n        \r\n        & > a {\r\n            width: 60px;\r\n            height: 60px;\r\n            border-radius: 50%;\r\n            text-align: center;\r\n            color: #fff;\r\n            display: inline-block;\r\n            line-height: 60px;\r\n            font-size: 30px;\r\n            \r\n            &:hover {\r\n                .opacity(0.85);\r\n            }\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/print.less",
    "content": "@media print {    \r\n    @page {\r\n        margin: 0;\r\n        padding: 0;\r\n        size: auto;\r\n    }\r\n    \r\n    body, #content, .container {\r\n        margin: 0mm 0mm 0mm 0mm !important;\r\n        padding: 0mm !important;\r\n    }\r\n    \r\n    \r\n    #header,\r\n    #sidebar,\r\n    #chat,\r\n    .growl-animated,\r\n    [data-action=\"print\"] {\r\n        display: none !important;\r\n    }\r\n\r\n    \r\n    /* --------------------------------------------------------\r\n        Invoice\r\n    -----------------------------------------------------------*/\r\n    .invoice {\r\n        padding: 30px !important;\r\n        -webkit-print-color-adjust: exact !important; \r\n        \r\n        .card-header {\r\n            background: #eee !important;\r\n            padding: 20px;\r\n            margin-bottom: 20px;\r\n            margin: -60px -30px 25px -30px;\r\n        }\r\n        \r\n        \r\n        .page-header { \r\n            display: none;\r\n        } \r\n        \r\n        .highlight {\r\n            background: #eee !important; \r\n        }\r\n    }\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/profile.less",
    "content": "#profile-main {\r\n    min-height: 500px;\r\n    position: relative;\r\n}\r\n\r\n.pm-overview {\r\n    overflow: hidden !important;\r\n    \r\n    @media (min-width: 1200px) {\r\n        width: 300px;\r\n    }\r\n    \r\n    @media (min-width: @screen-sm-min) and (max-width: 1200px) {\r\n        width: 250px;\r\n    }\r\n    \r\n    @media (min-width: @screen-sm-min) {\r\n        position: absolute;\r\n        left: 0;\r\n        top: 0;\r\n        height: 100%;\r\n        background: #f8f8f8;\r\n        border-right: 1px solid #eee;\r\n    }\r\n    \r\n    @media (max-width: @screen-xs-max) {\r\n        width: 100%;\r\n        background: #333;\r\n        text-align: center;\r\n    }\r\n    \r\n    &:hover {\r\n        .pmop-edit {\r\n            .opacity(1);\r\n            color: #fff;\r\n        }\r\n    }\r\n}\r\n\r\n.pm-body {\r\n    @media (min-width: 1200px) {\r\n        padding-left: 300px;\r\n    }\r\n    \r\n    @media (min-width: @screen-sm-min) and (max-width: 1200px) {\r\n        padding-left: 250px;\r\n    }\r\n    \r\n    @media (max-width: @screen-xs-max) {\r\n        padding-left: 0; \r\n    }\r\n}\r\n\r\n.pmo-pic {\r\n    position: relative;\r\n    margin: 20px;\r\n    \r\n    img { \r\n        @media(min-width: @screen-sm-min) {\r\n            width: 100%;\r\n            border-radius: 2px 2px 0 0;\r\n        }\r\n        \r\n        @media(max-width: @screen-xs-max) {\r\n            width: 180px;\r\n            display: inline-block;\r\n            height: 180px;\r\n            border-radius: 50%;\r\n            border: 4px solid #fff;\r\n        }\r\n    }\r\n}\r\n\r\n.pmo-stat {\r\n    border-radius: 0 0 2px 2px;\r\n    color: #fff;\r\n    text-align: center;\r\n    padding: 30px 5px 0;\r\n    \r\n    @media(min-width: @screen-sm-min) {\r\n         background: @amber;\r\n         padding-bottom: 15px;\r\n    }\r\n}\r\n\r\n.pmop-edit {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    color: #fff;\r\n    background: rgba(0, 0, 0, 0.38);\r\n    text-align: center;\r\n    padding: 10px 10px 11px; \r\n    \r\n    &:hover {\r\n        background: rgba(0, 0, 0, 0.8);\r\n    }\r\n    \r\n    i {\r\n        font-size: 18px;\r\n        vertical-align: middle;\r\n        margin-top: -3px;\r\n    }\r\n    \r\n    @media (min-width: @screen-sm-min) {\r\n        width: 100%;\r\n        .opacity(0);\r\n        \r\n        i {\r\n            margin-right: 4px;\r\n        }\r\n    }\r\n}\r\n\r\n.pmop-message  {\r\n    position: absolute;\r\n    bottom: 27px;\r\n    left: 50%; \r\n    margin-left: -25px;\r\n    \r\n    .dropdown-menu {\r\n        padding: 5px 0 55px; \r\n        left: -90px;\r\n        width: 228px;\r\n        height: 150px;\r\n        top: -74px;\r\n        \r\n        textarea {\r\n            width: 100%;\r\n            height: 95px;\r\n            border: 0;\r\n            resize: none;\r\n            padding: 10px 19px;\r\n        }\r\n        \r\n        button {\r\n            position: absolute;\r\n            bottom: 5px;\r\n            left: 93px;\r\n        }\r\n    }\r\n}\r\n    \r\n.pmb-block {\r\n    margin-bottom: 20px;\r\n    \r\n    @media (min-width: 1200px) {\r\n        padding: 40px 42px 0;\r\n    }\r\n    \r\n    @media (max-width: 1199px) {\r\n        padding: 30px 20px 0;\r\n    }\r\n    \r\n    &:last-child {\r\n        margin-bottom: 50px;\r\n    }\r\n    \r\n    &.toggled {\r\n        .pmbb-edit {\r\n            display: block;\r\n        }\r\n        \r\n        .pmbb-view {\r\n            display: none;\r\n        }\r\n    }\r\n}\r\n\r\n.pmbb-header {\r\n    margin-bottom: 25px;\r\n    position: relative;\r\n    \r\n    .actions {\r\n        position: absolute;\r\n        top: -2px;\r\n        right: 0;\r\n    }\r\n    \r\n    h2 {\r\n        margin: 0;\r\n        font-weight: 100;\r\n        font-size: 20px;\r\n    }\r\n}\r\n\r\n.pmbb-edit {\r\n    position: relative;\r\n    z-index: 1; \r\n    display: none;\r\n}\r\n\r\n.pmo-block {\r\n    padding: 25px;\r\n    \r\n    & > h2 {\r\n        font-size: 16px;\r\n        margin: 0 0 15px;\r\n    }\r\n}\r\n\r\n.pmo-items {\r\n    .pmob-body {\r\n        padding: 0 10px;\r\n    }\r\n    \r\n    a {\r\n        display: block;\r\n        padding: 4px;\r\n        \r\n        img {\r\n            width: 100%;\r\n        }\r\n    }\r\n}\r\n\r\n.pmopm-send {\r\n    background-color: #fff;\r\n    width: 50px;\r\n    height: 50px;\r\n    font-size: 24px;\r\n    line-height: 53px;\r\n    border-radius: 50%;\r\n    position: absolute;\r\n    color: #333;\r\n    bottom: -50px;\r\n    box-shadow: 0px 3px 10px rgba(0, 0, 0, 0.16);\r\n    text-align: center;\r\n    \r\n    &:hover {\r\n        color: #000;\r\n    }\r\n}\r\n\r\n.pmo-contact {\r\n    ul {\r\n        list-style: none;\r\n        margin: 0;\r\n        padding: 0; \r\n        \r\n        li {\r\n            position: relative;\r\n            padding: 8px 0 8px 35px;\r\n            \r\n            i {\r\n                font-size: 18px;\r\n                vertical-align: top;\r\n                line-height: 100%;\r\n                position: absolute;\r\n                left: 0;\r\n                width: 18px;\r\n                text-align: center;\r\n                color: #333;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n.pmo-map {\r\n    margin: 20px -21px -18px;\r\n    display: block;\r\n    \r\n    img {\r\n        width: 100%;\r\n    }\r\n}\r\n\r\n.p-header {\r\n    position: relative;\r\n    margin: 0 -7px;\r\n    \r\n    .actions {\r\n        position: absolute;\r\n        top: -18px;\r\n        right: 0;\r\n    }\r\n}\r\n\r\n.p-menu {\r\n    list-style: none;\r\n    padding: 0 8px;\r\n    margin: 0 0 30px;\r\n    \r\n    & > li {\r\n        display: inline-block;\r\n        vertical-align: top;\r\n        \r\n        & > a {\r\n            display: block;\r\n            padding: 5px 20px 5px 0;\r\n            font-weight: 500;\r\n            text-transform: uppercase;\r\n            font-size: 15px;\r\n            \r\n            & > i {\r\n                margin-right: 4px;\r\n                font-size: 20px;\r\n                vertical-align: middle;\r\n                margin-top: -5px;\r\n            }\r\n        }\r\n        \r\n        &:not(.active) > a {\r\n            color: #4285F4;\r\n            \r\n            &:hover {\r\n                color: #333;\r\n            }\r\n        }\r\n        \r\n        &.active > a {\r\n            color: #000;\r\n        }\r\n    }\r\n    \r\n    .pm-search {\r\n        @media(max-width: @screen-sm-max) {\r\n            margin: 20px 2px 30px;\r\n            display: block;\r\n            \r\n            input[type=\"text\"] {\r\n                width: 100%;\r\n                border: 1px solid #ccc;\r\n            }\r\n        }\r\n    }\r\n    \r\n    .pms-inner {\r\n        margin: -2px 0 0;\r\n        position: relative;\r\n        top: -2px;\r\n        overflow: hidden;\r\n        white-space: nowrap;\r\n        \r\n        i {\r\n            vertical-align: top;\r\n            font-size: 20px;\r\n            line-height: 100%;\r\n            position: absolute;\r\n            left: 9px;\r\n            top: 8px;\r\n            color: #333;\r\n        }\r\n        \r\n        input[type=\"text\"] {\r\n            height: 35px;\r\n            border-radius: 2px;\r\n            padding: 0 10px 0 40px;\r\n            \r\n            @media(min-width:  @screen-sm-min) {\r\n                border: 1px solid #fff; \r\n                width: 50px;\r\n                background: transparent;\r\n                position: relative;\r\n                z-index: 1;\r\n                .transition(all);\r\n                .transition-duration(300ms);\r\n            \r\n                &:focus {\r\n                    border-color: #DFDFDF;\r\n                    width: 200px;\r\n                }\r\n            }\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/progress-bar.less",
    "content": ".progress {\r\n    box-shadow: none;\r\n    border-radius: 0;\r\n    height: 5px;\r\n    margin-bottom: 0;\r\n    \r\n    .progress-bar {\r\n        box-shadow: none;\r\n    }\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/schema-browser.less",
    "content": ".schema-container {\n  height: 100%;\n  z-index: 10;\n  background-color: white;\n\n  .schema-browser {\n    overflow: hidden;\n    border: none;\n    padding-top: 10px;\n    position: relative;\n    height: 100%;\n\n    .schema-loading-state {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      height: 100%;\n    }\n\n    .collapse.in {\n      background: transparent;\n    }\n\n    .copy-to-editor {\n      visibility: hidden;\n      color: fade(@redash-gray, 90%);\n      width: 20px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      transition: none;\n    }\n\n    .schema-list-item {\n      display: flex;\n      border-radius: @redash-radius;\n      height: 22px;\n\n      .table-name {\n        flex: 1;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n        cursor: pointer;\n        padding: 2px 22px 2px 10px;\n      }\n\n      &:hover,\n      &:focus,\n      &:focus-within {\n        background: fade(@redash-gray, 10%);\n\n        .copy-to-editor {\n          visibility: visible;\n        }\n      }\n    }\n\n    .table-open {\n      .table-open-item {\n        display: flex;\n        height: 18px;\n        width: calc(100% - 22px);\n        padding-left: 22px;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n        transition: none;\n\n        div:first-child {\n          flex: 1;\n        }\n\n        .column-type {\n          color: fade(@text-color, 80%);\n          font-size: 10px;\n          margin-left: 2px;\n          text-transform: uppercase;\n        }\n\n        &:hover,\n        &:focus,\n        &:focus-within {\n          background: fade(@redash-gray, 10%);\n\n          .copy-to-editor {\n            visibility: visible;\n          }\n        }\n      }\n    }\n  }\n\n  .schema-control {\n    display: flex;\n    flex-wrap: nowrap;\n    padding: 0;\n\n    .ant-btn {\n      height: auto;\n    }\n  }\n\n  .parameter-label {\n    display: block;\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/sidebar.less",
    "content": "#sidebar {\r\n    background-color: @sidebar;\r\n    position: fixed;\r\n    left: 0;\r\n    top: @header-height;\r\n    z-index: 9;\r\n    height: ~\"calc(100% - 62px)\";\r\n    \r\n    @media (min-width: (@screen-lg-min + 80px)), (max-width: (@screen-sm-min)) { \r\n        width: @sidebar-left-width;\r\n        overflow: auto;\r\n    }\r\n    \r\n    @media (min-width: @screen-sm-min) and (max-width: (@screen-md-max + 80px)) {\r\n        &:not(.toggled) {\r\n            width: @sidebar-left-mid-width;\r\n            overflow: visible;\r\n        }\r\n        \r\n        &.toggled {\r\n            width: @sidebar-left-width;\r\n            overflow: auto;\r\n        }\r\n    }\r\n    \r\n    @media (max-width: @screen-sm-min)  {\r\n        display: none;\r\n        \r\n        &.toggled {\r\n            display: block;\r\n            z-index: 12;\r\n        }\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Profile Menu\r\n-----------------------------------------------------------*/\r\n.sms-profile {\r\n    margin: 12px 0 10px;\r\n    \r\n    & > a {\r\n        padding: 15px;\r\n        display: block;\r\n        color: @color-dark;\r\n        \r\n        & > img {\r\n            width: 28px;\r\n            height: 28px;\r\n            border-radius: 50%;\r\n            float: left;\r\n            margin-right: 10px;\r\n            margin-top: 3px;\r\n        }\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Sidebar Menu\r\n-----------------------------------------------------------*/\r\n.side-menu {\r\n    list-style: none;\r\n    padding: 0;\r\n    \r\n    a {\r\n        color: @color-dark;\r\n    }\r\n    \r\n    & > li {\r\n        width: 100%;\r\n        display: block;\r\n    \r\n        & > a {\r\n            display: block;\r\n            padding: 9px 10px 9px 16px;\r\n            position: relative;\r\n            white-space: nowrap;\r\n            .transition(color);            \r\n\r\n            & > .zmdi {\r\n                font-size: 13px;\r\n                width: 28px;\r\n                height: 28px;\r\n                border-radius: 50%;\r\n                background-color: #000;\r\n                line-height: 29px;\r\n                margin-right: 7px;\r\n                text-align: center;\r\n            }\r\n            \r\n            .label {\r\n                position: absolute;\r\n                top: 15px;\r\n                right: 12px;\r\n            }\r\n        }\r\n        \r\n        &.active > a,\r\n        &:hover > a  {\r\n            color: #fff;\r\n        }\r\n        \r\n        &.active > a {\r\n            background: @sidebar-active-bg;\r\n            \r\n            .zmdi {\r\n                background: #2C313A;\r\n                color: #fff;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n.sm-sub {\r\n    position: relative;\r\n    \r\n    &:not(.active) {\r\n        & > ul {\r\n            display: none;\r\n        }\r\n    }\r\n    \r\n    & > ul {\r\n        position: relative;\r\n        width: 100%;\r\n        padding: 0 0 0 27px;\r\n        background: darken(@sidebar, 1.5%);\r\n        margin-bottom: 0;\r\n        border: 0;\r\n        list-style: none;\r\n        \r\n        &:before {\r\n            content: \"\"; \r\n            height: 100%;\r\n            width: 1px;\r\n            position: absolute;\r\n            background: #1f2229;\r\n            left: 30px;\r\n            top: 0;\r\n        }\r\n        \r\n        & > li {\r\n            & > a {\r\n                padding: 7px 18px 7px 28px;\r\n                font-size: 12px;\r\n                display: block;\r\n                position: relative;\r\n                white-space: nowrap;\r\n                .transition(color);\r\n                \r\n                &:hover {\r\n                    color: #fff; \r\n                }\r\n                \r\n                &:before {\r\n                    content: \"\";\r\n                    width: 8px;\r\n                    height: 1px;\r\n                    background: #22252d;\r\n                    position: absolute;\r\n                    left: 4px; \r\n                    top: 14px;\r\n                }\r\n            }\r\n            \r\n            &.active > a {\r\n                color: #fff;\r\n            }\r\n            \r\n            &:first-child > a {\r\n                &:before {\r\n                    top: 20px;\r\n                }\r\n                \r\n                padding-top: 13px;\r\n            }\r\n            \r\n            &:last-child > a {\r\n                padding-bottom: 13px;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n\r\n/* --------------------------------------------------------\r\n    Sidebar for mid size screens\r\n-----------------------------------------------------------*/\r\n@media (min-width: @screen-sm-min) and (max-width: (@screen-md-max + 80px)) {\r\n    #sidebar:not(.toggled) {\r\n        .side-menu > li {\r\n            & > a {\r\n                & > span {\r\n                    position: absolute;\r\n                    left: @sidebar-left-mid-width;\r\n                    background-color: @sidebar-active-bg;\r\n                    width: 180px;\r\n                    padding: 14px 18px;\r\n                    display: none;\r\n                    text-transform: uppercase;\r\n                    .animated(fadeIn, 300ms);\r\n                }\r\n                \r\n                .label {\r\n                    display: none;\r\n                }\r\n            }\r\n            \r\n            &.sms-bottom > a > span {\r\n                bottom: 0;\r\n            }\r\n            \r\n            &:not(.sms-bottom) > a > span {\r\n                top: 0;\r\n            }\r\n            \r\n            &:hover {\r\n                & a > span {\r\n                    display: block;\r\n                }\r\n            }\r\n        }\r\n        \r\n        .sm-sub {\r\n            & > ul {\r\n                display: none !important;\r\n                position: absolute;\r\n                left: @sidebar-left-mid-width;\r\n                width: 180px;\r\n                padding-left: 0;\r\n                .animated(fadeIn, 300ms);\r\n                \r\n                &:before {\r\n                    display: none;\r\n                }\r\n                \r\n                & > li > a {\r\n                    padding-left: 18px;\r\n                    \r\n                    &:before {\r\n                        display: none;\r\n                    }\r\n                }\r\n            }\r\n            \r\n            &:not(.sms-bottom) > ul {\r\n                top: 46px;\r\n                border-top: 1px solid lighten(@sidebar, 5%);\r\n            }\r\n            \r\n            &.sms-bottom > ul {\r\n                bottom: 46px;\r\n                border-bottom: 1px solid lighten(@sidebar, 5%);\r\n            }\r\n            \r\n            &:hover {\r\n                & > ul {\r\n                    display: block !important;\r\n                }\r\n            }\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/table.less",
    "content": ".table {\n  margin-bottom: 0;\n\n  th.sortable-column {\n    cursor: pointer;\n  }\n\n  &:not(.table-striped) > thead > tr > th {\n    background-color: #fafafa;\n  }\n\n  [class*=\"bg-\"] {\n    & > tr > th {\n      color: #fff;\n      border-bottom: 0;\n      background: transparent !important;\n    }\n\n    & + tbody > tr:first-child > td {\n      border-top: 0;\n    }\n  }\n\n  & > thead > tr > th {\n    vertical-align: middle;\n    font-weight: 500;\n    color: #333;\n    border-width: 1px;\n    text-transform: uppercase;\n    padding: 15px 10px;\n  }\n\n  & > thead > tr,\n  & > tbody > tr,\n  & > tfoot > tr {\n    & > th,\n    & > td {\n      &:first-child {\n        padding-left: 30px;\n      }\n\n      &:last-child {\n        padding-right: 30px;\n      }\n    }\n  }\n\n  tbody > tr:last-child > td {\n    padding-bottom: 20px;\n  }\n}\n\n.table-bordered {\n  border: 0;\n\n  & > tbody > tr {\n    & > td,\n    & > th {\n      border-bottom: 0;\n      border-left: 0;\n\n      &:last-child {\n        border-right: 0;\n      }\n    }\n  }\n\n  & > thead > tr > th {\n    border-left: 0;\n\n    &:last-child {\n      border-right: 0;\n    }\n  }\n}\n\n.table-vmiddle {\n  td {\n    vertical-align: middle !important;\n  }\n}\n\n.table-responsive {\n  border: 0;\n}\n\n.tile .table {\n  & > thead:not([class*=\"bg-\"]) > tr > th {\n    border-top: 1px solid @table-border-color;\n  }\n}\n\n.table-hover > tbody > tr:hover {\n  background-color: #f4f4f4;\n}\n\n.table-data {\n  thead > tr > th {\n    white-space: nowrap;\n  }\n\n  tbody > tr > td {\n    padding-top: 5px !important;\n  }\n\n  .btn-favorite,\n  .btn-archive {\n    font-size: 15px;\n  }\n}\n\n.table-main-title {\n  font-weight: 500;\n  line-height: 1.7 !important;\n}\n\n.btn-favorite {\n  color: #d4d4d4;\n  transition: all 0.25s ease-in-out;\n\n  .fa-star {\n    color: @yellow-darker;\n  }\n\n  &:hover,\n  &:focus {\n    color: @yellow-darker;\n    cursor: pointer;\n\n    .fa-star {\n      filter: saturate(75%);\n      opacity: 0.75;\n    }\n  }\n}\n\n.btn-archive {\n  color: #d4d4d4;\n  transition: all 0.25s ease-in-out;\n\n  &:hover,\n  &:focus {\n    color: @gray-light;\n  }\n\n  .fa-archive {\n    color: @gray-light;\n  }\n}\n\n.table > thead > tr > th {\n  text-transform: none;\n}\n\n.table-data .label-tag {\n  display: inline-block;\n  max-width: 135px;\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/tile.less",
    "content": ".tile {\r\n    background-color: #fff;\r\n    margin-bottom: @grid-gutter-width;\r\n    position: relative;\r\n    border-radius: 3px;\r\n    box-shadow: fade(@redash-gray, 15%) 0px 4px 9px -3px;\r\n    \r\n    &[class*=\"bg-\"] {\r\n        color: #fff;\r\n    }\r\n    \r\n    @media (max-width: @screen-sm-min) {\r\n        margin-bottom: @grid-gutter-width/2;\r\n    }\r\n}\r\n.tiled {\r\n    border-radius: 3px;\r\n    box-shadow: fade(@redash-gray, 15%) 0px 4px 9px -3px;\r\n}\r\n\r\n.t-header {\r\n    .th-title {\r\n        line-height: 100%;\r\n    }\r\n\r\n    &:not(.th-alt) {\r\n        padding: 20px 23px;\r\n        \r\n        .th-title {\r\n            font-size: 17px;\r\n            font-weight: 400;\r\n            color: #333;\r\n            \r\n            small {\r\n                font-size: 12px;\r\n                color: #9C9C9C;\r\n                margin-top: 3px;\r\n                display: block;\r\n            }\r\n        }\r\n    }\r\n\r\n    &.widget {\r\n        padding: 5px;\r\n    }\r\n\r\n\r\n    &.th-alt {\r\n        padding: 10px 15px 9px;\r\n        \r\n        .actions {\r\n            & > a {\r\n                color: #fff;\r\n            }\r\n        }\r\n        \r\n        &[class*=\"bg-\"] {\r\n            .th-title {\r\n                color: #fff;\r\n            }\r\n        }\r\n    }\r\n    \r\n    .actions {\r\n        right: 0;\r\n        top: 0; \r\n        \r\n        & > a {\r\n            font-size: 24px;\r\n            line-height: 100%;\r\n            padding: 4px 10px 3px;\r\n            display: block;\r\n        }\r\n        \r\n        & > a:hover,\r\n        &.open > a {\r\n            background-color: rgba(0, 0, 0, 0.1);\r\n        }\r\n    }\r\n}\r\n\r\n.t-header:not(.th-alt) {\r\n    padding: 15px;\r\n  \r\n    ul {\r\n      margin-bottom: 0;\r\n      line-height: 2.2;\r\n    }\r\n  }\r\n\r\n.tb-padding {\r\n    padding: 20px 23px 30px;\r\n}\r\n\r\n.t-body a.actions {\r\n    font-size: 24px;\r\n    line-height: 100%;\r\n    padding: 4px 10px 3px;\r\n    display: block;\r\n}\r\n\r\n.t-body a.actions:hover,\r\n.t-body a.actions.open > a {\r\n    background-color: rgba(0, 0, 0, 0.1);\r\n}\r\n"
  },
  {
    "path": "client/app/assets/less/inc/tooltips.less",
    "content": ".tooltip-inner {\r\n    border-radius: 1px;\r\n    padding: 5px 10px;\r\n    font-size: 12px;\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/variables.less",
    "content": "/* --------------------------------------------------------\n    Paths\n-----------------------------------------------------------*/\n@imgpath:                               ~'../img';\n@fontpath:                              ~'../fonts';\n\n\n/* --------------------------------------------------------\n    Container\n-----------------------------------------------------------*/\n@container-tablet:                      100%;\n@container-desktop:                     100%;\n@container-large-desktop:               100%;\n\n\n/* --------------------------------------------------------\n    Template Variables\n-----------------------------------------------------------*/\n@header-height:                         60px;\n@sidebar-left-width:                    240px;\n@sidebar-left-mid-width:                64px;\n@logo-width:                            @sidebar-left-width;\n@logo-height:                           @header-height;\n@boxed-width:                           1170px;\n@body-bg:                               #edecec;\n@spacing:                               15px;\n@redash-radius:                         3px;\n\n\n/* --------------------------------------------------------\n    Branding\n-----------------------------------------------------------*/\n@brand-bg:                              #191C22;\n@sidebar:                               @brand-bg;\n@sidebar-active-bg:                     #121419;\n@color-dark:                            #9BA1B1;\n\n\n/* --------------------------------------------------------\n    Font\n-----------------------------------------------------------*/\n@font-icon:                             'Material-Design-Iconic-Font';\n@font-family-sans-serif:                'Roboto', sans-serif;\n@redash-font:                            -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n@font-size-base:                        13px;\n\n\n/* --------------------------------------------------------\n    Typograpgy\n-----------------------------------------------------------*/\n@text-color:                            #767676;\n@link:                                  #02a4c4;\n@link-hover-decoration:                 none;\n@headings-color:                        #333;\n\n\n/* --------------------------------------------------------\n    Form\n-----------------------------------------------------------*/\n@input-color:                           #595959;\n@input-color-placeholder:               #b4b4b4;\n@input-border:                          #e8e8e8;\n@input-border-radius:                   0;\n@input-border-radius-large:             0px;\n@redash-input-radius:                   2px;\n@input-height-large:                    40px;\n@input-height-base:                     35px;\n@input-height-small:                    30px;\n@input-border-focus:                    #79c2ff;\n@input-group-addon-bg:                  @light-gray;\n\n/* --------------------------------------------------------\n    Colors\n-----------------------------------------------------------*/\n@white:                                 #ffffff;\n@black:                                 #000000;\n@blue:                                  #2196F3;\n@red:                                   #F44336;\n@purple:                                #9C27B0;\n@deeppurple:                            #673AB7;\n@lightblue:                             #03A9F4;\n@cyan:                                  #00BCD4;\n@teal:                                  #009688;\n@green:                                 #4CAF50;\n@lightgreen:                            #8BC34A;\n@lime:                                  #CDDC39;\n@yellow:                                #FFEB3B;\n@yellow-darker:                         #fbd208;\n@amber:                                 #FFC107;\n@orange:                                #FF9800;\n@deeporange:                            #FF5722;\n@gray:                                  #9E9E9E;\n@bluegray:                              #607D8B;\n@indigo:                                #3F51B5;\n@pink:                                  #E91E63;\n@brown:                                 #795548;\n@light-gray:                            #FCFCFC;\n@gray-light:                            #828282;\n@ace:                                   #f8f8f8;\n\n@redash-gray: rgba(102, 136, 153, 1);\n@redash-orange: rgba(255, 120, 100, 1);\n@redash-black: rgba(0, 0, 0, 1);\n@redash-yellow: rgba(252, 252, 161, 0.75);\n\n /** Form States **/\n@state-success-text:                    @green;\n@state-info-text:                       @blue;\n@state-danger-text:                     lighten(@red, 5%);\n\n\n/* --------------------------------------------------------\n    Alert\n-----------------------------------------------------------*/\n@alert-success-border:                  transparent;\n@alert-info-border:                     transparent;\n@alert-danger-border:\t\t \t        transparent;\n@alert-inverse-border:\t\t \t        transparent;\n\n@alert-success-bg:                      fade(@green, 70%);\n@alert-info-bg:\t\t \t\t            fade(@blue, 70%);\n@alert-danger-bg:                       fade(@red, 70%);\n@alert-inverse-bg:                      #333;\n\n@alert-success-text:                    #fff;\n@alert-info-text:                       #fff;\n@alert-danger-text:                     #fff;\n@alert-inverse-text:                    #fff;\n\n\n/* --------------------------------------------------------\n    Bootstrap Brands\n-----------------------------------------------------------*/\n@brand-default:                         #eee;\n@brand-primary:                         @blue;\n@brand-info:                            @cyan;\n@brand-success:                         @green;\n@brand-warning:                         @orange;\n@brand-danger:                          @red;\n\n\n/* --------------------------------------------------------\n    Border Radius\n-----------------------------------------------------------*/\n@border-radius-base:                    2px;\n@border-radius-large:                   2px;\n@border-radius-small:                   2px;\n\n\n/* --------------------------------------------------------\n    Dropdown\n-----------------------------------------------------------*/\n@dropdown-fallback-border:              transparent;\n@dropdown-border:                       transparent;\n@dropdown-divider-bg:                   '';\n@dropdown-link-hover-bg:                rgba(0,0,0,0.075);\n@dropdown-link-color:                   #333;\n@dropdown-link-hover-color:             #333;\n@dropdown-link-disabled-color:          #e4e4e4;\n@dropdown-divider-bg:                   rgba(0,0,0,0.08);\n@dropdown-link-active-color:            #333;\n@dropdown-link-active-bg:               rgba(0, 0, 0, 0.075);\n@zindex-dropdown:                       9;\n@dropdown-shadow:                       0 2px 10px rgba(0, 0, 0, 0.2);\n\n/* --------------------------------------------------------\n    Page Header\n-----------------------------------------------------------*/\n@page-header-border-color:              transparent;\n\n\n/* --------------------------------------------------------\n    Buttons\n-----------------------------------------------------------*/\n@btn-default-border:                    @input-border;\n@btn-font-weight:\t\t\t\t        400;\n\n\n/* --------------------------------------------------------\n    Tables\n-----------------------------------------------------------*/\n@table-bg:                              #fff;\n@table-border-color:                    #f0f0f0;\n@table-cell-padding:                    10px;\n@table-condensed-cell-padding:          7px;\n@table-bg-accent:                       @light-gray;\n@table-bg-active:                       #FFFCBE;\n@table-bg-hover:                        lighten(@light-gray, 2%);\n\n\n/* --------------------------------------------------------\n    Pagination\n-----------------------------------------------------------*/\n@pagination-bg:                         #E2E2E2;\n@pagination-border:                     #fff;\n@pagination-color:                      #7E7E7E;\n@pagination-active-bg:                  @lightblue;\n@pagination-active-border:              @pagination-border;\n@pagination-disabled-bg:                #E2E2E2;\n@pagination-disabled-border:            @pagination-border;\n@pagination-hover-color:                #333;\n@pagination-hover-bg:                   #d7d7d7;\n@pagination-hover-border:               @pagination-border;\n\n\n/* --------------------------------------------------------\n    Thumbnail\n-----------------------------------------------------------*/\n@thumbnail-bg:                          #fff;\n@thumbnail-border:                      #eee;\n\n/* --------------------------------------------------------\n    Carousel\n-----------------------------------------------------------*/\n@carousel-caption-color:                #fff;\n\n\n/* --------------------------------------------------------\n    Modal\n-----------------------------------------------------------*/\n@modal-content-fallback-border-color:\ttransparent;\n@modal-content-border-color:\t\t\ttransparent;\n@modal-backdrop-bg:\t\t\t\t\t\t#000;\n@modal-header-border-color:\t\t\t\ttransparent;\n@modal-title-line-height:\t\t\t\ttransparent;\n@modal-footer-border-color:\t\t\t\ttransparent;\n@zindex-modal-background: \t\t\t\t10;\n\n\n/* --------------------------------------------------------\n    Tooltips\n-----------------------------------------------------------*/\n@tooltip-bg:                            #333;\n@tooltip-opacity:                       1;\n\n\n/* --------------------------------------------------------\n    Popobver\n-----------------------------------------------------------*/\n@zindex-popover:                        9;\n@popover-title-bg:                      #fff;\n@popover-border-color:                  #fff;\n@popover-fallback-border-color:         #fff;\n\n\n/* --------------------------------------------------------\n    Breacrumb\n-----------------------------------------------------------*/\n@breadcrumb-bg:                         transparent;\n@breadcrumb-padding-horizontal:         20px;\n@breadcrumb-active-color:               #7c7c7c;\n\n\n/* --------------------------------------------------------\n    Jumbotron\n-----------------------------------------------------------*/\n@jumbotron-bg:                          #F7F7F7;\n\n\n/* --------------------------------------------------------\n    List Group\n-----------------------------------------------------------*/\n@list-group-border:                     #f4f4f4;\n@list-group-active-color:               #000;\n@list-group-active-bg:                  #f5f5f5;\n@list-group-active-border:              @list-group-border;\n@list-group-disabled-color:             #B5B4B4;\n@list-group-disabled-bg:                #fff;\n@list-group-disabled-text-color:        #B5B4B4;\n\n\n/* --------------------------------------------------------\n    Badges\n-----------------------------------------------------------*/\n@badge-color:                           #fff;\n@badge-bg:                              @brand-primary;\n@badge-border-radius:                   2px;\n@badge-font-weight:                     400;\n@badge-active-color:                    #fff;\n@badge-active-bg:                       @brand-primary;\n\n\n/* --------------------------------------------------------\n    Misc\n-----------------------------------------------------------*/\n@code-bg:                               transparent;\n@tile-shadow:                           0 1px 1px rgba(0,0,0,0.07);\n"
  },
  {
    "path": "client/app/assets/less/inc/visualizations/box.less",
    "content": ".box {\r\n  font: 10px sans-serif;\r\n  line, rect, circle {\r\n    fill: #fff;\r\n    stroke: #000;\r\n    stroke-width: 1.5px;\r\n  }\r\n  .center {\r\n    stroke-dasharray: 3, 3;\r\n  }\r\n  .outlier {\r\n    fill: none;\r\n    stroke: #000;\r\n  }\r\n}\r\n\r\n.axis text {\r\n  font: 10px sans-serif;\r\n}\r\n\r\n.axis path,\r\n.axis line {\r\n  fill: none;\r\n  stroke: #000;\r\n  shape-rendering: crispEdges;\r\n}\r\n\r\n.grid-background {\r\n  fill: #ddd;\r\n}\r\n\r\n.grid path,\r\n.grid line {\r\n  fill: none;\r\n  stroke: #fff;\r\n  shape-rendering: crispEdges;\r\n}\r\n\r\n.grid .minor line {\r\n  stroke-opacity: .5;\r\n}\r\n\r\n.grid text {\r\n  display: none;\r\n}\r\n"
  },
  {
    "path": "client/app/assets/less/inc/visualizations/map.less",
    "content": ".map-visualization-container {\n  height: 500px;\n\n  > div:first-child {\n    width: 100%;\n    height: 100%;\n    z-index: 0;\n  }\n}\n\n.leaflet-popup-content img {\n  max-width: 100%;\n  height: auto;\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/visualizations/misc.less",
    "content": ".visualization-renderer {\n  display: block;\n\n  .pagination,\n  .ant-pagination {\n    margin: 0;\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/visualizations/pivot-table.less",
    "content": ".pivot-table-visualization-container > table,\n.visualization-renderer > .visualization-renderer-wrapper {\n  overflow: auto;\n}\n"
  },
  {
    "path": "client/app/assets/less/inc/well.less",
    "content": ".well {\r\n    border-radius: 0;\r\n    background: #fff;\r\n    box-shadow: none;\r\n}"
  },
  {
    "path": "client/app/assets/less/inc/widgets.less",
    "content": "/* --------------------------------------------------------\r\n    User Signups\r\n-----------------------------------------------------------*/\r\n.rounded-thumbs {\r\n    padding: 15px 25px 0;\r\n}\r\n\r\n.rt-item {\r\n    display: block;\r\n    padding-top: 10px;\r\n    padding-bottom: 10px;\r\n\r\n    img {\r\n        width: 100%;\r\n        height: 100%;\r\n        border-radius: 50%;\r\n    }\r\n\r\n    small {\r\n        .text-overflow();\r\n        text-align: center;\r\n        display: block;\r\n        color: #777;\r\n        margin-top: 3px;\r\n    }\r\n\r\n    &:hover {\r\n        background-color: @light-gray;\r\n    }\r\n}\r\n"
  },
  {
    "path": "client/app/assets/less/main.less",
    "content": "/** LESS Plugins **/\n@import \"inc/less-plugins/for\";\n\n/** Load Main Bootstrap LESS files **/\n@import \"~bootstrap/less/bootstrap\";\n\n/** Load Vendors Dependencies **/\n@import \"~font-awesome/less/font-awesome\";\n@import \"~material-design-iconic-font/dist/css/material-design-iconic-font.css\";\n\n@import \"inc/variables\";\n@import \"inc/mixins\";\n@import \"inc/font\";\n@import \"inc/print\";\n\n@import \"inc/bootstrap-overrides\";\n@import \"inc/base\";\n@import \"inc/generics\";\n@import \"inc/form\";\n@import \"inc/button\";\n@import \"inc/list\";\n@import \"inc/header\";\n@import \"inc/tile\";\n@import \"inc/label\";\n@import \"inc/dropdown\";\n@import \"inc/list-group\";\n@import \"inc/misc\";\n@import \"inc/progress-bar\";\n@import \"inc/widgets\";\n@import \"inc/table\";\n@import \"inc/alert\";\n@import \"inc/media\";\n@import \"inc/modal\";\n@import \"inc/panel\";\n@import \"inc/tooltips\";\n@import \"inc/popover\";\n@import \"inc/breadcrumb\";\n@import \"inc/jumbotron\";\n@import \"inc/profile\";\n@import \"inc/404\";\n@import \"inc/ie-warning\";\n@import \"inc/edit-in-place\";\n@import \"inc/flex\";\n@import \"inc/ace-editor\";\n@import \"inc/schema-browser\";\n@import \"inc/visualizations/box\";\n@import \"inc/visualizations/pivot-table\";\n@import \"inc/visualizations/map\";\n@import \"inc/visualizations/misc\";\n\n/** REDASH STYLING **/\n@import \"redash/redash-table\";\n@import \"redash/query\";\n@import \"redash/tags-control\";\n@import \"redash/css-logo\";\n@import \"redash/loading-indicator\";\n"
  },
  {
    "path": "client/app/assets/less/redash/css-logo.less",
    "content": "// based on https://github.com/outbrain/tech-companies-logos-in-css/pull/28\n\n@primary: #ff7964;\n@shadow: #ef6c58;\n@bar: white;\n\n#css-logo {\n  width: 100px;\n  height: 100px;\n  position: relative;\n  \n  #circle {\n    width: 79px;\n    height: 79px;\n    background-color: @shadow;\n    border-radius: 50%;\n    margin: auto;\n    overflow: hidden;\n    position: relative;\n\n    & > div {\n      width: 79px;\n      height: 73px;\n      background-color: @primary;\n      border-radius: 50%;\n      position: absolute;\n      top: 0;\n    }\n  }\n  \n  #bars {\n    position: absolute;\n    left: 0;\n    top: 24px;\n    right: 0;\n    height: 33px;\n    display: flex;\n    padding: 0 22px 0;\n\n    .bar {\n      background: @bar;\n      box-shadow: 0px 2px 0 0 @shadow;\n      display: inline-block;\n      border-radius: 1px;\n      align-self: flex-end;\n      flex: 1;\n      margin: 0 2px;\n      border-radius: 3px;\n\n      &:nth-child(1) {\n        height: 32%;\n      }\n      \n      &:nth-child(2) {\n        height: 71%;\n      }\n      \n      &:nth-child(3) {\n        height: 50%;\n      }\n      \n      &:nth-child(4) {\n        height: 100%;\n      }\n    }\n  }\n  \n  #point,\n  #point > div {\n    position: absolute;\n    width: 0;\n    height: 0;\n    border: 17px solid @shadow;\n    border-right-color: transparent !important;\n    border-bottom-color: transparent !important;\n    bottom: 0;\n    left: 48px;\n    transform: scaleX(0.87);\n    transform-origin: left;\n  }\n  \n  #point > div {\n    bottom: -12px;\n    border-color: @primary;\n    transform: scaleX(1.04);\n    left: -17px;\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/redash/loading-indicator.less",
    "content": ".loading-indicator {\n  position: fixed;\n  top: 50%;\n  left: 50%;\n  margin: -50px 0 0 -50px; // center\n  width: 100px;\n  height: 100px;\n  transition-duration: 150ms;\n  transition-timing-function: linear;\n  transition-property: opacity, transform;\n\n  #css-logo {\n    animation: hover 2s infinite;\n  }\n\n  #shadow {\n    width: 33px;\n    height: 12px;\n    border-radius: 50%;\n    background-color: black;\n    opacity: 0.25;\n    display: block;\n    position: absolute;\n    left: 34px;\n    top: 115px;\n    animation: shadow 2s infinite;\n  }\n\n  @keyframes hover {\n    50% {\n      transform: translateY(-5px);\n    }\n  }\n  @keyframes shadow {\n    50% {\n      transform: scaleX(0.9);\n      opacity: 0.2;\n    }\n  }\n}\n\n// hide indicator when application has content\n#application-root:not(:empty) ~ .loading-indicator {\n  opacity: 0;\n  transform: scale(0.9);\n  pointer-events: none;\n\n  * {\n    animation: none !important;\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/redash/query.less",
    "content": "body.fixed-layout {\n  padding: 0;\n  overflow: hidden;\n\n  #application-root {\n    display: flex;\n    flex-direction: row;\n    padding-bottom: 0;\n\n    width: 100vw;\n    height: 100%;\n\n    .application-layout-content > div {\n      display: flex;\n    }\n  }\n}\n\n.p-b-60 {\n  padding-bottom: 60px !important;\n}\n\n.bottom-controller-container {\n  box-shadow: 0 0 9px 0 rgba(102, 136, 153, 0.15);\n  z-index: 1;\n  border: none !important;\n  flex-shrink: 0;\n}\n\n// Editor\n.filter-container {\n  margin-bottom: 5px;\n}\n\n.schema-container {\n  background: transparent;\n  flex-grow: 1;\n  display: flex;\n  flex-direction: column;\n}\n\n.editor__left {\n  height: 100% !important;\n  width: calc(~\"25% - 10px\");\n  margin-right: 10px;\n\n  .form-control {\n    height: 30px;\n  }\n}\n\n.query-alerts {\n  .alert {\n    margin-bottom: 15px;\n  }\n}\n\n.query-log-line {\n  font-family: monospace;\n  white-space: pre;\n  margin: 0;\n}\n\n.paginator-container {\n  text-align: center;\n}\n\n.tile {\n  .paginator-container {\n    text-align: center;\n    margin-top: 10px;\n  }\n}\n\n.query__vis {\n  table {\n    border: 1px solid #f0f0f0;\n  }\n\n  .paginator-container {\n    text-align: center;\n    margin-top: 10px;\n\n    li:first-of-type {\n      margin-left: 0;\n    }\n  }\n}\n\n.embed__vis {\n  display: flex;\n  flex-flow: column;\n  height: calc(~'100% - 25px');\n\n  > .embed-heading {\n    flex: 0 0 auto;\n  }\n\n  > .query__vis {\n    flex: 1 1 auto;\n\n    .chart-visualization-container, .visualization-renderer-wrapper, .visualization-renderer {\n      height: 100%\n    }\n  }\n\n  > .tile__bottom-control {\n    flex: 0 0 auto;\n  }\n  width: 100%;\n}\n\n.embed-heading {\n  h3 {\n    line-height: 1.75;\n    margin: 0;\n  }\n}\n\n.widget-wrapper {\n  .body-container {\n    .filters-wrapper {\n      display: block;\n      padding-left: 15px;\n    }\n  }\n}\n\n// Don't let filters take all visualization space on query fixed layout\n.query-fixed-layout {\n  .filters-wrapper {\n    max-height: 40%;\n    overflow: auto;\n  }\n}\n\n.page-header--new {\n  .query-tags,\n  .query-tags__mobile {\n    .label-default,\n    .label-warning {\n      margin-right: 3px;\n    }\n  }\n}\n\n.label-tag {\n  background: fade(@redash-gray, 15%);\n  color: darken(@redash-gray, 15%);\n\n  &:hover,\n  &:focus,\n  &:active {\n    color: darken(@redash-gray, 15%);\n    background: fade(@redash-gray, 25%);\n  }\n}\n\n.query-page-wrapper {\n  display: flex;\n  flex-direction: column;\n  flex-grow: 1;\n  position: relative;\n}\n\n.query-fullscreen {\n  background: #fff;\n  padding: 0;\n  box-shadow: rgba(102, 136, 153, 0.15) 0 4px 9px -3px;\n  flex-grow: 1;\n  display: flex;\n\n  .resizable-component.react-resizable {\n    .react-resizable-handle-horizontal {\n      border-right: 1px solid #efefef;\n    }\n\n    .react-resizable-handle-vertical {\n      border-bottom: 1px solid #efefef;\n    }\n  }\n\n  .query-metadata.query-metadata-horizontal {\n    border-bottom: 1px solid #efefef;\n  }\n\n  .tile,\n  .tiled {\n    box-shadow: none;\n    padding: 15px 0 !important;\n  }\n\n  nav {\n    position: relative;\n    display: flex;\n    flex-flow: column;\n    flex-basis: 25%;\n    flex-shrink: 0;\n    max-width: 600px;\n    min-width: 10px;\n    overflow-x: hidden;\n\n    .editor__left__data-source,\n    .schema-control,\n    .editor {\n      flex-shrink: 0;\n    }\n\n    .editor__left__schema,\n    .editor__left__data-source {\n      padding: 15px;\n    }\n\n    .editor__left__data-source {\n      .ant-select {\n        .ant-select-selection-selected-value {\n          img,\n          span {\n            vertical-align: middle;\n          }\n        }\n      }\n    }\n\n    .editor__left__schema {\n      min-height: 120px;\n      flex-grow: 1;\n      display: flex;\n      flex-direction: column;\n      padding-bottom: 0;\n      padding-top: 0 !important;\n      position: relative;\n\n      .schema-container {\n        position: absolute;\n        left: 15px;\n        top: 0;\n        right: 15px;\n        bottom: 0;\n      }\n    }\n  }\n  .content {\n    background: #fff;\n    flex-grow: 1;\n    display: flex;\n    flex-flow: column nowrap;\n    justify-content: space-around;\n    align-content: space-around;\n    padding: 0;\n    overflow-x: hidden;\n  }\n  .row {\n    background: #fff;\n    min-height: 50px;\n\n    &.resizable {\n      flex: 0 0 300px;\n    }\n\n    &.editor {\n      display: flex;\n      flex-flow: row nowrap;\n      justify-content: space-around;\n      align-content: space-around;\n      overflow: hidden;\n\n      min-height: 10px;\n      max-height: 70vh;\n      flex: 0 0 300px;\n    }\n\n    .row {\n      display: block;\n      min-height: 0;\n    }\n  }\n\n  section {\n    box-sizing: border-box;\n    flex: 1;\n    min-width: 30px;\n    &.resizable {\n      flex: 0 0 300px;\n    }\n  }\n\n  // **********************************************************************\n  // directive styles\n  // **********************************************************************\n  .resizable {\n    position: relative;\n    &.no-transition {\n      transition: none !important;\n    }\n  }\n  .rg-right,\n  .rg-left,\n  .rg-top,\n  .rg-bottom {\n    display: block;\n    width: 10px;\n    height: 10px;\n    line-height: @spacing;\n    position: absolute;\n    z-index: 99;\n\n    span {\n      position: absolute;\n      box-sizing: border-box;\n      display: block;\n      border: 1px solid #ccc;\n    }\n  }\n  .rg-right,\n  .rg-left {\n    span {\n      border-width: 0 1px;\n      top: 50%;\n      margin: -10px 0 0 @spacing / 4;\n      height: 20px;\n      width: 3px;\n    }\n  }\n  .rg-top,\n  .rg-bottom {\n    span {\n      border-width: 1px 0;\n      left: 50%;\n      margin: @spacing / 4 0 0 -10px;\n      width: 20px;\n      height: 3px;\n    }\n  }\n  .rg-top {\n    cursor: row-resize;\n    width: 100%;\n    top: 0;\n    left: 0;\n    margin-top: -@spacing / 2;\n  }\n  .rg-right {\n    cursor: col-resize;\n    border-right: 1px solid #efefef;\n    height: 100%;\n    right: 0;\n    top: 0;\n    margin-right: 0px;\n\n    &:hover {\n      background: fade(@redash-gray, 6%);\n    }\n  }\n  .rg-bottom {\n    cursor: row-resize;\n    background: #fff;\n    width: 100%;\n    bottom: 0;\n    left: 0;\n    margin-bottom: 0;\n\n    &:hover {\n      background: fade(@redash-gray, 6%);\n    }\n  }\n  .rg-left {\n    cursor: col-resize;\n    height: 100%;\n    left: 0;\n    top: 0;\n    margin-left: -@spacing;\n  }\n}\n\n.datasource-small {\n  visibility: hidden;\n}\n\n// Visualization editor\n.modal-xl .modal-content {\n  border: none;\n}\n\n.visualization-editor {\n  .modal-title {\n    font-weight: 600;\n    font-size: 20px;\n  }\n\n  .modal-body {\n    bottom: 50px;\n  }\n\n  .modal-footer {\n    height: auto;\n  }\n\n  .visualization-editor__right {\n    margin-top: 23px;\n    border: 1px solid #eee;\n    border-radius: 3px;\n\n    .parameter-container {\n      padding-left: 25px;\n      margin-top: 10px;\n    }\n  }\n}\n\n// Left nav fixes for filling all the space\nnav .rg-bottom {\n  visibility: hidden;\n}\n\n.query-tags {\n  display: inline-block;\n  vertical-align: middle;\n}\n\n.query-tags__mobile {\n  display: none;\n}\n\n.table--permission {\n  .profile__image {\n    margin-right: 0;\n  }\n}\n\n.mp__permission-type {\n  text-transform: capitalize;\n}\n\n.edit-visualization {\n  margin-right: 5px;\n}\n\n@media (min-width: 880px) {\n  .query-fullscreen {\n    .query-metadata.query-metadata-horizontal {\n      display: none;\n    }\n  }\n}\n\n// Smaller screens\n\n@media (max-width: 880px) {\n  .btn--showhide,\n  .query-actions-menu .dropdown-toggle {\n    margin-bottom: 5px;\n  }\n\n  .btn-publish {\n    display: none;\n  }\n\n  .query-fullscreen {\n    flex-direction: column;\n    overflow: hidden;\n\n    nav {\n      display: none;\n    }\n\n    .schema-container {\n      display: none;\n    }\n\n    main {\n      flex-direction: column-reverse;\n\n      nav {\n        width: 100%;\n        max-width: 100%;\n        border-right: none;\n\n        .editor__left__schema {\n          height: 300px !important;\n        }\n\n        .rg-right {\n          display: none;\n        }\n      }\n    }\n\n    .content {\n      width: 100%;\n      height: 100%;\n\n      .static-position__mobile {\n        position: static !important;\n      }\n    }\n\n    .bottom-controller-container {\n      z-index: 9;\n    }\n  }\n\n  .datasource-small {\n    visibility: visible;\n  }\n}\n\n@media (max-width: 768px) {\n  .editor__left__schema,\n  .editor__left__data-source {\n    display: none;\n  }\n\n  .filter-container {\n    padding-right: 0;\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/redash/redash-table.less",
    "content": ".table {\n  margin-bottom: 0;\n\n  [class*=\"bg-\"] {\n    & > tr > th {\n      color: #fff;\n      border-bottom: 0;\n      background: transparent !important;\n    }\n\n    & + tbody > tr:first-child > td {\n      border-top: 0;\n    }\n  }\n\n  & > thead > tr > th {\n    vertical-align: middle;\n    font-weight: 500;\n    color: #333;\n    border-width: 1px;\n    text-transform: none;\n    padding: 15px 10px;\n  }\n\n  & > thead > tr,\n  & > tbody > tr,\n  & > tfoot > tr {\n\n    & > th, & > td {\n\n      &:first-child {\n        padding-left: 15px;\n      }\n\n      &:last-child {\n        padding-right: 15px;\n      }\n\n    }\n  }\n\n  tbody > tr:last-child > td {\n    padding-bottom: 10px;\n  }\n}\n\n.table.table-condensed {\n  tbody > tr:last-child > td {\n    padding-bottom: 7px;\n  }\n}\n\n.table-bordered {\n  border: 0;\n\n  & > tbody > tr {\n    & > td, & > th {\n      border-bottom: 0;\n      border-left: 0;\n\n      &:last-child {\n        border-right: 0;\n      }\n    }\n  }\n\n  & > thead > tr > th {\n    border-left: 0;\n\n    &:last-child {\n      border-right: 0;\n    }\n  }\n}\n\n.table-vmiddle {\n  td {\n    vertical-align: middle !important;\n  }\n}\n\n.table-responsive {\n  border: 0;\n}\n\n.tile .table  {\n\n  & > thead:not([class*=\"bg-\"]) > tr > th {\n    border-top: 1px solid @table-border-color;\n\n  }\n}\n\n.table-hover > tbody > tr:hover {\n  background-color: #fff !important;\n  background-color: fade(@redash-gray, 5%) !important;\n}\n\n.table:not(.table-striped) > thead > tr > th {\n  background-color: #fff !important;\n  background-color: fade(@redash-gray, 3%) !important;\n}\n\n\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n  vertical-align: middle;\n}\n\n\n.table-condensed > tbody > tr > td {\n  padding: 7px 10px;\n}\n\n.table-border {\n  border: 1px solid rgb(240, 240, 240);\n}\n\n"
  },
  {
    "path": "client/app/assets/less/redash/tags-control.less",
    "content": ".tags-control {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n  align-items: stretch;\n  justify-content: flex-start;\n  line-height: 1em;\n\n  &.inline-tags-control {\n    display: inline-block;\n  }\n\n  .tag-separator {\n    margin: 4px 3px 0 0;\n  }\n\n  &.disabled {\n    opacity: 0.4;\n  }\n}\n"
  },
  {
    "path": "client/app/assets/less/server.less",
    "content": "/** LESS Plugins **/\n@import 'inc/less-plugins/for';\n\n/** Load Main Bootstrap LESS files **/\n@import '~bootstrap/less/bootstrap';\n@import '~material-design-iconic-font/dist/css/material-design-iconic-font.css';\n\n@import 'inc/variables';\n@import 'inc/mixins';\n@import 'inc/font';\n@import 'inc/print';\n\n@import 'inc/bootstrap-overrides';\n@import 'inc/base';\n@import 'inc/generics';\n@import 'inc/form';\n@import 'inc/button';\n@import 'inc/404';\n@import 'inc/ie-warning';\n@import 'inc/flex';\n\nhtml, body {\n  height: 100%;\n  margin: 0;\n  padding: 0;\n  background: #F6F8F9;\n}\n\n.signed-out {\n\n}\n\nhr {\n  border-top-width: 2px;\n  margin: 25px 0;\n}\n\n.tiled {\n  padding: 25px;\n}\n\n.header {\n  margin-top: 25px;\n\n  img {\n    height: 40px;\n  }\n}\n\n.fixed-width-page {\n  width: 500px;\n}\n\n@media (max-width: 767px) {\n  .fixed-width-page {\n    width: 80vw;\n  }\n}\n\n.login-button {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin: 20px 0;\n\n  &:first-of-type {\n    margin-top: 0;\n  }\n  &:last-of-type {\n    margin-bottom: 0;\n  }\n\n  img {\n    height: 25px;\n    margin-right: 5px;\n  }\n\n  &:before {\n    content: \"\";\n    display: inline-block;\n    height: 25px;\n  }\n}\n"
  },
  {
    "path": "client/app/assets/robots.txt",
    "content": "# robotstxt.org\n\nUser-agent: *\n"
  },
  {
    "path": "client/app/components/AceEditorInput.jsx",
    "content": "import React, { forwardRef } from \"react\";\nimport AceEditor from \"react-ace\";\n\nimport \"./AceEditorInput.less\";\n\nfunction AceEditorInput(props, ref) {\n  return (\n    <div className=\"ace-editor-input\" data-test={props[\"data-test\"]}>\n      <AceEditor\n        ref={ref}\n        mode=\"sql\"\n        theme=\"textmate\"\n        height=\"100px\"\n        editorProps={{ $blockScrolling: Infinity }}\n        showPrintMargin={false}\n        {...props}\n      />\n    </div>\n  );\n}\n\nexport default forwardRef(AceEditorInput);\n"
  },
  {
    "path": "client/app/components/AceEditorInput.less",
    "content": ".ace-editor-input {\n  // hide ghost cursor when not focused\n  .ace_hidden-cursors {\n    opacity: 0;\n  }\n\n  // allow Ant Form feedback icon to hover scrollbar\n  .ace_scrollbar {\n    z-index: auto;\n  }\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.jsx",
    "content": "import React, { useMemo } from \"react\";\nimport { first, includes } from \"lodash\";\nimport Menu from \"antd/lib/menu\";\nimport Link from \"@/components/Link\";\nimport PlainButton from \"@/components/PlainButton\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport CreateDashboardDialog from \"@/components/dashboards/CreateDashboardDialog\";\nimport { useCurrentRoute } from \"@/components/ApplicationArea/Router\";\nimport { Auth, currentUser } from \"@/services/auth\";\nimport settingsMenu from \"@/services/settingsMenu\";\nimport logoUrl from \"@/assets/images/redash_icon_small.png\";\n\nimport DesktopOutlinedIcon from \"@ant-design/icons/DesktopOutlined\";\nimport CodeOutlinedIcon from \"@ant-design/icons/CodeOutlined\";\nimport AlertOutlinedIcon from \"@ant-design/icons/AlertOutlined\";\nimport PlusOutlinedIcon from \"@ant-design/icons/PlusOutlined\";\nimport QuestionCircleOutlinedIcon from \"@ant-design/icons/QuestionCircleOutlined\";\nimport SettingOutlinedIcon from \"@ant-design/icons/SettingOutlined\";\nimport VersionInfo from \"./VersionInfo\";\n\nimport \"./DesktopNavbar.less\";\n\nfunction NavbarSection({ children, ...props }) {\n  return (\n    <Menu selectable={false} mode=\"vertical\" theme=\"dark\" {...props}>\n      {children}\n    </Menu>\n  );\n}\n\nfunction useNavbarActiveState() {\n  const currentRoute = useCurrentRoute();\n\n  return useMemo(\n    () => ({\n      dashboards: includes(\n        [\n          \"Dashboards.List\",\n          \"Dashboards.Favorites\",\n          \"Dashboards.My\",\n          \"Dashboards.ViewOrEdit\",\n          \"Dashboards.LegacyViewOrEdit\",\n        ],\n        currentRoute.id\n      ),\n      queries: includes(\n        [\n          \"Queries.List\",\n          \"Queries.Favorites\",\n          \"Queries.Archived\",\n          \"Queries.My\",\n          \"Queries.View\",\n          \"Queries.New\",\n          \"Queries.Edit\",\n        ],\n        currentRoute.id\n      ),\n      dataSources: includes([\"DataSources.List\"], currentRoute.id),\n      alerts: includes([\"Alerts.List\", \"Alerts.New\", \"Alerts.View\", \"Alerts.Edit\"], currentRoute.id),\n    }),\n    [currentRoute.id]\n  );\n}\n\nexport default function DesktopNavbar() {\n  const firstSettingsTab = first(settingsMenu.getAvailableItems());\n\n  const activeState = useNavbarActiveState();\n\n  const canCreateQuery = currentUser.hasPermission(\"create_query\");\n  const canCreateDashboard = currentUser.hasPermission(\"create_dashboard\");\n  const canCreateAlert = currentUser.hasPermission(\"list_alerts\");\n\n  return (\n    <nav className=\"desktop-navbar\">\n      <NavbarSection className=\"desktop-navbar-logo\">\n        <div role=\"menuitem\">\n          <Link href=\"./\">\n            <img src={logoUrl} alt=\"Redash\" />\n          </Link>\n        </div>\n      </NavbarSection>\n\n      <NavbarSection>\n        {currentUser.hasPermission(\"list_dashboards\") && (\n          <Menu.Item key=\"dashboards\" className={activeState.dashboards ? \"navbar-active-item\" : null}>\n            <Link href=\"dashboards\">\n              <DesktopOutlinedIcon aria-label=\"Dashboard navigation button\" />\n              <span className=\"desktop-navbar-label\">Dashboards</span>\n            </Link>\n          </Menu.Item>\n        )}\n        {currentUser.hasPermission(\"view_query\") && (\n          <Menu.Item key=\"queries\" className={activeState.queries ? \"navbar-active-item\" : null}>\n            <Link href=\"queries\">\n              <CodeOutlinedIcon aria-label=\"Queries navigation button\" />\n              <span className=\"desktop-navbar-label\">Queries</span>\n            </Link>\n          </Menu.Item>\n        )}\n        {currentUser.hasPermission(\"list_alerts\") && (\n          <Menu.Item key=\"alerts\" className={activeState.alerts ? \"navbar-active-item\" : null}>\n            <Link href=\"alerts\">\n              <AlertOutlinedIcon aria-label=\"Alerts navigation button\" />\n              <span className=\"desktop-navbar-label\">Alerts</span>\n            </Link>\n          </Menu.Item>\n        )}\n      </NavbarSection>\n\n      <NavbarSection className=\"desktop-navbar-spacer\">\n        {(canCreateQuery || canCreateDashboard || canCreateAlert) && (\n          <Menu.SubMenu\n            key=\"create\"\n            popupClassName=\"desktop-navbar-submenu\"\n            data-test=\"CreateButton\"\n            tabIndex={0}\n            title={\n              <React.Fragment>\n                <PlusOutlinedIcon />\n                <span className=\"desktop-navbar-label\">Create</span>\n              </React.Fragment>\n            }>\n            {canCreateQuery && (\n              <Menu.Item key=\"new-query\">\n                <Link href=\"queries/new\" data-test=\"CreateQueryMenuItem\">\n                  New Query\n                </Link>\n              </Menu.Item>\n            )}\n            {canCreateDashboard && (\n              <Menu.Item key=\"new-dashboard\">\n                <PlainButton data-test=\"CreateDashboardMenuItem\" onClick={() => CreateDashboardDialog.showModal()}>\n                  New Dashboard\n                </PlainButton>\n              </Menu.Item>\n            )}\n            {canCreateAlert && (\n              <Menu.Item key=\"new-alert\">\n                <Link data-test=\"CreateAlertMenuItem\" href=\"alerts/new\">\n                  New Alert\n                </Link>\n              </Menu.Item>\n            )}\n          </Menu.SubMenu>\n        )}\n      </NavbarSection>\n\n      <NavbarSection>\n        <Menu.Item key=\"help\">\n          <HelpTrigger showTooltip={false} type=\"HOME\" tabIndex={0}>\n            <QuestionCircleOutlinedIcon />\n            <span className=\"desktop-navbar-label\">Help</span>\n          </HelpTrigger>\n        </Menu.Item>\n        {firstSettingsTab && (\n          <Menu.Item key=\"settings\" className={activeState.dataSources ? \"navbar-active-item\" : null}>\n            <Link href={firstSettingsTab.path} data-test=\"SettingsLink\">\n              <SettingOutlinedIcon />\n              <span className=\"desktop-navbar-label\">Settings</span>\n            </Link>\n          </Menu.Item>\n        )}\n      </NavbarSection>\n\n      <NavbarSection className=\"desktop-navbar-profile-menu\">\n        <Menu.SubMenu\n          key=\"profile\"\n          popupClassName=\"desktop-navbar-submenu\"\n          tabIndex={0}\n          title={\n            <span data-test=\"ProfileDropdown\" className=\"desktop-navbar-profile-menu-title\">\n              <img className=\"profile__image_thumb\" src={currentUser.profile_image_url} alt={currentUser.name} />\n            </span>\n          }>\n          <Menu.Item key=\"profile\">\n            <Link href=\"users/me\">Profile</Link>\n          </Menu.Item>\n          {currentUser.hasPermission(\"super_admin\") && (\n            <Menu.Item key=\"status\">\n              <Link href=\"admin/status\">System Status</Link>\n            </Menu.Item>\n          )}\n          <Menu.Divider />\n          <Menu.Item key=\"logout\">\n            <PlainButton data-test=\"LogOutButton\" onClick={() => Auth.logout()}>\n              Log out\n            </PlainButton>\n          </Menu.Item>\n          <Menu.Divider />\n          <Menu.Item key=\"version\" role=\"presentation\" disabled className=\"version-info\">\n            <VersionInfo />\n          </Menu.Item>\n        </Menu.SubMenu>\n      </NavbarSection>\n    </nav>\n  );\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.less",
    "content": "@backgroundColor: #001529;\n@dividerColor: rgba(255, 255, 255, 0.5);\n@textColor: rgba(255, 255, 255, 0.75);\n@brandColor: #ff7964; // Redash logo color\n@activeItemColor: @brandColor;\n@iconSize: 26px;\n\n.desktop-navbar {\n  background: @backgroundColor;\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n  width: 80px;\n  overflow: hidden;\n\n  &-spacer {\n    flex: 1 1 auto;\n  }\n\n  &-logo.ant-menu {\n    padding-top: 20px;\n    padding-bottom: 20px;\n    text-align: center;\n\n    img {\n      height: 40px;\n      transition: all 270ms;\n    }\n  }\n\n  .help-trigger {\n    font: inherit;\n  }\n\n  .ant-menu {\n    .ant-menu-item,\n    .ant-menu-submenu {\n      font-weight: 500;\n      color: @textColor;\n\n      &.navbar-active-item {\n        box-shadow: inset 3px 0 0 @activeItemColor;\n\n        .anticon {\n          color: @activeItemColor;\n        }\n      }\n\n      &.ant-menu-submenu-open,\n      &.ant-menu-submenu-active,\n      &:hover,\n      &:active,\n      &:focus,\n      &:focus-within {\n        color: #fff;\n      }\n\n      .anticon {\n        font-size: @iconSize;\n        margin: 0;\n      }\n\n      .desktop-navbar-label {\n        margin-top: 4px;\n        font-size: 11px;\n      }\n\n      a,\n      span,\n      .anticon {\n        color: inherit;\n      }\n    }\n\n    .ant-menu-submenu-arrow {\n      display: none;\n    }\n\n    .ant-menu-item,\n    .ant-menu-submenu {\n      padding: 0;\n      height: 60px;\n      display: flex;\n      align-items: center;\n      flex-direction: column;\n      justify-content: center;\n    }\n\n    .ant-menu-submenu-title {\n      width: 100%;\n      padding: 0;\n    }\n\n    a,\n    &.ant-menu-vertical > .ant-menu-submenu > .ant-menu-submenu-title,\n    .ant-menu-submenu-title {\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      justify-content: center;\n      line-height: normal;\n      height: auto;\n      background: none;\n      color: inherit;\n    }\n  }\n\n  .desktop-navbar-profile-menu {\n    .desktop-navbar-profile-menu-title {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      overflow: hidden;\n\n      .profile__image_thumb {\n        margin: 0;\n        vertical-align: middle;\n        width: @iconSize;\n        height: @iconSize;\n      }\n    }\n  }\n}\n\n.desktop-navbar-submenu {\n  .ant-menu {\n    .ant-menu-item-divider {\n      background: @dividerColor;\n    }\n\n    .ant-menu-item {\n      font-weight: 500;\n      color: @textColor;\n\n      &:hover,\n      &:active,\n      &:focus,\n      &:focus-within {\n        color: #fff;\n      }\n\n      a,\n      span,\n      .anticon {\n        color: inherit;\n      }\n\n      .zmdi,\n      .fa {\n        margin-right: 5px;\n      }\n\n      &.version-info {\n        height: auto;\n        line-height: normal;\n        padding-top: 12px;\n        padding-bottom: 12px;\n\n        a {\n          color: rgba(255, 255, 255, 0.8);\n\n          &:hover,\n          &:active,\n          &:focus,\n          &:focus-within {\n            color: rgba(255, 255, 255, 1);\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ApplicationLayout/MobileNavbar.jsx",
    "content": "import { first } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport MenuOutlinedIcon from \"@ant-design/icons/MenuOutlined\";\nimport Dropdown from \"antd/lib/dropdown\";\nimport Menu from \"antd/lib/menu\";\nimport Link from \"@/components/Link\";\nimport { Auth, currentUser } from \"@/services/auth\";\nimport settingsMenu from \"@/services/settingsMenu\";\nimport logoUrl from \"@/assets/images/redash_icon_small.png\";\n\nimport \"./MobileNavbar.less\";\n\nexport default function MobileNavbar({ getPopupContainer }) {\n  const firstSettingsTab = first(settingsMenu.getAvailableItems());\n\n  return (\n    <div className=\"mobile-navbar\">\n      <div className=\"mobile-navbar-logo\">\n        <Link href=\"./\">\n          <img src={logoUrl} alt=\"Redash\" />\n        </Link>\n      </div>\n      <div>\n        <Dropdown\n          overlayStyle={{ minWidth: 200 }}\n          trigger={[\"click\"]}\n          getPopupContainer={getPopupContainer} // so the overlay menu stays with the fixed header when page scrolls\n          overlay={\n            <Menu mode=\"vertical\" theme=\"dark\" selectable={false} className=\"mobile-navbar-menu\">\n              {currentUser.hasPermission(\"list_dashboards\") && (\n                <Menu.Item key=\"dashboards\">\n                  <Link href=\"dashboards\">Dashboards</Link>\n                </Menu.Item>\n              )}\n              {currentUser.hasPermission(\"view_query\") && (\n                <Menu.Item key=\"queries\">\n                  <Link href=\"queries\">Queries</Link>\n                </Menu.Item>\n              )}\n              {currentUser.hasPermission(\"list_alerts\") && (\n                <Menu.Item key=\"alerts\">\n                  <Link href=\"alerts\">Alerts</Link>\n                </Menu.Item>\n              )}\n              <Menu.Item key=\"profile\">\n                <Link href=\"users/me\">Edit Profile</Link>\n              </Menu.Item>\n              <Menu.Divider />\n              {firstSettingsTab && (\n                <Menu.Item key=\"settings\">\n                  <Link href={firstSettingsTab.path}>Settings</Link>\n                </Menu.Item>\n              )}\n              {currentUser.hasPermission(\"super_admin\") && (\n                <Menu.Item key=\"status\">\n                  <Link href=\"admin/status\">System Status</Link>\n                </Menu.Item>\n              )}\n              {currentUser.hasPermission(\"super_admin\") && <Menu.Divider />}\n              <Menu.Item key=\"help\">\n                {/* eslint-disable-next-line react/jsx-no-target-blank */}\n                <Link href=\"https://redash.io/help\" target=\"_blank\" rel=\"noopener\">\n                  Help\n                </Link>\n              </Menu.Item>\n              <Menu.Item key=\"logout\" onClick={() => Auth.logout()}>\n                Log out\n              </Menu.Item>\n            </Menu>\n          }>\n          <Button className=\"mobile-navbar-toggle-button\" ghost>\n            <MenuOutlinedIcon />\n          </Button>\n        </Dropdown>\n      </div>\n    </div>\n  );\n}\n\nMobileNavbar.propTypes = {\n  getPopupContainer: PropTypes.func,\n};\n\nMobileNavbar.defaultProps = {\n  getPopupContainer: null,\n};\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ApplicationLayout/MobileNavbar.less",
    "content": "@backgroundColor: #001529;\n@dividerColor: rgba(255, 255, 255, 0.5);\n@textColor: rgba(255, 255, 255, 0.75);\n\n.mobile-navbar {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  background: @backgroundColor;\n  box-shadow: 0 4px 9px -3px rgba(102, 136, 153, 0.15);\n  padding: 0 15px;\n  height: 100%;\n\n  &-logo {\n    img {\n      height: 40px;\n      width: 40px;\n    }\n  }\n\n  .ant-btn.mobile-navbar-toggle-button {\n    padding: 0 10px;\n  }\n}\n\n.mobile-navbar-menu {\n  .ant-dropdown-menu-item {\n    font-weight: 500;\n    color: @textColor;\n  }\n\n  .ant-dropdown-menu-item-divider {\n    background: @dividerColor;\n  }\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ApplicationLayout/VersionInfo.jsx",
    "content": "import React from \"react\";\nimport Link from \"@/components/Link\";\nimport { clientConfig, currentUser } from \"@/services/auth\";\nimport frontendVersion from \"@/version.json\";\n\nexport default function VersionInfo() {\n  return (\n    <React.Fragment>\n      <div>\n        Version: {clientConfig.version}\n        {frontendVersion !== clientConfig.version && ` (${frontendVersion.substring(0, 8)})`}\n      </div>\n      {clientConfig.newVersionAvailable && currentUser.hasPermission(\"super_admin\") && (\n        <div className=\"m-t-10\">\n          {/* eslint-disable react/jsx-no-target-blank */}\n          <Link href=\"https://version.redash.io/\" className=\"update-available\" target=\"_blank\" rel=\"noopener\">\n            Update Available <i className=\"fa fa-external-link m-l-5\" aria-hidden=\"true\" />\n            <span className=\"sr-only\">(opens in a new tab)</span>\n          </Link>\n        </div>\n      )}\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ApplicationLayout/index.jsx",
    "content": "import React, { useRef, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport DesktopNavbar from \"./DesktopNavbar\";\nimport MobileNavbar from \"./MobileNavbar\";\n\nimport \"./index.less\";\n\nexport default function ApplicationLayout({ children }) {\n  const mobileNavbarContainerRef = useRef();\n\n  const getMobileNavbarPopupContainer = useCallback(() => mobileNavbarContainerRef.current, []);\n\n  return (\n    <React.Fragment>\n      <DynamicComponent name=\"ApplicationWrapper\">\n        <div className=\"application-layout-side-menu\">\n          <DynamicComponent name=\"ApplicationDesktopNavbar\">\n            <DesktopNavbar />\n          </DynamicComponent>\n        </div>\n        <div className=\"application-layout-content\">\n          <nav className=\"application-layout-top-menu\" ref={mobileNavbarContainerRef}>\n            <DynamicComponent name=\"ApplicationMobileNavbar\" getPopupContainer={getMobileNavbarPopupContainer}>\n              <MobileNavbar getPopupContainer={getMobileNavbarPopupContainer} />\n            </DynamicComponent>\n          </nav>\n          {children}\n        </div>\n      </DynamicComponent>\n    </React.Fragment>\n  );\n}\n\nApplicationLayout.propTypes = {\n  children: PropTypes.node,\n};\n\nApplicationLayout.defaultProps = {\n  children: null,\n};\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ApplicationLayout/index.less",
    "content": "@mobileBreakpoint: ~\"(max-width: 767px)\";\n\nbody #application-root {\n  @topMenuHeight: 49px;\n\n  display: flex;\n  flex-direction: row;\n  justify-content: stretch;\n  padding-bottom: 0 !important;\n  height: 100%;\n\n  .application-layout-side-menu {\n    height: 100%;\n    position: relative;\n\n    @media @mobileBreakpoint {\n      display: none;\n    }\n  }\n\n  .application-layout-top-menu {\n    height: @topMenuHeight;\n    display: none;\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    box-sizing: border-box;\n    z-index: 1000;\n\n    @media @mobileBreakpoint {\n      display: block;\n    }\n  }\n\n  .application-layout-content {\n    display: flex;\n    flex-direction: column;\n    overflow-y: auto;\n\n    flex: 1 1 auto;\n    padding-bottom: 15px;\n\n    @media @mobileBreakpoint {\n      margin-top: @topMenuHeight; // compensate for app header fixed position\n    }\n  }\n}\n\nbody > section {\n  height: 100%;\n}\n\nbody.fixed-layout #application-root {\n  .application-layout-content {\n    padding-bottom: 0;\n  }\n}\n\nbody.headless #application-root {\n  .application-layout-side-menu,\n  .application-layout-top-menu {\n    display: none !important;\n  }\n\n  .application-layout-content {\n    margin-top: 0;\n  }\n}\n\n// Fixes for proper snapshots in Percy (move vertical scroll to body level\n// to capture entire page, otherwise it wll be cut by viewport)\n@media only percy {\n  body #application-root {\n    height: auto;\n\n    .application-layout-side-menu {\n      height: auto;\n    }\n\n    .application-layout-content {\n      overflow: visible;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ErrorMessage.jsx",
    "content": "import { get, isObject } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport \"./ErrorMessage.less\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { ErrorMessageDetails } from \"@/components/ApplicationArea/ErrorMessageDetails\";\n\nfunction getErrorMessageByStatus(status, defaultMessage) {\n  switch (status) {\n    case 404:\n      return \"It seems like the page you're looking for cannot be found.\";\n    case 401:\n    case 403:\n      return \"It seems like you don’t have permission to see this page.\";\n    default:\n      return defaultMessage;\n  }\n}\n\nfunction getErrorMessage(error) {\n  const message = \"It seems like we encountered an error. Try refreshing this page or contact your administrator.\";\n  if (isObject(error)) {\n    // HTTP errors\n    if (error.isAxiosError && isObject(error.response)) {\n      return getErrorMessageByStatus(error.response.status, get(error, \"response.data.message\", message));\n    }\n    // Router errors\n    if (error.status) {\n      return getErrorMessageByStatus(error.status, message);\n    }\n  }\n  return message;\n}\n\nexport default function ErrorMessage({ error, message }) {\n  if (!error) {\n    return null;\n  }\n\n  console.error(error);\n\n  const errorDetailsProps = {\n    error,\n    message: message || getErrorMessage(error),\n  };\n\n  return (\n    <div className=\"error-message-container\" data-test=\"ErrorMessage\" role=\"alert\">\n      <div className=\"error-state bg-white tiled\">\n        <div className=\"error-state__icon\">\n          <i className=\"zmdi zmdi-alert-circle-o\" aria-hidden=\"true\" />\n        </div>\n        <div className=\"error-state__details\">\n          <DynamicComponent\n            name=\"ErrorMessageDetails\"\n            fallback={<ErrorMessageDetails {...errorDetailsProps} />}\n            {...errorDetailsProps}\n          />\n        </div>\n      </div>\n    </div>\n  );\n}\n\nErrorMessage.propTypes = {\n  error: PropTypes.object.isRequired,\n  message: PropTypes.string,\n};\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ErrorMessage.less",
    "content": ".error-message-container {\n  width: 100%;\n  padding: 0 15px;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: flex-start;\n\n  .error-state {\n    max-width: 1200px;\n    width: 100%;\n\n    @media (min-width: 768px) {\n      width: 65%;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ErrorMessage.test.js",
    "content": "import React from \"react\";\nimport { mount } from \"enzyme\";\nimport ErrorMessage from \"./ErrorMessage\";\n\nconst ErrorMessages = {\n  UNAUTHORIZED: \"It seems like you don’t have permission to see this page.\",\n  NOT_FOUND: \"It seems like the page you're looking for cannot be found.\",\n  GENERIC: \"It seems like we encountered an error. Try refreshing this page or contact your administrator.\",\n};\n\nfunction mockAxiosError(status = 500, response = {}) {\n  const error = new Error(`Failed with code ${status}.`);\n  error.isAxiosError = true;\n  error.response = { status, ...response };\n  return error;\n}\n\ndescribe(\"Error Message\", () => {\n  const spyError = jest.spyOn(console, \"error\");\n\n  beforeEach(() => {\n    spyError.mockReset();\n  });\n\n  function expectErrorMessageToBe(error, errorMessage) {\n    const component = mount(<ErrorMessage error={error} />);\n\n    expect(component.find(\".error-state__details h4\").text()).toBe(errorMessage);\n    expect(spyError).toHaveBeenCalledWith(error);\n  }\n\n  test(\"displays a generic message on adhoc errors\", () => {\n    expectErrorMessageToBe(new Error(\"technical information\"), ErrorMessages.GENERIC);\n  });\n\n  test(\"displays a not found message on axios errors with 404 code\", () => {\n    expectErrorMessageToBe(mockAxiosError(404), ErrorMessages.NOT_FOUND);\n  });\n\n  test(\"displays a unauthorized message on axios errors with 401 code\", () => {\n    expectErrorMessageToBe(mockAxiosError(401), ErrorMessages.UNAUTHORIZED);\n  });\n\n  test(\"displays a unauthorized message on axios errors with 403 code\", () => {\n    expectErrorMessageToBe(mockAxiosError(403), ErrorMessages.UNAUTHORIZED);\n  });\n\n  test(\"displays a generic message on axios errors with 500 code\", () => {\n    expectErrorMessageToBe(mockAxiosError(500), ErrorMessages.GENERIC);\n  });\n});\n"
  },
  {
    "path": "client/app/components/ApplicationArea/ErrorMessageDetails.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\n\nexport function ErrorMessageDetails(props) {\n  return <h4>{props.message}</h4>;\n}\n\nErrorMessageDetails.propTypes = {\n  error: PropTypes.instanceOf(Error).isRequired,\n  message: PropTypes.string.isRequired,\n};\n"
  },
  {
    "path": "client/app/components/ApplicationArea/Router.jsx",
    "content": "import { isFunction, startsWith, trimStart, trimEnd } from \"lodash\";\nimport React, { useState, useEffect, useRef, useContext } from \"react\";\nimport PropTypes from \"prop-types\";\nimport UniversalRouter from \"universal-router\";\nimport ErrorBoundary from \"@redash/viz/lib/components/ErrorBoundary\";\nimport location from \"@/services/location\";\nimport url from \"@/services/url\";\n\nimport ErrorMessage from \"./ErrorMessage\";\n\nfunction generateRouteKey() {\n  return Math.random()\n    .toString(32)\n    .substr(2);\n}\n\nexport const CurrentRouteContext = React.createContext(null);\n\nexport function useCurrentRoute() {\n  return useContext(CurrentRouteContext);\n}\n\nexport function stripBase(href) {\n  // Resolve provided link and '' (root) relative to document's base.\n  // If provided href is not related to current document (does not\n  // start with resolved root) - return false. Otherwise\n  // strip root and return relative url.\n\n  const baseHref = trimEnd(url.normalize(\"\"), \"/\") + \"/\";\n  href = url.normalize(href);\n\n  if (startsWith(href, baseHref)) {\n    return \"/\" + trimStart(href.substr(baseHref.length), \"/\");\n  }\n\n  return false;\n}\n\nexport default function Router({ routes, onRouteChange }) {\n  const [currentRoute, setCurrentRoute] = useState(null);\n\n  const currentPathRef = useRef(null);\n  const errorHandlerRef = useRef();\n\n  useEffect(() => {\n    let isAbandoned = false;\n\n    const router = new UniversalRouter(routes, {\n      resolveRoute({ route }, routeParams) {\n        if (isFunction(route.render)) {\n          return { ...route, routeParams };\n        }\n      },\n    });\n\n    function resolve(action) {\n      if (!isAbandoned) {\n        if (errorHandlerRef.current) {\n          errorHandlerRef.current.reset();\n        }\n\n        const pathname = stripBase(location.path) || \"/\";\n\n        // This is a optimization for route resolver: if current route was already resolved\n        // from this path - do nothing. It also prevents router from using outdated route in a case\n        // when user navigated to another path while current one was still resolving.\n        // Note: this lock uses only `path` fragment of URL to distinguish routes because currently\n        // all pages depend only on this fragment and handle search/hash on their own. If router\n        // should reload page on search/hash change - this fragment (and few checks below) should be updated\n        if (pathname === currentPathRef.current) {\n          return;\n        }\n        currentPathRef.current = pathname;\n\n        // Don't reload controller if URL was replaced\n        if (action === \"REPLACE\") {\n          return;\n        }\n\n        router\n          .resolve({ pathname })\n          .then(route => {\n            if (!isAbandoned && currentPathRef.current === pathname) {\n              setCurrentRoute({ ...route, key: generateRouteKey() });\n            }\n          })\n          .catch(error => {\n            if (!isAbandoned && currentPathRef.current === pathname) {\n              setCurrentRoute({\n                render: currentRoute => <ErrorMessage {...currentRoute.routeParams} />,\n                routeParams: { error },\n              });\n            }\n          });\n      }\n    }\n\n    resolve(\"PUSH\");\n\n    const unlisten = location.listen((unused, action) => resolve(action));\n\n    return () => {\n      isAbandoned = true;\n      currentPathRef.current = null;\n      unlisten();\n    };\n  }, [routes]);\n\n  useEffect(() => {\n    onRouteChange(currentRoute);\n  }, [currentRoute, onRouteChange]);\n\n  if (!currentRoute) {\n    return null;\n  }\n\n  return (\n    <CurrentRouteContext.Provider value={currentRoute}>\n      <ErrorBoundary ref={errorHandlerRef} renderError={error => <ErrorMessage error={error} />}>\n        {currentRoute.render(currentRoute)}\n      </ErrorBoundary>\n    </CurrentRouteContext.Provider>\n  );\n}\n\nRouter.propTypes = {\n  routes: PropTypes.arrayOf(\n    PropTypes.shape({\n      path: PropTypes.string.isRequired,\n      render: PropTypes.func, // (routeParams: PropTypes.object; currentRoute; location) => PropTypes.node\n      // Additional props to be injected into route component.\n      // Object keys are props names. Object values will become prop values:\n      // - if value is a function - it will be called without arguments, and result will be used; otherwise value will be used;\n      // - after previous step, if value is a promise - router will wait for it to resolve; resolved value then will be used;\n      //   otherwise value will be used directly.\n      resolve: PropTypes.objectOf(PropTypes.any),\n    })\n  ),\n  onRouteChange: PropTypes.func,\n};\n\nRouter.defaultProps = {\n  routes: [],\n  onRouteChange: () => {},\n};\n"
  },
  {
    "path": "client/app/components/ApplicationArea/handleNavigationIntent.js",
    "content": "import { isString } from \"lodash\";\nimport navigateTo from \"./navigateTo\";\n\nexport default function handleNavigationIntent(event) {\n  let element = event.target;\n  while (element) {\n    if (element.tagName === \"A\") {\n      break;\n    }\n    element = element.parentNode;\n  }\n  if (!element || !element.hasAttribute(\"href\") || element.hasAttribute(\"download\") || element.dataset.skipRouter) {\n    return;\n  }\n\n  // Keep some default behaviour\n  if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {\n    return;\n  }\n\n  const target = element.getAttribute(\"target\");\n  if (isString(target) && target.toLowerCase() === \"_blank\") {\n    return;\n  }\n\n  event.preventDefault();\n\n  navigateTo(element.href);\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/index.jsx",
    "content": "import React, { useState, useEffect } from \"react\";\nimport routes from \"@/services/routes\";\nimport Router from \"./Router\";\nimport handleNavigationIntent from \"./handleNavigationIntent\";\nimport ErrorMessage from \"./ErrorMessage\";\n\nexport default function ApplicationArea() {\n  const [currentRoute, setCurrentRoute] = useState(null);\n  const [unhandledError, setUnhandledError] = useState(null);\n\n  useEffect(() => {\n    if (currentRoute && currentRoute.title) {\n      document.title = currentRoute.title;\n    }\n  }, [currentRoute]);\n\n  useEffect(() => {\n    function globalErrorHandler(event) {\n      event.preventDefault();\n      if (event.message === \"Uncaught SyntaxError: Unexpected token '<'\") {\n        // if we see a javascript error on unexpected token where the unexpected token is '<', this usually means that a fallback html file (like index.html)\n        // was served as content of script rather than the expected script, give a friendlier message in the console on what could be going on\n        console.error(\n          `[Uncaught SyntaxError: Unexpected token '<'] usually means that a fallback html file was returned from server rather than the expected script. Check that the server is properly serving the file ${event.filename}.`\n        );\n      }\n      setUnhandledError(event.error);\n    }\n\n    document.body.addEventListener(\"click\", handleNavigationIntent, false);\n    window.addEventListener(\"error\", globalErrorHandler, false);\n\n    return () => {\n      document.body.removeEventListener(\"click\", handleNavigationIntent, false);\n      window.removeEventListener(\"error\", globalErrorHandler, false);\n    };\n  }, []);\n\n  if (unhandledError) {\n    return <ErrorMessage error={unhandledError} />;\n  }\n\n  return <Router routes={routes.items} onRouteChange={setCurrentRoute} />;\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/navigateTo.js",
    "content": "import location from \"@/services/location\";\nimport url from \"@/services/url\";\nimport { stripBase } from \"./Router\";\n\n// When `replace` is set to `true` - it will just replace current URL\n// without reloading current page (router will skip this location change)\nexport default function navigateTo(href, replace = false) {\n  // Allow calling chain to roll up, and then navigate\n  setTimeout(() => {\n    const isExternal = stripBase(href) === false;\n    if (isExternal) {\n      window.location = href;\n      return;\n    }\n    href = url.parse(href);\n    location.update(\n      {\n        path: href.pathname,\n        search: href.search,\n        hash: href.hash,\n      },\n      replace\n    );\n  }, 10);\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/routeWithApiKeySession.jsx",
    "content": "import React, { useEffect, useState, useContext } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { ErrorBoundaryContext } from \"@redash/viz/lib/components/ErrorBoundary\";\nimport { Auth, clientConfig } from \"@/services/auth\";\n\n// This wrapper modifies `route.render` function and instead of passing `currentRoute` passes an object\n// that contains:\n// - `currentRoute.routeParams`\n// - `pageTitle` field which is equal to `currentRoute.title`\n// - `onError` field which is a `handleError` method of nearest error boundary\n// - `apiKey` field\n\nfunction ApiKeySessionWrapper({ apiKey, currentRoute, renderChildren }) {\n  const [isAuthenticated, setIsAuthenticated] = useState(false);\n  const { handleError } = useContext(ErrorBoundaryContext);\n\n  useEffect(() => {\n    let isCancelled = false;\n    Auth.setApiKey(apiKey);\n    Auth.loadConfig()\n      .then(() => {\n        if (!isCancelled) {\n          setIsAuthenticated(true);\n        }\n      })\n      .catch(() => {\n        if (!isCancelled) {\n          setIsAuthenticated(false);\n        }\n      });\n    return () => {\n      isCancelled = true;\n    };\n  }, [apiKey]);\n\n  if (!isAuthenticated || clientConfig.disablePublicUrls) {\n    return null;\n  }\n\n  return (\n    <React.Fragment key={currentRoute.key}>\n      {renderChildren({ ...currentRoute.routeParams, pageTitle: currentRoute.title, onError: handleError, apiKey })}\n    </React.Fragment>\n  );\n}\n\nApiKeySessionWrapper.propTypes = {\n  apiKey: PropTypes.string.isRequired,\n  renderChildren: PropTypes.func,\n};\n\nApiKeySessionWrapper.defaultProps = {\n  renderChildren: () => null,\n};\n\nexport default function routeWithApiKeySession({ render, getApiKey, ...rest }) {\n  return {\n    ...rest,\n    render: currentRoute => (\n      <ApiKeySessionWrapper apiKey={getApiKey(currentRoute)} currentRoute={currentRoute} renderChildren={render} />\n    ),\n  };\n}\n"
  },
  {
    "path": "client/app/components/ApplicationArea/routeWithUserSession.tsx",
    "content": "import React, { useEffect, useState } from \"react\";\nimport ErrorBoundary, { ErrorBoundaryContext } from \"@redash/viz/lib/components/ErrorBoundary\";\nimport { Auth } from \"@/services/auth\";\nimport { policy } from \"@/services/policy\";\nimport { CurrentRoute } from \"@/services/routes\";\nimport organizationStatus from \"@/services/organizationStatus\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport ApplicationLayout from \"./ApplicationLayout\";\nimport ErrorMessage from \"./ErrorMessage\";\n\nexport type UserSessionWrapperRenderChildrenProps<P> = {\n  pageTitle?: string;\n  onError: (error: Error) => void;\n} & P;\n\nexport interface UserSessionWrapperProps<P> {\n  render: (props: UserSessionWrapperRenderChildrenProps<P>) => React.ReactNode;\n  currentRoute: CurrentRoute<P>;\n  bodyClass?: string;\n}\n\n// This wrapper modifies `route.render` function and instead of passing `currentRoute` passes an object\n// that contains:\n// - `currentRoute.routeParams`\n// - `pageTitle` field which is equal to `currentRoute.title`\n// - `onError` field which is a `handleError` method of nearest error boundary\n\nexport function UserSessionWrapper<P>({ bodyClass, currentRoute, render }: UserSessionWrapperProps<P>) {\n  const [isAuthenticated, setIsAuthenticated] = useState(!!Auth.isAuthenticated());\n  useEffect(() => {\n    let isCancelled = false;\n    Promise.all([Auth.requireSession(), organizationStatus.refresh(), policy.refresh()])\n      .then(() => {\n        if (!isCancelled) {\n          setIsAuthenticated(!!Auth.isAuthenticated());\n        }\n      })\n      .catch(() => {\n        if (!isCancelled) {\n          setIsAuthenticated(false);\n        }\n      });\n    return () => {\n      isCancelled = true;\n    };\n  }, []);\n\n  useEffect(() => {\n    if (bodyClass) {\n      document.body.classList.toggle(bodyClass, true);\n      return () => {\n        document.body.classList.toggle(bodyClass, false);\n      };\n    }\n  }, [bodyClass]);\n\n  if (!isAuthenticated) {\n    return null;\n  }\n\n  return (\n    <ApplicationLayout>\n      <React.Fragment key={currentRoute.key}>\n        {/* @ts-expect-error FIXME */}\n        <ErrorBoundary renderError={(error: Error) => <ErrorMessage error={error} />}>\n          <ErrorBoundaryContext.Consumer>\n            {(\n              {\n                handleError,\n              } /* : { handleError: UserSessionWrapperRenderChildrenProps<P>[\"onError\"] } FIXME bring back type */\n            ) => render({ ...currentRoute.routeParams, pageTitle: currentRoute.title, onError: handleError })}\n          </ErrorBoundaryContext.Consumer>\n        </ErrorBoundary>\n      </React.Fragment>\n    </ApplicationLayout>\n  );\n}\n\nexport type RouteWithUserSessionOptions<P> = {\n  render: (props: UserSessionWrapperRenderChildrenProps<P>) => React.ReactNode;\n  bodyClass?: string;\n  title: string;\n  path: string;\n};\n\nexport const UserSessionWrapperDynamicComponentName = \"UserSessionWrapper\";\n\nexport default function routeWithUserSession<P extends {} = {}>({\n  render: originalRender,\n  bodyClass,\n  ...rest\n}: RouteWithUserSessionOptions<P>) {\n  return {\n    ...rest,\n    render: (currentRoute: CurrentRoute<P>) => {\n      const props = {\n        render: originalRender,\n        bodyClass,\n        currentRoute,\n      };\n      return (\n        <DynamicComponent\n          {...props}\n          name={UserSessionWrapperDynamicComponentName}\n          fallback={<UserSessionWrapper {...props} />}\n        />\n      );\n    },\n  };\n}\n"
  },
  {
    "path": "client/app/components/BeaconConsent.jsx",
    "content": "import React, { useState } from \"react\";\nimport Card from \"antd/lib/card\";\nimport Button from \"antd/lib/button\";\nimport Typography from \"antd/lib/typography\";\nimport { clientConfig } from \"@/services/auth\";\nimport Link from \"@/components/Link\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport OrgSettings from \"@/services/organizationSettings\";\n\nconst Text = Typography.Text;\n\nfunction BeaconConsent() {\n  const [hide, setHide] = useState(false);\n\n  if (!clientConfig.showBeaconConsentMessage || hide) {\n    return null;\n  }\n\n  const hideConsentCard = () => {\n    clientConfig.showBeaconConsentMessage = false;\n    setHide(true);\n  };\n\n  const confirmConsent = (confirm) => {\n    let message = \"🙏 Thank you.\";\n\n    if (!confirm) {\n      message = \"Settings Saved.\";\n    }\n\n    OrgSettings.save({ beacon_consent: confirm }, message)\n      // .then(() => {\n      //   // const settings = get(response, 'settings');\n      //   // this.setState({ settings, formValues: { ...settings } });\n      // })\n      .finally(hideConsentCard);\n  };\n\n  return (\n    <DynamicComponent name=\"BeaconConsent\">\n      <div className=\"m-t-10 tiled\">\n        <Card\n          title={\n            <>\n              Would you be ok with sharing anonymous usage data with the Redash team?{\" \"}\n              <HelpTrigger type=\"USAGE_DATA_SHARING\" />\n            </>\n          }\n          bordered={false}\n        >\n          <Text>Help Redash improve by automatically sending anonymous usage data:</Text>\n          <div className=\"m-t-5\">\n            <ul>\n              <li> Number of users, queries, dashboards, alerts, widgets and visualizations.</li>\n              <li> Types of data sources, alert destinations and visualizations.</li>\n            </ul>\n          </div>\n          <Text>All data is aggregated and will never include any sensitive or private data.</Text>\n          <div className=\"m-t-5\">\n            <Button type=\"primary\" className=\"m-r-5\" onClick={() => confirmConsent(true)}>\n              Yes\n            </Button>\n            <Button type=\"default\" onClick={() => confirmConsent(false)}>\n              No\n            </Button>\n          </div>\n          <div className=\"m-t-15\">\n            <Text type=\"secondary\">\n              You can change this setting anytime from the <Link href=\"settings/general\">Settings</Link> page.\n            </Text>\n          </div>\n        </Card>\n      </div>\n    </DynamicComponent>\n  );\n}\n\nexport default BeaconConsent;\n"
  },
  {
    "path": "client/app/components/BigMessage.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { useUniqueId } from \"@/lib/hooks/useUniqueId\";\nimport cx from \"classnames\";\n\nfunction BigMessage({ message, icon, children, className }) {\n  const messageId = useUniqueId(\"bm-message\");\n  return (\n    <div\n      className={\"big-message p-15 text-center \" + className}\n      role=\"status\"\n      aria-live=\"assertive\"\n      aria-relevant=\"additions removals\">\n      <h3 className=\"m-t-0 m-b-0\" aria-labelledby={messageId}>\n        <i className={cx(\"fa\", icon)} aria-hidden=\"true\" />\n      </h3>\n      <br />\n      <span id={messageId}>{message}</span>\n      {children}\n    </div>\n  );\n}\n\nBigMessage.propTypes = {\n  message: PropTypes.string,\n  icon: PropTypes.string.isRequired,\n  children: PropTypes.node,\n  className: PropTypes.string,\n};\n\nBigMessage.defaultProps = {\n  message: \"\",\n  children: null,\n  className: \"tiled bg-white\",\n};\n\nexport default BigMessage;\n"
  },
  {
    "path": "client/app/components/CodeBlock.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport Tooltip from \"@/components/Tooltip\";\nimport CopyOutlinedIcon from \"@ant-design/icons/CopyOutlined\";\nimport \"./CodeBlock.less\";\n\nexport default class CodeBlock extends React.Component {\n  static propTypes = {\n    copyable: PropTypes.bool,\n    children: PropTypes.node,\n  };\n\n  static defaultProps = {\n    copyable: false,\n    children: null,\n  };\n\n  state = { copied: null };\n\n  constructor(props) {\n    super(props);\n    this.ref = React.createRef();\n    this.copyFeatureEnabled = props.copyable && document.queryCommandSupported(\"copy\");\n    this.resetCopyState = null;\n  }\n\n  componentWillUnmount() {\n    if (this.resetCopyState) {\n      clearTimeout(this.resetCopyState);\n    }\n  }\n\n  copy = () => {\n    // select text\n    window.getSelection().selectAllChildren(this.ref.current);\n\n    // copy\n    try {\n      const success = document.execCommand(\"copy\");\n      if (!success) {\n        throw new Error();\n      }\n      this.setState({ copied: \"Copied!\" });\n    } catch (err) {\n      this.setState({\n        copied: \"Copy failed\",\n      });\n    }\n\n    // reset selection\n    window.getSelection().removeAllRanges();\n\n    // reset tooltip\n    this.resetCopyState = setTimeout(() => this.setState({ copied: null }), 2000);\n  };\n\n  render() {\n    const { copyable, children, ...props } = this.props;\n\n    const copyButton = (\n      <Tooltip title={this.state.copied || \"Copy\"}>\n        <Button icon={<CopyOutlinedIcon />} type=\"dashed\" size=\"small\" onClick={this.copy} />\n      </Tooltip>\n    );\n\n    return (\n      <div className=\"code-block\">\n        <code {...props} ref={this.ref}>\n          {children}\n        </code>\n        {this.copyFeatureEnabled && copyButton}\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/components/CodeBlock.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\";\n\n.code-block {\n  background: rgba(0, 0, 0, 0.06);\n  border: 1px solid rgba(0, 0, 0, 0.06);\n  border-radius: 2px;\n  padding: 3px 27px 3px 3px;\n  position: relative;\n  min-height: 32px;\n\n  code {\n    padding: 0;\n    font-size: 85%;\n  }\n\n  .@{btn-prefix-cls} {\n    position: absolute;\n    right: 3px;\n    bottom: 3px;\n    padding-left: 3px !important;\n    padding-right: 3px !important;\n  }\n}\n"
  },
  {
    "path": "client/app/components/Collapse.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport AntCollapse from \"antd/lib/collapse\";\n\nexport default function Collapse({ collapsed, children, className, ...props }) {\n  return (\n    <AntCollapse\n      {...props}\n      activeKey={collapsed ? null : \"content\"}\n      className={cx(className, \"ant-collapse-headerless\")}>\n      <AntCollapse.Panel key=\"content\" header=\"\">\n        {children}\n      </AntCollapse.Panel>\n    </AntCollapse>\n  );\n}\n\nCollapse.propTypes = {\n  collapsed: PropTypes.bool,\n  children: PropTypes.node,\n  className: PropTypes.string,\n};\n\nCollapse.defaultProps = {\n  collapsed: true,\n  children: null,\n  className: \"\",\n};\n"
  },
  {
    "path": "client/app/components/CreateSourceDialog.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { isEmpty, toUpper, includes, get, uniqueId } from \"lodash\";\nimport Button from \"antd/lib/button\";\nimport List from \"antd/lib/list\";\nimport Modal from \"antd/lib/modal\";\nimport Input from \"antd/lib/input\";\nimport Steps from \"antd/lib/steps\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport Link from \"@/components/Link\";\nimport { PreviewCard } from \"@/components/PreviewCard\";\nimport EmptyState from \"@/components/items-list/components/EmptyState\";\nimport DynamicForm from \"@/components/dynamic-form/DynamicForm\";\nimport helper from \"@/components/dynamic-form/dynamicFormHelper\";\nimport HelpTrigger, { TYPES as HELP_TRIGGER_TYPES } from \"@/components/HelpTrigger\";\n\nconst { Step } = Steps;\nconst { Search } = Input;\n\nconst StepEnum = {\n  SELECT_TYPE: 0,\n  CONFIGURE_IT: 1,\n  DONE: 2,\n};\n\nclass CreateSourceDialog extends React.Component {\n  static propTypes = {\n    dialog: DialogPropType.isRequired,\n    types: PropTypes.arrayOf(PropTypes.object),\n    sourceType: PropTypes.string.isRequired,\n    imageFolder: PropTypes.string.isRequired,\n    helpTriggerPrefix: PropTypes.string,\n    onCreate: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    types: [],\n    helpTriggerPrefix: null,\n  };\n\n  state = {\n    searchText: \"\",\n    selectedType: null,\n    savingSource: false,\n    currentStep: StepEnum.SELECT_TYPE,\n  };\n\n  formId = uniqueId(\"sourceForm\");\n\n  selectType = selectedType => {\n    this.setState({ selectedType, currentStep: StepEnum.CONFIGURE_IT });\n  };\n\n  resetType = () => {\n    if (this.state.currentStep === StepEnum.CONFIGURE_IT) {\n      this.setState({ searchText: \"\", selectedType: null, currentStep: StepEnum.SELECT_TYPE });\n    }\n  };\n\n  createSource = (values, successCallback, errorCallback) => {\n    const { selectedType, savingSource } = this.state;\n    if (!savingSource) {\n      this.setState({ savingSource: true, currentStep: StepEnum.DONE });\n      this.props\n        .onCreate(selectedType, values)\n        .then(data => {\n          successCallback(\"Saved.\");\n          this.props.dialog.close({ success: true, data });\n        })\n        .catch(error => {\n          this.setState({ savingSource: false, currentStep: StepEnum.CONFIGURE_IT });\n          errorCallback(get(error, \"response.data.message\", \"Failed saving.\"));\n        });\n    }\n  };\n\n  renderTypeSelector() {\n    const { types } = this.props;\n    const { searchText } = this.state;\n    const filteredTypes = types.filter(\n      type => isEmpty(searchText) || includes(type.name.toLowerCase(), searchText.toLowerCase())\n    );\n    return (\n      <div className=\"m-t-10\">\n        <Search\n          placeholder=\"Search...\"\n          aria-label=\"Search\"\n          onChange={e => this.setState({ searchText: e.target.value })}\n          autoFocus\n          data-test=\"SearchSource\"\n        />\n        <div className=\"scrollbox p-5 m-t-10\" style={{ minHeight: \"30vh\", maxHeight: \"40vh\" }}>\n          {isEmpty(filteredTypes) ? (\n            <EmptyState className=\"\" />\n          ) : (\n            <List size=\"small\" dataSource={filteredTypes} renderItem={item => this.renderItem(item)} />\n          )}\n        </div>\n      </div>\n    );\n  }\n\n  renderForm() {\n    const { imageFolder, helpTriggerPrefix } = this.props;\n    const { selectedType } = this.state;\n    const fields = helper.getFields(selectedType);\n    const helpTriggerType = `${helpTriggerPrefix}${toUpper(selectedType.type)}`;\n    return (\n      <div>\n        <div className=\"d-flex justify-content-center align-items-center\">\n          <img className=\"p-5\" src={`${imageFolder}/${selectedType.type}.png`} alt={selectedType.name} width=\"48\" />\n          <h4 className=\"m-0\">{selectedType.name}</h4>\n        </div>\n        <div className=\"text-right\">\n          {HELP_TRIGGER_TYPES[helpTriggerType] && (\n            <HelpTrigger className=\"f-13\" type={helpTriggerType}>\n              Setup Instructions <i className=\"fa fa-question-circle\" aria-hidden=\"true\" />\n              <span className=\"sr-only\">(help)</span>\n            </HelpTrigger>\n          )}\n        </div>\n        <DynamicForm id={this.formId} fields={fields} onSubmit={this.createSource} feedbackIcons hideSubmitButton />\n        {selectedType.type === \"databricks\" && (\n          <small>\n            By using the Databricks Data Source you agree to the Databricks JDBC/ODBC{\" \"}\n            <Link href=\"https://databricks.com/spark/odbc-driver-download\" target=\"_blank\" rel=\"noopener noreferrer\">\n              Driver Download Terms and Conditions\n            </Link>\n            .\n          </small>\n        )}\n      </div>\n    );\n  }\n\n  renderItem(item) {\n    const { imageFolder } = this.props;\n    return (\n      <List.Item className=\"p-l-10 p-r-10 clickable\" onClick={() => this.selectType(item)}>\n        <PreviewCard\n          title={item.name}\n          imageUrl={`${imageFolder}/${item.type}.png`}\n          roundedImage={false}\n          data-test=\"PreviewItem\"\n          data-test-type={item.type}>\n          <i className=\"fa fa-angle-double-right\" aria-hidden=\"true\" />\n        </PreviewCard>\n      </List.Item>\n    );\n  }\n\n  render() {\n    const { currentStep, savingSource } = this.state;\n    const { dialog, sourceType } = this.props;\n    return (\n      <Modal\n        {...dialog.props}\n        title={`Create a New ${sourceType}`}\n        footer={\n          currentStep === StepEnum.SELECT_TYPE\n            ? [\n                <Button key=\"cancel\" onClick={() => dialog.dismiss()} data-test=\"CreateSourceCancelButton\">\n                  Cancel\n                </Button>,\n                <Button key=\"submit\" type=\"primary\" disabled>\n                  Create\n                </Button>,\n              ]\n            : [\n                <Button key=\"previous\" onClick={this.resetType}>\n                  Previous\n                </Button>,\n                <Button\n                  key=\"submit\"\n                  htmlType=\"submit\"\n                  form={this.formId}\n                  type=\"primary\"\n                  loading={savingSource}\n                  data-test=\"CreateSourceSaveButton\">\n                  Create\n                </Button>,\n              ]\n        }>\n        <div data-test=\"CreateSourceDialog\">\n          <Steps className=\"hidden-xs m-b-10\" size=\"small\" current={currentStep} progressDot>\n            {currentStep === StepEnum.CONFIGURE_IT ? (\n              <Step title={<a>Type Selection</a>} className=\"clickable\" onClick={this.resetType} />\n            ) : (\n              <Step title=\"Type Selection\" />\n            )}\n            <Step title=\"Configuration\" />\n            <Step title=\"Done\" />\n          </Steps>\n          {currentStep === StepEnum.SELECT_TYPE && this.renderTypeSelector()}\n          {currentStep !== StepEnum.SELECT_TYPE && this.renderForm()}\n        </div>\n      </Modal>\n    );\n  }\n}\n\nexport default wrapDialog(CreateSourceDialog);\n"
  },
  {
    "path": "client/app/components/DateInput.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport DatePicker from \"antd/lib/date-picker\";\nimport { clientConfig } from \"@/services/auth\";\nimport { Moment } from \"@/components/proptypes\";\n\nconst DateInput = React.forwardRef(({ defaultValue, value, onSelect, className, ...props }, ref) => {\n  const format = clientConfig.dateFormat || \"YYYY-MM-DD\";\n  const additionalAttributes = {};\n  if (defaultValue && defaultValue.isValid()) {\n    additionalAttributes.defaultValue = defaultValue;\n  }\n  if (value === null || (value && value.isValid())) {\n    additionalAttributes.value = value;\n  }\n  return (\n    <DatePicker\n      ref={ref}\n      className={className}\n      {...additionalAttributes}\n      format={format}\n      placeholder=\"Select Date\"\n      onChange={onSelect}\n      {...props}\n    />\n  );\n});\n\nDateInput.propTypes = {\n  defaultValue: Moment,\n  value: Moment,\n  onSelect: PropTypes.func,\n  className: PropTypes.string,\n};\n\nDateInput.defaultProps = {\n  defaultValue: null,\n  value: undefined,\n  onSelect: () => {},\n  className: \"\",\n};\n\nexport default DateInput;\n"
  },
  {
    "path": "client/app/components/DateRangeInput.jsx",
    "content": "import { isArray } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport DatePicker from \"antd/lib/date-picker\";\nimport { clientConfig } from \"@/services/auth\";\nimport { Moment } from \"@/components/proptypes\";\n\nconst { RangePicker } = DatePicker;\n\nconst DateRangeInput = React.forwardRef(({ defaultValue, value, onSelect, className, ...props }, ref) => {\n  const format = clientConfig.dateFormat || \"YYYY-MM-DD\";\n  const additionalAttributes = {};\n  if (isArray(defaultValue) && defaultValue[0].isValid() && defaultValue[1].isValid()) {\n    additionalAttributes.defaultValue = defaultValue;\n  }\n  if (value === null || (isArray(value) && value[0].isValid() && value[1].isValid())) {\n    additionalAttributes.value = value;\n  }\n  return (\n    <RangePicker\n      ref={ref}\n      className={className}\n      {...additionalAttributes}\n      format={format}\n      onChange={onSelect}\n      {...props}\n    />\n  );\n});\n\nDateRangeInput.propTypes = {\n  defaultValue: PropTypes.arrayOf(Moment),\n  value: PropTypes.arrayOf(Moment),\n  onSelect: PropTypes.func,\n  className: PropTypes.string,\n};\n\nDateRangeInput.defaultProps = {\n  defaultValue: null,\n  value: undefined,\n  onSelect: () => {},\n  className: \"\",\n};\n\nexport default DateRangeInput;\n"
  },
  {
    "path": "client/app/components/DateTimeInput.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport DatePicker from \"antd/lib/date-picker\";\nimport { clientConfig } from \"@/services/auth\";\nimport { Moment } from \"@/components/proptypes\";\n\nconst DateTimeInput = React.forwardRef(({ defaultValue, value, withSeconds, onSelect, className, ...props }, ref) => {\n  const format = (clientConfig.dateFormat || \"YYYY-MM-DD\") + (withSeconds ? \" HH:mm:ss\" : \" HH:mm\");\n  const additionalAttributes = {};\n  if (defaultValue && defaultValue.isValid()) {\n    additionalAttributes.defaultValue = defaultValue;\n  }\n  if (value === null || (value && value.isValid())) {\n    additionalAttributes.value = value;\n  }\n  return (\n    <DatePicker\n      ref={ref}\n      className={className}\n      showTime\n      {...additionalAttributes}\n      format={format}\n      placeholder=\"Select Date and Time\"\n      onChange={onSelect}\n      {...props}\n    />\n  );\n});\n\nDateTimeInput.propTypes = {\n  defaultValue: Moment,\n  value: Moment,\n  withSeconds: PropTypes.bool,\n  onSelect: PropTypes.func,\n  className: PropTypes.string,\n};\n\nDateTimeInput.defaultProps = {\n  defaultValue: null,\n  value: undefined,\n  withSeconds: false,\n  onSelect: () => {},\n  className: \"\",\n};\n\nexport default DateTimeInput;\n"
  },
  {
    "path": "client/app/components/DateTimeRangeInput.jsx",
    "content": "import { isArray } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport DatePicker from \"antd/lib/date-picker\";\nimport { clientConfig } from \"@/services/auth\";\nimport { Moment } from \"@/components/proptypes\";\n\nconst { RangePicker } = DatePicker;\n\nconst DateTimeRangeInput = React.forwardRef(\n  ({ defaultValue, value, withSeconds, onSelect, className, ...props }, ref) => {\n    const format = (clientConfig.dateFormat || \"YYYY-MM-DD\") + (withSeconds ? \" HH:mm:ss\" : \" HH:mm\");\n    const additionalAttributes = {};\n    if (isArray(defaultValue) && defaultValue[0].isValid() && defaultValue[1].isValid()) {\n      additionalAttributes.defaultValue = defaultValue;\n    }\n    if (value === null || (isArray(value) && value[0].isValid() && value[1].isValid())) {\n      additionalAttributes.value = value;\n    }\n    return (\n      <RangePicker\n        ref={ref}\n        className={className}\n        showTime\n        {...additionalAttributes}\n        format={format}\n        onChange={onSelect}\n        {...props}\n      />\n    );\n  }\n);\n\nDateTimeRangeInput.propTypes = {\n  defaultValue: PropTypes.arrayOf(Moment),\n  value: PropTypes.arrayOf(Moment),\n  withSeconds: PropTypes.bool,\n  onSelect: PropTypes.func,\n  className: PropTypes.string,\n};\n\nDateTimeRangeInput.defaultProps = {\n  defaultValue: null,\n  value: undefined,\n  withSeconds: false,\n  onSelect: () => {},\n  className: \"\",\n};\n\nexport default DateTimeRangeInput;\n"
  },
  {
    "path": "client/app/components/DialogWrapper.d.ts",
    "content": "import { ModalProps } from \"antd/lib/modal/Modal\";\n\nexport interface DialogProps<ROk, RCancel> {\n  props: ModalProps;\n  close: (result: ROk) => void;\n  dismiss: (result: RCancel) => void;\n}\n\nexport type DialogWrapperChildProps<ROk, RCancel> = {\n  dialog: DialogProps<ROk, RCancel>;\n};\n\nexport type DialogComponentType<ROk = void, P = {}, RCancel = void> = React.ComponentType<\n  DialogWrapperChildProps<ROk, RCancel> & P\n>;\n\nexport function wrap<ROk = void, P = {}, RCancel = void>(\n  DialogComponent: DialogComponentType<ROk, P, RCancel>\n): {\n  Component: DialogComponentType<ROk, P, RCancel>;\n  showModal: (\n    props?: P\n  ) => {\n    update: (props: P) => void;\n    onClose: (handler: (result: ROk) => Promise<void> | void) => void;\n    onDismiss: (handler: (result: RCancel) => Promise<void> | void) => void;\n    close: (result: ROk) => void;\n    dismiss: (result: RCancel) => void;\n  };\n};\n"
  },
  {
    "path": "client/app/components/DialogWrapper.jsx",
    "content": "import { isFunction } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport ReactDOM from \"react-dom\";\n\n/**\n  Wrapper for dialogs based on Ant's <Modal> component.\n\n\n  Using wrapped dialogs\n  =====================\n\n  Wrapped component is an object with two fields:\n\n    {\n      showModal: (dialogProps) => object({\n          close: (result) => void,\n          dismiss: (reason) => void,\n          onClose: (handler) => this,\n          onDismiss: (handler) => this,\n        }),\n      Component: React.Component, // wrapped dialog component\n    }\n\n  To open dialog, use `showModal` method; optionally you can pass additional properties that\n  will be expanded on wrapped component:\n\n    const dialog = SomeWrappedDialog.showModal()\n\n    const dialog = SomeWrappedDialog.showModal({ greeting: 'Hello' })\n\n  To get result of modal, use `onClose`/`onDismiss` setters:\n\n    dialog\n      .onClose(result => { ... }) // pressed OK button or used `close` method\n      .onDismiss(result => { ... }) // pressed Cancel button or used `dismiss` method\n\n  If `onClose`/`onDismiss` returns a promise - dialog wrapper will stop handling further close/dismiss\n  requests and will show loader on a corresponding button until that promise is fulfilled (either resolved or\n  rejected). If that promise will be rejected - dialog close/dismiss will be abandoned. Use promise returned\n  from `close`/`dismiss` methods to handle errors (if needed).\n\n  Also, dialog has `close` and `dismiss` methods that allows to close dialog by caller. Passed arguments\n  will be passed to a corresponding handler. Both methods will return the promise returned from `onClose` and\n `onDismiss` callbacks. `update` method allows to pass new properties to dialog.\n\n\n  Creating a dialog\n  ================\n\n  1. Add imports:\n\n    import { wrap as wrapDialog, DialogPropType } from 'path/to/DialogWrapper';\n\n  2. define a `dialog` property on your component:\n\n    propTypes = {\n      dialog: DialogPropType.isRequired,\n    };\n\n  `dialog` property is an object:\n\n    {\n      props: object, // properties for <Modal> component;\n      close: (result) => void, // method to confirm dialog; `result` will be returned to caller\n      dismiss: (reason) => void, // method to reject dialog; `reason` will be returned to caller\n    }\n\n  3. expand additional properties on <Modal> component:\n\n    render() {\n      const { dialog } = this.props;\n      return (\n        <Modal {...dialog.props}>\n      );\n    }\n\n  4. wrap your component and export it:\n\n    export default wrapDialog(YourComponent).\n\n  Your component is ready to use. Wrapper will manage <Modal>'s visibility and events.\n  If you want to override behavior of `onOk`/`onCancel` - don't forget to close dialog:\n\n    customOkHandler() {\n      this.saveData().then(() => {\n         this.props.dialog.close({ success: true }); // or dismiss();\n      });\n    }\n\n    render() {\n      const { dialog } = this.props;\n        return (\n          <Modal {...dialog.props} onOk={() => this.customOkHandler()}>\n        );\n    }\n*/\n\nexport const DialogPropType = PropTypes.shape({\n  props: PropTypes.shape({\n    visible: PropTypes.bool,\n    onOk: PropTypes.func,\n    onCancel: PropTypes.func,\n    afterClose: PropTypes.func,\n  }).isRequired,\n  close: PropTypes.func.isRequired,\n  dismiss: PropTypes.func.isRequired,\n});\n\nfunction openDialog(DialogComponent, props) {\n  const dialog = {\n    props: {\n      visible: true,\n      okButtonProps: {},\n      cancelButtonProps: {},\n      onOk: () => {},\n      onCancel: () => {},\n      afterClose: () => {},\n    },\n    close: () => {},\n    dismiss: () => {},\n  };\n\n  let pendingCloseTask = null;\n\n  const handlers = {\n    onClose: () => {},\n    onDismiss: () => {},\n  };\n\n  const container = document.createElement(\"div\");\n  document.body.appendChild(container);\n\n  function render() {\n    ReactDOM.render(<DialogComponent {...props} dialog={dialog} />, container);\n  }\n\n  function destroyDialog() {\n    // Allow calling chain to roll up, and then destroy component\n    setTimeout(() => {\n      ReactDOM.unmountComponentAtNode(container);\n      document.body.removeChild(container);\n    }, 10);\n  }\n\n  function processDialogClose(result, setAdditionalDialogProps) {\n    dialog.props.okButtonProps = { disabled: true };\n    dialog.props.cancelButtonProps = { disabled: true };\n    setAdditionalDialogProps();\n    render();\n\n    return Promise.resolve(result)\n      .then(() => {\n        dialog.props.visible = false;\n      })\n      .finally(() => {\n        dialog.props.okButtonProps = {};\n        dialog.props.cancelButtonProps = {};\n        render();\n      });\n  }\n\n  function closeDialog(result) {\n    if (!pendingCloseTask) {\n      pendingCloseTask = processDialogClose(handlers.onClose(result), () => {\n        dialog.props.okButtonProps.loading = true;\n      }).finally(() => {\n        pendingCloseTask = null;\n      });\n    }\n    return pendingCloseTask;\n  }\n\n  function dismissDialog(result) {\n    if (!pendingCloseTask) {\n      pendingCloseTask = processDialogClose(handlers.onDismiss(result), () => {\n        dialog.props.cancelButtonProps.loading = true;\n      }).finally(() => {\n        pendingCloseTask = null;\n      });\n    }\n    return pendingCloseTask;\n  }\n\n  dialog.props.onOk = closeDialog;\n  dialog.props.onCancel = dismissDialog;\n  dialog.props.afterClose = destroyDialog;\n  dialog.close = closeDialog;\n  dialog.dismiss = dismissDialog;\n\n  const result = {\n    close: closeDialog,\n    dismiss: dismissDialog,\n    update: newProps => {\n      props = { ...props, ...newProps };\n      render();\n    },\n    onClose: handler => {\n      if (isFunction(handler)) {\n        handlers.onClose = handler;\n      }\n      return result;\n    },\n    onDismiss: handler => {\n      if (isFunction(handler)) {\n        handlers.onDismiss = handler;\n      }\n      return result;\n    },\n  };\n\n  render(); // show it only when all structures initialized to avoid unnecessary re-rendering\n\n  return result;\n}\n\nexport function wrap(DialogComponent) {\n  return {\n    Component: DialogComponent,\n    showModal: props => openDialog(DialogComponent, props),\n  };\n}\n\nexport default {\n  DialogPropType,\n  wrap,\n};\n"
  },
  {
    "path": "client/app/components/DynamicComponent.jsx",
    "content": "import { isFunction, isString, isUndefined } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nconst componentsRegistry = new Map();\nconst activeInstances = new Set();\n\nexport function registerComponent(name, component) {\n  if (isString(name) && name !== \"\") {\n    componentsRegistry.set(name, isFunction(component) ? component : null);\n    // Refresh active DynamicComponent instances which use this component\n    activeInstances.forEach(dynamicComponent => {\n      if (dynamicComponent.props.name === name) {\n        dynamicComponent.forceUpdate();\n      }\n    });\n  }\n}\n\nexport function unregisterComponent(name) {\n  registerComponent(name, null);\n}\n\nexport default class DynamicComponent extends React.Component {\n  static propTypes = {\n    name: PropTypes.string.isRequired,\n    fallback: PropTypes.node,\n    children: PropTypes.node,\n  };\n\n  static defaultProps = {\n    children: null,\n  };\n\n  componentDidMount() {\n    activeInstances.add(this);\n  }\n\n  componentWillUnmount() {\n    activeInstances.delete(this);\n  }\n\n  render() {\n    const { name, children, fallback, ...props } = this.props;\n    const RealComponent = componentsRegistry.get(name);\n    if (!RealComponent) {\n      // return fallback if any, otherwise return children\n      return isUndefined(fallback) ? children : fallback;\n    }\n    return <RealComponent {...props}>{children}</RealComponent>;\n  }\n}\n"
  },
  {
    "path": "client/app/components/EditInPlace.jsx",
    "content": "import { trim } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport Input from \"antd/lib/input\";\n\nexport default class EditInPlace extends React.Component {\n  static propTypes = {\n    ignoreBlanks: PropTypes.bool,\n    isEditable: PropTypes.bool,\n    placeholder: PropTypes.string,\n    value: PropTypes.string,\n    onDone: PropTypes.func.isRequired,\n    onStopEditing: PropTypes.func,\n    multiline: PropTypes.bool,\n    editorProps: PropTypes.object,\n    defaultEditing: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    ignoreBlanks: false,\n    isEditable: true,\n    placeholder: \"\",\n    value: \"\",\n    onStopEditing: () => {},\n    multiline: false,\n    editorProps: {},\n    defaultEditing: false,\n  };\n\n  constructor(props) {\n    super(props);\n    this.state = {\n      editing: props.defaultEditing,\n    };\n  }\n\n  componentDidUpdate(_, prevState) {\n    if (!this.state.editing && prevState.editing) {\n      this.props.onStopEditing();\n    }\n  }\n\n  startEditing = () => {\n    if (this.props.isEditable) {\n      this.setState({ editing: true });\n    }\n  };\n\n  stopEditing = currentValue => {\n    const newValue = trim(currentValue);\n    const ignorableBlank = this.props.ignoreBlanks && newValue === \"\";\n    if (!ignorableBlank && newValue !== this.props.value) {\n      this.props.onDone(newValue);\n    }\n    this.setState({ editing: false });\n  };\n\n  handleKeyDown = event => {\n    if (event.keyCode === 13 && !event.shiftKey) {\n      event.preventDefault();\n      this.stopEditing(event.target.value);\n    } else if (event.keyCode === 27) {\n      this.setState({ editing: false });\n    }\n  };\n\n  renderNormal = () =>\n    this.props.value ? (\n      <span\n        role=\"presentation\"\n        onFocus={this.startEditing}\n        onClick={this.startEditing}\n        className={this.props.isEditable ? \"editable\" : \"\"}>\n        {this.props.value}\n      </span>\n    ) : (\n      <a className=\"clickable\" onClick={this.startEditing}>\n        {this.props.placeholder}\n      </a>\n    );\n\n  renderEdit = () => {\n    const { multiline, value, editorProps } = this.props;\n    const InputComponent = multiline ? Input.TextArea : Input;\n    return (\n      <InputComponent\n        defaultValue={value}\n        aria-label=\"Editing\"\n        onBlur={e => this.stopEditing(e.target.value)}\n        onKeyDown={this.handleKeyDown}\n        autoFocus\n        {...editorProps}\n      />\n    );\n  };\n\n  render() {\n    return (\n      <span className={cx(\"edit-in-place\", { active: this.state.editing }, this.props.className)}>\n        {this.state.editing ? this.renderEdit() : this.renderNormal()}\n      </span>\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/components/EditParameterSettingsDialog.jsx",
    "content": "import { includes, words, capitalize, clone, isNull } from \"lodash\";\nimport React, { useState, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Checkbox from \"antd/lib/checkbox\";\nimport Modal from \"antd/lib/modal\";\nimport Form from \"antd/lib/form\";\nimport Button from \"antd/lib/button\";\nimport Select from \"antd/lib/select\";\nimport Input from \"antd/lib/input\";\nimport Divider from \"antd/lib/divider\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport QuerySelector from \"@/components/QuerySelector\";\nimport { Query } from \"@/services/query\";\nimport { useUniqueId } from \"@/lib/hooks/useUniqueId\";\nimport \"./EditParameterSettingsDialog.less\";\n\nconst { Option } = Select;\nconst formItemProps = { labelCol: { span: 6 }, wrapperCol: { span: 16 } };\n\nfunction getDefaultTitle(text) {\n  return capitalize(words(text).join(\" \")); // humanize\n}\n\nfunction isTypeDateRange(type) {\n  return /-range/.test(type);\n}\n\nfunction joinExampleList(multiValuesOptions) {\n  const { prefix, suffix } = multiValuesOptions;\n  return [\"value1\", \"value2\", \"value3\"].map((value) => `${prefix}${value}${suffix}`).join(\",\");\n}\n\nfunction NameInput({ name, type, onChange, existingNames, setValidation }) {\n  let helpText = \"\";\n  let validateStatus = \"\";\n\n  if (!name) {\n    helpText = \"Choose a keyword for this parameter\";\n    setValidation(false);\n  } else if (includes(existingNames, name)) {\n    helpText = \"Parameter with this name already exists\";\n    setValidation(false);\n    validateStatus = \"error\";\n  } else {\n    if (isTypeDateRange(type)) {\n      helpText = (\n        <React.Fragment>\n          Appears in query as{\" \"}\n          <code style={{ display: \"inline-block\", color: \"inherit\" }}>{`{{${name}.start}} {{${name}.end}}`}</code>\n        </React.Fragment>\n      );\n    }\n    setValidation(true);\n  }\n\n  return (\n    <Form.Item required label=\"Keyword\" help={helpText} validateStatus={validateStatus} {...formItemProps}>\n      <Input onChange={(e) => onChange(e.target.value)} autoFocus />\n    </Form.Item>\n  );\n}\n\nNameInput.propTypes = {\n  name: PropTypes.string.isRequired,\n  onChange: PropTypes.func.isRequired,\n  existingNames: PropTypes.arrayOf(PropTypes.string).isRequired,\n  setValidation: PropTypes.func.isRequired,\n  type: PropTypes.string.isRequired,\n};\n\nfunction EditParameterSettingsDialog(props) {\n  const [param, setParam] = useState(clone(props.parameter));\n  const [isNameValid, setIsNameValid] = useState(true);\n  const [initialQuery, setInitialQuery] = useState();\n  const [userInput, setUserInput] = useState(param.regex || \"\");\n  const [isValidRegex, setIsValidRegex] = useState(true);\n\n  const isNew = !props.parameter.name;\n\n  // fetch query by id\n  useEffect(() => {\n    const queryId = props.parameter.queryId;\n    if (queryId) {\n      Query.get({ id: queryId }).then(setInitialQuery);\n    }\n  }, [props.parameter.queryId]);\n\n  function isFulfilled() {\n    // name\n    if (!isNameValid) {\n      return false;\n    }\n\n    // title\n    if (param.title === \"\") {\n      return false;\n    }\n\n    // query\n    if (param.type === \"query\" && !param.queryId) {\n      return false;\n    }\n\n    return true;\n  }\n\n  function onConfirm() {\n    // update title to default\n    if (!param.title) {\n      // forced to do this cause param won't update in time for save\n      param.title = getDefaultTitle(param.name);\n      setParam(param);\n    }\n\n    props.dialog.close(param);\n  }\n\n  const paramFormId = useUniqueId(\"paramForm\");\n\n  const handleRegexChange = (e) => {\n    setUserInput(e.target.value);\n    try {\n      new RegExp(e.target.value);\n      setParam({ ...param, regex: e.target.value });\n      setIsValidRegex(true);\n    } catch (error) {\n      setIsValidRegex(false);\n    }\n  };\n\n  return (\n    <Modal\n      {...props.dialog.props}\n      title={isNew ? \"Add Parameter\" : param.name}\n      width={600}\n      footer={[\n        <Button key=\"cancel\" onClick={props.dialog.dismiss}>\n          Cancel\n        </Button>,\n        <Button\n          key=\"submit\"\n          htmlType=\"submit\"\n          disabled={!isFulfilled()}\n          type=\"primary\"\n          form={paramFormId}\n          data-test=\"SaveParameterSettings\"\n        >\n          {isNew ? \"Add Parameter\" : \"OK\"}\n        </Button>,\n      ]}\n    >\n      <Form layout=\"horizontal\" onFinish={onConfirm} id={paramFormId}>\n        {isNew && (\n          <NameInput\n            name={param.name}\n            onChange={(name) => setParam({ ...param, name })}\n            setValidation={setIsNameValid}\n            existingNames={props.existingParams}\n            type={param.type}\n          />\n        )}\n        <Form.Item required label=\"Title\" {...formItemProps}>\n          <Input\n            value={isNull(param.title) ? getDefaultTitle(param.name) : param.title}\n            onChange={(e) => setParam({ ...param, title: e.target.value })}\n            data-test=\"ParameterTitleInput\"\n          />\n        </Form.Item>\n        <Form.Item label=\"Type\" {...formItemProps}>\n          <Select value={param.type} onChange={(type) => setParam({ ...param, type })} data-test=\"ParameterTypeSelect\">\n            <Option value=\"text\" data-test=\"TextParameterTypeOption\">\n              Text\n            </Option>\n            <Option value=\"text-pattern\">Text Pattern</Option>\n            <Option value=\"number\" data-test=\"NumberParameterTypeOption\">\n              Number\n            </Option>\n            <Option value=\"enum\">Dropdown List</Option>\n            <Option value=\"query\">Query Based Dropdown List</Option>\n            <Option disabled key=\"dv1\">\n              <Divider className=\"select-option-divider\" />\n            </Option>\n            <Option value=\"date\" data-test=\"DateParameterTypeOption\">\n              Date\n            </Option>\n            <Option value=\"datetime-local\" data-test=\"DateTimeParameterTypeOption\">\n              Date and Time\n            </Option>\n            <Option value=\"datetime-with-seconds\">Date and Time (with seconds)</Option>\n            <Option disabled key=\"dv2\">\n              <Divider className=\"select-option-divider\" />\n            </Option>\n            <Option value=\"date-range\" data-test=\"DateRangeParameterTypeOption\">\n              Date Range\n            </Option>\n            <Option value=\"datetime-range\">Date and Time Range</Option>\n            <Option value=\"datetime-range-with-seconds\">Date and Time Range (with seconds)</Option>\n          </Select>\n        </Form.Item>\n        {param.type === \"text-pattern\" && (\n          <Form.Item\n            label=\"Regex\"\n            help={!isValidRegex ? \"Invalid Regex Pattern\" : \"Valid Regex Pattern\"}\n            {...formItemProps}\n          >\n            <Input\n              value={userInput}\n              onChange={handleRegexChange}\n              className={!isValidRegex ? \"input-error\" : \"\"}\n              data-test=\"RegexPatternInput\"\n            />\n          </Form.Item>\n        )}\n        {param.type === \"enum\" && (\n          <Form.Item label=\"Values\" help=\"Dropdown list values (newline delimited)\" {...formItemProps}>\n            <Input.TextArea\n              rows={3}\n              value={param.enumOptions}\n              onChange={(e) => setParam({ ...param, enumOptions: e.target.value })}\n            />\n          </Form.Item>\n        )}\n        {param.type === \"query\" && (\n          <Form.Item label=\"Query\" help=\"Select query to load dropdown values from\" {...formItemProps}>\n            <QuerySelector\n              selectedQuery={initialQuery}\n              onChange={(q) => setParam({ ...param, queryId: q && q.id })}\n              type=\"select\"\n            />\n          </Form.Item>\n        )}\n        {(param.type === \"enum\" || param.type === \"query\") && (\n          <Form.Item className=\"m-b-0\" label=\" \" colon={false} {...formItemProps}>\n            <Checkbox\n              defaultChecked={!!param.multiValuesOptions}\n              onChange={(e) =>\n                setParam({\n                  ...param,\n                  multiValuesOptions: e.target.checked\n                    ? {\n                        prefix: \"\",\n                        suffix: \"\",\n                        separator: \",\",\n                      }\n                    : null,\n                })\n              }\n              data-test=\"AllowMultipleValuesCheckbox\"\n            >\n              Allow multiple values\n            </Checkbox>\n          </Form.Item>\n        )}\n        {(param.type === \"enum\" || param.type === \"query\") && param.multiValuesOptions && (\n          <Form.Item\n            label=\"Quotation\"\n            help={\n              <React.Fragment>\n                Placed in query as: <code>{joinExampleList(param.multiValuesOptions)}</code>\n              </React.Fragment>\n            }\n            {...formItemProps}\n          >\n            <Select\n              value={param.multiValuesOptions.prefix}\n              onChange={(quoteOption) =>\n                setParam({\n                  ...param,\n                  multiValuesOptions: {\n                    ...param.multiValuesOptions,\n                    prefix: quoteOption,\n                    suffix: quoteOption,\n                  },\n                })\n              }\n              data-test=\"QuotationSelect\"\n            >\n              <Option value=\"\">None (default)</Option>\n              <Option value=\"'\">Single Quotation Mark</Option>\n              <Option value={'\"'} data-test=\"DoubleQuotationMarkOption\">\n                Double Quotation Mark\n              </Option>\n            </Select>\n          </Form.Item>\n        )}\n      </Form>\n    </Modal>\n  );\n}\n\nEditParameterSettingsDialog.propTypes = {\n  parameter: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  dialog: DialogPropType.isRequired,\n  existingParams: PropTypes.arrayOf(PropTypes.string),\n};\n\nEditParameterSettingsDialog.defaultProps = {\n  existingParams: [],\n};\n\nexport default wrapDialog(EditParameterSettingsDialog);\n"
  },
  {
    "path": "client/app/components/EditParameterSettingsDialog.less",
    "content": ".input-error {\n    border-color: red !important;\n}"
  },
  {
    "path": "client/app/components/EditVisualizationButton/QueryControlDropdown.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Dropdown from \"antd/lib/dropdown\";\nimport Menu from \"antd/lib/menu\";\nimport Button from \"antd/lib/button\";\nimport PlainButton from \"@/components/PlainButton\";\nimport { clientConfig } from \"@/services/auth\";\n\nimport PlusCircleFilledIcon from \"@ant-design/icons/PlusCircleFilled\";\nimport ShareAltOutlinedIcon from \"@ant-design/icons/ShareAltOutlined\";\nimport FileOutlinedIcon from \"@ant-design/icons/FileOutlined\";\nimport FileExcelOutlinedIcon from \"@ant-design/icons/FileExcelOutlined\";\nimport EllipsisOutlinedIcon from \"@ant-design/icons/EllipsisOutlined\";\n\nimport QueryResultsLink from \"./QueryResultsLink\";\n\nexport default function QueryControlDropdown(props) {\n  const menu = (\n    <Menu>\n      {!props.query.isNew() && (!props.query.is_draft || !props.query.is_archived) && (\n        <Menu.Item>\n          <PlainButton onClick={() => props.openAddToDashboardForm(props.selectedTab)}>\n            <PlusCircleFilledIcon /> Add to Dashboard\n          </PlainButton>\n        </Menu.Item>\n      )}\n      {!clientConfig.disablePublicUrls && !props.query.isNew() && (\n        <Menu.Item>\n          <PlainButton\n            onClick={() => props.showEmbedDialog(props.query, props.selectedTab)}\n            data-test=\"ShowEmbedDialogButton\">\n            <ShareAltOutlinedIcon /> Embed Elsewhere\n          </PlainButton>\n        </Menu.Item>\n      )}\n      <Menu.Item>\n        <QueryResultsLink\n          fileType=\"csv\"\n          disabled={props.queryExecuting || !props.queryResult.getData || !props.queryResult.getData()}\n          query={props.query}\n          queryResult={props.queryResult}\n          embed={props.embed}\n          apiKey={props.apiKey}>\n          <FileOutlinedIcon /> Download as CSV File\n        </QueryResultsLink>\n      </Menu.Item>\n      <Menu.Item>\n        <QueryResultsLink\n          fileType=\"tsv\"\n          disabled={props.queryExecuting || !props.queryResult.getData || !props.queryResult.getData()}\n          query={props.query}\n          queryResult={props.queryResult}\n          embed={props.embed}\n          apiKey={props.apiKey}>\n          <FileOutlinedIcon /> Download as TSV File\n        </QueryResultsLink>\n      </Menu.Item>\n      <Menu.Item>\n        <QueryResultsLink\n          fileType=\"xlsx\"\n          disabled={props.queryExecuting || !props.queryResult.getData || !props.queryResult.getData()}\n          query={props.query}\n          queryResult={props.queryResult}\n          embed={props.embed}\n          apiKey={props.apiKey}>\n          <FileExcelOutlinedIcon /> Download as Excel File\n        </QueryResultsLink>\n      </Menu.Item>\n    </Menu>\n  );\n\n  return (\n    <Dropdown trigger={[\"click\"]} overlay={menu} overlayClassName=\"query-control-dropdown-overlay\">\n      <Button data-test=\"QueryControlDropdownButton\">\n        <EllipsisOutlinedIcon rotate={90} />\n      </Button>\n    </Dropdown>\n  );\n}\n\nQueryControlDropdown.propTypes = {\n  query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  queryResult: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  queryExecuting: PropTypes.bool.isRequired,\n  showEmbedDialog: PropTypes.func.isRequired,\n  embed: PropTypes.bool,\n  apiKey: PropTypes.string,\n  selectedTab: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n  openAddToDashboardForm: PropTypes.func.isRequired,\n};\n\nQueryControlDropdown.defaultProps = {\n  queryResult: {},\n  embed: false,\n  apiKey: \"\",\n  selectedTab: \"\",\n};\n"
  },
  {
    "path": "client/app/components/EditVisualizationButton/QueryResultsLink.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Link from \"@/components/Link\";\n\nexport default function QueryResultsLink(props) {\n  let href = \"\";\n\n  const { query, queryResult, fileType } = props;\n  const resultId = queryResult.getId && queryResult.getId();\n  const resultData = queryResult.getData && queryResult.getData();\n\n  if (resultId && resultData && query.name) {\n    if (query.id) {\n      href = `api/queries/${query.id}/results/${resultId}.${fileType}${props.embed ? `?api_key=${props.apiKey}` : \"\"}`;\n    } else {\n      href = `api/query_results/${resultId}.${fileType}`;\n    }\n  }\n\n  return (\n    <Link target=\"_blank\" rel=\"noopener noreferrer\" disabled={props.disabled} href={href} download>\n      {props.children}\n    </Link>\n  );\n}\n\nQueryResultsLink.propTypes = {\n  query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  queryResult: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  fileType: PropTypes.string,\n  disabled: PropTypes.bool.isRequired,\n  embed: PropTypes.bool,\n  apiKey: PropTypes.string,\n  children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,\n};\n\nQueryResultsLink.defaultProps = {\n  queryResult: {},\n  fileType: \"csv\",\n  embed: false,\n  apiKey: \"\",\n};\n"
  },
  {
    "path": "client/app/components/EditVisualizationButton/index.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport FormOutlinedIcon from \"@ant-design/icons/FormOutlined\";\n\nexport default function EditVisualizationButton(props) {\n  return (\n    <Button\n      data-test=\"EditVisualization\"\n      className=\"edit-visualization\"\n      onClick={() => props.openVisualizationEditor(props.selectedTab)}>\n      <FormOutlinedIcon />\n      <span className=\"hidden-xs hidden-s hidden-m\">Edit Visualization</span>\n    </Button>\n  );\n}\n\nEditVisualizationButton.propTypes = {\n  openVisualizationEditor: PropTypes.func.isRequired,\n  selectedTab: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n};\n\nEditVisualizationButton.defaultProps = {\n  selectedTab: \"\",\n};\n"
  },
  {
    "path": "client/app/components/EmailSettingsWarning.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { clientConfig, currentUser } from \"@/services/auth\";\nimport Tooltip from \"@/components/Tooltip\";\nimport Alert from \"antd/lib/alert\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport { useUniqueId } from \"@/lib/hooks/useUniqueId\";\n\nexport default function EmailSettingsWarning({ featureName, className, mode, adminOnly }) {\n  const messageDescriptionId = useUniqueId(\"sr-mail-description\");\n\n  if (!clientConfig.mailSettingsMissing) {\n    return null;\n  }\n\n  if (adminOnly && !currentUser.isAdmin) {\n    return null;\n  }\n\n  const message = (\n    <span id={messageDescriptionId}>\n      Your mail server isn&apos;t configured correctly, and is needed for {featureName} to work.{\" \"}\n      <HelpTrigger type=\"MAIL_CONFIG\" className=\"f-inherit\" />\n    </span>\n  );\n\n  if (mode === \"icon\") {\n    return (\n      <Tooltip title={message} placement=\"topRight\" arrowPointAtCenter>\n        {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}\n        <span className={className} aria-label=\"Mail alert\" aria-describedby={messageDescriptionId} tabIndex={0}>\n          <i className={\"fa fa-exclamation-triangle\"} aria-hidden=\"true\" />\n        </span>\n      </Tooltip>\n    );\n  }\n\n  return <Alert message={message} type=\"error\" className={className} />;\n}\n\nEmailSettingsWarning.propTypes = {\n  featureName: PropTypes.string.isRequired,\n  className: PropTypes.string,\n  mode: PropTypes.oneOf([\"alert\", \"icon\"]),\n  adminOnly: PropTypes.bool,\n};\n\nEmailSettingsWarning.defaultProps = {\n  className: null,\n  mode: \"alert\",\n  adminOnly: false,\n};\n"
  },
  {
    "path": "client/app/components/FavoritesControl.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport PlainButton from \"@/components/PlainButton\";\n\nexport default class FavoritesControl extends React.Component {\n  static propTypes = {\n    item: PropTypes.shape({\n      is_favorite: PropTypes.bool.isRequired,\n    }).isRequired,\n    onChange: PropTypes.func,\n  };\n\n  static defaultProps = {\n    onChange: () => {},\n  };\n\n  toggleItem(event, item, callback) {\n    const action = item.is_favorite ? item.unfavorite.bind(item) : item.favorite.bind(item);\n    const savedIsFavorite = item.is_favorite;\n\n    action().then(() => {\n      item.is_favorite = !savedIsFavorite;\n      this.forceUpdate();\n      callback();\n    });\n  }\n\n  render() {\n    const { item, onChange } = this.props;\n    const icon = item.is_favorite ? \"fa fa-star\" : \"fa fa-star-o\";\n    const title = item.is_favorite ? \"Remove from favorites\" : \"Add to favorites\";\n    return (\n      <PlainButton\n        title={title}\n        aria-label={title}\n        className=\"favorites-control btn-favorite\"\n        onClick={event => this.toggleItem(event, item, onChange)}>\n        <i className={icon} aria-hidden=\"true\" />\n      </PlainButton>\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/components/Filters.jsx",
    "content": "import { isArray, indexOf, get, map, includes, every, some, toNumber } from \"lodash\";\nimport moment from \"moment\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Select from \"antd/lib/select\";\nimport { formatColumnValue } from \"@/lib/utils\";\n\nconst ALL_VALUES = \"###Redash::Filters::SelectAll###\";\nconst NONE_VALUES = \"###Redash::Filters::Clear###\";\n\nexport const FilterType = PropTypes.shape({\n  name: PropTypes.string.isRequired,\n  friendlyName: PropTypes.string.isRequired,\n  multiple: PropTypes.bool,\n  current: PropTypes.oneOfType([PropTypes.any, PropTypes.arrayOf(PropTypes.any)]),\n  values: PropTypes.arrayOf(PropTypes.any).isRequired,\n});\n\nexport const FiltersType = PropTypes.arrayOf(FilterType);\n\nfunction createFilterChangeHandler(filters, onChange) {\n  return (filter, values) => {\n    if (isArray(values)) {\n      values = map(values, value => filter.values[toNumber(value.key)] || value.key);\n    } else {\n      const _values = filter.values[toNumber(values.key)];\n      values = _values !== undefined ? _values : values.key;\n    }\n\n    if (filter.multiple && includes(values, ALL_VALUES)) {\n      values = [...filter.values];\n    }\n    if (filter.multiple && includes(values, NONE_VALUES)) {\n      values = [];\n    }\n    filters = map(filters, f => (f.name === filter.name ? { ...filter, current: values } : f));\n    onChange(filters);\n  };\n}\n\nexport function filterData(rows, filters = []) {\n  if (!isArray(rows)) {\n    return [];\n  }\n\n  let result = rows;\n\n  if (isArray(filters) && filters.length > 0) {\n    // \"every\" field's value should match \"some\" of corresponding filter's values\n    result = result.filter(row =>\n      every(filters, filter => {\n        const rowValue = row[filter.name];\n        const filterValues = isArray(filter.current) ? filter.current : [filter.current];\n        return some(filterValues, filterValue => {\n          if (moment.isMoment(rowValue)) {\n            return rowValue.isSame(filterValue);\n          }\n          // We compare with either the value or the String representation of the value,\n          // because Select2 casts true/false to \"true\"/\"false\".\n          return filterValue === rowValue || String(rowValue) === filterValue;\n        });\n      })\n    );\n  }\n\n  return result;\n}\n\nfunction Filters({ filters, onChange }) {\n  if (filters.length === 0) {\n    return null;\n  }\n\n  onChange = createFilterChangeHandler(filters, onChange);\n\n  return (\n    <div className=\"filters-wrapper\" data-test=\"Filters\">\n      <div className=\"container bg-white\">\n        <div className=\"row\">\n          {map(filters, filter => {\n            const options = map(filter.values, (value, index) => (\n              <Select.Option key={index}>{formatColumnValue(value, get(filter, \"column.type\"))}</Select.Option>\n            ));\n\n            return (\n              <div\n                key={filter.name}\n                className=\"col-sm-6 p-l-0 filter-container\"\n                data-test={`FilterName-${filter.name}`}>\n                <label>{filter.friendlyName}</label>\n                {options.length === 0 && <Select className=\"w-100\" disabled value=\"No values\" />}\n                {options.length > 0 && (\n                  <Select\n                    labelInValue\n                    className=\"w-100\"\n                    mode={filter.multiple ? \"multiple\" : \"default\"}\n                    value={\n                      isArray(filter.current)\n                        ? map(filter.current, value => ({\n                            key: `${indexOf(filter.values, value)}`,\n                            label: formatColumnValue(value),\n                          }))\n                        : { key: `${indexOf(filter.values, filter.current)}`, label: formatColumnValue(filter.current) }\n                    }\n                    allowClear={filter.multiple}\n                    optionFilterProp=\"children\"\n                    showSearch\n                    maxTagCount={3}\n                    maxTagTextLength={10}\n                    maxTagPlaceholder={num => `+${num.length} more`}\n                    onChange={values => onChange(filter, values)}>\n                    {!filter.multiple && options}\n                    {filter.multiple && [\n                      <Select.Option key={NONE_VALUES} data-test=\"ClearOption\">\n                        <i className=\"fa fa-square-o m-r-5\" aria-hidden=\"true\" />\n                        Clear\n                      </Select.Option>,\n                      <Select.Option key={ALL_VALUES} data-test=\"SelectAllOption\">\n                        <i className=\"fa fa-check-square-o m-r-5\" aria-hidden=\"true\" />\n                        Select All\n                      </Select.Option>,\n                      <Select.OptGroup key=\"Values\" title=\"Values\">\n                        {options}\n                      </Select.OptGroup>,\n                    ]}\n                  </Select>\n                )}\n              </div>\n            );\n          })}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nFilters.propTypes = {\n  filters: FiltersType.isRequired,\n  onChange: PropTypes.func, // (name, value) => void\n};\n\nFilters.defaultProps = {\n  onChange: () => {},\n};\n\nexport default Filters;\n"
  },
  {
    "path": "client/app/components/HelpTrigger.jsx",
    "content": "import { startsWith, get, some, mapValues } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport Tooltip from \"@/components/Tooltip\";\nimport Drawer from \"antd/lib/drawer\";\nimport Link from \"@/components/Link\";\nimport PlainButton from \"@/components/PlainButton\";\nimport CloseOutlinedIcon from \"@ant-design/icons/CloseOutlined\";\nimport BigMessage from \"@/components/BigMessage\";\nimport DynamicComponent, { registerComponent } from \"@/components/DynamicComponent\";\n\nimport \"./HelpTrigger.less\";\n\nconst DOMAIN = \"https://redash.io\";\nconst HELP_PATH = \"/help\";\nconst IFRAME_TIMEOUT = 20000;\nconst IFRAME_URL_UPDATE_MESSAGE = \"iframe_url\";\n\nexport const TYPES = mapValues(\n  {\n    HOME: [\"\", \"Help\"],\n    VALUE_SOURCE_OPTIONS: [\"/user-guide/querying/query-parameters#Value-Source-Options\", \"Guide: Value Source Options\"],\n    SHARE_DASHBOARD: [\"/user-guide/dashboards/sharing-dashboards\", \"Guide: Sharing and Embedding Dashboards\"],\n    AUTHENTICATION_OPTIONS: [\"/user-guide/users/authentication-options\", \"Guide: Authentication Options\"],\n    USAGE_DATA_SHARING: [\"/open-source/admin-guide/usage-data\", \"Help: Anonymous Usage Data Sharing\"],\n    DS_ATHENA: [\"/data-sources/amazon-athena-setup\", \"Guide: Help Setting up Amazon Athena\"],\n    DS_BIGQUERY: [\"/data-sources/bigquery-setup\", \"Guide: Help Setting up BigQuery\"],\n    DS_URL: [\"/data-sources/querying-urls\", \"Guide: Help Setting up URL\"],\n    DS_MONGODB: [\"/data-sources/mongodb-setup\", \"Guide: Help Setting up MongoDB\"],\n    DS_GOOGLE_SPREADSHEETS: [\n      \"/data-sources/querying-a-google-spreadsheet\",\n      \"Guide: Help Setting up Google Spreadsheets\",\n    ],\n    DS_GOOGLE_ANALYTICS: [\"/data-sources/google-analytics-setup\", \"Guide: Help Setting up Google Analytics\"],\n    DS_AXIBASETSD: [\"/data-sources/axibase-time-series-database\", \"Guide: Help Setting up Axibase Time Series\"],\n    DS_RESULTS: [\"/user-guide/querying/query-results-data-source\", \"Guide: Help Setting up Query Results\"],\n    ALERT_SETUP: [\"/user-guide/alerts/setting-up-an-alert\", \"Guide: Setting Up a New Alert\"],\n    MAIL_CONFIG: [\"/open-source/setup/#Mail-Configuration\", \"Guide: Mail Configuration\"],\n    ALERT_NOTIF_TEMPLATE_GUIDE: [\"/user-guide/alerts/custom-alert-notifications\", \"Guide: Custom Alerts Notifications\"],\n    FAVORITES: [\"/user-guide/querying/favorites-tagging/#Favorites\", \"Guide: Favorites\"],\n    MANAGE_PERMISSIONS: [\n      \"/user-guide/querying/writing-queries#Managing-Query-Permissions\",\n      \"Guide: Managing Query Permissions\",\n    ],\n    NUMBER_FORMAT_SPECS: [\"/user-guide/visualizations/formatting-numbers\", \"Formatting Numbers\"],\n    GETTING_STARTED: [\"/user-guide/getting-started\", \"Guide: Getting Started\"],\n    DASHBOARDS: [\"/user-guide/dashboards\", \"Guide: Dashboards\"],\n    QUERIES: [\"/user-guide/querying\", \"Guide: Queries\"],\n    ALERTS: [\"/user-guide/alerts\", \"Guide: Alerts\"],\n  },\n  ([url, title]) => [DOMAIN + HELP_PATH + url, title]\n);\n\nconst HelpTriggerPropTypes = {\n  type: PropTypes.string,\n  href: PropTypes.string,\n  title: PropTypes.node,\n  className: PropTypes.string,\n  showTooltip: PropTypes.bool,\n  renderAsLink: PropTypes.bool,\n  children: PropTypes.node,\n};\n\nconst HelpTriggerDefaultProps = {\n  type: null,\n  href: null,\n  title: null,\n  className: null,\n  showTooltip: true,\n  renderAsLink: false,\n  children: <i className=\"fa fa-question-circle\" aria-hidden=\"true\" />,\n};\n\nexport function helpTriggerWithTypes(types, allowedDomains = [], drawerClassName = null) {\n  return class HelpTrigger extends React.Component {\n    static propTypes = {\n      ...HelpTriggerPropTypes,\n      type: PropTypes.oneOf(Object.keys(types)),\n    };\n\n    static defaultProps = HelpTriggerDefaultProps;\n\n    iframeRef = React.createRef();\n\n    iframeLoadingTimeout = null;\n\n    state = {\n      visible: false,\n      loading: false,\n      error: false,\n      currentUrl: null,\n    };\n\n    componentDidMount() {\n      window.addEventListener(\"message\", this.onPostMessageReceived, false);\n    }\n\n    componentWillUnmount() {\n      window.removeEventListener(\"message\", this.onPostMessageReceived);\n      clearTimeout(this.iframeLoadingTimeout);\n    }\n\n    loadIframe = (url) => {\n      clearTimeout(this.iframeLoadingTimeout);\n      this.setState({ loading: true, error: false });\n\n      this.iframeRef.current.src = url;\n      this.iframeLoadingTimeout = setTimeout(() => {\n        this.setState({ error: url, loading: false });\n      }, IFRAME_TIMEOUT); // safety\n    };\n\n    onIframeLoaded = () => {\n      this.setState({ loading: false });\n      clearTimeout(this.iframeLoadingTimeout);\n    };\n\n    onPostMessageReceived = (event) => {\n      if (!some(allowedDomains, (domain) => startsWith(event.origin, domain))) {\n        return;\n      }\n\n      const { type, message: currentUrl } = event.data || {};\n      if (type !== IFRAME_URL_UPDATE_MESSAGE) {\n        return;\n      }\n\n      this.setState({ currentUrl });\n    };\n\n    getUrl = () => {\n      const helpTriggerType = get(types, this.props.type);\n      return helpTriggerType ? helpTriggerType[0] : this.props.href;\n    };\n\n    openDrawer = (e) => {\n      // keep \"open in new tab\" behavior\n      if (!e.shiftKey && !e.ctrlKey && !e.metaKey) {\n        e.preventDefault();\n        this.setState({ visible: true });\n        // wait for drawer animation to complete so there's no animation jank\n        setTimeout(() => this.loadIframe(this.getUrl()), 300);\n      }\n    };\n\n    closeDrawer = (event) => {\n      if (event) {\n        event.preventDefault();\n      }\n      this.setState({ visible: false });\n      this.setState({ visible: false, currentUrl: null });\n    };\n\n    render() {\n      const targetUrl = this.getUrl();\n      if (!targetUrl) {\n        return null;\n      }\n\n      const tooltip = get(types, `${this.props.type}[1]`, this.props.title);\n      const className = cx(\"help-trigger\", this.props.className);\n      const url = this.state.currentUrl;\n      const isAllowedDomain = some(allowedDomains, (domain) => startsWith(url || targetUrl, domain));\n      const shouldRenderAsLink = this.props.renderAsLink || !isAllowedDomain;\n\n      return (\n        <React.Fragment>\n          <Tooltip\n            title={\n              this.props.showTooltip ? (\n                <>\n                  {tooltip}\n                  {shouldRenderAsLink && (\n                    <>\n                      {\" \"}\n                      <i className=\"fa fa-external-link\" style={{ marginLeft: 5 }} aria-hidden=\"true\" />\n                      <span className=\"sr-only\">(opens in a new tab)</span>\n                    </>\n                  )}\n                </>\n              ) : null\n            }\n          >\n            <Link\n              href={url || this.getUrl()}\n              className={className}\n              rel=\"noopener noreferrer\"\n              target=\"_blank\"\n              onClick={shouldRenderAsLink ? () => {} : this.openDrawer}\n            >\n              {this.props.children}\n            </Link>\n          </Tooltip>\n          <Drawer\n            placement=\"right\"\n            closable={false}\n            onClose={this.closeDrawer}\n            visible={this.state.visible}\n            className={cx(\"help-drawer\", drawerClassName)}\n            destroyOnClose\n            width={400}\n          >\n            <div className=\"drawer-wrapper\">\n              <div className=\"drawer-menu\">\n                {url && (\n                  <Tooltip title=\"Open page in a new window\" placement=\"left\">\n                    {/* eslint-disable-next-line react/jsx-no-target-blank */}\n                    <Link href={url} target=\"_blank\">\n                      <i className=\"fa fa-external-link\" aria-hidden=\"true\" />\n                      <span className=\"sr-only\">(opens in a new tab)</span>\n                    </Link>\n                  </Tooltip>\n                )}\n                <Tooltip title=\"Close\" placement=\"bottom\">\n                  <PlainButton onClick={this.closeDrawer}>\n                    <CloseOutlinedIcon />\n                  </PlainButton>\n                </Tooltip>\n              </div>\n\n              {/* iframe */}\n              {!this.state.error && (\n                <iframe\n                  ref={this.iframeRef}\n                  title=\"Usage Help\"\n                  src=\"about:blank\"\n                  className={cx({ ready: !this.state.loading })}\n                  onLoad={this.onIframeLoaded}\n                />\n              )}\n\n              {/* loading indicator */}\n              {this.state.loading && (\n                <BigMessage icon=\"fa-spinner fa-2x fa-pulse\" message=\"Loading...\" className=\"help-message\" />\n              )}\n\n              {/* error message */}\n              {this.state.error && (\n                <BigMessage icon=\"fa-exclamation-circle\" className=\"help-message\">\n                  Something went wrong.\n                  <br />\n                  {/* eslint-disable-next-line react/jsx-no-target-blank */}\n                  <Link href={this.state.error} target=\"_blank\" rel=\"noopener\">\n                    Click here\n                  </Link>{\" \"}\n                  to open the page in a new window.\n                </BigMessage>\n              )}\n            </div>\n\n            {/* extra content */}\n            <DynamicComponent name=\"HelpDrawerExtraContent\" onLeave={this.closeDrawer} openPageUrl={this.loadIframe} />\n          </Drawer>\n        </React.Fragment>\n      );\n    }\n  };\n}\n\nregisterComponent(\"HelpTrigger\", helpTriggerWithTypes(TYPES, [DOMAIN]));\n\nexport default function HelpTrigger(props) {\n  return <DynamicComponent {...props} name=\"HelpTrigger\" />;\n}\n\nHelpTrigger.propTypes = HelpTriggerPropTypes;\nHelpTrigger.defaultProps = HelpTriggerDefaultProps;\n"
  },
  {
    "path": "client/app/components/HelpTrigger.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\";\n\n@help-doc-bg: #f7f7f7; // according to https://github.com/getredash/website/blob/13daff2d8b570956565f482236f6245042e8477f/src/scss/_components/_variables.scss#L15\n\n.help-trigger {\n  font-size: 15px;\n\n  &:hover {\n    cursor: pointer;\n  }\n}\n\n.help-drawer {\n  .ant-drawer-body {\n    padding: 0;\n    height: 100%; // to allow iframe full dimensions\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    flex-direction: column;\n  }\n\n  .drawer-wrapper {\n    flex: 1;\n    display: flex;\n    align-items: center;\n    width: 100%;\n    justify-content: center;\n  }\n\n  .drawer-menu {\n    position: fixed;\n    z-index: 1;\n    top: 13px;\n    right: 13px;\n    border-radius: 3px;\n    background: rgba(@help-doc-bg, 0.75); // makes it dissolve over help doc bg\n    border: 2px solid @help-doc-bg;\n    display: flex;\n\n    a,\n    .plain-button {\n      height: 26px;\n      width: 26px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: @text-color-secondary;\n      transition: color @animation-duration-slow;\n      position: relative;\n      cursor: pointer;\n\n      &:hover {\n        color: @icon-color-hover;\n        text-decoration: none;\n      }\n\n      .anticon {\n        font-size: 15px;\n      }\n\n      .fa-external-link {\n        position: relative;\n        top: 1px;\n        font-size: 14px;\n      }\n\n      // divider\n      &:not(:first-child):before {\n        content: \"\";\n        position: absolute;\n        width: 1px;\n        height: 9px;\n        left: 0;\n        top: 9px;\n        border-left: 1px dotted rgba(0, 0, 0, 0.12);\n      }\n    }\n  }\n\n  iframe {\n    width: 0;\n    visibility: hidden;\n  }\n\n  iframe.ready {\n    border: 0;\n    width: 100%;\n    height: 100%;\n    visibility: visible;\n  }\n}\n"
  },
  {
    "path": "client/app/components/InputWithCopy.jsx",
    "content": "import React from \"react\";\nimport Input from \"antd/lib/input\";\nimport CopyOutlinedIcon from \"@ant-design/icons/CopyOutlined\";\nimport Tooltip from \"@/components/Tooltip\";\nimport PlainButton from \"./PlainButton\";\n\nexport default class InputWithCopy extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = { copied: null };\n    this.ref = React.createRef();\n    this.copyFeatureSupported = document.queryCommandSupported(\"copy\");\n    this.resetCopyState = null;\n  }\n\n  componentWillUnmount() {\n    if (this.resetCopyState) {\n      clearTimeout(this.resetCopyState);\n    }\n  }\n\n  copy = () => {\n    // select text\n    this.ref.current.select();\n\n    // copy\n    try {\n      const success = document.execCommand(\"copy\");\n      if (!success) {\n        throw new Error();\n      }\n      this.setState({ copied: \"Copied!\" });\n    } catch (err) {\n      this.setState({\n        copied: \"Copy failed\",\n      });\n    }\n\n    // reset tooltip\n    this.resetCopyState = setTimeout(() => this.setState({ copied: null }), 2000);\n  };\n\n  render() {\n    const copyButton = (\n      <Tooltip title={this.state.copied || \"Copy\"}>\n        <PlainButton onClick={this.copy}>\n          {/* TODO: lacks visual feedback */}\n          <CopyOutlinedIcon />\n        </PlainButton>\n      </Tooltip>\n    );\n\n    return <Input {...this.props} ref={this.ref} addonAfter={this.copyFeatureSupported && copyButton} />;\n  }\n}\n"
  },
  {
    "path": "client/app/components/Link.tsx",
    "content": "import React from \"react\";\nimport Button, { ButtonProps as AntdButtonProps } from \"antd/lib/button\";\n\nfunction DefaultLinkComponent({ children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n  return <a {...props}>{children}</a>;\n}\n\nLink.Component = DefaultLinkComponent;\n\ninterface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, \"role\" | \"type\" | \"target\"> {\n  href: string;\n}\nfunction Link({ children, ...props }: LinkProps) {\n  return <Link.Component {...props}>{children}</Link.Component>;\n}\n\ninterface LinkWithIconProps extends LinkProps {\n  children: string;\n  icon: JSX.Element;\n  alt: string;\n  target?: \"_self\" | \"_blank\" | \"_parent\" | \"_top\";\n}\n\nfunction LinkWithIcon({ icon, alt, children, ...props }: LinkWithIconProps) {\n  return (\n    <Link.Component {...props}>\n      {children} {icon} <span className=\"sr-only\">{alt}</span>\n    </Link.Component>\n  );\n}\n\nLink.WithIcon = LinkWithIcon;\n\nfunction ExternalLink({\n  icon = <i className=\"fa fa-external-link\" aria-hidden=\"true\" />,\n  alt = \"(opens in a new tab)\",\n  ...props\n}: Omit<LinkWithIconProps, \"target\">) {\n  return <Link.WithIcon target=\"_blank\" rel=\"noopener noreferrer\" icon={icon} alt={alt} {...props} />;\n}\n\nLink.External = ExternalLink;\n\n// Ant Button will render an <a> if href is present.\nfunction DefaultButtonLinkComponent(props: ButtonProps) {\n  return <Button {...props} />;\n}\n\nButtonLink.Component = DefaultButtonLinkComponent;\n\ninterface ButtonProps extends AntdButtonProps {\n  href: string;\n}\n\nfunction ButtonLink(props: ButtonProps) {\n  return <ButtonLink.Component {...props} />;\n}\n\nLink.Button = ButtonLink;\n\nexport default Link;\n"
  },
  {
    "path": "client/app/components/NoTaggedObjectsFound.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport BigMessage from \"@/components/BigMessage\";\nimport { TagsControl } from \"@/components/tags-control/TagsControl\";\n\nexport default function NoTaggedObjectsFound({ objectType, tags }) {\n  return (\n    <BigMessage icon=\"fa-tags\">\n      No {objectType} found tagged with&nbsp;\n      <TagsControl className=\"inline-tags-control\" tags={Array.from(tags)} tagSeparator={\"+\"} />.\n    </BigMessage>\n  );\n}\n\nNoTaggedObjectsFound.propTypes = {\n  objectType: PropTypes.string.isRequired,\n  tags: PropTypes.oneOfType([PropTypes.array, PropTypes.objectOf(Set)]).isRequired,\n};\n"
  },
  {
    "path": "client/app/components/PageHeader/index.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport \"./index.less\";\n\nexport default function PageHeader({ title, actions }) {\n  return (\n    <div className=\"page-header-wrapper\">\n      <h3>{title}</h3>\n      {actions && <div className=\"page-header-actions\">{actions}</div>}\n    </div>\n  );\n}\n\nPageHeader.propTypes = {\n  title: PropTypes.string,\n  actions: PropTypes.node,\n};\n\nPageHeader.defaultProps = {\n  title: \"\",\n  actions: null,\n};\n"
  },
  {
    "path": "client/app/components/PageHeader/index.less",
    "content": ".page-header-wrapper {\n  margin: 15px 0 10px 0;\n  display: flex;\n  flex-direction: row;\n  flex-wrap: nowrap;\n  align-items: center;\n  justify-content: stretch;\n\n  h3 {\n    margin: 0;\n    line-height: 1.3;\n    font-weight: 500;\n    flex: 1 1 auto;\n  }\n\n  .page-header-actions {\n    flex: 0 0 auto;\n    padding: 0 0 0 15px;\n  }\n}\n"
  },
  {
    "path": "client/app/components/Paginator.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Pagination from \"antd/lib/pagination\";\n\nconst MIN_ITEMS_PER_PAGE = 5;\n\nexport default function Paginator({ page, showPageSizeSelect, pageSize, onPageSizeChange, totalCount, onChange }) {\n  if (totalCount <= (showPageSizeSelect ? MIN_ITEMS_PER_PAGE : pageSize)) {\n    return null;\n  }\n  return (\n    <div className=\"paginator-container\">\n      <Pagination\n        showSizeChanger={showPageSizeSelect}\n        pageSizeOptions={[\"5\", \"10\", \"20\", \"50\", \"100\"]}\n        onShowSizeChange={(_, size) => onPageSizeChange(size)}\n        defaultCurrent={page}\n        pageSize={pageSize}\n        total={totalCount}\n        onChange={onChange}\n      />\n    </div>\n  );\n}\n\nPaginator.propTypes = {\n  page: PropTypes.number.isRequired,\n  showPageSizeSelect: PropTypes.bool,\n  pageSize: PropTypes.number.isRequired,\n  totalCount: PropTypes.number.isRequired,\n  onPageSizeChange: PropTypes.func,\n  onChange: PropTypes.func,\n};\n\nPaginator.defaultProps = {\n  showPageSizeSelect: false,\n  onChange: () => {},\n  onPageSizeChange: () => {},\n};\n"
  },
  {
    "path": "client/app/components/ParameterApplyButton.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport Badge from \"antd/lib/badge\";\nimport Tooltip from \"@/components/Tooltip\";\nimport KeyboardShortcuts from \"@/services/KeyboardShortcuts\";\n\nfunction ParameterApplyButton({ paramCount, onClick }) {\n  // show spinner when count is empty so the fade out is consistent\n  const icon = !paramCount ? (\n    <span role=\"status\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n      <i className=\"fa fa-spinner fa-pulse\" aria-hidden=\"true\" />\n      <span className=\"sr-only\">Loading...</span>\n    </span>\n  ) : (\n    <i className=\"fa fa-check\" aria-hidden=\"true\" />\n  );\n\n  return (\n    <div className=\"parameter-apply-button\" data-show={!!paramCount} data-test=\"ParameterApplyButton\">\n      <Badge count={paramCount}>\n        <Tooltip title={paramCount ? `${KeyboardShortcuts.modKey} + Enter` : null}>\n          <span>\n            <Button onClick={onClick}>{icon} Apply Changes</Button>\n          </span>\n        </Tooltip>\n      </Badge>\n    </div>\n  );\n}\n\nParameterApplyButton.propTypes = {\n  onClick: PropTypes.func.isRequired,\n  paramCount: PropTypes.number.isRequired,\n};\n\nexport default ParameterApplyButton;\n"
  },
  {
    "path": "client/app/components/ParameterMappingInput.jsx",
    "content": "/* eslint-disable react/no-multi-comp */\n\nimport { isString, extend, each, has, map, includes, findIndex, find, fromPairs, clone, isEmpty } from \"lodash\";\nimport React, { Fragment } from \"react\";\nimport PropTypes from \"prop-types\";\nimport classNames from \"classnames\";\nimport Select from \"antd/lib/select\";\nimport Table from \"antd/lib/table\";\nimport Popover from \"antd/lib/popover\";\nimport Button from \"antd/lib/button\";\nimport Tag from \"antd/lib/tag\";\nimport Input from \"antd/lib/input\";\nimport Radio from \"antd/lib/radio\";\nimport Form from \"antd/lib/form\";\nimport Tooltip from \"@/components/Tooltip\";\nimport ParameterValueInput from \"@/components/ParameterValueInput\";\nimport { ParameterMappingType } from \"@/services/widget\";\nimport { Parameter, cloneParameter } from \"@/services/parameters\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\n\nimport QuestionCircleFilledIcon from \"@ant-design/icons/QuestionCircleFilled\";\nimport EditOutlinedIcon from \"@ant-design/icons/EditOutlined\";\nimport CloseOutlinedIcon from \"@ant-design/icons/CloseOutlined\";\nimport CheckOutlinedIcon from \"@ant-design/icons/CheckOutlined\";\n\nimport \"./ParameterMappingInput.less\";\n\nexport const MappingType = {\n  DashboardAddNew: \"dashboard-add-new\",\n  DashboardMapToExisting: \"dashboard-map-to-existing\",\n  WidgetLevel: \"widget-level\",\n  StaticValue: \"static-value\",\n};\n\nexport function parameterMappingsToEditableMappings(mappings, parameters, existingParameterNames = []) {\n  return map(mappings, (mapping) => {\n    const result = extend({}, mapping);\n    const alreadyExists = includes(existingParameterNames, mapping.mapTo);\n    result.param = find(parameters, (p) => p.name === mapping.name);\n    switch (mapping.type) {\n      case ParameterMappingType.DashboardLevel:\n        result.type = alreadyExists ? MappingType.DashboardMapToExisting : MappingType.DashboardAddNew;\n        result.value = null;\n        break;\n      case ParameterMappingType.StaticValue:\n        result.type = MappingType.StaticValue;\n        result.param = cloneParameter(result.param);\n        result.param.setValue(result.value);\n        break;\n      case ParameterMappingType.WidgetLevel:\n        result.type = MappingType.WidgetLevel;\n        result.value = null;\n        break;\n      // no default\n    }\n    return result;\n  });\n}\n\nexport function editableMappingsToParameterMappings(mappings) {\n  return fromPairs(\n    map(\n      // convert to map\n      mappings,\n      (mapping) => {\n        const result = extend({}, mapping);\n        switch (mapping.type) {\n          case MappingType.DashboardAddNew:\n            result.type = ParameterMappingType.DashboardLevel;\n            result.value = null;\n            break;\n          case MappingType.DashboardMapToExisting:\n            result.type = ParameterMappingType.DashboardLevel;\n            result.value = null;\n            break;\n          case MappingType.StaticValue:\n            result.type = ParameterMappingType.StaticValue;\n            result.param = cloneParameter(mapping.param);\n            result.param.setValue(result.value);\n            result.value = result.param.value;\n            break;\n          case MappingType.WidgetLevel:\n            result.type = ParameterMappingType.WidgetLevel;\n            result.value = null;\n            break;\n          // no default\n        }\n        delete result.param;\n        return [result.name, result];\n      }\n    )\n  );\n}\n\nexport function synchronizeWidgetTitles(sourceMappings, widgets) {\n  const affectedWidgets = [];\n\n  each(sourceMappings, (sourceMapping) => {\n    if (sourceMapping.type === ParameterMappingType.DashboardLevel) {\n      each(widgets, (widget) => {\n        const widgetMappings = widget.options.parameterMappings;\n        each(widgetMappings, (widgetMapping) => {\n          // check if mapped to the same dashboard-level parameter\n          if (\n            widgetMapping.type === ParameterMappingType.DashboardLevel &&\n            widgetMapping.mapTo === sourceMapping.mapTo\n          ) {\n            // dirty check - update only when needed\n            if (widgetMapping.title !== sourceMapping.title) {\n              widgetMapping.title = sourceMapping.title;\n              affectedWidgets.push(widget);\n            }\n          }\n        });\n      });\n    }\n  });\n\n  return affectedWidgets;\n}\n\nexport class ParameterMappingInput extends React.Component {\n  static propTypes = {\n    mapping: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n    existingParamNames: PropTypes.arrayOf(PropTypes.string),\n    onChange: PropTypes.func,\n    inputError: PropTypes.string,\n  };\n\n  static defaultProps = {\n    mapping: {},\n    existingParamNames: [],\n    onChange: () => {},\n    inputError: null,\n  };\n\n  formItemProps = {\n    labelCol: { span: 5 },\n    wrapperCol: { span: 16 },\n    className: \"form-item\",\n  };\n\n  updateSourceType = (type) => {\n    let {\n      mapping: { mapTo },\n    } = this.props;\n    const { existingParamNames } = this.props;\n\n    // if mapped name doesn't already exists\n    // default to first select option\n    if (type === MappingType.DashboardMapToExisting && !includes(existingParamNames, mapTo)) {\n      mapTo = existingParamNames[0];\n    }\n\n    this.updateParamMapping({ type, mapTo });\n  };\n\n  updateParamMapping = (update) => {\n    const { onChange, mapping } = this.props;\n    const newMapping = extend({}, mapping, update);\n    if (newMapping.value !== mapping.value) {\n      newMapping.param = cloneParameter(newMapping.param);\n      newMapping.param.setValue(newMapping.value);\n    }\n    if (has(update, \"type\")) {\n      if (update.type === MappingType.StaticValue) {\n        newMapping.value = newMapping.param.value;\n      } else {\n        newMapping.value = null;\n      }\n    }\n    onChange(newMapping);\n  };\n\n  renderMappingTypeSelector() {\n    const noExisting = isEmpty(this.props.existingParamNames);\n    return (\n      <Radio.Group value={this.props.mapping.type} onChange={(e) => this.updateSourceType(e.target.value)}>\n        <Radio className=\"radio\" value={MappingType.DashboardAddNew} data-test=\"NewDashboardParameterOption\">\n          New dashboard parameter\n        </Radio>\n        <Radio className=\"radio\" value={MappingType.DashboardMapToExisting} disabled={noExisting}>\n          Existing dashboard parameter{\" \"}\n          {noExisting ? (\n            <Tooltip title=\"There are no dashboard parameters corresponding to this data type\">\n              <QuestionCircleFilledIcon />\n            </Tooltip>\n          ) : null}\n        </Radio>\n        <Radio className=\"radio\" value={MappingType.WidgetLevel} data-test=\"WidgetParameterOption\">\n          Widget parameter\n        </Radio>\n        <Radio className=\"radio\" value={MappingType.StaticValue} data-test=\"StaticValueOption\">\n          Static value\n        </Radio>\n      </Radio.Group>\n    );\n  }\n\n  renderDashboardAddNew() {\n    const {\n      mapping: { mapTo },\n    } = this.props;\n    return (\n      <Input\n        value={mapTo}\n        aria-label=\"Parameter name (key)\"\n        onChange={(e) => this.updateParamMapping({ mapTo: e.target.value })}\n      />\n    );\n  }\n\n  renderDashboardMapToExisting() {\n    const { mapping, existingParamNames } = this.props;\n    const options = map(existingParamNames, (paramName) => ({ label: paramName, value: paramName }));\n\n    return <Select value={mapping.mapTo} onChange={(mapTo) => this.updateParamMapping({ mapTo })} options={options} />;\n  }\n\n  renderStaticValue() {\n    const { mapping } = this.props;\n    return (\n      <ParameterValueInput\n        type={mapping.param.type}\n        value={mapping.param.normalizedValue}\n        enumOptions={mapping.param.enumOptions}\n        queryId={mapping.param.queryId}\n        parameter={mapping.param}\n        onSelect={(value) => this.updateParamMapping({ value })}\n        regex={mapping.param.regex}\n      />\n    );\n  }\n\n  renderInputBlock() {\n    const { mapping } = this.props;\n    switch (mapping.type) {\n      case MappingType.DashboardAddNew:\n        return [\"Key\", \"Enter a new parameter keyword\", this.renderDashboardAddNew()];\n      case MappingType.DashboardMapToExisting:\n        return [\"Key\", \"Select from a list of existing parameters\", this.renderDashboardMapToExisting()];\n      case MappingType.StaticValue:\n        return [\"Value\", null, this.renderStaticValue()];\n      default:\n        return [];\n    }\n  }\n\n  render() {\n    const { inputError } = this.props;\n    const [label, help, input] = this.renderInputBlock();\n\n    return (\n      <Form layout=\"horizontal\">\n        <Form.Item label=\"Source\" {...this.formItemProps}>\n          {this.renderMappingTypeSelector()}\n        </Form.Item>\n        <Form.Item\n          style={{ height: 60, visibility: input ? \"visible\" : \"hidden\" }}\n          label={label}\n          {...this.formItemProps}\n          validateStatus={inputError ? \"error\" : \"\"}\n          help={inputError || help} // empty space so line doesn't collapse\n        >\n          {input}\n        </Form.Item>\n      </Form>\n    );\n  }\n}\n\nclass MappingEditor extends React.Component {\n  static propTypes = {\n    mapping: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n    existingParamNames: PropTypes.arrayOf(PropTypes.string).isRequired,\n    onChange: PropTypes.func.isRequired,\n  };\n\n  constructor(props) {\n    super(props);\n    this.state = {\n      visible: false,\n      mapping: clone(this.props.mapping),\n      inputError: null,\n    };\n  }\n\n  onVisibleChange = (visible) => {\n    if (visible) this.show();\n    else this.hide();\n  };\n\n  onChange = (mapping) => {\n    let inputError = null;\n\n    if (mapping.type === MappingType.DashboardAddNew) {\n      if (isEmpty(mapping.mapTo)) {\n        inputError = \"Keyword must have a value\";\n      } else if (includes(this.props.existingParamNames, mapping.mapTo)) {\n        inputError = \"A parameter with this name already exists\";\n      }\n    }\n\n    this.setState({ mapping, inputError });\n  };\n\n  save = () => {\n    this.props.onChange(this.props.mapping, this.state.mapping);\n    this.hide();\n  };\n\n  show = () => {\n    this.setState({\n      visible: true,\n      mapping: clone(this.props.mapping), // restore original state\n    });\n  };\n\n  hide = () => {\n    this.setState({ visible: false });\n  };\n\n  renderContent() {\n    const { mapping, inputError } = this.state;\n\n    return (\n      <div className=\"parameter-mapping-editor\" data-test=\"EditParamMappingPopover\">\n        <header>\n          Edit Source and Value <HelpTrigger type=\"VALUE_SOURCE_OPTIONS\" />\n        </header>\n        <ParameterMappingInput\n          mapping={mapping}\n          existingParamNames={this.props.existingParamNames}\n          onChange={this.onChange}\n          inputError={inputError}\n        />\n        <footer>\n          <Button onClick={this.hide}>Cancel</Button>\n          <Button onClick={this.save} disabled={!!inputError} type=\"primary\">\n            OK\n          </Button>\n        </footer>\n      </div>\n    );\n  }\n\n  render() {\n    const { visible, mapping } = this.state;\n    return (\n      <Popover\n        placement=\"left\"\n        trigger=\"click\"\n        content={this.renderContent()}\n        visible={visible}\n        onVisibleChange={this.onVisibleChange}\n      >\n        <Button size=\"small\" type=\"dashed\" data-test={`EditParamMappingButton-${mapping.param.name}`}>\n          <EditOutlinedIcon />\n        </Button>\n      </Popover>\n    );\n  }\n}\n\nclass TitleEditor extends React.Component {\n  static propTypes = {\n    existingParams: PropTypes.arrayOf(PropTypes.object),\n    mapping: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n    onChange: PropTypes.func.isRequired,\n  };\n\n  static defaultProps = {\n    existingParams: [],\n  };\n\n  state = {\n    showPopup: false,\n    title: \"\", // will be set on editing\n  };\n\n  onPopupVisibleChange = (showPopup) => {\n    this.setState({\n      showPopup,\n      title: showPopup ? this.getMappingTitle() : \"\",\n    });\n  };\n\n  onEditingTitleChange = (event) => {\n    this.setState({ title: event.target.value });\n  };\n\n  getMappingTitle() {\n    let { mapping } = this.props;\n\n    if (isString(mapping.title) && mapping.title !== \"\") {\n      return mapping.title;\n    }\n\n    // if mapped to dashboard, find source param and return it's title\n    if (mapping.type === MappingType.DashboardMapToExisting) {\n      const source = find(this.props.existingParams, { name: mapping.mapTo });\n      if (source) {\n        mapping = source;\n      }\n    }\n\n    return mapping.title || mapping.param.title;\n  }\n\n  save = () => {\n    const newMapping = extend({}, this.props.mapping, { title: this.state.title });\n    this.props.onChange(newMapping);\n    this.hide();\n  };\n\n  hide = () => {\n    this.setState({ showPopup: false });\n  };\n\n  renderPopover() {\n    const {\n      param: { title: paramTitle },\n    } = this.props.mapping;\n\n    return (\n      <div className=\"parameter-mapping-title-editor\">\n        <Input\n          size=\"small\"\n          value={this.state.title}\n          placeholder={paramTitle}\n          aria-label=\"Edit parameter title\"\n          onChange={this.onEditingTitleChange}\n          onPressEnter={this.save}\n          maxLength={100}\n          autoFocus\n        />\n        <Button size=\"small\" type=\"dashed\" onClick={this.hide}>\n          <CloseOutlinedIcon />\n        </Button>\n        <Button size=\"small\" type=\"dashed\" onClick={this.save}>\n          <CheckOutlinedIcon />\n        </Button>\n      </div>\n    );\n  }\n\n  renderEditButton() {\n    const { mapping } = this.props;\n    if (mapping.type === MappingType.StaticValue) {\n      return (\n        <Tooltip placement=\"right\" title=\"Titles for static values don't appear in widgets\">\n          {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}\n          <span tabIndex={0}>\n            <i className=\"fa fa-eye-slash\" aria-hidden=\"true\" />\n          </span>\n        </Tooltip>\n      );\n    }\n    return (\n      <Popover\n        placement=\"right\"\n        trigger=\"click\"\n        content={this.renderPopover()}\n        visible={this.state.showPopup}\n        onVisibleChange={this.onPopupVisibleChange}\n      >\n        <Button size=\"small\" type=\"dashed\">\n          <EditOutlinedIcon />\n        </Button>\n      </Popover>\n    );\n  }\n\n  render() {\n    const { mapping } = this.props;\n    // static value are non-editable hence disabled\n    const disabled = mapping.type === MappingType.StaticValue;\n\n    return (\n      <div className={classNames(\"parameter-mapping-title\", { disabled })}>\n        <span className=\"text\">{this.getMappingTitle()}</span>\n        {this.renderEditButton()}\n      </div>\n    );\n  }\n}\n\nexport class ParameterMappingListInput extends React.Component {\n  static propTypes = {\n    mappings: PropTypes.arrayOf(PropTypes.object),\n    existingParams: PropTypes.arrayOf(PropTypes.object),\n    onChange: PropTypes.func,\n  };\n\n  static defaultProps = {\n    mappings: [],\n    existingParams: [],\n    onChange: () => {},\n  };\n\n  static getStringValue(value) {\n    // null\n    if (!value) {\n      return \"\";\n    }\n\n    // range\n    if (value instanceof Object && \"start\" in value && \"end\" in value) {\n      return `${value.start} ~ ${value.end}`;\n    }\n\n    // just to be safe, array or object\n    if (typeof value === \"object\") {\n      return map(value, (v) => this.getStringValue(v)).join(\", \");\n    }\n\n    // rest\n    return value.toString();\n  }\n\n  static getDefaultValue(mapping, existingParams) {\n    const { type, mapTo, name } = mapping;\n    let { param } = mapping;\n\n    // if mapped to another param, swap 'em\n    if (type === MappingType.DashboardMapToExisting && mapTo !== name) {\n      const mappedTo = find(existingParams, { name: mapTo });\n      if (mappedTo) {\n        // just being safe\n        param = mappedTo;\n      }\n\n      // static type is different since it's fed param.normalizedValue\n    } else if (type === MappingType.StaticValue) {\n      param = cloneParameter(param).setValue(mapping.value);\n    }\n\n    let value = Parameter.getExecutionValue(param);\n\n    // in case of dynamic value display the name instead of value\n    if (param.hasDynamicValue) {\n      value = param.normalizedValue.name;\n    }\n\n    return this.getStringValue(value);\n  }\n\n  static getSourceTypeLabel({ type, mapTo }) {\n    switch (type) {\n      case MappingType.DashboardAddNew:\n      case MappingType.DashboardMapToExisting:\n        return (\n          <Fragment>\n            Dashboard <Tag className=\"tag\">{mapTo}</Tag>\n          </Fragment>\n        );\n      case MappingType.WidgetLevel:\n        return \"Widget parameter\";\n      case MappingType.StaticValue:\n        return \"Static value\";\n      default:\n        return \"\"; // won't happen (typescript-ftw)\n    }\n  }\n\n  updateParamMapping(oldMapping, newMapping) {\n    const mappings = [...this.props.mappings];\n    const index = findIndex(mappings, oldMapping);\n    if (index >= 0) {\n      // This should be the only possible case, but need to handle `else` too\n      mappings[index] = newMapping;\n    } else {\n      mappings.push(newMapping);\n    }\n    this.props.onChange(mappings);\n  }\n\n  render() {\n    const { existingParams } = this.props; // eslint-disable-line react/prop-types\n    const dataSource = this.props.mappings.map((mapping) => ({ mapping }));\n\n    return (\n      <div className=\"parameters-mapping-list\">\n        <Table dataSource={dataSource} size=\"middle\" pagination={false} rowKey={(record, idx) => `row${idx}`}>\n          <Table.Column\n            title=\"Title\"\n            dataIndex=\"mapping\"\n            key=\"title\"\n            render={(mapping) => (\n              <TitleEditor\n                existingParams={existingParams}\n                mapping={mapping}\n                onChange={(newMapping) => this.updateParamMapping(mapping, newMapping)}\n              />\n            )}\n          />\n          <Table.Column\n            title=\"Keyword\"\n            dataIndex=\"mapping\"\n            key=\"keyword\"\n            className=\"keyword\"\n            render={(mapping) => <code>{`{{ ${mapping.name} }}`}</code>}\n          />\n          <Table.Column\n            title=\"Default Value\"\n            dataIndex=\"mapping\"\n            key=\"value\"\n            render={(mapping) => this.constructor.getDefaultValue(mapping, this.props.existingParams)}\n          />\n          <Table.Column\n            title=\"Value Source\"\n            dataIndex=\"mapping\"\n            key=\"source\"\n            render={(mapping) => {\n              const existingParamsNames = existingParams\n                .filter(({ type }) => type === mapping.param.type) // exclude mismatching param types\n                .map(({ name }) => name); // keep names only\n\n              return (\n                <Fragment>\n                  {this.constructor.getSourceTypeLabel(mapping)}{\" \"}\n                  <MappingEditor\n                    mapping={mapping}\n                    existingParamNames={existingParamsNames}\n                    onChange={(oldMapping, newMapping) => this.updateParamMapping(oldMapping, newMapping)}\n                  />\n                </Fragment>\n              );\n            }}\n          />\n        </Table>\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/components/ParameterMappingInput.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\"; // for ant @vars\n\n.parameters-mapping-list {\n  .keyword {\n    max-width: 100px;\n    overflow: hidden;\n    text-overflow: ellipsis;\n\n    code {\n      white-space: nowrap; // so the curly braces don't line break\n    }\n  }\n\n  // Ant <Tag> overrides\n  .tag {\n    margin: 0;\n    pointer-events: none; // unclickable\n\n    &:empty {\n      display: none;\n    }\n  }\n}\n\n.parameter-mapping-editor {\n  width: 390px;\n\n  .radio {\n    display: block;\n    height: 30px;\n    line-height: 30px;\n  }\n\n  .form-item {\n    margin-bottom: 10px;\n  }\n\n  header {\n    padding: 0 16px 10px;\n    margin: 0 -16px 20px;\n    border-bottom: @border-width-base @border-style-base @border-color-split;\n    font-size: @font-size-lg;\n    font-weight: 500;\n    color: @heading-color;\n    display: flex;\n    justify-content: space-between;\n  }\n\n  footer {\n    border-top: @border-width-base @border-style-base @border-color-split;\n    padding: 10px 16px 0;\n    margin: 0 -16px;\n    text-align: right;\n\n    button {\n      margin-left: 8px;\n    }\n  }\n}\n\n.parameter-mapping-title {\n  .text {\n    margin-right: 3px;\n  }\n\n  &.disabled,\n  .fa {\n    color: #a4a4a4;\n  }\n\n  .fa-eye-slash {\n    margin-left: 1px;\n  }\n}\n\n.parameter-mapping-title-editor {\n  input {\n    width: 100px;\n  }\n\n  button {\n    margin-left: 2px;\n  }\n}\n"
  },
  {
    "path": "client/app/components/ParameterValueInput.jsx",
    "content": "import { isEqual, isEmpty, map } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport SelectWithVirtualScroll from \"@/components/SelectWithVirtualScroll\";\nimport Input from \"antd/lib/input\";\nimport InputNumber from \"antd/lib/input-number\";\nimport DateParameter from \"@/components/dynamic-parameters/DateParameter\";\nimport DateRangeParameter from \"@/components/dynamic-parameters/DateRangeParameter\";\nimport QueryBasedParameterInput from \"./QueryBasedParameterInput\";\n\nimport \"./ParameterValueInput.less\";\nimport Tooltip from \"./Tooltip\";\n\nconst multipleValuesProps = {\n  maxTagCount: 3,\n  maxTagTextLength: 10,\n  maxTagPlaceholder: (num) => `+${num.length} more`,\n};\n\nclass ParameterValueInput extends React.Component {\n  static propTypes = {\n    type: PropTypes.string,\n    value: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n    enumOptions: PropTypes.string,\n    queryId: PropTypes.number,\n    parameter: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n    onSelect: PropTypes.func,\n    className: PropTypes.string,\n    regex: PropTypes.string,\n  };\n\n  static defaultProps = {\n    type: \"text\",\n    value: null,\n    enumOptions: \"\",\n    queryId: null,\n    parameter: null,\n    onSelect: () => {},\n    className: \"\",\n    regex: \"\",\n  };\n\n  constructor(props) {\n    super(props);\n    this.state = {\n      value: props.parameter.hasPendingValue ? props.parameter.pendingValue : props.value,\n      isDirty: props.parameter.hasPendingValue,\n    };\n  }\n\n  componentDidUpdate = (prevProps) => {\n    const { value, parameter } = this.props;\n    // if value prop updated, reset dirty state\n    if (prevProps.value !== value || prevProps.parameter !== parameter) {\n      this.setState({\n        value: parameter.hasPendingValue ? parameter.pendingValue : value,\n        isDirty: parameter.hasPendingValue,\n      });\n    }\n  };\n\n  onSelect = (value) => {\n    const isDirty = !isEqual(value, this.props.value);\n    this.setState({ value, isDirty });\n    this.props.onSelect(value, isDirty);\n  };\n\n  renderDateParameter() {\n    const { type, parameter } = this.props;\n    const { value } = this.state;\n    return (\n      <DateParameter\n        type={type}\n        className={this.props.className}\n        value={value}\n        parameter={parameter}\n        onSelect={this.onSelect}\n      />\n    );\n  }\n\n  renderDateRangeParameter() {\n    const { type, parameter } = this.props;\n    const { value } = this.state;\n    return (\n      <DateRangeParameter\n        type={type}\n        className={this.props.className}\n        value={value}\n        parameter={parameter}\n        onSelect={this.onSelect}\n      />\n    );\n  }\n\n  renderEnumInput() {\n    const { enumOptions, parameter } = this.props;\n    const { value } = this.state;\n    const enumOptionsArray = enumOptions.split(\"\\n\").filter((v) => v !== \"\");\n    // Antd Select doesn't handle null in multiple mode\n    const normalize = (val) => (parameter.multiValuesOptions && val === null ? [] : val);\n\n    return (\n      <SelectWithVirtualScroll\n        className={this.props.className}\n        mode={parameter.multiValuesOptions ? \"multiple\" : \"default\"}\n        value={normalize(value)}\n        onChange={this.onSelect}\n        options={map(enumOptionsArray, (opt) => ({ label: String(opt), value: opt }))}\n        showSearch\n        showArrow\n        notFoundContent={isEmpty(enumOptionsArray) ? \"No options available\" : null}\n        {...multipleValuesProps}\n      />\n    );\n  }\n\n  renderQueryBasedInput() {\n    const { queryId, parameter } = this.props;\n    const { value } = this.state;\n    return (\n      <QueryBasedParameterInput\n        className={this.props.className}\n        mode={parameter.multiValuesOptions ? \"multiple\" : \"default\"}\n        parameter={parameter}\n        value={value}\n        queryId={queryId}\n        onSelect={this.onSelect}\n        style={{ minWidth: 60 }}\n        {...multipleValuesProps}\n      />\n    );\n  }\n\n  renderNumberInput() {\n    const { className } = this.props;\n    const { value } = this.state;\n\n    const normalize = (val) => (isNaN(val) ? undefined : val);\n\n    return (\n      <InputNumber\n        className={className}\n        value={normalize(value)}\n        aria-label=\"Parameter number value\"\n        onChange={(val) => this.onSelect(normalize(val))}\n      />\n    );\n  }\n\n  renderTextPatternInput() {\n    const { className } = this.props;\n    const { value } = this.state;\n\n    return (\n      <React.Fragment>\n        <Tooltip title={`Regex to match: ${this.props.regex}`} placement=\"right\">\n          <Input\n            className={className}\n            value={value}\n            aria-label=\"Parameter text pattern value\"\n            onChange={(e) => this.onSelect(e.target.value)}\n          />\n        </Tooltip>\n      </React.Fragment>\n    );\n  }\n\n  renderTextInput() {\n    const { className } = this.props;\n    const { value } = this.state;\n\n    return (\n      <Input\n        className={className}\n        value={value}\n        aria-label=\"Parameter text value\"\n        data-test=\"TextParamInput\"\n        onChange={(e) => this.onSelect(e.target.value)}\n      />\n    );\n  }\n\n  renderInput() {\n    const { type } = this.props;\n    switch (type) {\n      case \"datetime-with-seconds\":\n      case \"datetime-local\":\n      case \"date\":\n        return this.renderDateParameter();\n      case \"datetime-range-with-seconds\":\n      case \"datetime-range\":\n      case \"date-range\":\n        return this.renderDateRangeParameter();\n      case \"enum\":\n        return this.renderEnumInput();\n      case \"query\":\n        return this.renderQueryBasedInput();\n      case \"number\":\n        return this.renderNumberInput();\n      case \"text-pattern\":\n        return this.renderTextPatternInput();\n      default:\n        return this.renderTextInput();\n    }\n  }\n\n  render() {\n    const { isDirty } = this.state;\n\n    return (\n      <div className=\"parameter-input\" data-dirty={isDirty || null} data-test=\"ParameterValueInput\">\n        {this.renderInput()}\n      </div>\n    );\n  }\n}\n\nexport default ParameterValueInput;\n"
  },
  {
    "path": "client/app/components/ParameterValueInput.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\"; // for ant @vars\n\n@input-dirty: #fffce1;\n\n.parameter-input {\n  display: inline-block;\n  position: relative;\n  width: 100%;\n\n  .@{ant-prefix}-input,\n  .@{ant-prefix}-input-number {\n    min-width: 100% !important;\n  }\n\n  .@{ant-prefix}-select {\n    width: 100%;\n  }\n\n  &[data-dirty] {\n    .@{ant-prefix}-input,\n    .@{ant-prefix}-input-number,\n    .@{ant-prefix}-select-selector,\n    .@{ant-prefix}-picker {\n      background-color: @input-dirty;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/Parameters.jsx",
    "content": "import { size, filter, forEach, extend, isEmpty } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { SortableContainer, SortableElement, DragHandle } from \"@redash/viz/lib/components/sortable\";\nimport location from \"@/services/location\";\nimport { Parameter, createParameter } from \"@/services/parameters\";\nimport ParameterApplyButton from \"@/components/ParameterApplyButton\";\nimport ParameterValueInput from \"@/components/ParameterValueInput\";\nimport PlainButton from \"@/components/PlainButton\";\nimport EditParameterSettingsDialog from \"./EditParameterSettingsDialog\";\nimport { toHuman } from \"@/lib/utils\";\n\nimport \"./Parameters.less\";\n\nfunction updateUrl(parameters) {\n  const params = extend({}, location.search);\n  parameters.forEach((param) => {\n    extend(params, param.toUrlParams());\n  });\n  location.setSearch(params, true);\n}\n\nexport default class Parameters extends React.Component {\n  static propTypes = {\n    parameters: PropTypes.arrayOf(PropTypes.instanceOf(Parameter)),\n    editable: PropTypes.bool,\n    sortable: PropTypes.bool,\n    disableUrlUpdate: PropTypes.bool,\n    onValuesChange: PropTypes.func,\n    onPendingValuesChange: PropTypes.func,\n    onParametersEdit: PropTypes.func,\n    appendSortableToParent: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    parameters: [],\n    editable: false,\n    sortable: false,\n    disableUrlUpdate: false,\n    onValuesChange: () => {},\n    onPendingValuesChange: () => {},\n    onParametersEdit: () => {},\n    appendSortableToParent: true,\n  };\n\n  toCamelCase = (str) => {\n    if (isEmpty(str)) {\n      return \"\";\n    }\n    return str.replace(/\\s+/g, \"\").toLowerCase();\n  };\n\n  constructor(props) {\n    super(props);\n    const { parameters, disableUrlUpdate } = props;\n    this.state = { parameters };\n    if (!disableUrlUpdate) {\n      updateUrl(parameters);\n    }\n    const hideRegex = /hide_filter=([^&]+)/g;\n    const matches = window.location.search.matchAll(hideRegex);\n    this.hideValues = Array.from(matches, (match) => match[1]);\n  }\n\n  componentDidUpdate = (prevProps) => {\n    const { parameters, disableUrlUpdate } = this.props;\n    const parametersChanged = prevProps.parameters !== parameters;\n    const disableUrlUpdateChanged = prevProps.disableUrlUpdate !== disableUrlUpdate;\n    if (parametersChanged) {\n      this.setState({ parameters });\n    }\n    if ((parametersChanged || disableUrlUpdateChanged) && !disableUrlUpdate) {\n      updateUrl(parameters);\n    }\n  };\n\n  handleKeyDown = (e) => {\n    // Cmd/Ctrl/Alt + Enter\n    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey || e.altKey)) {\n      e.stopPropagation();\n      this.applyChanges();\n    }\n  };\n\n  setPendingValue = (param, value, isDirty) => {\n    const { onPendingValuesChange } = this.props;\n    this.setState(({ parameters }) => {\n      if (isDirty) {\n        param.setPendingValue(value);\n      } else {\n        param.clearPendingValue();\n      }\n      onPendingValuesChange();\n      return { parameters };\n    });\n  };\n\n  moveParameter = ({ oldIndex, newIndex }) => {\n    const { onParametersEdit } = this.props;\n    if (oldIndex !== newIndex) {\n      this.setState(({ parameters }) => {\n        parameters.splice(newIndex, 0, parameters.splice(oldIndex, 1)[0]);\n        onParametersEdit(parameters);\n        return { parameters };\n      });\n    }\n  };\n\n  applyChanges = () => {\n    const { onValuesChange, disableUrlUpdate } = this.props;\n    this.setState(({ parameters }) => {\n      const parametersWithPendingValues = parameters.filter((p) => p.hasPendingValue);\n      forEach(parameters, (p) => p.applyPendingValue());\n      if (!disableUrlUpdate) {\n        updateUrl(parameters);\n      }\n      onValuesChange(parametersWithPendingValues);\n      return { parameters };\n    });\n  };\n\n  showParameterSettings = (parameter, index) => {\n    const { onParametersEdit } = this.props;\n    EditParameterSettingsDialog.showModal({ parameter }).onClose((updated) => {\n      this.setState(({ parameters }) => {\n        const updatedParameter = extend(parameter, updated);\n        parameters[index] = createParameter(updatedParameter, updatedParameter.parentQueryId);\n        onParametersEdit(parameters);\n        return { parameters };\n      });\n    });\n  };\n\n  renderParameter(param, index) {\n    if (this.hideValues.some((value) => this.toCamelCase(value) === this.toCamelCase(param.name))) {\n      return null;\n    }\n    const { editable } = this.props;\n    if (param.hidden) {\n      return null;\n    }\n    return (\n      <div key={param.name} className=\"di-block\" data-test={`ParameterName-${param.name}`}>\n        <div className=\"parameter-heading\">\n          <label>{param.title || toHuman(param.name)}</label>\n          {editable && (\n            <PlainButton\n              className=\"btn btn-default btn-xs m-l-5\"\n              aria-label=\"Edit\"\n              onClick={() => this.showParameterSettings(param, index)}\n              data-test={`ParameterSettings-${param.name}`}\n              type=\"button\"\n            >\n              <i className=\"fa fa-cog\" aria-hidden=\"true\" />\n            </PlainButton>\n          )}\n        </div>\n\n        <ParameterValueInput\n          type={param.type}\n          value={param.normalizedValue}\n          parameter={param}\n          enumOptions={param.enumOptions}\n          queryId={param.queryId}\n          onSelect={(value, isDirty) => this.setPendingValue(param, value, isDirty)}\n          regex={param.regex}\n        />\n      </div>\n    );\n  }\n\n  render() {\n    const { parameters } = this.state;\n    const { sortable, appendSortableToParent } = this.props;\n    const dirtyParamCount = size(filter(parameters, \"hasPendingValue\"));\n    return (\n      <SortableContainer\n        disabled={!sortable}\n        axis=\"xy\"\n        useDragHandle\n        lockToContainerEdges\n        helperClass=\"parameter-dragged\"\n        helperContainer={(containerEl) => (appendSortableToParent ? containerEl : document.body)}\n        updateBeforeSortStart={this.onBeforeSortStart}\n        onSortEnd={this.moveParameter}\n        containerProps={{\n          className: \"parameter-container\",\n          onKeyDown: dirtyParamCount ? this.handleKeyDown : null,\n        }}\n      >\n        {parameters &&\n          parameters.map((param, index) => (\n            <SortableElement key={param.name} index={index}>\n              <div\n                className=\"parameter-block\"\n                data-editable={sortable || null}\n                data-test={`ParameterBlock-${param.name}`}\n              >\n                {sortable && <DragHandle data-test={`DragHandle-${param.name}`} />}\n                {this.renderParameter(param, index)}\n              </div>\n            </SortableElement>\n          ))}\n        <ParameterApplyButton onClick={this.applyChanges} paramCount={dirtyParamCount} />\n      </SortableContainer>\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/components/Parameters.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\";\n\n.parameter-block {\n  display: inline-block;\n  background: white;\n  padding: 0 12px 6px 0;\n  vertical-align: top;\n  z-index: 1;\n  white-space: nowrap;\n\n  .drag-handle {\n    padding: 0 5px;\n    margin-left: -5px;\n    height: 36px;\n  }\n\n  .parameter-container.sortable-container & {\n    margin: 4px 0 0 4px;\n    padding: 3px 6px 6px;\n  }\n\n  &.parameter-dragged {\n    z-index: 2;\n    margin: 4px 0 0 4px;\n    padding: 3px 6px 6px;\n    box-shadow: 0 4px 9px -3px rgba(102, 136, 153, 0.15);\n  }\n}\n\n.parameter-heading {\n  display: flex;\n  align-items: center;\n  padding-bottom: 4px;\n\n  label {\n    margin-bottom: 1px;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    min-width: 100%;\n    max-width: 195px;\n    white-space: nowrap;\n\n    .parameter-block[data-editable] & {\n      min-width: calc(100% - 27px); // make room for settings button\n      max-width: 195px - 27px;\n    }\n  }\n}\n\n.parameter-container {\n  position: relative;\n\n  &.sortable-container {\n    padding: 0 4px 4px 0;\n  }\n\n  .parameter-apply-button {\n    display: none; // default for mobile\n\n    // \"floating\" on desktop\n    @media (min-width: 768px) {\n      position: absolute;\n      bottom: -36px;\n      left: -15px;\n      border-radius: 2px;\n      z-index: 2;\n      transition: opacity 150ms ease-out;\n      box-shadow: 0 4px 9px -3px rgba(102, 136, 153, 0.15);\n      background-color: #ffffff;\n      padding: 4px;\n      padding-left: 16px;\n      opacity: 0;\n      display: block;\n      pointer-events: none; // so tooltip doesn't remain after button hides\n    }\n\n    &[data-show=\"true\"] {\n      opacity: 1;\n      display: block;\n      pointer-events: auto;\n    }\n\n    button {\n      padding: 0 8px 0 6px;\n      color: #2096f3;\n      border-color: #50acf6;\n\n      // smaller on desktop\n      @media (min-width: 768px) {\n        font-size: 12px;\n        height: 27px;\n      }\n\n      &:hover,\n      &:focus,\n      &:active {\n        background-color: #eef7fe;\n      }\n\n      i {\n        margin-right: 3px;\n      }\n    }\n\n    .ant-badge-count {\n      min-width: 15px;\n      height: 15px;\n      padding: 0 5px;\n      font-size: 10px;\n      line-height: 15px;\n      background: #f77b74;\n      border-radius: 7px;\n      box-shadow: 0 0 0 1px white, -1px 1px 0 1px #5d6f7d85;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/PermissionsEditorDialog/index.jsx",
    "content": "import React, { useState, useEffect, useCallback } from \"react\";\nimport { axios } from \"@/services/axios\";\nimport PropTypes from \"prop-types\";\nimport { each, debounce, get, find } from \"lodash\";\nimport Button from \"antd/lib/button\";\nimport List from \"antd/lib/list\";\nimport Modal from \"antd/lib/modal\";\nimport Select from \"antd/lib/select\";\nimport Tag from \"antd/lib/tag\";\nimport Tooltip from \"@/components/Tooltip\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport { toHuman } from \"@/lib/utils\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport { UserPreviewCard } from \"@/components/PreviewCard\";\nimport PlainButton from \"@/components/PlainButton\";\nimport notification from \"@/services/notification\";\nimport User from \"@/services/user\";\n\nimport \"./index.less\";\n\nconst { Option } = Select;\nconst DEBOUNCE_SEARCH_DURATION = 200;\n\nfunction useGrantees(url) {\n  const loadGrantees = useCallback(\n    () =>\n      axios.get(url).then((data) => {\n        const resultGrantees = [];\n        each(data, (grantees, accessType) => {\n          grantees.forEach((grantee) => {\n            grantee.accessType = toHuman(accessType);\n            resultGrantees.push(grantee);\n          });\n        });\n        return resultGrantees;\n      }),\n    [url]\n  );\n\n  const addPermission = useCallback(\n    (userId, accessType = \"modify\") =>\n      axios\n        .post(url, { access_type: accessType, user_id: userId })\n        .catch(() => notification.error(\"Could not grant permission to the user\")),\n    [url]\n  );\n\n  const removePermission = useCallback(\n    (userId, accessType = \"modify\") =>\n      axios\n        .delete(url, { data: { access_type: accessType, user_id: userId } })\n        .catch(() => notification.error(\"Could not remove permission from the user\")),\n    [url]\n  );\n\n  return { loadGrantees, addPermission, removePermission };\n}\n\nconst searchUsers = (searchTerm) =>\n  User.query({ q: searchTerm })\n    .then(({ results }) => results)\n    .catch(() => []);\n\nfunction PermissionsEditorDialogHeader({ context }) {\n  return (\n    <>\n      Manage Permissions\n      <div className=\"modal-header-desc\">\n        {`Editing this ${context} is enabled for the users in this list and for admins. `}\n        <HelpTrigger type=\"MANAGE_PERMISSIONS\" />\n      </div>\n    </>\n  );\n}\n\nPermissionsEditorDialogHeader.propTypes = { context: PropTypes.oneOf([\"query\", \"dashboard\"]) };\nPermissionsEditorDialogHeader.defaultProps = { context: \"query\" };\n\nfunction UserSelect({ onSelect, shouldShowUser }) {\n  const [loadingUsers, setLoadingUsers] = useState(true);\n  const [users, setUsers] = useState([]);\n  const [searchTerm, setSearchTerm] = useState(\"\");\n\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  const debouncedSearchUsers = useCallback(\n    debounce(\n      (search) =>\n        searchUsers(search)\n          .then(setUsers)\n          .finally(() => setLoadingUsers(false)),\n      DEBOUNCE_SEARCH_DURATION\n    ),\n    []\n  );\n\n  useEffect(() => {\n    setLoadingUsers(true);\n    debouncedSearchUsers(searchTerm);\n  }, [debouncedSearchUsers, searchTerm]);\n\n  return (\n    <Select\n      className=\"w-100 m-b-10\"\n      placeholder=\"Add users...\"\n      showSearch\n      onSearch={setSearchTerm}\n      suffixIcon={\n        loadingUsers ? (\n          <span role=\"status\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n            <i className=\"fa fa-spinner fa-pulse\" aria-hidden=\"true\" />\n            <span className=\"sr-only\">Loading...</span>\n          </span>\n        ) : (\n          <i className=\"fa fa-search\" aria-hidden=\"true\" />\n        )\n      }\n      filterOption={false}\n      notFoundContent={null}\n      value={undefined}\n      getPopupContainer={(trigger) => trigger.parentNode}\n      onSelect={onSelect}\n    >\n      {users.filter(shouldShowUser).map((user) => (\n        <Option key={user.id} value={user.id}>\n          <UserPreviewCard user={user} />\n        </Option>\n      ))}\n    </Select>\n  );\n}\n\nUserSelect.propTypes = {\n  onSelect: PropTypes.func,\n  shouldShowUser: PropTypes.func,\n};\nUserSelect.defaultProps = { onSelect: () => {}, shouldShowUser: () => true };\n\nfunction PermissionsEditorDialog({ dialog, author, context, aclUrl }) {\n  const [loadingGrantees, setLoadingGrantees] = useState(true);\n  const [grantees, setGrantees] = useState([]);\n  const { loadGrantees, addPermission, removePermission } = useGrantees(aclUrl);\n  const loadUsersWithPermissions = useCallback(() => {\n    setLoadingGrantees(true);\n    loadGrantees()\n      .then(setGrantees)\n      .catch(() => notification.error(\"Failed to load grantees list\"))\n      .finally(() => setLoadingGrantees(false));\n  }, [loadGrantees]);\n\n  const userHasPermission = useCallback(\n    (user) => user.id === author.id || !!get(find(grantees, { id: user.id }), \"accessType\"),\n    [author.id, grantees]\n  );\n\n  useEffect(() => {\n    loadUsersWithPermissions();\n  }, [aclUrl, loadUsersWithPermissions]);\n\n  return (\n    <Modal\n      {...dialog.props}\n      className=\"permissions-editor-dialog\"\n      title={<PermissionsEditorDialogHeader context={context} />}\n      footer={<Button onClick={dialog.dismiss}>Close</Button>}\n    >\n      <UserSelect\n        onSelect={(userId) => addPermission(userId).then(loadUsersWithPermissions)}\n        shouldShowUser={(user) => !userHasPermission(user)}\n      />\n      <div className=\"d-flex align-items-center m-t-5\">\n        <h5 className=\"flex-fill\">Users with permissions</h5>\n        {loadingGrantees && (\n          <span role=\"status\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n            <i className=\"fa fa-spinner fa-pulse\" aria-hidden=\"true\" />\n            <span className=\"sr-only\">Loading...</span>\n          </span>\n        )}\n      </div>\n      <div className=\"scrollbox p-5\" style={{ maxHeight: \"40vh\" }}>\n        <List\n          size=\"small\"\n          dataSource={[author, ...grantees]}\n          renderItem={(user) => (\n            <List.Item>\n              <UserPreviewCard key={user.id} user={user}>\n                {user.id === author.id ? (\n                  <Tag className=\"m-0\">Author</Tag>\n                ) : (\n                  <Tooltip title=\"Remove user permissions\">\n                    <PlainButton\n                      aria-label=\"Remove permissions\"\n                      onClick={() => removePermission(user.id).then(loadUsersWithPermissions)}\n                    >\n                      <i className=\"fa fa-remove clickable\" aria-hidden=\"true\" />\n                    </PlainButton>\n                  </Tooltip>\n                )}\n              </UserPreviewCard>\n            </List.Item>\n          )}\n        />\n      </div>\n    </Modal>\n  );\n}\n\nPermissionsEditorDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n  author: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  context: PropTypes.oneOf([\"query\", \"dashboard\"]),\n  aclUrl: PropTypes.string.isRequired,\n};\n\nPermissionsEditorDialog.defaultProps = { context: \"query\" };\n\nexport default wrapDialog(PermissionsEditorDialog);\n"
  },
  {
    "path": "client/app/components/PermissionsEditorDialog/index.less",
    "content": ".permissions-editor-dialog {\n  .ant-select-dropdown-menu-item-disabled {\n    // make sure .text-muted has the disabled color\n    &, .text-muted {\n      color: rgba(0, 0, 0, 0.25);\n    }\n  }\n}"
  },
  {
    "path": "client/app/components/PlainButton.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\";\n\n.plain-button {\n  all: unset;\n  transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);\n\n  .@{dropdown-prefix-cls}-menu-item > & {\n    width: 100%;\n    margin: -5px -12px;\n    padding: 5px 12px;\n  }\n\n  .@{menu-prefix-cls}-item > & {\n    width: 100%;\n    margin: 0 -16px;\n    padding: 0 16px;\n  }\n}\n\n.plain-button-link {\n  .btn-link();\n}\n"
  },
  {
    "path": "client/app/components/PlainButton.tsx",
    "content": "import classNames from \"classnames\";\nimport React from \"react\";\n\nimport \"./PlainButton.less\";\n\nexport interface PlainButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"type\"> {\n  type?: \"link\" | \"button\";\n}\n\nfunction PlainButton({ className, type, ...rest }: PlainButtonProps) {\n  return (\n    <button\n      className={classNames(\"plain-button\", \"clickable\", { \"plain-button-link\": type === \"link\" }, className)}\n      type=\"button\"\n      {...rest}\n    />\n  );\n}\n\nexport default PlainButton;\n"
  },
  {
    "path": "client/app/components/PreviewCard.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport classNames from \"classnames\";\nimport Link from \"@/components/Link\";\n\n// PreviewCard\n\nexport function PreviewCard({ imageUrl, roundedImage, title, body, children, className, ...props }) {\n  return (\n    <div {...props} className={className + \" w-100 d-flex align-items-center\"}>\n      <img\n        src={imageUrl}\n        width=\"32\"\n        height=\"32\"\n        className={classNames({ \"profile__image--settings\": roundedImage }, \"m-r-5\")}\n        alt=\"Logo/Avatar\"\n      />\n      <div className=\"flex-fill\">\n        <div>{title}</div>\n        {body && <div className=\"text-muted\">{body}</div>}\n      </div>\n      {children}\n    </div>\n  );\n}\n\nPreviewCard.propTypes = {\n  imageUrl: PropTypes.string.isRequired,\n  title: PropTypes.node.isRequired,\n  body: PropTypes.node,\n  roundedImage: PropTypes.bool,\n  className: PropTypes.string,\n  children: PropTypes.node,\n};\n\nPreviewCard.defaultProps = {\n  body: null,\n  roundedImage: true,\n  className: \"\",\n  children: null,\n};\n\n// UserPreviewCard\n\nexport function UserPreviewCard({ user, withLink, children, ...props }) {\n  const title = withLink ? <Link href={\"users/\" + user.id}>{user.name}</Link> : user.name;\n  return (\n    <PreviewCard {...props} imageUrl={user.profile_image_url} title={title} body={user.email}>\n      {children}\n    </PreviewCard>\n  );\n}\n\nUserPreviewCard.propTypes = {\n  user: PropTypes.shape({\n    profile_image_url: PropTypes.string.isRequired,\n    name: PropTypes.string.isRequired,\n    email: PropTypes.string.isRequired,\n  }).isRequired,\n  withLink: PropTypes.bool,\n  children: PropTypes.node,\n};\n\nUserPreviewCard.defaultProps = {\n  withLink: false,\n  children: null,\n};\n\n// DataSourcePreviewCard\n\nexport function DataSourcePreviewCard({ dataSource, withLink, children, ...props }) {\n  const imageUrl = `/static/images/db-logos/${dataSource.type}.png`;\n  const title = withLink ? <Link href={\"data_sources/\" + dataSource.id}>{dataSource.name}</Link> : dataSource.name;\n  return (\n    <PreviewCard {...props} imageUrl={imageUrl} title={title}>\n      {children}\n    </PreviewCard>\n  );\n}\n\nDataSourcePreviewCard.propTypes = {\n  dataSource: PropTypes.shape({\n    name: PropTypes.string.isRequired,\n    type: PropTypes.string.isRequired,\n  }).isRequired,\n  withLink: PropTypes.bool,\n  children: PropTypes.node,\n};\n\nDataSourcePreviewCard.defaultProps = {\n  withLink: false,\n  children: null,\n};\n"
  },
  {
    "path": "client/app/components/QueryBasedParameterInput.jsx",
    "content": "import { find, isArray, get, first, map, intersection, isEqual, isEmpty } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport SelectWithVirtualScroll from \"@/components/SelectWithVirtualScroll\";\n\nexport default class QueryBasedParameterInput extends React.Component {\n  static propTypes = {\n    parameter: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n    value: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n    mode: PropTypes.oneOf([\"default\", \"multiple\"]),\n    queryId: PropTypes.number,\n    onSelect: PropTypes.func,\n    className: PropTypes.string,\n  };\n\n  static defaultProps = {\n    value: null,\n    mode: \"default\",\n    parameter: null,\n    queryId: null,\n    onSelect: () => {},\n    className: \"\",\n  };\n\n  constructor(props) {\n    super(props);\n    this.state = {\n      options: [],\n      value: null,\n      loading: false,\n    };\n  }\n\n  componentDidMount() {\n    this._loadOptions(this.props.queryId);\n  }\n\n  componentDidUpdate(prevProps) {\n    if (this.props.queryId !== prevProps.queryId) {\n      this._loadOptions(this.props.queryId);\n    }\n    if (this.props.value !== prevProps.value) {\n      this.setValue(this.props.value);\n    }\n  }\n\n  setValue(value) {\n    const { options } = this.state;\n    if (this.props.mode === \"multiple\") {\n      value = isArray(value) ? value : [value];\n      const optionValues = map(options, option => option.value);\n      const validValues = intersection(value, optionValues);\n      this.setState({ value: validValues });\n      return validValues;\n    }\n    const found = find(options, option => option.value === this.props.value) !== undefined;\n    value = found ? value : get(first(options), \"value\");\n    this.setState({ value });\n    return value;\n  }\n\n  async _loadOptions(queryId) {\n    if (queryId && queryId !== this.state.queryId) {\n      this.setState({ loading: true });\n      const options = await this.props.parameter.loadDropdownValues();\n\n      // stale queryId check\n      if (this.props.queryId === queryId) {\n        this.setState({ options, loading: false }, () => {\n          const updatedValue = this.setValue(this.props.value);\n          if (!isEqual(updatedValue, this.props.value)) {\n            this.props.onSelect(updatedValue);\n          }\n        });\n      }\n    }\n  }\n\n  render() {\n    const { className, mode, onSelect, queryId, value, ...otherProps } = this.props;\n    const { loading, options } = this.state;\n    return (\n      <span>\n        <SelectWithVirtualScroll\n          className={className}\n          disabled={loading}\n          loading={loading}\n          mode={mode}\n          value={this.state.value}\n          onChange={onSelect}\n          options={map(options, ({ value, name }) => ({ label: String(name), value }))}\n          showSearch\n          showArrow\n          notFoundContent={isEmpty(options) ? \"No options available\" : null}\n          {...otherProps}\n        />\n      </span>\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/components/QueryLink.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { VisualizationType } from \"@redash/viz/lib\";\nimport Link from \"@/components/Link\";\nimport VisualizationName from \"@/components/visualizations/VisualizationName\";\n\nimport \"./QueryLink.less\";\n\nfunction QueryLink({ query, visualization, readOnly }) {\n  const getUrl = () => {\n    let hash = null;\n    if (visualization) {\n      if (visualization.type === \"TABLE\") {\n        // link to hard-coded table tab instead of the (hidden) visualization tab\n        hash = \"table\";\n      } else {\n        hash = visualization.id;\n      }\n    }\n\n    return query.getUrl(false, hash);\n  };\n\n  const QueryLinkWrapper = props => (readOnly ? <span {...props} /> : <Link href={getUrl()} {...props} />);\n\n  return (\n    <QueryLinkWrapper className=\"query-link\">\n      <VisualizationName visualization={visualization} /> <span>{query.name}</span>\n    </QueryLinkWrapper>\n  );\n}\n\nQueryLink.propTypes = {\n  query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  visualization: VisualizationType,\n  readOnly: PropTypes.bool,\n};\n\nQueryLink.defaultProps = {\n  visualization: null,\n  readOnly: false,\n};\n\nexport default QueryLink;\n"
  },
  {
    "path": "client/app/components/QueryLink.less",
    "content": ".query-link {\n  .visualization-name {\n    font-size: 15px;\n    font-weight: 500;\n    color: rgba(0, 0, 0, 0.8);\n  }\n}"
  },
  {
    "path": "client/app/components/QuerySelector.jsx",
    "content": "import { find } from \"lodash\";\nimport React, { useState, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport Input from \"antd/lib/input\";\nimport Select from \"antd/lib/select\";\nimport { Query } from \"@/services/query\";\nimport PlainButton from \"@/components/PlainButton\";\nimport notification from \"@/services/notification\";\nimport { QueryTagsControl } from \"@/components/tags-control/TagsControl\";\nimport useSearchResults from \"@/lib/hooks/useSearchResults\";\n\nconst { Option } = Select;\nfunction search(term) {\n  if (term === null) {\n    return Promise.resolve(null);\n  }\n\n  // get recent\n  if (!term) {\n    return Query.recent().then(results => results.filter(item => !item.is_draft)); // filter out draft\n  }\n\n  // search by query\n  return Query.query({ q: term }).then(({ results }) => results);\n}\n\nexport default function QuerySelector(props) {\n  const [searchTerm, setSearchTerm] = useState(\"\");\n  const [selectedQuery, setSelectedQuery] = useState();\n  const [doSearch, searchResults, searching] = useSearchResults(search, { initialResults: [] });\n\n  const placeholder = \"Search a query by name\";\n  const clearIcon = (\n    <i\n      className=\"fa fa-times hide-in-percy\"\n      role=\"button\"\n      tabIndex={0}\n      aria-label=\"Clear\"\n      onClick={() => selectQuery(null)}\n    />\n  );\n  const spinIcon = (\n    <span role=\"status\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n      <i className={cx(\"fa fa-spinner fa-pulse hide-in-percy\", { hidden: !searching })} aria-hidden=\"true\" />\n      <span className=\"sr-only\">Searching...</span>\n    </span>\n  );\n\n  useEffect(() => {\n    doSearch(searchTerm);\n  }, [doSearch, searchTerm]);\n\n  // set selected from prop\n  useEffect(() => {\n    if (props.selectedQuery) {\n      setSelectedQuery(props.selectedQuery);\n    }\n  }, [props.selectedQuery]);\n\n  function selectQuery(queryId) {\n    let query = null;\n    if (queryId) {\n      query = find(searchResults, { id: queryId });\n      if (!query) {\n        // shouldn't happen\n        notification.error(\"Something went wrong...\", \"Couldn't select query\");\n      }\n    }\n\n    setSearchTerm(query ? null : \"\"); // empty string triggers recent fetch\n    setSelectedQuery(query);\n    props.onChange(query);\n  }\n\n  function renderResults() {\n    if (!searchResults.length) {\n      return <div className=\"text-muted\">No results matching search term.</div>;\n    }\n\n    return (\n      <ul className=\"list-group\">\n        {searchResults.map(q => (\n          <PlainButton\n            className={cx(\"query-selector-result\", \"list-group-item\", { inactive: q.is_draft })}\n            key={q.id}\n            role=\"listitem\"\n            onClick={() => selectQuery(q.id)}\n            data-test={`QueryId${q.id}`}>\n            {q.name} <QueryTagsControl isDraft={q.is_draft} tags={q.tags} className=\"inline-tags-control\" />\n          </PlainButton>\n        ))}\n      </ul>\n    );\n  }\n\n  if (props.disabled) {\n    return (\n      <Input value={selectedQuery && selectedQuery.name} aria-label=\"Tied query\" placeholder={placeholder} disabled />\n    );\n  }\n\n  if (props.type === \"select\") {\n    const suffixIcon = selectedQuery ? clearIcon : null;\n    const value = selectedQuery ? selectedQuery.name : searchTerm;\n\n    return (\n      <Select\n        showSearch\n        dropdownMatchSelectWidth={false}\n        placeholder={placeholder}\n        value={value || undefined} // undefined for the placeholder to show\n        onSearch={setSearchTerm}\n        onChange={selectQuery}\n        suffixIcon={searching ? spinIcon : suffixIcon}\n        notFoundContent={null}\n        filterOption={false}\n        defaultActiveFirstOption={false}\n        className={props.className}\n        data-test=\"QuerySelector\">\n        {searchResults &&\n          searchResults.map(q => {\n            const disabled = q.is_draft;\n            return (\n              <Option\n                value={q.id}\n                key={q.id}\n                disabled={disabled}\n                className=\"query-selector-result\"\n                data-test={`QueryId${q.id}`}>\n                {q.name}{\" \"}\n                <QueryTagsControl\n                  isDraft={q.is_draft}\n                  tags={q.tags}\n                  className={cx(\"inline-tags-control\", { disabled })}\n                />\n              </Option>\n            );\n          })}\n      </Select>\n    );\n  }\n\n  return (\n    <span data-test=\"QuerySelector\">\n      {selectedQuery ? (\n        <Input value={selectedQuery.name} aria-label=\"Tied query\" suffix={clearIcon} readOnly />\n      ) : (\n        <Input\n          placeholder={placeholder}\n          value={searchTerm}\n          aria-label=\"Tied query\"\n          onChange={e => setSearchTerm(e.target.value)}\n          suffix={spinIcon}\n        />\n      )}\n      <div className=\"scrollbox\" style={{ maxHeight: \"50vh\", marginTop: 15 }}>\n        {searchResults && renderResults()}\n      </div>\n    </span>\n  );\n}\n\nQuerySelector.propTypes = {\n  onChange: PropTypes.func.isRequired,\n  selectedQuery: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  type: PropTypes.oneOf([\"select\", \"default\"]),\n  className: PropTypes.string,\n  disabled: PropTypes.bool,\n};\n\nQuerySelector.defaultProps = {\n  selectedQuery: null,\n  type: \"default\",\n  className: null,\n  disabled: false,\n};\n"
  },
  {
    "path": "client/app/components/Resizable/index.jsx",
    "content": "import d3 from \"d3\";\nimport React, { useRef, useMemo, useCallback, useState, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { Resizable as ReactResizable } from \"react-resizable\";\nimport KeyboardShortcuts from \"@/services/KeyboardShortcuts\";\n\nimport \"./index.less\";\n\nexport default function Resizable({ toggleShortcut, direction, sizeAttribute, children }) {\n  const [size, setSize] = useState(0);\n  const elementRef = useRef();\n  const wasUsingTouchEventsRef = useRef(false);\n  const wasResizedRef = useRef(false);\n\n  const sizeProp = direction === \"horizontal\" ? \"width\" : \"height\";\n  sizeAttribute = sizeAttribute || sizeProp;\n\n  const getElementSize = useCallback(() => {\n    if (!elementRef.current) {\n      return 0;\n    }\n    return Math.floor(elementRef.current.getBoundingClientRect()[sizeProp]);\n  }, [sizeProp]);\n\n  const savedSize = useRef(null);\n  const toggle = useCallback(() => {\n    if (!elementRef.current) {\n      return;\n    }\n\n    const element = d3.select(elementRef.current);\n    let targetSize;\n    if (savedSize.current === null) {\n      targetSize = \"0px\";\n      savedSize.current = `${getElementSize()}px`;\n    } else {\n      targetSize = savedSize.current;\n      savedSize.current = null;\n    }\n\n    element\n      .style(sizeAttribute, savedSize.current || \"0px\")\n      .transition()\n      .duration(200)\n      .ease(\"swing\")\n      .style(sizeAttribute, targetSize);\n\n    // update state to new element's size\n    setSize(parseInt(targetSize) || 0);\n  }, [getElementSize, sizeAttribute]);\n\n  const resizeHandle = useMemo(\n    () => (\n      // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions\n      <span\n        className={`react-resizable-handle react-resizable-handle-${direction}`}\n        role=\"separator\"\n        onClick={() => {\n          // TODO: add key controls\n          // On desktops resize uses `mousedown`/`mousemove`/`mouseup` events, and there is a conflict\n          // with this `click` handler: after user releases mouse - this handler will be executed.\n          // So we use `wasResized` flag to check if there was actual resize or user just pressed and released\n          // left mouse button (see also resize event handlers where ths flag is set).\n          // On mobile devices `touchstart`/`touchend` events wll be used, so it's safe to just execute this handler.\n          // To detect which set of events was actually used during particular resize operation, we pass\n          // `onMouseDown` handler to draggable core and check event type there (see also that handler's code).\n          if (wasUsingTouchEventsRef.current || !wasResizedRef.current) {\n            toggle();\n          }\n          wasUsingTouchEventsRef.current = false;\n          wasResizedRef.current = false;\n        }}\n      />\n    ),\n    [direction, toggle]\n  );\n\n  useEffect(() => {\n    if (toggleShortcut) {\n      const shortcuts = {\n        [toggleShortcut]: toggle,\n      };\n\n      KeyboardShortcuts.bind(shortcuts);\n      return () => {\n        KeyboardShortcuts.unbind(shortcuts);\n      };\n    }\n  }, [toggleShortcut, toggle]);\n\n  const resizeEventHandlers = useMemo(\n    () => ({\n      onResizeStart: () => {\n        // use element's size as initial value (it will also check constraints set in CSS)\n        // updated here and in `draggableCore::onMouseDown` handler to ensure that right value will be used\n        setSize(getElementSize());\n      },\n      onResize: (unused, data) => {\n        // update element directly for better UI responsiveness\n        d3.select(elementRef.current).style(sizeAttribute, `${data.size[sizeProp]}px`);\n        setSize(data.size[sizeProp]);\n        wasResizedRef.current = true;\n      },\n      onResizeStop: () => {\n        if (wasResizedRef.current) {\n          savedSize.current = null;\n        }\n      },\n    }),\n    [sizeProp, getElementSize, sizeAttribute]\n  );\n\n  const draggableCoreOptions = useMemo(\n    () => ({\n      onMouseDown: e => {\n        // In some cases this handler is executed twice during the same resize operation - first time\n        // with `touchstart` event and second time with `mousedown` (probably emulated by browser).\n        // Therefore we set the flag only when we receive `touchstart` because in ths case it's definitely\n        // mobile browser (desktop browsers will also send `mousedown` but never `touchstart`).\n        if (e.type === \"touchstart\") {\n          wasUsingTouchEventsRef.current = true;\n        }\n\n        // use element's size as initial value (it will also check constraints set in CSS)\n        // updated here and in `onResizeStart` handler to ensure that right value will be used\n        setSize(getElementSize());\n      },\n    }),\n    [getElementSize]\n  );\n\n  if (!children) {\n    return null;\n  }\n\n  children = React.createElement(children.type, { ...children.props, ref: elementRef });\n\n  return (\n    <ReactResizable\n      className=\"resizable-component\"\n      axis={direction === \"horizontal\" ? \"x\" : \"y\"}\n      resizeHandles={[direction === \"horizontal\" ? \"e\" : \"s\"]}\n      handle={resizeHandle}\n      width={direction === \"horizontal\" ? size : 0}\n      height={direction === \"vertical\" ? size : 0}\n      minConstraints={[0, 0]}\n      {...resizeEventHandlers}\n      draggableOpts={draggableCoreOptions}>\n      {children}\n    </ReactResizable>\n  );\n}\n\nResizable.propTypes = {\n  direction: PropTypes.oneOf([\"horizontal\", \"vertical\"]),\n  sizeAttribute: PropTypes.string,\n  toggleShortcut: PropTypes.string,\n  children: PropTypes.element,\n};\n\nResizable.defaultProps = {\n  direction: \"horizontal\",\n  sizeAttribute: null, // \"width\"/\"height\" - depending on `direction`\n  toggleShortcut: null,\n  children: null,\n};\n"
  },
  {
    "path": "client/app/components/Resizable/index.less",
    "content": "@import (reference, less) \"~@/assets/less/inc/variables.less\";\n\n.resizable-component.react-resizable {\n  position: relative;\n\n  .react-resizable-handle {\n    position: absolute;\n    background: #fff;\n    margin: 0;\n    padding: 0;\n\n    display: flex;\n    align-items: center;\n    justify-content: center;\n\n    &:hover,\n    &:active {\n      background: mix(@redash-gray, #fff, 6%);\n    }\n\n    &.react-resizable-handle-horizontal {\n      cursor: col-resize;\n      width: 10px;\n      height: auto;\n      right: 0;\n      top: 0;\n      bottom: 0;\n\n      &:before {\n        content: \"\";\n        display: inline-block;\n        width: 3px;\n        height: 25px;\n        border-left: 1px solid #ccc;\n        border-right: 1px solid #ccc;\n      }\n    }\n\n    &.react-resizable-handle-vertical {\n      cursor: row-resize;\n      width: auto;\n      height: 10px;\n      left: 0;\n      right: 0;\n      bottom: 0;\n\n      &:before {\n        content: \"\";\n        display: inline-block;\n        width: 25px;\n        height: 3px;\n        border-top: 1px solid #ccc;\n        border-bottom: 1px solid #ccc;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/SelectItemsDialog.jsx",
    "content": "import { filter, find, isEmpty, size } from \"lodash\";\nimport React, { useState, useCallback, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport classNames from \"classnames\";\nimport Modal from \"antd/lib/modal\";\nimport Input from \"antd/lib/input\";\nimport List from \"antd/lib/list\";\nimport Button from \"antd/lib/button\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport BigMessage from \"@/components/BigMessage\";\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport notification from \"@/services/notification\";\nimport useSearchResults from \"@/lib/hooks/useSearchResults\";\n\nimport \"./SelectItemsDialog.less\";\n\nfunction ItemsList({ items, renderItem, onItemClick }) {\n  const renderListItem = useCallback(\n    item => {\n      const { content, className, isDisabled } = renderItem(item);\n\n      return (\n        <List.Item\n          className={classNames(\"select-items-list\", \"w-100\", \"p-l-10\", \"p-r-10\", { disabled: isDisabled }, className)}\n          onClick={isDisabled ? null : () => onItemClick(item)}>\n          {content}\n        </List.Item>\n      );\n    },\n    [renderItem, onItemClick]\n  );\n\n  return <List size=\"small\" dataSource={items} renderItem={renderListItem} />;\n}\n\nItemsList.propTypes = {\n  items: PropTypes.array,\n  renderItem: PropTypes.func,\n  onItemClick: PropTypes.func,\n};\n\nItemsList.defaultProps = {\n  items: [],\n  renderItem: () => {},\n  onItemClick: () => {},\n};\n\nfunction SelectItemsDialog({\n  dialog,\n  dialogTitle,\n  inputPlaceholder,\n  itemKey,\n  renderItem,\n  renderStagedItem,\n  searchItems,\n  selectedItemsTitle,\n  width,\n  showCount,\n  extraFooterContent,\n}) {\n  const [selectedItems, setSelectedItems] = useState([]);\n  const [search, items, isLoading] = useSearchResults(searchItems, { initialResults: [] });\n  const hasResults = items.length > 0;\n\n  useEffect(() => {\n    search();\n  }, [search]);\n\n  const isItemSelected = useCallback(\n    item => {\n      const key = itemKey(item);\n      return !!find(selectedItems, i => itemKey(i) === key);\n    },\n    [selectedItems, itemKey]\n  );\n\n  const toggleItem = useCallback(\n    item => {\n      if (isItemSelected(item)) {\n        const key = itemKey(item);\n        setSelectedItems(filter(selectedItems, i => itemKey(i) !== key));\n      } else {\n        setSelectedItems([...selectedItems, item]);\n      }\n    },\n    [selectedItems, itemKey, isItemSelected]\n  );\n\n  const save = useCallback(() => {\n    dialog.close(selectedItems).catch(error => {\n      if (error) {\n        notification.error(\"Failed to save some of selected items.\");\n      }\n    });\n  }, [dialog, selectedItems]);\n\n  return (\n    <Modal\n      {...dialog.props}\n      className=\"select-items-dialog\"\n      width={width}\n      title={dialogTitle}\n      footer={\n        <div className=\"d-flex align-items-center\">\n          <span className=\"flex-fill m-r-5\" style={{ textAlign: \"left\", color: \"rgba(0, 0, 0, 0.5)\" }}>\n            {extraFooterContent}\n          </span>\n          <Button {...dialog.props.cancelButtonProps} onClick={dialog.dismiss}>\n            Cancel\n          </Button>\n          <Button\n            {...dialog.props.okButtonProps}\n            onClick={save}\n            disabled={selectedItems.length === 0 || dialog.props.okButtonProps.disabled}\n            type=\"primary\">\n            Save\n            {showCount && !isEmpty(selectedItems) ? ` (${size(selectedItems)})` : null}\n          </Button>\n        </div>\n      }>\n      <div className=\"d-flex align-items-center m-b-10\">\n        <div className=\"flex-fill\">\n          <Input.Search\n            onChange={event => search(event.target.value)}\n            placeholder={inputPlaceholder}\n            aria-label={inputPlaceholder}\n            autoFocus\n          />\n        </div>\n        {renderStagedItem && (\n          <div className=\"w-50 m-l-20\">\n            <h5 className=\"m-0\">{selectedItemsTitle}</h5>\n          </div>\n        )}\n      </div>\n\n      <div className=\"d-flex align-items-stretch\" style={{ minHeight: \"30vh\", maxHeight: \"50vh\" }}>\n        <div className=\"flex-fill scrollbox\">\n          {isLoading && <LoadingState className=\"\" />}\n          {!isLoading && !hasResults && (\n            <BigMessage icon=\"fa-search\" message=\"No items match your search.\" className=\"\" />\n          )}\n          {!isLoading && hasResults && (\n            <ItemsList\n              items={items}\n              renderItem={item => renderItem(item, { isSelected: isItemSelected(item) })}\n              onItemClick={toggleItem}\n            />\n          )}\n        </div>\n        {renderStagedItem && (\n          <div className=\"w-50 m-l-20 scrollbox\">\n            {selectedItems.length > 0 && (\n              <ItemsList\n                items={selectedItems}\n                renderItem={item => renderStagedItem(item, { isSelected: true })}\n                onItemClick={toggleItem}\n              />\n            )}\n          </div>\n        )}\n      </div>\n    </Modal>\n  );\n}\n\nSelectItemsDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n  dialogTitle: PropTypes.string,\n  inputPlaceholder: PropTypes.string,\n  selectedItemsTitle: PropTypes.string,\n  searchItems: PropTypes.func.isRequired, // (searchTerm: string): Promise<Items[]> if `searchTerm === ''` load all\n  itemKey: PropTypes.func, // (item) => string|number - return key of item (by default `id`)\n  // left list\n  // (item, { isSelected }) => {\n  //   content: node, // item contents\n  //   className: string = '', // additional class for item wrapper\n  //   isDisabled: bool = false, // is item clickable or disabled\n  // }\n  renderItem: PropTypes.func,\n  // right list; args/results save as for `renderItem`. if not specified - `renderItem` will be used\n  renderStagedItem: PropTypes.func,\n  width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n  extraFooterContent: PropTypes.node,\n  showCount: PropTypes.bool,\n};\n\nSelectItemsDialog.defaultProps = {\n  dialogTitle: \"Add Items\",\n  inputPlaceholder: \"Search...\",\n  selectedItemsTitle: \"Selected items\",\n  itemKey: item => item.id,\n  renderItem: () => \"\",\n  renderStagedItem: null, // hidden by default\n  width: \"80%\",\n  extraFooterContent: null,\n  showCount: false,\n};\n\nexport default wrapDialog(SelectItemsDialog);\n"
  },
  {
    "path": "client/app/components/SelectItemsDialog.less",
    "content": ".select-items-list {\n  &:hover,\n  &:focus,\n  &:focus-within {\n    color: #555;\n    background-color: #f5f5f5;\n    transition: all 150ms ease-in-out;\n  }\n}\n"
  },
  {
    "path": "client/app/components/SelectWithVirtualScroll.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport { maxBy } from \"lodash\";\nimport AntdSelect, { SelectProps, LabeledValue } from \"antd/lib/select\";\nimport { calculateTextWidth } from \"@/lib/calculateTextWidth\";\n\nconst MIN_LEN_FOR_VIRTUAL_SCROLL = 400;\n\ninterface VirtualScrollLabeledValue extends LabeledValue {\n  label: string;\n}\n\ninterface VirtualScrollSelectProps extends Omit<SelectProps<string>, \"optionFilterProp\" | \"children\"> {\n  options: Array<VirtualScrollLabeledValue>;\n}\nfunction SelectWithVirtualScroll({ options, ...props }: VirtualScrollSelectProps): JSX.Element {\n  const dropdownMatchSelectWidth = useMemo<number | boolean>(() => {\n    if (options && options.length > MIN_LEN_FOR_VIRTUAL_SCROLL) {\n      const largestOpt = maxBy(options, \"label.length\");\n\n      if (largestOpt) {\n        const offset = 40;\n        const optionText = largestOpt.label;\n        const width = calculateTextWidth(optionText);\n        if (width) {\n          return width + offset;\n        }\n      }\n\n      return true;\n    }\n\n    return false;\n  }, [options]);\n\n  return (\n    <AntdSelect<string>\n      dropdownMatchSelectWidth={dropdownMatchSelectWidth}\n      options={options}\n      allowClear={true}\n      optionFilterProp=\"label\" // as this component expects \"options\" prop\n      {...props}\n    />\n  );\n}\n\nexport default SelectWithVirtualScroll;\n"
  },
  {
    "path": "client/app/components/SettingsWrapper.jsx",
    "content": "import React from \"react\";\nimport Menu from \"antd/lib/menu\";\nimport PageHeader from \"@/components/PageHeader\";\nimport Link from \"@/components/Link\";\nimport location from \"@/services/location\";\nimport settingsMenu from \"@/services/settingsMenu\";\n\nfunction wrapSettingsTab(id, options, WrappedComponent) {\n  settingsMenu.add(id, options);\n\n  return function SettingsTab(props) {\n    const activeItem = settingsMenu.getActiveItem(location.path);\n    return (\n      <div className=\"settings-screen\">\n        <div className=\"container\">\n          <PageHeader title=\"Settings\" />\n          <div className=\"bg-white tiled\">\n            <Menu selectedKeys={[activeItem && activeItem.title]} selectable={false} mode=\"horizontal\">\n              {settingsMenu.getAvailableItems().map(item => (\n                <Menu.Item key={item.title}>\n                  <Link href={item.path} data-test=\"SettingsScreenItem\">\n                    {item.title}\n                  </Link>\n                </Menu.Item>\n              ))}\n            </Menu>\n            <div className=\"p-15\">\n              <div>\n                <WrappedComponent {...props} />\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    );\n  };\n}\n\nexport default wrapSettingsTab;\n"
  },
  {
    "path": "client/app/components/TagsList.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\";\n\n.tags-list {\n  .tags-list-title {\n    margin: 15px 5px 5px 5px;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n\n    .tags-list-label {\n      display: block;\n      white-space: nowrap;\n      margin: 0;\n    }\n\n    a,\n    .plain-button {\n      display: block;\n      white-space: nowrap;\n      cursor: pointer;\n\n      .anticon {\n        font-size: 75%;\n        margin-right: 2px;\n      }\n    }\n  }\n\n  .ant-badge-count {\n    background-color: fade(@redash-gray, 10%);\n    color: fade(@redash-gray, 75%);\n  }\n\n  .ant-menu.ant-menu-inline {\n    border: none;\n\n    .ant-menu-item {\n      width: 100%;\n    }\n\n    .ant-menu-item-selected {\n      .ant-badge-count {\n        background-color: @primary-color;\n        color: white;\n      }\n    }\n\n    .ant-menu-item {\n      &:hover,\n      &:active,\n      &:focus,\n      &:focus-within {\n        color: @primary-color;\n        transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/TagsList.tsx",
    "content": "import { map, includes, difference } from \"lodash\";\nimport React, { useState, useCallback, useEffect } from \"react\";\nimport Badge from \"antd/lib/badge\";\nimport Menu from \"antd/lib/menu\";\nimport CloseOutlinedIcon from \"@ant-design/icons/CloseOutlined\";\nimport getTags from \"@/services/getTags\";\nimport PlainButton from \"@/components/PlainButton\";\n\nimport \"./TagsList.less\";\n\ntype Tag = {\n  name: string;\n  count?: number;\n};\n\ntype TagsListProps = {\n  tagsUrl: string;\n  showUnselectAll: boolean;\n  onUpdate?: (selectedTags: string[]) => void;\n};\n\nfunction TagsList({ tagsUrl, showUnselectAll = false, onUpdate }: TagsListProps): JSX.Element | null {\n  const [allTags, setAllTags] = useState<Tag[]>([]);\n  const [selectedTags, setSelectedTags] = useState<string[]>([]);\n\n  useEffect(() => {\n    let isCancelled = false;\n\n    getTags(tagsUrl).then(tags => {\n      if (!isCancelled) {\n        setAllTags(tags);\n      }\n    });\n\n    return () => {\n      isCancelled = true;\n    };\n  }, [tagsUrl]);\n\n  const toggleTag = useCallback(\n    (event, tag) => {\n      let newSelectedTags;\n      if (event.shiftKey) {\n        // toggle tag\n        if (includes(selectedTags, tag)) {\n          newSelectedTags = difference(selectedTags, [tag]);\n        } else {\n          newSelectedTags = [...selectedTags, tag];\n        }\n      } else {\n        // if the tag is the only selected, deselect it, otherwise select only it\n        if (includes(selectedTags, tag) && selectedTags.length === 1) {\n          newSelectedTags = [];\n        } else {\n          newSelectedTags = [tag];\n        }\n      }\n\n      setSelectedTags(newSelectedTags);\n      if (onUpdate) {\n        onUpdate([...newSelectedTags]);\n      }\n    },\n    [selectedTags, onUpdate]\n  );\n\n  const unselectAll = useCallback(() => {\n    setSelectedTags([]);\n    if (onUpdate) {\n      onUpdate([]);\n    }\n  }, [onUpdate]);\n\n  if (allTags.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"tags-list\">\n      <div className=\"tags-list-title\">\n        <span className=\"tags-list-label\">Tags</span>\n        {showUnselectAll && selectedTags.length > 0 && (\n          <PlainButton type=\"link\" onClick={unselectAll}>\n            <CloseOutlinedIcon />\n            clear selection\n          </PlainButton>\n        )}\n      </div>\n\n      <div className=\"tiled\">\n        <Menu className=\"invert-stripe-position\" mode=\"inline\" selectedKeys={selectedTags}>\n          {map(allTags, tag => (\n            <Menu.Item key={tag.name} className=\"m-0\">\n              <PlainButton\n                className=\"d-flex align-items-center justify-content-between\"\n                onClick={event => toggleTag(event, tag.name)}>\n                <span className=\"max-character col-xs-11\">{tag.name}</span>\n                <Badge count={tag.count} />\n              </PlainButton>\n            </Menu.Item>\n          ))}\n        </Menu>\n      </div>\n    </div>\n  );\n}\n\nexport default TagsList;\n"
  },
  {
    "path": "client/app/components/TimeAgo.jsx",
    "content": "import moment from \"moment\";\nimport { isNil } from \"lodash\";\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { Moment } from \"@/components/proptypes\";\nimport { clientConfig } from \"@/services/auth\";\nimport Tooltip from \"@/components/Tooltip\";\n\nfunction toMoment(value) {\n  value = !isNil(value) ? moment(value) : null;\n  return value && value.isValid() ? value : null;\n}\n\nexport default function TimeAgo({ date, placeholder, autoUpdate, variation }) {\n  const startDate = toMoment(date);\n  const [value, setValue] = useState(null);\n  const title = useMemo(() => (startDate ? startDate.format(clientConfig.dateTimeFormat) : null), [startDate]);\n\n  useEffect(() => {\n    function update() {\n      setValue(startDate ? startDate.fromNow() : placeholder);\n    }\n    update();\n\n    if (autoUpdate) {\n      const timer = setInterval(update, 30 * 1000);\n      return () => clearInterval(timer);\n    }\n  }, [autoUpdate, startDate, placeholder]);\n\n  if (variation === \"timeAgoInTooltip\") {\n    return (\n      <Tooltip title={value}>\n        <span data-test=\"TimeAgo\">{title}</span>\n      </Tooltip>\n    );\n  }\n  return (\n    <Tooltip title={title}>\n      <span data-test=\"TimeAgo\">{value}</span>\n    </Tooltip>\n  );\n}\n\nTimeAgo.propTypes = {\n  date: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Date), Moment]),\n  placeholder: PropTypes.string,\n  autoUpdate: PropTypes.bool,\n  variation: PropTypes.oneOf([\"timeAgoInTooltip\"]),\n};\n\nTimeAgo.defaultProps = {\n  date: null,\n  placeholder: \"\",\n  autoUpdate: true,\n};\n"
  },
  {
    "path": "client/app/components/Timer.jsx",
    "content": "import React, { useMemo, useState, useEffect } from \"react\";\nimport moment from \"moment\";\nimport PropTypes from \"prop-types\";\nimport { Moment } from \"@/components/proptypes\";\n\nexport default function Timer({ from }) {\n  const startTime = useMemo(() => moment(from).valueOf(), [from]);\n  const [value, setValue] = useState(null);\n\n  useEffect(() => {\n    function update() {\n      const diff = moment.now() - startTime;\n      const format = diff > 1000 * 60 * 60 ? \"HH:mm:ss\" : \"mm:ss\"; // no HH under an hour\n      setValue(moment.utc(diff).format(format));\n    }\n    update();\n\n    const timer = setInterval(update, 1000);\n    return () => clearInterval(timer);\n  }, [startTime]);\n\n  return <span className=\"rd-timer\">{value}</span>;\n}\n\nTimer.propTypes = {\n  from: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Date), Moment]),\n};\n\nTimer.defaultProps = {\n  from: null,\n};\n"
  },
  {
    "path": "client/app/components/Tooltip.tsx",
    "content": "import React from \"react\";\nimport AntTooltip, { TooltipProps } from \"antd/lib/tooltip\";\nimport { isNil } from \"lodash\";\n\nexport default function Tooltip({ title, ...restProps }: TooltipProps) {\n  const liveTitle = !isNil(title) ? (\n    <span role=\"status\" aria-live=\"assertive\" aria-relevant=\"additions\">\n      {title}\n    </span>\n  ) : null;\n\n  return <AntTooltip trigger={[\"hover\", \"focus\"]} title={liveTitle} {...restProps} />;\n}\n"
  },
  {
    "path": "client/app/components/UserGroups.jsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Tag from \"antd/lib/tag\";\nimport Link from \"@/components/Link\";\n\nimport \"./UserGroups.less\";\n\nexport default function UserGroups({ groups, linkGroups, ...props }) {\n  return (\n    <div className=\"user-groups\" {...props}>\n      {map(groups, group => (\n        <Tag key={group.id}>{linkGroups ? <Link href={`groups/${group.id}`}>{group.name}</Link> : group.name}</Tag>\n      ))}\n    </div>\n  );\n}\n\nUserGroups.propTypes = {\n  groups: PropTypes.arrayOf(\n    PropTypes.shape({\n      id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,\n      name: PropTypes.string,\n    })\n  ),\n  linkGroups: PropTypes.bool,\n};\n\nUserGroups.defaultProps = {\n  groups: [],\n  linkGroups: true,\n};\n"
  },
  {
    "path": "client/app/components/UserGroups.less",
    "content": ".user-groups {\n  margin: -5px 0 0 -5px;\n\n  .ant-tag {\n    margin: 5px 0 0 5px;\n  }\n}\n"
  },
  {
    "path": "client/app/components/admin/Layout.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Menu from \"antd/lib/menu\";\nimport PageHeader from \"@/components/PageHeader\";\nimport Link from \"@/components/Link\";\n\nimport \"./layout.less\";\n\nexport default function Layout({ activeTab, children }) {\n  return (\n    <div className=\"admin-page-layout\">\n      <div className=\"container\">\n        <PageHeader title=\"Admin\" />\n        <div className=\"bg-white tiled\">\n          <Menu selectedKeys={[activeTab]} selectable={false} mode=\"horizontal\">\n            <Menu.Item key=\"system_status\">\n              <Link href=\"admin/status\">System Status</Link>\n            </Menu.Item>\n            <Menu.Item key=\"jobs\">\n              <Link href=\"admin/queries/jobs\">RQ Status</Link>\n            </Menu.Item>\n            <Menu.Item key=\"outdated_queries\">\n              <Link href=\"admin/queries/outdated\">Outdated Queries</Link>\n            </Menu.Item>\n          </Menu>\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nLayout.propTypes = {\n  activeTab: PropTypes.string,\n  children: PropTypes.node,\n};\n\nLayout.defaultProps = {\n  activeTab: \"system_status\",\n  children: null,\n};\n"
  },
  {
    "path": "client/app/components/admin/RQStatus.jsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport Badge from \"antd/lib/badge\";\nimport Card from \"antd/lib/card\";\nimport Spin from \"antd/lib/spin\";\nimport Table from \"antd/lib/table\";\nimport { Columns } from \"@/components/items-list/components/ItemsTable\";\n\n// CounterCard\n\nexport function CounterCard({ title, value, loading }) {\n  return (\n    <Spin spinning={loading}>\n      <Card>\n        {title}\n        <div className=\"f-20\">{value}</div>\n      </Card>\n    </Spin>\n  );\n}\n\nCounterCard.propTypes = {\n  title: PropTypes.string.isRequired,\n  value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),\n  loading: PropTypes.bool.isRequired,\n};\n\nCounterCard.defaultProps = {\n  value: \"\",\n};\n\n// Tables\n\nconst queryJobsColumns = [\n  { title: \"Queue\", dataIndex: \"origin\" },\n  { title: \"Query ID\", dataIndex: [\"meta\", \"query_id\"] },\n  { title: \"Org ID\", dataIndex: [\"meta\", \"org_id\"] },\n  { title: \"Data Source ID\", dataIndex: [\"meta\", \"data_source_id\"] },\n  { title: \"User ID\", dataIndex: [\"meta\", \"user_id\"] },\n  Columns.custom(scheduled => scheduled.toString(), { title: \"Scheduled\", dataIndex: [\"meta\", \"scheduled\"] }),\n  Columns.timeAgo({ title: \"Start Time\", dataIndex: \"started_at\" }),\n  Columns.timeAgo({ title: \"Enqueue Time\", dataIndex: \"enqueued_at\" }),\n];\n\nconst otherJobsColumns = [\n  { title: \"Queue\", dataIndex: \"origin\" },\n  { title: \"Job Name\", dataIndex: \"name\" },\n  Columns.timeAgo({ title: \"Start Time\", dataIndex: \"started_at\" }),\n  Columns.timeAgo({ title: \"Enqueue Time\", dataIndex: \"enqueued_at\" }),\n];\n\nconst workersColumns = [\n  Columns.custom(\n    value => (\n      <span>\n        <Badge status={{ busy: \"processing\", idle: \"default\", started: \"success\", suspended: \"warning\" }[value]} />{\" \"}\n        {value}\n      </span>\n    ),\n    { title: \"State\", dataIndex: \"state\" }\n  ),\n]\n  .concat(\n    map([\"Hostname\", \"PID\", \"Name\", \"Queues\", \"Current Job\", \"Successful Jobs\", \"Failed Jobs\"], c => ({\n      title: c,\n      dataIndex: c.toLowerCase().replace(/\\s/g, \"_\"),\n    }))\n  )\n  .concat([\n    Columns.dateTime({ title: \"Birth Date\", dataIndex: \"birth_date\" }),\n    Columns.duration({ title: \"Total Working Time\", dataIndex: \"total_working_time\" }),\n  ]);\n\nconst queuesColumns = map([\"Name\", \"Started\", \"Queued\"], c => ({ title: c, dataIndex: c.toLowerCase() }));\n\nconst TablePropTypes = {\n  loading: PropTypes.bool.isRequired,\n  items: PropTypes.arrayOf(PropTypes.object).isRequired,\n};\n\nexport function WorkersTable({ loading, items }) {\n  return (\n    <Table\n      loading={loading}\n      columns={workersColumns}\n      rowKey=\"name\"\n      dataSource={items}\n      pagination={{\n        defaultPageSize: 25,\n        pageSizeOptions: [\"10\", \"25\", \"50\"],\n        showSizeChanger: true,\n      }}\n    />\n  );\n}\n\nWorkersTable.propTypes = TablePropTypes;\n\nexport function QueuesTable({ loading, items }) {\n  return (\n    <Table\n      loading={loading}\n      columns={queuesColumns}\n      rowKey=\"name\"\n      dataSource={items}\n      pagination={{\n        defaultPageSize: 25,\n        pageSizeOptions: [\"10\", \"25\", \"50\"],\n        showSizeChanger: true,\n      }}\n    />\n  );\n}\n\nQueuesTable.propTypes = TablePropTypes;\n\nexport function QueryJobsTable({ loading, items }) {\n  return (\n    <Table\n      loading={loading}\n      columns={queryJobsColumns}\n      rowKey=\"id\"\n      dataSource={items}\n      pagination={{\n        defaultPageSize: 25,\n        pageSizeOptions: [\"10\", \"25\", \"50\"],\n        showSizeChanger: true,\n      }}\n    />\n  );\n}\n\nQueryJobsTable.propTypes = TablePropTypes;\n\nexport function OtherJobsTable({ loading, items }) {\n  return (\n    <Table\n      loading={loading}\n      columns={otherJobsColumns}\n      rowKey=\"id\"\n      dataSource={items}\n      pagination={{\n        defaultPageSize: 25,\n        pageSizeOptions: [\"10\", \"25\", \"50\"],\n        showSizeChanger: true,\n      }}\n    />\n  );\n}\n\nOtherJobsTable.propTypes = TablePropTypes;\n"
  },
  {
    "path": "client/app/components/admin/StatusBlock.jsx",
    "content": "/* eslint-disable react/prop-types */\n\nimport { toPairs } from \"lodash\";\nimport React from \"react\";\n\nimport List from \"antd/lib/list\";\nimport Card from \"antd/lib/card\";\nimport TimeAgo from \"@/components/TimeAgo\";\n\nimport { toHuman, prettySize } from \"@/lib/utils\";\n\nexport function General({ info }) {\n  info = toPairs(info);\n  return (\n    <Card title=\"General\" size=\"small\">\n      {info.length === 0 && <div className=\"text-muted text-center\">No data</div>}\n      {info.length > 0 && (\n        <List\n          size=\"small\"\n          itemLayout=\"vertical\"\n          dataSource={info}\n          renderItem={([name, value]) => (\n            <List.Item extra={<span className=\"badge\">{value}</span>}>{toHuman(name)}</List.Item>\n          )}\n        />\n      )}\n    </Card>\n  );\n}\n\nexport function DatabaseMetrics({ info }) {\n  return (\n    <Card title=\"Redash Database\" size=\"small\">\n      {info.length === 0 && <div className=\"text-muted text-center\">No data</div>}\n      {info.length > 0 && (\n        <List\n          size=\"small\"\n          itemLayout=\"vertical\"\n          dataSource={info}\n          renderItem={([name, size]) => (\n            <List.Item extra={<span className=\"badge\">{prettySize(size)}</span>}>{name}</List.Item>\n          )}\n        />\n      )}\n    </Card>\n  );\n}\n\nexport function Queues({ info }) {\n  info = toPairs(info);\n  return (\n    <Card title=\"Queues\" size=\"small\">\n      {info.length === 0 && <div className=\"text-muted text-center\">No data</div>}\n      {info.length > 0 && (\n        <List\n          size=\"small\"\n          itemLayout=\"vertical\"\n          dataSource={info}\n          renderItem={([name, queue]) => (\n            <List.Item extra={<span className=\"badge\">{queue.size}</span>}>{name}</List.Item>\n          )}\n        />\n      )}\n    </Card>\n  );\n}\n\nexport function Manager({ info }) {\n  const items = info\n    ? [\n        <List.Item\n          extra={\n            <span className=\"badge\">\n              <TimeAgo date={info.lastRefreshAt} placeholder=\"n/a\" />\n            </span>\n          }>\n          Last Refresh\n        </List.Item>,\n        <List.Item\n          extra={\n            <span className=\"badge\">\n              <TimeAgo date={info.startedAt} placeholder=\"n/a\" />\n            </span>\n          }>\n          Started\n        </List.Item>,\n        <List.Item extra={<span className=\"badge\">{info.outdatedQueriesCount}</span>}>\n          Outdated Queries Count\n        </List.Item>,\n      ]\n    : [];\n\n  return (\n    <Card title=\"Manager\" size=\"small\">\n      {!info && <div className=\"text-muted text-center\">No data</div>}\n      {info && <List size=\"small\" itemLayout=\"vertical\" dataSource={items} renderItem={item => item} />}\n    </Card>\n  );\n}\n"
  },
  {
    "path": "client/app/components/admin/layout.less",
    "content": ".admin-page-layout {\n  .ant-table {\n    overflow-x: auto;\n  }\n}\n"
  },
  {
    "path": "client/app/components/cards-list/CardsList.less",
    "content": "@import (reference, less) \"~@/assets/less/inc/variables\";\n\n.visual-card-list {\n  width: 100%;\n  margin: -5px 0 0 -5px; // compensate for .visual-card spacing\n}\n\n.visual-card {\n  background: #ffffff;\n  border: 1px solid fade(@redash-gray, 15%);\n  border-radius: 3px;\n  margin: 5px;\n  width: 212px;\n  padding: 15px 5px;\n  cursor: pointer;\n  box-shadow: none;\n  transition: transform 0.12s ease-out;\n  transition-duration: 0.3s;\n  transition-property: box-shadow;\n\n  display: flex;\n  align-items: center;\n\n  &:hover,\n  &:focus,\n  &:focus-within {\n    box-shadow: rgba(102, 136, 153, 0.15) 0px 4px 9px -3px;\n  }\n\n  img {\n    width: 64px !important;\n    height: 64px !important;\n    margin-right: 5px;\n  }\n\n  h3 {\n    font-size: 13px;\n    color: #323232;\n    margin: 0 !important;\n    text-overflow: ellipsis;\n    overflow: hidden;\n  }\n}\n\n@media (max-width: 1200px) {\n  .visual-card {\n    width: 217px;\n  }\n}\n\n@media (max-width: 755px) {\n  .visual-card {\n    width: 47%;\n  }\n}\n\n@media (max-width: 515px) {\n  .visual-card {\n    width: 47%;\n\n    img {\n      width: 48px;\n      height: 48px;\n    }\n  }\n}\n\n@media (max-width: 408px) {\n  .visual-card {\n    width: 100%;\n    padding: 5px;\n\n    img {\n      width: 48px;\n      height: 48px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/cards-list/CardsList.tsx",
    "content": "import { includes, isEmpty } from \"lodash\";\nimport PropTypes from \"prop-types\";\nimport React, { useState } from \"react\";\nimport Input from \"antd/lib/input\";\nimport Link from \"@/components/Link\";\nimport PlainButton from \"@/components/PlainButton\";\nimport EmptyState from \"@/components/items-list/components/EmptyState\";\n\nimport \"./CardsList.less\";\n\nexport interface CardsListItem {\n  title: string;\n  imgSrc: string;\n  href?: string;\n  onClick?: React.MouseEventHandler<HTMLElement>;\n}\n\nexport interface CardsListProps {\n  items?: CardsListItem[];\n  showSearch?: boolean;\n}\n\ninterface ListItemProps {\n  item: CardsListItem;\n  keySuffix: string;\n}\n\nfunction ListItem({ item, keySuffix }: ListItemProps) {\n  const commonProps = {\n    key: `card${keySuffix}`,\n    className: \"visual-card\",\n    onClick: item.onClick,\n    children: (\n      <>\n        <img alt={item.title} src={item.imgSrc} />\n        <h3>{item.title}</h3>\n      </>\n    ),\n  };\n\n  return item.href ? <Link href={item.href} {...commonProps} /> : <PlainButton type=\"link\" {...commonProps} />;\n}\n\nexport default function CardsList({ items = [], showSearch = false }: CardsListProps) {\n  const [searchText, setSearchText] = useState(\"\");\n  const filteredItems = items.filter(\n    item => isEmpty(searchText) || includes(item.title.toLowerCase(), searchText.toLowerCase())\n  );\n\n  return (\n    <div data-test=\"CardsList\">\n      {showSearch && (\n        <div className=\"row p-10\">\n          <div className=\"col-md-4 col-md-offset-4\">\n            <Input.Search\n              placeholder=\"Search...\"\n              aria-label=\"Search cards\"\n              onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchText(e.target.value)}\n              autoFocus\n            />\n          </div>\n        </div>\n      )}\n      {isEmpty(filteredItems) ? (\n        <EmptyState className=\"\" />\n      ) : (\n        <div className=\"row\">\n          <div className=\"col-lg-12 d-inline-flex flex-wrap visual-card-list\">\n            {filteredItems.map((item: CardsListItem, index: number) => (\n              <ListItem key={index} item={item} keySuffix={index.toString()} />\n            ))}\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n\nCardsList.propTypes = {\n  items: PropTypes.arrayOf(\n    PropTypes.shape({\n      title: PropTypes.string.isRequired,\n      imgSrc: PropTypes.string.isRequired,\n      onClick: PropTypes.func,\n      href: PropTypes.string,\n    })\n  ),\n  showSearch: PropTypes.bool,\n};\n"
  },
  {
    "path": "client/app/components/dashboards/AddWidgetDialog.jsx",
    "content": "import { map, includes, groupBy, first, find } from \"lodash\";\nimport React, { useState, useMemo, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Select from \"antd/lib/select\";\nimport Modal from \"antd/lib/modal\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport { MappingType, ParameterMappingListInput } from \"@/components/ParameterMappingInput\";\nimport QuerySelector from \"@/components/QuerySelector\";\nimport notification from \"@/services/notification\";\nimport { Query } from \"@/services/query\";\nimport { useUniqueId } from \"@/lib/hooks/useUniqueId\";\n\nfunction VisualizationSelect({ query, visualization, onChange }) {\n  const visualizationGroups = useMemo(() => {\n    return query ? groupBy(query.visualizations, \"type\") : {};\n  }, [query]);\n\n  const vizSelectId = useUniqueId(\"visualization-select\");\n\n  const handleChange = useCallback(\n    visualizationId => {\n      const selectedVisualization = query ? find(query.visualizations, { id: visualizationId }) : null;\n      onChange(selectedVisualization || null);\n    },\n    [query, onChange]\n  );\n\n  if (!query) {\n    return null;\n  }\n\n  return (\n    <div>\n      <div className=\"form-group\">\n        <label htmlFor={vizSelectId}>Choose Visualization</label>\n        <Select\n          id={vizSelectId}\n          className=\"w-100\"\n          value={visualization ? visualization.id : undefined}\n          onChange={handleChange}>\n          {map(visualizationGroups, (visualizations, groupKey) => (\n            <Select.OptGroup key={groupKey} label={groupKey}>\n              {map(visualizations, visualization => (\n                <Select.Option key={`${visualization.id}`} value={visualization.id}>\n                  {visualization.name}\n                </Select.Option>\n              ))}\n            </Select.OptGroup>\n          ))}\n        </Select>\n      </div>\n    </div>\n  );\n}\n\nVisualizationSelect.propTypes = {\n  query: PropTypes.object,\n  visualization: PropTypes.object,\n  onChange: PropTypes.func,\n};\n\nVisualizationSelect.defaultProps = {\n  query: null,\n  visualization: null,\n  onChange: () => {},\n};\n\nfunction AddWidgetDialog({ dialog, dashboard }) {\n  const [selectedQuery, setSelectedQuery] = useState(null);\n  const [selectedVisualization, setSelectedVisualization] = useState(null);\n  const [parameterMappings, setParameterMappings] = useState([]);\n\n  const selectQuery = useCallback(\n    queryId => {\n      // Clear previously selected query (if any)\n      setSelectedQuery(null);\n      setSelectedVisualization(null);\n      setParameterMappings([]);\n\n      if (queryId) {\n        Query.get({ id: queryId }).then(query => {\n          if (query) {\n            const existingParamNames = map(dashboard.getParametersDefs(), param => param.name);\n            setSelectedQuery(query);\n            setParameterMappings(\n              map(query.getParametersDefs(), param => ({\n                name: param.name,\n                type: includes(existingParamNames, param.name)\n                  ? MappingType.DashboardMapToExisting\n                  : MappingType.DashboardAddNew,\n                mapTo: param.name,\n                value: param.normalizedValue,\n                title: \"\",\n                param,\n              }))\n            );\n            if (query.visualizations.length > 0) {\n              setSelectedVisualization(first(query.visualizations));\n            }\n          }\n        });\n      }\n    },\n    [dashboard]\n  );\n\n  const saveWidget = useCallback(() => {\n    dialog.close({ visualization: selectedVisualization, parameterMappings }).catch(() => {\n      notification.error(\"Widget could not be added\");\n    });\n  }, [dialog, selectedVisualization, parameterMappings]);\n\n  const existingParams = dashboard.getParametersDefs();\n  const parameterMappingsId = useUniqueId(\"parameter-mappings\");\n\n  return (\n    <Modal\n      {...dialog.props}\n      title=\"Add Widget\"\n      onOk={saveWidget}\n      okButtonProps={{\n        ...dialog.props.okButtonProps,\n        disabled: !selectedQuery || dialog.props.okButtonProps.disabled,\n      }}\n      okText=\"Add to Dashboard\"\n      width={700}>\n      <div data-test=\"AddWidgetDialog\">\n        <QuerySelector onChange={query => selectQuery(query ? query.id : null)} />\n\n        {selectedQuery && (\n          <VisualizationSelect\n            query={selectedQuery}\n            visualization={selectedVisualization}\n            onChange={setSelectedVisualization}\n          />\n        )}\n\n        {parameterMappings.length > 0 && [\n          <label key=\"parameters-title\" htmlFor={parameterMappingsId}>\n            Parameters\n          </label>,\n          <ParameterMappingListInput\n            key=\"parameters-list\"\n            id={parameterMappingsId}\n            mappings={parameterMappings}\n            existingParams={existingParams}\n            onChange={setParameterMappings}\n          />,\n        ]}\n      </div>\n    </Modal>\n  );\n}\n\nAddWidgetDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n  dashboard: PropTypes.object.isRequired,\n};\n\nexport default wrapDialog(AddWidgetDialog);\n"
  },
  {
    "path": "client/app/components/dashboards/AutoHeightController.js",
    "content": "import { includes, reduce, some } from \"lodash\";\n\n// TODO: Revisit this implementation when migrating widget component to React\n\nconst WIDGET_SELECTOR = '[data-widgetid=\"{0}\"]';\nconst WIDGET_CONTENT_SELECTOR = [\n  \".widget-header\", // header\n  \".visualization-renderer\", // visualization\n  \".scrollbox .alert\", // error state\n  \".spinner-container\", // loading state\n  \".tile__bottom-control\", // footer\n].join(\",\");\nconst INTERVAL = 200;\n\nexport default class AutoHeightController {\n  widgets = {};\n\n  interval = null;\n\n  onHeightChange = null;\n\n  constructor(handler) {\n    this.onHeightChange = handler;\n  }\n\n  update(widgets) {\n    const newWidgetIds = widgets\n      .filter(widget => widget.options.position.autoHeight)\n      .map(widget => widget.id.toString());\n\n    // added\n    newWidgetIds.filter(id => !includes(Object.keys(this.widgets), id)).forEach(this.add);\n\n    // removed\n    Object.keys(this.widgets)\n      .filter(id => !includes(newWidgetIds, id))\n      .forEach(this.remove);\n  }\n\n  add = id => {\n    if (this.isEmpty()) {\n      this.start();\n    }\n\n    const selector = WIDGET_SELECTOR.replace(\"{0}\", id);\n    this.widgets[id] = [\n      function getHeight() {\n        const widgetEl = document.querySelector(selector);\n        if (!widgetEl) {\n          return undefined; // safety\n        }\n\n        // get all content elements\n        const els = widgetEl.querySelectorAll(WIDGET_CONTENT_SELECTOR);\n\n        // calculate accumulated height\n        return reduce(\n          els,\n          (acc, el) => {\n            const height = el ? el.getBoundingClientRect().height : 0;\n            return acc + height;\n          },\n          0\n        );\n      },\n    ];\n  };\n\n  remove = id => {\n    // ignore if not an active autoHeight widget\n    if (!this.exists(id)) {\n      return;\n    }\n\n    // not actually deleting from this.widgets to prevent case of unwanted re-adding\n    this.widgets[id.toString()] = false;\n\n    if (this.isEmpty()) {\n      this.stop();\n    }\n  };\n\n  exists = id => !!this.widgets[id.toString()];\n\n  isEmpty = () => !some(this.widgets);\n\n  checkHeightChanges = () => {\n    Object.keys(this.widgets)\n      .filter(this.exists) // reject already removed items\n      .forEach(id => {\n        const [getHeight, prevHeight] = this.widgets[id];\n        const height = getHeight();\n        if (height && height !== prevHeight) {\n          this.widgets[id][1] = height; // save\n          this.onHeightChange(id, height); // dispatch\n        }\n      });\n  };\n\n  start = () => {\n    this.stop();\n    this.interval = setInterval(this.checkHeightChanges, INTERVAL);\n  };\n\n  stop = () => {\n    clearInterval(this.interval);\n  };\n\n  resume = () => {\n    if (!this.isEmpty()) {\n      this.start();\n    }\n  };\n\n  destroy = () => {\n    this.stop();\n    this.widgets = null;\n  };\n}\n"
  },
  {
    "path": "client/app/components/dashboards/CreateDashboardDialog.jsx",
    "content": "import { trim } from \"lodash\";\nimport React, { useState } from \"react\";\nimport Modal from \"antd/lib/modal\";\nimport Input from \"antd/lib/input\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\nimport recordEvent from \"@/services/recordEvent\";\nimport { policy } from \"@/services/policy\";\nimport { Dashboard } from \"@/services/dashboard\";\n\nfunction CreateDashboardDialog({ dialog }) {\n  const [name, setName] = useState(\"\");\n  const [isValid, setIsValid] = useState(false);\n  const [saveInProgress, setSaveInProgress] = useState(false);\n  const isCreateDashboardEnabled = policy.isCreateDashboardEnabled();\n\n  function handleNameChange(event) {\n    const value = trim(event.target.value);\n    setName(value);\n    setIsValid(value !== \"\");\n  }\n\n  function save() {\n    if (name !== \"\") {\n      setSaveInProgress(true);\n\n      Dashboard.save({ name }).then(data => {\n        dialog.close();\n        navigateTo(`${data.url}?edit`);\n      });\n      recordEvent(\"create\", \"dashboard\");\n    }\n  }\n\n  return (\n    <Modal\n      {...dialog.props}\n      {...(isCreateDashboardEnabled ? {} : { footer: null })}\n      title=\"New Dashboard\"\n      okText=\"Save\"\n      cancelText=\"Close\"\n      okButtonProps={{\n        disabled: !isValid || saveInProgress,\n        loading: saveInProgress,\n        \"data-test\": \"DashboardSaveButton\",\n      }}\n      cancelButtonProps={{\n        disabled: saveInProgress,\n      }}\n      onOk={save}\n      closable={!saveInProgress}\n      maskClosable={!saveInProgress}\n      wrapProps={{\n        \"data-test\": \"CreateDashboardDialog\",\n      }}>\n      <DynamicComponent name=\"CreateDashboardDialogExtra\" disabled={!isCreateDashboardEnabled}>\n        <Input\n          defaultValue={name}\n          onChange={handleNameChange}\n          onPressEnter={save}\n          placeholder=\"Dashboard Name\"\n          aria-label=\"Dashboard name\"\n          disabled={saveInProgress}\n          autoFocus\n        />\n      </DynamicComponent>\n    </Modal>\n  );\n}\n\nCreateDashboardDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n};\n\nexport default wrapDialog(CreateDashboardDialog);\n"
  },
  {
    "path": "client/app/components/dashboards/DashboardGrid.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { chain, cloneDeep, find } from \"lodash\";\nimport cx from \"classnames\";\nimport { Responsive, WidthProvider } from \"react-grid-layout\";\nimport { VisualizationWidget, TextboxWidget, RestrictedWidget } from \"@/components/dashboards/dashboard-widget\";\nimport { FiltersType } from \"@/components/Filters\";\nimport cfg from \"@/config/dashboard-grid-options\";\nimport AutoHeightController from \"./AutoHeightController\";\nimport { WidgetTypeEnum } from \"@/services/widget\";\n\nimport \"react-grid-layout/css/styles.css\";\nimport \"./dashboard-grid.less\";\n\nconst ResponsiveGridLayout = WidthProvider(Responsive);\n\nconst WidgetType = PropTypes.shape({\n  id: PropTypes.number.isRequired,\n  options: PropTypes.shape({\n    position: PropTypes.shape({\n      col: PropTypes.number.isRequired,\n      row: PropTypes.number.isRequired,\n      sizeY: PropTypes.number.isRequired,\n      minSizeY: PropTypes.number.isRequired,\n      maxSizeY: PropTypes.number.isRequired,\n      sizeX: PropTypes.number.isRequired,\n      minSizeX: PropTypes.number.isRequired,\n      maxSizeX: PropTypes.number.isRequired,\n    }).isRequired,\n  }).isRequired,\n});\n\nconst SINGLE = \"single-column\";\nconst MULTI = \"multi-column\";\n\nconst DashboardWidget = React.memo(\n  function DashboardWidget({\n    widget,\n    dashboard,\n    onLoadWidget,\n    onRefreshWidget,\n    onRemoveWidget,\n    onParameterMappingsChange,\n    isEditing,\n    canEdit,\n    isPublic,\n    isLoading,\n    filters,\n  }) {\n    const { type } = widget;\n    const onLoad = () => onLoadWidget(widget);\n    const onRefresh = () => onRefreshWidget(widget);\n    const onDelete = () => onRemoveWidget(widget.id);\n\n    if (type === WidgetTypeEnum.VISUALIZATION) {\n      return (\n        <VisualizationWidget\n          widget={widget}\n          dashboard={dashboard}\n          filters={filters}\n          isEditing={isEditing}\n          canEdit={canEdit}\n          isPublic={isPublic}\n          isLoading={isLoading}\n          onLoad={onLoad}\n          onRefresh={onRefresh}\n          onDelete={onDelete}\n          onParameterMappingsChange={onParameterMappingsChange}\n        />\n      );\n    }\n    if (type === WidgetTypeEnum.TEXTBOX) {\n      return <TextboxWidget widget={widget} canEdit={canEdit} isPublic={isPublic} onDelete={onDelete} />;\n    }\n    return <RestrictedWidget widget={widget} />;\n  },\n  (prevProps, nextProps) =>\n    prevProps.widget === nextProps.widget &&\n    prevProps.canEdit === nextProps.canEdit &&\n    prevProps.isPublic === nextProps.isPublic &&\n    prevProps.isLoading === nextProps.isLoading &&\n    prevProps.filters === nextProps.filters &&\n    prevProps.isEditing === nextProps.isEditing\n);\n\nclass DashboardGrid extends React.Component {\n  static propTypes = {\n    isEditing: PropTypes.bool.isRequired,\n    isPublic: PropTypes.bool,\n    dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n    widgets: PropTypes.arrayOf(WidgetType).isRequired,\n    filters: FiltersType,\n    onBreakpointChange: PropTypes.func,\n    onLoadWidget: PropTypes.func,\n    onRefreshWidget: PropTypes.func,\n    onRemoveWidget: PropTypes.func,\n    onLayoutChange: PropTypes.func,\n    onParameterMappingsChange: PropTypes.func,\n  };\n\n  static defaultProps = {\n    isPublic: false,\n    filters: [],\n    onLoadWidget: () => {},\n    onRefreshWidget: () => {},\n    onRemoveWidget: () => {},\n    onLayoutChange: () => {},\n    onBreakpointChange: () => {},\n    onParameterMappingsChange: () => {},\n  };\n\n  static normalizeFrom(widget) {\n    const {\n      id,\n      options: { position: pos },\n    } = widget;\n\n    return {\n      i: id.toString(),\n      x: pos.col,\n      y: pos.row,\n      w: pos.sizeX,\n      h: pos.sizeY,\n      minW: pos.minSizeX,\n      maxW: pos.maxSizeX,\n      minH: pos.minSizeY,\n      maxH: pos.maxSizeY,\n    };\n  }\n\n  mode = null;\n\n  autoHeightCtrl = null;\n\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      layouts: {},\n      disableAnimations: true,\n    };\n\n    // init AutoHeightController\n    this.autoHeightCtrl = new AutoHeightController(this.onWidgetHeightUpdated);\n    this.autoHeightCtrl.update(this.props.widgets);\n  }\n\n  componentDidMount() {\n    this.onBreakpointChange(document.body.offsetWidth <= cfg.mobileBreakPoint ? SINGLE : MULTI);\n    // Work-around to disable initial animation on widgets; `measureBeforeMount` doesn't work properly:\n    // it disables animation, but it cannot detect scrollbars.\n    setTimeout(() => {\n      this.setState({ disableAnimations: false });\n    }, 50);\n  }\n\n  componentDidUpdate() {\n    // update, in case widgets added or removed\n    this.autoHeightCtrl.update(this.props.widgets);\n  }\n\n  componentWillUnmount() {\n    this.autoHeightCtrl.destroy();\n  }\n\n  onLayoutChange = (_, layouts) => {\n    // workaround for when dashboard starts at single mode and then multi is empty or carries single col data\n    // fixes test dashboard_spec['shows widgets with full width']\n    // TODO: open react-grid-layout issue\n    if (layouts[MULTI]) {\n      this.setState({ layouts });\n    }\n\n    // workaround for https://github.com/STRML/react-grid-layout/issues/889\n    // remove next line when fix lands\n    this.mode = document.body.offsetWidth <= cfg.mobileBreakPoint ? SINGLE : MULTI;\n    // end workaround\n\n    // don't save single column mode layout\n    if (this.mode === SINGLE) {\n      return;\n    }\n\n    const normalized = chain(layouts[MULTI])\n      .keyBy(\"i\")\n      .mapValues(this.normalizeTo)\n      .value();\n\n    this.props.onLayoutChange(normalized);\n  };\n\n  onBreakpointChange = mode => {\n    this.mode = mode;\n    this.props.onBreakpointChange(mode === SINGLE);\n  };\n\n  // height updated by auto-height\n  onWidgetHeightUpdated = (widgetId, newHeight) => {\n    this.setState(({ layouts }) => {\n      const layout = cloneDeep(layouts[MULTI]); // must clone to allow react-grid-layout to compare prev/next state\n      const item = find(layout, { i: widgetId.toString() });\n      if (item) {\n        // update widget height\n        item.h = Math.ceil((newHeight + cfg.margins) / cfg.rowHeight);\n      }\n\n      return { layouts: { [MULTI]: layout } };\n    });\n  };\n\n  // height updated by manual resize\n  onWidgetResize = (layout, oldItem, newItem) => {\n    if (oldItem.h !== newItem.h) {\n      this.autoHeightCtrl.remove(Number(newItem.i));\n    }\n\n    this.autoHeightCtrl.resume();\n  };\n\n  normalizeTo = layout => ({\n    col: layout.x,\n    row: layout.y,\n    sizeX: layout.w,\n    sizeY: layout.h,\n    autoHeight: this.autoHeightCtrl.exists(layout.i),\n  });\n\n  render() {\n    const {\n      onLoadWidget,\n      onRefreshWidget,\n      onRemoveWidget,\n      onParameterMappingsChange,\n      filters,\n      dashboard,\n      isPublic,\n      isEditing,\n      widgets,\n    } = this.props;\n    const className = cx(\"dashboard-wrapper\", isEditing ? \"editing-mode\" : \"preview-mode\");\n\n    return (\n      <div className={className}>\n        <ResponsiveGridLayout\n          draggableCancel=\"input,.sortable-container\"\n          className={cx(\"layout\", { \"disable-animations\": this.state.disableAnimations })}\n          cols={{ [MULTI]: cfg.columns, [SINGLE]: 1 }}\n          rowHeight={cfg.rowHeight - cfg.margins}\n          margin={[cfg.margins, cfg.margins]}\n          isDraggable={isEditing}\n          isResizable={isEditing}\n          onResizeStart={this.autoHeightCtrl.stop}\n          onResizeStop={this.onWidgetResize}\n          layouts={this.state.layouts}\n          onLayoutChange={this.onLayoutChange}\n          onBreakpointChange={this.onBreakpointChange}\n          breakpoints={{ [MULTI]: cfg.mobileBreakPoint, [SINGLE]: 0 }}>\n          {widgets.map(widget => (\n            <div\n              key={widget.id}\n              data-grid={DashboardGrid.normalizeFrom(widget)}\n              data-widgetid={widget.id}\n              data-test={`WidgetId${widget.id}`}\n              className={cx(\"dashboard-widget-wrapper\", {\n                \"widget-auto-height-enabled\": this.autoHeightCtrl.exists(widget.id),\n              })}>\n              <DashboardWidget\n                dashboard={dashboard}\n                widget={widget}\n                filters={filters}\n                isPublic={isPublic}\n                isLoading={widget.loading}\n                isEditing={isEditing}\n                canEdit={dashboard.canEdit()}\n                onLoadWidget={onLoadWidget}\n                onRefreshWidget={onRefreshWidget}\n                onRemoveWidget={onRemoveWidget}\n                onParameterMappingsChange={onParameterMappingsChange}\n              />\n            </div>\n          ))}\n        </ResponsiveGridLayout>\n      </div>\n    );\n  }\n}\n\nexport default DashboardGrid;\n"
  },
  {
    "path": "client/app/components/dashboards/EditParameterMappingsDialog.jsx",
    "content": "import { isMatch, map, find, sortBy } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Modal from \"antd/lib/modal\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport {\n  MappingType,\n  ParameterMappingListInput,\n  parameterMappingsToEditableMappings,\n  editableMappingsToParameterMappings,\n  synchronizeWidgetTitles,\n} from \"@/components/ParameterMappingInput\";\nimport notification from \"@/services/notification\";\n\nexport function getParamValuesSnapshot(mappings, dashboardParameters) {\n  return map(\n    sortBy(mappings, m => m.name),\n    m => {\n      let param;\n      switch (m.type) {\n        case MappingType.StaticValue:\n          return [m.name, m.value];\n        case MappingType.WidgetLevel:\n          return [m.name, m.param.value];\n        case MappingType.DashboardAddNew:\n        case MappingType.DashboardMapToExisting:\n          param = find(dashboardParameters, p => p.name === m.mapTo);\n          return [m.name, param ? param.value : null];\n        // no default\n      }\n    }\n  );\n}\n\nclass EditParameterMappingsDialog extends React.Component {\n  static propTypes = {\n    dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n    widget: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n    dialog: DialogPropType.isRequired,\n  };\n\n  originalParamValuesSnapshot = null;\n\n  constructor(props) {\n    super(props);\n\n    const parameterMappings = parameterMappingsToEditableMappings(\n      props.widget.options.parameterMappings,\n      props.widget.query.getParametersDefs(),\n      map(this.props.dashboard.getParametersDefs(), p => p.name)\n    );\n\n    this.originalParamValuesSnapshot = getParamValuesSnapshot(\n      parameterMappings,\n      this.props.dashboard.getParametersDefs()\n    );\n\n    this.state = {\n      saveInProgress: false,\n      parameterMappings,\n    };\n  }\n\n  saveWidget() {\n    const widget = this.props.widget;\n\n    this.setState({ saveInProgress: true });\n\n    const newMappings = editableMappingsToParameterMappings(this.state.parameterMappings);\n    widget.options.parameterMappings = newMappings;\n\n    const valuesChanged = !isMatch(\n      this.originalParamValuesSnapshot,\n      getParamValuesSnapshot(this.state.parameterMappings, this.props.dashboard.getParametersDefs())\n    );\n\n    const widgetsToSave = [\n      widget,\n      ...synchronizeWidgetTitles(widget.options.parameterMappings, this.props.dashboard.widgets),\n    ];\n\n    Promise.all(map(widgetsToSave, w => w.save()))\n      .then(() => {\n        this.props.dialog.close(valuesChanged);\n      })\n      .catch(() => {\n        notification.error(\"Widget cannot be updated\");\n      })\n      .finally(() => {\n        this.setState({ saveInProgress: false });\n      });\n  }\n\n  updateParamMappings(parameterMappings) {\n    this.setState({ parameterMappings });\n  }\n\n  render() {\n    const { dialog } = this.props;\n    return (\n      <Modal\n        {...dialog.props}\n        title=\"Parameters\"\n        onOk={() => this.saveWidget()}\n        okButtonProps={{ loading: this.state.saveInProgress }}\n        width={700}>\n        {this.state.parameterMappings.length > 0 && (\n          <ParameterMappingListInput\n            mappings={this.state.parameterMappings}\n            existingParams={this.props.dashboard.getParametersDefs()}\n            onChange={mappings => this.updateParamMappings(mappings)}\n          />\n        )}\n      </Modal>\n    );\n  }\n}\n\nexport default wrapDialog(EditParameterMappingsDialog);\n"
  },
  {
    "path": "client/app/components/dashboards/ExpandedWidgetDialog.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport Modal from \"antd/lib/modal\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport { FiltersType } from \"@/components/Filters\";\nimport VisualizationRenderer from \"@/components/visualizations/VisualizationRenderer\";\nimport VisualizationName from \"@/components/visualizations/VisualizationName\";\n\nfunction ExpandedWidgetDialog({ dialog, widget, filters }) {\n  return (\n    <Modal\n      {...dialog.props}\n      title={\n        <>\n          <VisualizationName visualization={widget.visualization} /> <span>{widget.getQuery().name}</span>\n        </>\n      }\n      width=\"95%\"\n      footer={<Button onClick={dialog.dismiss}>Close</Button>}>\n      <VisualizationRenderer\n        visualization={widget.visualization}\n        queryResult={widget.getQueryResult()}\n        filters={filters}\n        context=\"widget\"\n      />\n    </Modal>\n  );\n}\n\nExpandedWidgetDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n  widget: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  filters: FiltersType,\n};\n\nExpandedWidgetDialog.defaultProps = {\n  filters: [],\n};\n\nexport default wrapDialog(ExpandedWidgetDialog);\n"
  },
  {
    "path": "client/app/components/dashboards/TextboxDialog.jsx",
    "content": "import { toString } from \"lodash\";\nimport { markdown } from \"markdown\";\nimport React, { useState, useEffect, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport Modal from \"antd/lib/modal\";\nimport Input from \"antd/lib/input\";\nimport Tooltip from \"@/components/Tooltip\";\nimport Divider from \"antd/lib/divider\";\nimport Link from \"@/components/Link\";\nimport HtmlContent from \"@redash/viz/lib/components/HtmlContent\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport notification from \"@/services/notification\";\n\nimport \"./TextboxDialog.less\";\n\nfunction TextboxDialog({ dialog, isNew, ...props }) {\n  const [text, setText] = useState(toString(props.text));\n  const [preview, setPreview] = useState(null);\n\n  useEffect(() => {\n    setText(props.text);\n    setPreview(markdown.toHTML(props.text));\n  }, [props.text]);\n\n  const [updatePreview] = useDebouncedCallback(() => {\n    setPreview(markdown.toHTML(text));\n  }, 200);\n\n  const handleInputChange = useCallback(\n    event => {\n      setText(event.target.value);\n      updatePreview();\n    },\n    [updatePreview]\n  );\n\n  const saveWidget = useCallback(() => {\n    dialog.close(text).catch(() => {\n      notification.error(isNew ? \"Widget could not be added\" : \"Widget could not be saved\");\n    });\n  }, [dialog, isNew, text]);\n\n  const confirmDialogDismiss = useCallback(() => {\n    const originalText = props.text;\n    if (text !== originalText) {\n      Modal.confirm({\n        title: \"Quit editing?\",\n        content: \"Changes you made so far will not be saved. Are you sure?\",\n        okText: \"Yes, quit\",\n        okType: \"danger\",\n        onOk: () => dialog.dismiss(),\n        maskClosable: true,\n        autoFocusButton: null,\n        style: { top: 170 },\n      });\n    } else {\n      dialog.dismiss();\n    }\n  }, [dialog, text, props.text]);\n\n  return (\n    <Modal\n      {...dialog.props}\n      title={isNew ? \"Add Textbox\" : \"Edit Textbox\"}\n      onOk={saveWidget}\n      onCancel={confirmDialogDismiss}\n      okText={isNew ? \"Add to Dashboard\" : \"Save\"}\n      width={500}\n      wrapProps={{ \"data-test\": \"TextboxDialog\" }}>\n      <div className=\"textbox-dialog\">\n        <Input.TextArea\n          className=\"resize-vertical\"\n          rows=\"5\"\n          value={text}\n          aria-label=\"Textbox widget content\"\n          onChange={handleInputChange}\n          autoFocus\n          placeholder=\"This is where you write some text\"\n        />\n        <small>\n          Supports basic{\" \"}\n          <Link\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            href=\"https://www.markdownguide.org/cheat-sheet/#basic-syntax\">\n            <Tooltip title=\"Markdown guide opens in new window\">Markdown</Tooltip>\n          </Link>\n          .\n        </small>\n        {text && (\n          <React.Fragment>\n            <Divider dashed />\n            <strong className=\"preview-title\">Preview:</strong>\n            <HtmlContent className=\"preview markdown\">{preview}</HtmlContent>\n          </React.Fragment>\n        )}\n      </div>\n    </Modal>\n  );\n}\n\nTextboxDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n  isNew: PropTypes.bool,\n  text: PropTypes.string,\n};\n\nTextboxDialog.defaultProps = {\n  isNew: false,\n  text: \"\",\n};\n\nexport default wrapDialog(TextboxDialog);\n"
  },
  {
    "path": "client/app/components/dashboards/TextboxDialog.less",
    "content": ".textbox-dialog {\n  small {\n    display: block;\n    margin-top: 4px;\n  }\n\n  .preview {\n    padding: 9px 9px 1px;\n    background-color: #f7f7f7;\n    margin-top: 8px;\n    word-wrap: break-word\n  }\n\n  .preview-title {\n    display: block;\n    margin-top: -5px;\n  }\n}"
  },
  {
    "path": "client/app/components/dashboards/dashboard-grid.less",
    "content": ".dashboard-wrapper {\n  flex-grow: 1;\n  margin-bottom: 70px;\n\n  .layout {\n    margin: -15px -15px 0;\n  }\n\n  .tile {\n    display: flex;\n    position: absolute;\n    left: 0;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    width: auto;\n    height: auto;\n    overflow: hidden;\n    margin: 0;\n    padding: 0;\n  }\n\n  .pivot-table-visualization-container > table,\n  .visualization-renderer > .visualization-renderer-wrapper {\n    overflow: visible;\n  }\n\n  &.preview-mode {\n    .widget-menu-regular {\n      display: block;\n    }\n    .widget-menu-remove {\n      display: none;\n    }\n  }\n\n  &.editing-mode {\n    /* Y axis lines */\n    background: linear-gradient(to right, transparent, transparent 1px, #f6f8f9 1px, #f6f8f9),\n      linear-gradient(to bottom, #b3babf, #b3babf 1px, transparent 1px, transparent);\n    background-size: 5px 50px;\n    background-position-y: -8px;\n\n    /* X axis lines */\n    &::before {\n      content: \"\";\n      position: absolute;\n      top: 0;\n      left: 0;\n      bottom: 85px;\n      right: 0;\n      background: linear-gradient(to bottom, transparent, transparent 2px, #f6f8f9 2px, #f6f8f9 5px),\n        linear-gradient(to left, #b3babf, #b3babf 1px, transparent 1px, transparent);\n      background-size: calc((100% + 15px) / 12) 5px;\n      background-position: -7px 1px;\n    }\n  }\n\n  .widget-auto-height-enabled {\n    .spinner {\n      position: static;\n    }\n\n    .scrollbox {\n      overflow-y: hidden;\n    }\n  }\n}\n\n.react-grid-layout {\n  &.disable-animations {\n    & > .react-grid-item {\n      transition: none !important;\n    }\n  }\n}\n\n.dashboard-wrapper .dashboard-widget-wrapper:not(.widget-auto-height-enabled),\n.query-fixed-layout {\n  .visualization-renderer {\n    display: flex;\n    flex-direction: column;\n    position: absolute;\n    left: 0;\n    top: 0;\n    right: 0;\n    bottom: 0;\n\n    > .visualization-renderer-wrapper {\n      flex-grow: 1;\n      position: relative;\n    }\n\n    > .filters-wrapper {\n      flex-grow: 0;\n      flex-shrink: 0;\n    }\n  }\n\n  .sunburst-visualization-container,\n  .sankey-visualization-container,\n  .map-visualization-container,\n  .word-cloud-visualization-container,\n  .box-plot-deprecated-visualization-container,\n  .chart-visualization-container {\n    position: absolute;\n    left: 0;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    width: auto;\n    height: auto;\n    overflow: hidden;\n  }\n\n  .counter-visualization-container {\n    height: 100%;\n\n    .counter-visualization-content {\n      position: absolute;\n      left: 10px;\n      top: 15px;\n      right: 10px;\n      bottom: 15px;\n      height: auto;\n      overflow: hidden;\n      padding: 0;\n    }\n  }\n}\n\n.query-fixed-layout {\n  .visualization-renderer > .visualization-renderer-wrapper {\n    .counter-visualization-container {\n      // counter is too large on Query pages, so let's add some constraints\n      max-width: 600px;\n      max-height: 400px;\n      // center it\n      position: absolute;\n      left: 0;\n      top: 0;\n      right: 0;\n      bottom: 0;\n      margin: auto;\n    }\n  }\n}\n\n// react-grid-layout overrides\n.react-grid-item {\n  touch-action: initial !important; // react-draggable disables touch by default\n\n  &.react-draggable {\n    touch-action: none !important;\n  }\n\n  // placeholder color\n  &.react-grid-placeholder {\n    border-radius: 3px;\n    background-color: #e0e6eb;\n    opacity: 0.5;\n  }\n\n  // resize placeholder behind widget, the lib's default is above 🤷‍♂️\n  &.resizing {\n    z-index: 3;\n  }\n\n  // auto-height animation\n  &.cssTransforms:not(.resizing) {\n    transition-property: transform, height; // added \", height\"\n  }\n\n  // resize handle size\n  & > .react-resizable-handle {\n    background: none;\n    &:after {\n      width: 11px;\n      height: 11px;\n      right: 5px;\n      bottom: 5px;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/dashboards/dashboard-widget/RestrictedWidget.jsx",
    "content": "import React from \"react\";\nimport Widget from \"./Widget\";\n\nfunction RestrictedWidget(props) {\n  return (\n    <Widget {...props} className=\"d-flex justify-content-center align-items-center widget-restricted\">\n      <div className=\"t-body scrollbox\">\n        <div className=\"text-center\">\n          <h1>\n            <span className=\"zmdi zmdi-lock\" />\n          </h1>\n          <p className=\"text-muted\">This widget requires access to a data source you don&apos;t have access to.</p>\n        </div>\n      </div>\n    </Widget>\n  );\n}\n\nexport default RestrictedWidget;\n"
  },
  {
    "path": "client/app/components/dashboards/dashboard-widget/TextboxWidget.jsx",
    "content": "import React, { useState } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { markdown } from \"markdown\";\nimport Menu from \"antd/lib/menu\";\nimport HtmlContent from \"@redash/viz/lib/components/HtmlContent\";\nimport TextboxDialog from \"@/components/dashboards/TextboxDialog\";\nimport Widget from \"./Widget\";\n\nfunction TextboxWidget(props) {\n  const { widget, canEdit } = props;\n  const [text, setText] = useState(widget.text);\n\n  const editTextBox = () => {\n    TextboxDialog.showModal({\n      text: widget.text,\n    }).onClose(newText => {\n      widget.text = newText;\n      setText(newText);\n      return widget.save();\n    });\n  };\n\n  const TextboxMenuOptions = [\n    <Menu.Item key=\"edit\" onClick={editTextBox}>\n      Edit\n    </Menu.Item>,\n  ];\n\n  if (!widget.width) {\n    return null;\n  }\n\n  return (\n    <Widget {...props} menuOptions={canEdit ? TextboxMenuOptions : null} className=\"widget-text\">\n      <HtmlContent className=\"body-row-auto scrollbox t-body p-15 markdown\">{markdown.toHTML(text || \"\")}</HtmlContent>\n    </Widget>\n  );\n}\n\nTextboxWidget.propTypes = {\n  widget: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  canEdit: PropTypes.bool,\n};\n\nTextboxWidget.defaultProps = {\n  canEdit: false,\n};\n\nexport default TextboxWidget;\n"
  },
  {
    "path": "client/app/components/dashboards/dashboard-widget/VisualizationWidget.jsx",
    "content": "import React, { useState } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { compact, isEmpty, invoke, map } from \"lodash\";\nimport { markdown } from \"markdown\";\nimport cx from \"classnames\";\nimport Menu from \"antd/lib/menu\";\nimport HtmlContent from \"@redash/viz/lib/components/HtmlContent\";\nimport { currentUser } from \"@/services/auth\";\nimport recordEvent from \"@/services/recordEvent\";\nimport { formatDateTime } from \"@/lib/utils\";\nimport Link from \"@/components/Link\";\nimport Parameters from \"@/components/Parameters\";\nimport TimeAgo from \"@/components/TimeAgo\";\nimport Timer from \"@/components/Timer\";\nimport { Moment } from \"@/components/proptypes\";\nimport QueryLink from \"@/components/QueryLink\";\nimport { FiltersType } from \"@/components/Filters\";\nimport PlainButton from \"@/components/PlainButton\";\nimport ExpandedWidgetDialog from \"@/components/dashboards/ExpandedWidgetDialog\";\nimport EditParameterMappingsDialog from \"@/components/dashboards/EditParameterMappingsDialog\";\nimport VisualizationRenderer from \"@/components/visualizations/VisualizationRenderer\";\n\nimport Widget from \"./Widget\";\n\nfunction visualizationWidgetMenuOptions({ widget, canEditDashboard, onParametersEdit }) {\n  const canViewQuery = currentUser.hasPermission(\"view_query\");\n  const canEditParameters = canEditDashboard && !isEmpty(invoke(widget, \"query.getParametersDefs\"));\n  const widgetQueryResult = widget.getQueryResult();\n  const isQueryResultEmpty = !widgetQueryResult || !widgetQueryResult.isEmpty || widgetQueryResult.isEmpty();\n\n  const downloadLink = fileType => widgetQueryResult.getLink(widget.getQuery().id, fileType);\n  const downloadName = fileType => widgetQueryResult.getName(widget.getQuery().name, fileType);\n  return compact([\n    <Menu.Item key=\"download_csv\" disabled={isQueryResultEmpty}>\n      {!isQueryResultEmpty ? (\n        <Link href={downloadLink(\"csv\")} download={downloadName(\"csv\")} target=\"_self\">\n          Download as CSV File\n        </Link>\n      ) : (\n        \"Download as CSV File\"\n      )}\n    </Menu.Item>,\n    <Menu.Item key=\"download_tsv\" disabled={isQueryResultEmpty}>\n      {!isQueryResultEmpty ? (\n        <Link href={downloadLink(\"tsv\")} download={downloadName(\"tsv\")} target=\"_self\">\n          Download as TSV File\n        </Link>\n      ) : (\n        \"Download as TSV File\"\n      )}\n    </Menu.Item>,\n    <Menu.Item key=\"download_excel\" disabled={isQueryResultEmpty}>\n      {!isQueryResultEmpty ? (\n        <Link href={downloadLink(\"xlsx\")} download={downloadName(\"xlsx\")} target=\"_self\">\n          Download as Excel File\n        </Link>\n      ) : (\n        \"Download as Excel File\"\n      )}\n    </Menu.Item>,\n    (canViewQuery || canEditParameters) && <Menu.Divider key=\"divider\" />,\n    canViewQuery && (\n      <Menu.Item key=\"view_query\">\n        <Link href={widget.getQuery().getUrl(true, widget.visualization.id)}>View Query</Link>\n      </Menu.Item>\n    ),\n    canEditParameters && (\n      <Menu.Item key=\"edit_parameters\" onClick={onParametersEdit}>\n        Edit Parameters\n      </Menu.Item>\n    ),\n  ]);\n}\n\nfunction RefreshIndicator({ refreshStartedAt }) {\n  return (\n    <div className=\"refresh-indicator\">\n      <div className=\"refresh-icon\">\n        <i className=\"zmdi zmdi-refresh zmdi-hc-spin\" aria-hidden=\"true\" />\n        <span className=\"sr-only\">Refreshing...</span>\n      </div>\n      <Timer from={refreshStartedAt} />\n    </div>\n  );\n}\n\nRefreshIndicator.propTypes = { refreshStartedAt: Moment };\nRefreshIndicator.defaultProps = { refreshStartedAt: null };\n\nfunction VisualizationWidgetHeader({\n  widget,\n  refreshStartedAt,\n  parameters,\n  isEditing,\n  onParametersUpdate,\n  onParametersEdit,\n}) {\n  const canViewQuery = currentUser.hasPermission(\"view_query\");\n\n  return (\n    <>\n      <RefreshIndicator refreshStartedAt={refreshStartedAt} />\n      <div className=\"t-header widget clearfix\">\n        <div className=\"th-title\">\n          <p>\n            <QueryLink query={widget.getQuery()} visualization={widget.visualization} readOnly={!canViewQuery} />\n          </p>\n          {!isEmpty(widget.getQuery().description) && (\n            <HtmlContent className=\"text-muted markdown query--description\">\n              {markdown.toHTML(widget.getQuery().description || \"\")}\n            </HtmlContent>\n          )}\n        </div>\n      </div>\n      {!isEmpty(parameters) && (\n        <div className=\"m-b-10\">\n          <Parameters\n            parameters={parameters}\n            sortable={isEditing}\n            appendSortableToParent={false}\n            onValuesChange={onParametersUpdate}\n            onParametersEdit={onParametersEdit}\n          />\n        </div>\n      )}\n    </>\n  );\n}\n\nVisualizationWidgetHeader.propTypes = {\n  widget: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  refreshStartedAt: Moment,\n  parameters: PropTypes.arrayOf(PropTypes.object),\n  isEditing: PropTypes.bool,\n  onParametersUpdate: PropTypes.func,\n  onParametersEdit: PropTypes.func,\n};\n\nVisualizationWidgetHeader.defaultProps = {\n  refreshStartedAt: null,\n  onParametersUpdate: () => {},\n  onParametersEdit: () => {},\n  isEditing: false,\n  parameters: [],\n};\n\nfunction VisualizationWidgetFooter({ widget, isPublic, onRefresh, onExpand }) {\n  const widgetQueryResult = widget.getQueryResult();\n  const updatedAt = invoke(widgetQueryResult, \"getUpdatedAt\");\n  const [refreshClickButtonId, setRefreshClickButtonId] = useState();\n\n  const refreshWidget = buttonId => {\n    if (!refreshClickButtonId) {\n      setRefreshClickButtonId(buttonId);\n      onRefresh().finally(() => setRefreshClickButtonId(null));\n    }\n  };\n\n  return widgetQueryResult ? (\n    <>\n      <span>\n        {!isPublic && !!widgetQueryResult && (\n          <PlainButton\n            className=\"refresh-button hidden-print btn btn-sm btn-default btn-transparent\"\n            onClick={() => refreshWidget(1)}\n            data-test=\"RefreshButton\">\n            <i className={cx(\"zmdi zmdi-refresh\", { \"zmdi-hc-spin\": refreshClickButtonId === 1 })} aria-hidden=\"true\" />\n            <span className=\"sr-only\">\n              {refreshClickButtonId === 1 ? \"Refreshing, please wait. \" : \"Press to refresh. \"}\n            </span>{\" \"}\n            <TimeAgo date={updatedAt} />\n          </PlainButton>\n        )}\n        <span className=\"visible-print\">\n          <i className=\"zmdi zmdi-time-restore\" aria-hidden=\"true\" /> {formatDateTime(updatedAt)}\n        </span>\n        {isPublic && (\n          <span className=\"small hidden-print\">\n            <i className=\"zmdi zmdi-time-restore\" aria-hidden=\"true\" /> <TimeAgo date={updatedAt} />\n          </span>\n        )}\n      </span>\n      <span>\n        {!isPublic && (\n          <PlainButton\n            className=\"btn btn-sm btn-default hidden-print btn-transparent btn__refresh\"\n            onClick={() => refreshWidget(2)}>\n            <i className={cx(\"zmdi zmdi-refresh\", { \"zmdi-hc-spin\": refreshClickButtonId === 2 })} aria-hidden=\"true\" />\n            <span className=\"sr-only\">\n              {refreshClickButtonId === 2 ? \"Refreshing, please wait.\" : \"Press to refresh.\"}\n            </span>\n          </PlainButton>\n        )}\n        <PlainButton className=\"btn btn-sm btn-default hidden-print btn-transparent btn__refresh\" onClick={onExpand}>\n          <i className=\"zmdi zmdi-fullscreen\" aria-hidden=\"true\" />\n        </PlainButton>\n      </span>\n    </>\n  ) : null;\n}\n\nVisualizationWidgetFooter.propTypes = {\n  widget: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  isPublic: PropTypes.bool,\n  onRefresh: PropTypes.func.isRequired,\n  onExpand: PropTypes.func.isRequired,\n};\n\nVisualizationWidgetFooter.defaultProps = { isPublic: false };\n\nclass VisualizationWidget extends React.Component {\n  static propTypes = {\n    widget: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n    dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n    filters: FiltersType,\n    isPublic: PropTypes.bool,\n    isLoading: PropTypes.bool,\n    canEdit: PropTypes.bool,\n    isEditing: PropTypes.bool,\n    onLoad: PropTypes.func,\n    onRefresh: PropTypes.func,\n    onDelete: PropTypes.func,\n    onParameterMappingsChange: PropTypes.func,\n  };\n\n  static defaultProps = {\n    filters: [],\n    isPublic: false,\n    isLoading: false,\n    canEdit: false,\n    isEditing: false,\n    onLoad: () => {},\n    onRefresh: () => {},\n    onDelete: () => {},\n    onParameterMappingsChange: () => {},\n  };\n\n  constructor(props) {\n    super(props);\n    this.state = {\n      localParameters: props.widget.getLocalParameters(),\n      localFilters: props.filters,\n    };\n  }\n\n  componentDidMount() {\n    const { widget, onLoad } = this.props;\n    recordEvent(\"view\", \"query\", widget.visualization.query.id, { dashboard: true });\n    recordEvent(\"view\", \"visualization\", widget.visualization.id, { dashboard: true });\n    onLoad();\n  }\n\n  onLocalFiltersChange = localFilters => {\n    this.setState({ localFilters });\n  };\n\n  expandWidget = () => {\n    ExpandedWidgetDialog.showModal({ widget: this.props.widget, filters: this.state.localFilters });\n  };\n\n  editParameterMappings = () => {\n    const { widget, dashboard, onRefresh, onParameterMappingsChange } = this.props;\n    EditParameterMappingsDialog.showModal({\n      dashboard,\n      widget,\n    }).onClose(valuesChanged => {\n      // refresh widget if any parameter value has been updated\n      if (valuesChanged) {\n        onRefresh();\n      }\n      onParameterMappingsChange();\n      this.setState({ localParameters: widget.getLocalParameters() });\n    });\n  };\n\n  renderVisualization() {\n    const { widget, filters } = this.props;\n    const widgetQueryResult = widget.getQueryResult();\n    const widgetStatus = widgetQueryResult && widgetQueryResult.getStatus();\n    switch (widgetStatus) {\n      case \"failed\":\n        return (\n          <div className=\"body-row-auto scrollbox\">\n            {widgetQueryResult.getError() && (\n              <div className=\"alert alert-danger m-5\">\n                Error running query: <strong>{widgetQueryResult.getError()}</strong>\n              </div>\n            )}\n          </div>\n        );\n      case \"done\":\n        return (\n          <div className=\"body-row-auto scrollbox\">\n            <VisualizationRenderer\n              visualization={widget.visualization}\n              queryResult={widgetQueryResult}\n              filters={filters}\n              onFiltersChange={this.onLocalFiltersChange}\n              context=\"widget\"\n            />\n          </div>\n        );\n      default:\n        return (\n          <div\n            className=\"body-row-auto spinner-container\"\n            role=\"status\"\n            aria-live=\"polite\"\n            aria-relevant=\"additions removals\">\n            <div className=\"spinner\">\n              <i className=\"zmdi zmdi-refresh zmdi-hc-spin zmdi-hc-5x\" aria-hidden=\"true\" />\n              <span className=\"sr-only\">Loading...</span>\n            </div>\n          </div>\n        );\n    }\n  }\n\n  render() {\n    const { widget, isLoading, isPublic, canEdit, isEditing, onRefresh } = this.props;\n    const { localParameters } = this.state;\n    const widgetQueryResult = widget.getQueryResult();\n    const isRefreshing = isLoading && !!(widgetQueryResult && widgetQueryResult.getStatus());\n    const onParametersEdit = parameters => {\n      const paramOrder = map(parameters, \"name\");\n      widget.options.paramOrder = paramOrder;\n      widget.save(\"options\", { paramOrder });\n    };\n\n    return (\n      <Widget\n        {...this.props}\n        className=\"widget-visualization\"\n        menuOptions={visualizationWidgetMenuOptions({\n          widget,\n          canEditDashboard: canEdit,\n          onParametersEdit: this.editParameterMappings,\n        })}\n        header={\n          <VisualizationWidgetHeader\n            widget={widget}\n            refreshStartedAt={isRefreshing ? widget.refreshStartedAt : null}\n            parameters={localParameters}\n            isEditing={isEditing}\n            onParametersUpdate={onRefresh}\n            onParametersEdit={onParametersEdit}\n          />\n        }\n        footer={\n          <VisualizationWidgetFooter\n            widget={widget}\n            isPublic={isPublic}\n            onRefresh={onRefresh}\n            onExpand={this.expandWidget}\n          />\n        }\n        tileProps={{ \"data-refreshing\": isRefreshing }}>\n        {this.renderVisualization()}\n      </Widget>\n    );\n  }\n}\n\nexport default VisualizationWidget;\n"
  },
  {
    "path": "client/app/components/dashboards/dashboard-widget/Widget.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport { isEmpty } from \"lodash\";\nimport Dropdown from \"antd/lib/dropdown\";\nimport Modal from \"antd/lib/modal\";\nimport Menu from \"antd/lib/menu\";\nimport recordEvent from \"@/services/recordEvent\";\nimport { Moment } from \"@/components/proptypes\";\nimport PlainButton from \"@/components/PlainButton\";\n\nimport \"./Widget.less\";\n\nfunction WidgetDropdownButton({ extraOptions, showDeleteOption, onDelete }) {\n  const WidgetMenu = (\n    <Menu data-test=\"WidgetDropdownButtonMenu\">\n      {extraOptions}\n      {showDeleteOption && extraOptions && <Menu.Divider />}\n      {showDeleteOption && <Menu.Item onClick={onDelete}>Remove from Dashboard</Menu.Item>}\n    </Menu>\n  );\n\n  return (\n    <div className=\"widget-menu-regular\">\n      <Dropdown overlay={WidgetMenu} placement=\"bottomRight\" trigger={[\"click\"]}>\n        <PlainButton className=\"action p-l-15 p-r-15\" data-test=\"WidgetDropdownButton\" aria-label=\"More options\">\n          <i className=\"zmdi zmdi-more-vert\" aria-hidden=\"true\" />\n        </PlainButton>\n      </Dropdown>\n    </div>\n  );\n}\n\nWidgetDropdownButton.propTypes = {\n  extraOptions: PropTypes.node,\n  showDeleteOption: PropTypes.bool,\n  onDelete: PropTypes.func,\n};\n\nWidgetDropdownButton.defaultProps = {\n  extraOptions: null,\n  showDeleteOption: false,\n  onDelete: () => {},\n};\n\nfunction WidgetDeleteButton({ onClick }) {\n  return (\n    <div className=\"widget-menu-remove\">\n      <PlainButton\n        className=\"action\"\n        title=\"Remove From Dashboard\"\n        onClick={onClick}\n        data-test=\"WidgetDeleteButton\"\n        aria-label=\"Close\">\n        <i className=\"zmdi zmdi-close\" aria-hidden=\"true\" />\n      </PlainButton>\n    </div>\n  );\n}\n\nWidgetDeleteButton.propTypes = { onClick: PropTypes.func };\nWidgetDeleteButton.defaultProps = { onClick: () => {} };\n\nclass Widget extends React.Component {\n  static propTypes = {\n    widget: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n    className: PropTypes.string,\n    children: PropTypes.node,\n    header: PropTypes.node,\n    footer: PropTypes.node,\n    canEdit: PropTypes.bool,\n    isPublic: PropTypes.bool,\n    refreshStartedAt: Moment,\n    menuOptions: PropTypes.node,\n    tileProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n    onDelete: PropTypes.func,\n  };\n\n  static defaultProps = {\n    className: \"\",\n    children: null,\n    header: null,\n    footer: null,\n    canEdit: false,\n    isPublic: false,\n    refreshStartedAt: null,\n    menuOptions: null,\n    tileProps: {},\n    onDelete: () => {},\n  };\n\n  componentDidMount() {\n    const { widget } = this.props;\n    recordEvent(\"view\", \"widget\", widget.id);\n  }\n\n  deleteWidget = () => {\n    const { widget, onDelete } = this.props;\n\n    Modal.confirm({\n      title: \"Delete Widget\",\n      content: \"Are you sure you want to remove this widget from the dashboard?\",\n      okText: \"Delete\",\n      okType: \"danger\",\n      onOk: () => widget.delete().then(onDelete),\n      maskClosable: true,\n      autoFocusButton: null,\n    });\n  };\n\n  render() {\n    const { className, children, header, footer, canEdit, isPublic, menuOptions, tileProps } = this.props;\n    const showDropdownButton = !isPublic && (canEdit || !isEmpty(menuOptions));\n    return (\n      <div className=\"widget-wrapper\">\n        <div className={cx(\"tile body-container\", className)} {...tileProps}>\n          <div className=\"widget-actions\">\n            {showDropdownButton && (\n              <WidgetDropdownButton\n                extraOptions={menuOptions}\n                showDeleteOption={canEdit}\n                onDelete={this.deleteWidget}\n              />\n            )}\n            {canEdit && <WidgetDeleteButton onClick={this.deleteWidget} />}\n          </div>\n          <div className=\"body-row widget-header\">{header}</div>\n          {children}\n          {footer && <div className=\"body-row tile__bottom-control\">{footer}</div>}\n        </div>\n      </div>\n    );\n  }\n}\n\nexport default Widget;\n"
  },
  {
    "path": "client/app/components/dashboards/dashboard-widget/Widget.less",
    "content": "@import (reference, less) \"~@/assets/less/inc/variables\";\n\n.widget-wrapper {\n  .widget-actions {\n    display: flex;\n    position: absolute;\n    top: 0;\n    right: 0;\n    z-index: 1;\n\n    .action {\n      font-size: 24px;\n      cursor: pointer;\n      line-height: 100%;\n      display: block;\n      padding: 4px 10px 3px;\n\n      &:focus {\n        background-color: rgba(0, 0, 0, 0.1);\n      }\n\n      &:hover {\n        background-color: transparent;\n        color: @blue;\n      }\n\n      &:active {\n        filter: brightness(75%);\n      }\n    }\n  }\n\n  .parameter-container {\n    margin: 0 15px;\n  }\n\n  .body-container {\n    display: flex;\n    flex-direction: column;\n    align-items: stretch;\n\n    .body-row {\n      flex: 0 1 auto;\n    }\n\n    .body-row-auto {\n      flex: 1 1 auto;\n    }\n  }\n\n  .spinner-container {\n    position: relative;\n\n    .spinner {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      text-align: center;\n      position: absolute;\n      left: 0;\n      top: 0;\n      width: 100%;\n      height: 100%;\n    }\n  }\n\n  .scrollbox:empty {\n    padding: 0 !important;\n    font-size: 1px !important;\n  }\n\n  .widget-text {\n    :first-child {\n      margin-top: 0;\n    }\n    :last-child {\n      margin-bottom: 0;\n    }\n  }\n}\n\n.editing-mode {\n  .widget-menu-remove {\n    display: block;\n  }\n\n  .query-link {\n    pointer-events: none;\n    cursor: move;\n  }\n\n  .th-title {\n    cursor: move;\n  }\n\n  .refresh-indicator {\n    transition-duration: 0s;\n\n    .rd-timer {\n      display: none;\n    }\n\n    .refresh-indicator-mini();\n  }\n}\n\n.refresh-indicator {\n  font-size: 18px;\n  color: #86a1af;\n  transition: all 100ms linear;\n  transition-delay: 150ms; // waits for widget-menu to fade out before moving back over it\n  transform: translateX(22px);\n  position: absolute;\n  right: 29px;\n  top: 8px;\n  display: flex;\n  flex-direction: row-reverse;\n\n  .refresh-icon {\n    position: relative;\n\n    &:before {\n      content: \"\";\n      position: absolute;\n      top: 0px;\n      right: 0;\n      width: 24px;\n      height: 24px;\n      background-color: #e8ecf0;\n      border-radius: 50%;\n      transition: opacity 100ms linear;\n      transition-delay: 150ms;\n    }\n\n    i {\n      height: 24px;\n      width: 24px;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n    }\n  }\n\n  .rd-timer {\n    font-size: 13px;\n    display: inline-block;\n    font-variant-numeric: tabular-nums;\n    opacity: 0;\n    transform: translateX(-6px);\n    transition: all 100ms linear;\n    transition-delay: 150ms;\n    color: #bbbbbb;\n    background-color: rgba(255, 255, 255, 0.9);\n    padding-left: 2px;\n    padding-right: 1px;\n    margin-right: -4px;\n    margin-top: 2px;\n  }\n\n  .widget-visualization[data-refreshing=\"false\"] & {\n    display: none;\n  }\n}\n\n.refresh-indicator-mini() {\n  font-size: 13px;\n  transition-delay: 0s;\n  color: #bbbbbb;\n  transform: translateY(-4px);\n\n  .refresh-icon:before {\n    transition-delay: 0s;\n    opacity: 0;\n  }\n\n  .rd-timer {\n    transition-delay: 0s;\n    opacity: 1;\n    transform: translateX(0);\n  }\n}\n\n.tile {\n  .widget-menu-regular,\n  .btn__refresh {\n    opacity: 0 !important;\n    transition: opacity 0.35s ease-in-out;\n  }\n\n  .t-header {\n    .th-title {\n      padding-right: 23px; // no overlap on RefreshIndicator\n\n      .hidden-print {\n        margin-bottom: 0;\n      }\n\n      .query-link {\n        color: fade(@redash-black, 80%);\n        font-size: 15px;\n        font-weight: 500;\n\n        &:not(.visualization-name) {\n          color: fade(@redash-black, 50%);\n        }\n      }\n    }\n\n    .query--description {\n      font-size: 14px;\n      line-height: 1.5;\n      font-style: italic;\n\n      p {\n        margin-bottom: 0;\n      }\n    }\n  }\n\n  .t-header.widget {\n    padding: 15px;\n  }\n\n  &:hover,\n  &:focus,\n  &:active,\n  &:focus-within {\n    .widget-menu-regular,\n    .btn__refresh {\n      opacity: 1 !important;\n      transition: opacity 0.35s ease-in-out;\n    }\n\n    .refresh-indicator {\n      .refresh-indicator-mini();\n    }\n  }\n\n  .tile__bottom-control {\n    padding: 10px 15px;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n\n    .btn-transparent {\n      &:first-child {\n        margin-left: -10px;\n      }\n\n      &:last-child {\n        margin-right: -10px;\n      }\n    }\n\n    a,\n    .plain-button {\n      color: fade(@redash-black, 65%);\n\n      &:hover,\n      &:focus {\n        color: fade(@redash-black, 95%);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/dashboards/dashboard-widget/index.js",
    "content": "export { default as VisualizationWidget } from \"./VisualizationWidget\";\nexport { default as TextboxWidget } from \"./TextboxWidget\";\nexport { default as RestrictedWidget } from \"./RestrictedWidget\";\n"
  },
  {
    "path": "client/app/components/dynamic-form/DynamicForm.jsx",
    "content": "import React, { useState, useReducer, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport Form from \"antd/lib/form\";\nimport Button from \"antd/lib/button\";\nimport { includes, isFunction, filter, find, difference, isEmpty, mapValues } from \"lodash\";\nimport notification from \"@/services/notification\";\nimport Collapse from \"@/components/Collapse\";\nimport DynamicFormField, { FieldType } from \"./DynamicFormField\";\nimport getFieldLabel from \"./getFieldLabel\";\nimport helper from \"./dynamicFormHelper\";\n\nimport \"./DynamicForm.less\";\n\nconst ActionType = PropTypes.shape({\n  name: PropTypes.string.isRequired,\n  callback: PropTypes.func.isRequired,\n  type: PropTypes.string,\n  pullRight: PropTypes.bool,\n  disabledWhenDirty: PropTypes.bool,\n});\n\nconst AntdFormType = PropTypes.shape({\n  validateFieldsAndScroll: PropTypes.func,\n});\n\nconst fieldRules = ({ type, required, minLength }) => {\n  const requiredRule = required;\n  const minLengthRule = minLength && includes([\"text\", \"email\", \"password\"], type);\n  const emailTypeRule = type === \"email\";\n\n  return [\n    requiredRule && { required, message: \"This field is required.\" },\n    minLengthRule && { min: minLength, message: \"This field is too short.\" },\n    emailTypeRule && { type: \"email\", message: \"This field must be a valid email.\" },\n  ].filter(rule => rule);\n};\n\nfunction normalizeEmptyValuesToNull(fields, values) {\n  return mapValues(values, (value, key) => {\n    const { initialValue } = find(fields, { name: key }) || {};\n    if ((initialValue === null || initialValue === undefined || initialValue === \"\") && value === \"\") {\n      return null;\n    }\n    return value;\n  });\n}\n\nfunction DynamicFormFields({ fields, feedbackIcons, form }) {\n  return fields.map(field => {\n    const { name, type, initialValue, contentAfter } = field;\n    const fieldLabel = getFieldLabel(field);\n\n    const formItemProps = {\n      name,\n      className: \"m-b-10\",\n      hasFeedback: type !== \"checkbox\" && type !== \"file\" && feedbackIcons,\n      label: type === \"checkbox\" ? \"\" : fieldLabel,\n      rules: fieldRules(field),\n      valuePropName: type === \"checkbox\" ? \"checked\" : \"value\",\n      initialValue,\n    };\n\n    if (type === \"file\") {\n      formItemProps.valuePropName = \"data-value\";\n      formItemProps.getValueFromEvent = e => {\n        if (e && e.fileList[0]) {\n          helper.getBase64(e.file).then(value => {\n            form.setFieldsValue({ [name]: value });\n          });\n        }\n        return undefined;\n      };\n    }\n\n    return (\n      <React.Fragment key={name}>\n        <Form.Item {...formItemProps}>\n          <DynamicFormField field={field} form={form} />\n        </Form.Item>\n        {isFunction(contentAfter) ? contentAfter(form.getFieldValue(name)) : contentAfter}\n      </React.Fragment>\n    );\n  });\n}\n\nDynamicFormFields.propTypes = {\n  fields: PropTypes.arrayOf(FieldType),\n  feedbackIcons: PropTypes.bool,\n  form: AntdFormType.isRequired,\n};\n\nDynamicFormFields.defaultProps = {\n  fields: [],\n  feedbackIcons: false,\n};\n\nconst reducerForActionSet = (state, action) => {\n  if (action.inProgress) {\n    state.add(action.actionName);\n  } else {\n    state.delete(action.actionName);\n  }\n  return new Set(state);\n};\n\nfunction DynamicFormActions({ actions, isFormDirty }) {\n  const [inProgressActions, setActionInProgress] = useReducer(reducerForActionSet, new Set());\n\n  const handleAction = useCallback(action => {\n    const actionName = action.name;\n    if (isFunction(action.callback)) {\n      setActionInProgress({ actionName, inProgress: true });\n      action.callback(() => {\n        setActionInProgress({ actionName, inProgress: false });\n      });\n    }\n  }, []);\n  return actions.map(action => (\n    <Button\n      key={action.name}\n      htmlType=\"button\"\n      className={cx(\"m-t-10\", { \"pull-right\": action.pullRight })}\n      type={action.type}\n      disabled={isFormDirty && action.disableWhenDirty}\n      loading={inProgressActions.has(action.name)}\n      onClick={() => handleAction(action)}>\n      {action.name}\n    </Button>\n  ));\n}\n\nDynamicFormActions.propTypes = {\n  actions: PropTypes.arrayOf(ActionType),\n  isFormDirty: PropTypes.bool,\n};\n\nDynamicFormActions.defaultProps = {\n  actions: [],\n  isFormDirty: false,\n};\n\nexport default function DynamicForm({\n  id,\n  fields,\n  actions,\n  feedbackIcons,\n  hideSubmitButton,\n  defaultShowExtraFields,\n  saveText,\n  onSubmit,\n}) {\n  const [isSubmitting, setIsSubmitting] = useState(false);\n  const [isTouched, setIsTouched] = useState(false);\n  const [showExtraFields, setShowExtraFields] = useState(defaultShowExtraFields);\n  const [form] = Form.useForm();\n  const extraFields = filter(fields, { extra: true });\n  const regularFields = difference(fields, extraFields);\n\n  const handleFinish = useCallback(\n    values => {\n      setIsSubmitting(true);\n      values = normalizeEmptyValuesToNull(fields, values);\n      onSubmit(\n        values,\n        msg => {\n          setIsSubmitting(false);\n          setIsTouched(false); // reset form touched state\n          notification.success(msg);\n        },\n        msg => {\n          setIsSubmitting(false);\n          notification.error(msg);\n        }\n      );\n    },\n    [fields, onSubmit]\n  );\n\n  const handleFinishFailed = useCallback(\n    ({ errorFields }) => {\n      form.scrollToField(errorFields[0].name);\n    },\n    [form]\n  );\n\n  return (\n    <Form\n      form={form}\n      onFieldsChange={() => {\n        setIsTouched(true);\n      }}\n      id={id}\n      className=\"dynamic-form\"\n      layout=\"vertical\"\n      onFinish={handleFinish}\n      onFinishFailed={handleFinishFailed}>\n      <DynamicFormFields fields={regularFields} feedbackIcons={feedbackIcons} form={form} />\n      {!isEmpty(extraFields) && (\n        <div className=\"extra-options\">\n          <Button\n            type=\"dashed\"\n            block\n            className=\"extra-options-button\"\n            onClick={() => setShowExtraFields(currentShowExtraFields => !currentShowExtraFields)}>\n            Additional Settings\n            <i\n              className={cx(\"fa m-l-5\", { \"fa-caret-up\": showExtraFields, \"fa-caret-down\": !showExtraFields })}\n              aria-hidden=\"true\"\n            />\n          </Button>\n          <Collapse collapsed={!showExtraFields} className=\"extra-options-content\">\n            <DynamicFormFields fields={extraFields} feedbackIcons={feedbackIcons} form={form} />\n          </Collapse>\n        </div>\n      )}\n      {!hideSubmitButton && (\n        <Button className=\"w-100 m-t-20\" type=\"primary\" htmlType=\"submit\" disabled={isSubmitting}>\n          {saveText}\n        </Button>\n      )}\n      <DynamicFormActions actions={actions} isFormDirty={isTouched} />\n    </Form>\n  );\n}\n\nDynamicForm.propTypes = {\n  id: PropTypes.string,\n  fields: PropTypes.arrayOf(FieldType),\n  actions: PropTypes.arrayOf(ActionType),\n  feedbackIcons: PropTypes.bool,\n  hideSubmitButton: PropTypes.bool,\n  defaultShowExtraFields: PropTypes.bool,\n  saveText: PropTypes.string,\n  onSubmit: PropTypes.func,\n};\n\nDynamicForm.defaultProps = {\n  id: null,\n  fields: [],\n  actions: [],\n  feedbackIcons: false,\n  hideSubmitButton: false,\n  defaultShowExtraFields: false,\n  saveText: \"Save\",\n  onSubmit: () => {},\n};\n"
  },
  {
    "path": "client/app/components/dynamic-form/DynamicForm.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\";\n\n@btn-extra-options-bg: fade(@redash-gray, 10%);\n@btn-extra-options-border: fade(@redash-gray, 15%);\n\n.dynamic-form {\n  .extra-options {\n    margin: 25px 0 10px;\n  }\n\n  .extra-options-button {\n    &,\n    &:focus,\n    &:hover {\n      height: 40px;\n      font-weight: 500;\n      background-color: @btn-extra-options-bg;\n      border-color: @btn-extra-options-border;\n      color: @btn-default-color;\n    }\n\n    &:focus,\n    &:hover {\n      background-color: fade(@btn-extra-options-bg, 15%);\n    }\n  }\n\n  .extra-options-content {\n    margin-top: 15px;\n\n    .ant-form-item:last-of-type {\n      margin-bottom: 0 !important;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/dynamic-form/DynamicFormField.jsx",
    "content": "import React from \"react\";\nimport { get } from \"lodash\";\nimport PropTypes from \"prop-types\";\nimport getFieldLabel from \"./getFieldLabel\";\n\nimport {\n  AceEditorField,\n  CheckboxField,\n  ContentField,\n  FileField,\n  InputField,\n  NumberField,\n  SelectField,\n  TextAreaField,\n} from \"./fields\";\n\nexport const FieldType = PropTypes.shape({\n  name: PropTypes.string.isRequired,\n  title: PropTypes.string,\n  type: PropTypes.oneOf([\n    \"ace\",\n    \"text\",\n    \"textarea\",\n    \"email\",\n    \"password\",\n    \"number\",\n    \"checkbox\",\n    \"file\",\n    \"select\",\n    \"content\",\n  ]).isRequired,\n  initialValue: PropTypes.oneOfType([\n    PropTypes.string,\n    PropTypes.number,\n    PropTypes.bool,\n    PropTypes.arrayOf(PropTypes.string),\n    PropTypes.arrayOf(PropTypes.number),\n  ]),\n  content: PropTypes.node,\n  mode: PropTypes.string,\n  required: PropTypes.bool,\n  extra: PropTypes.bool,\n  readOnly: PropTypes.bool,\n  autoFocus: PropTypes.bool,\n  minLength: PropTypes.number,\n  placeholder: PropTypes.string,\n  contentAfter: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),\n  loading: PropTypes.bool,\n  props: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n});\n\nconst FieldTypeComponent = {\n  checkbox: CheckboxField,\n  file: FileField,\n  select: SelectField,\n  number: NumberField,\n  textarea: TextAreaField,\n  ace: AceEditorField,\n  content: ContentField,\n};\n\nexport default function DynamicFormField({ form, field, ...otherProps }) {\n  const { name, type, readOnly, autoFocus } = field;\n  const fieldLabel = getFieldLabel(field);\n\n  const fieldProps = {\n    ...field.props,\n    className: \"w-100\",\n    name,\n    type,\n    readOnly,\n    autoFocus,\n    placeholder: field.placeholder,\n    \"data-test\": fieldLabel,\n    ...otherProps,\n  };\n\n  const FieldComponent = get(FieldTypeComponent, type, InputField);\n  return <FieldComponent {...fieldProps} form={form} field={field} />;\n}\n\nDynamicFormField.propTypes = { field: FieldType.isRequired };\n"
  },
  {
    "path": "client/app/components/dynamic-form/dynamicFormHelper.js",
    "content": "import React from \"react\";\nimport { each, includes, isUndefined, isEmpty, isNil, map, get, some } from \"lodash\";\n\nfunction orderedInputs(properties, order, targetOptions) {\n  const inputs = new Array(order.length);\n  Object.keys(properties).forEach(key => {\n    const position = order.indexOf(key);\n    const input = {\n      name: key,\n      title: properties[key].title,\n      type: properties[key].type,\n      placeholder: isNil(properties[key].default) ? null : properties[key].default.toString(),\n      required: properties[key].required,\n      extra: properties[key].extra,\n      initialValue: targetOptions[key],\n    };\n\n    if (input.type === \"select\") {\n      input.placeholder = \"Select an option\";\n      input.options = properties[key].options;\n    }\n\n    if (position > -1) {\n      inputs[position] = input;\n    } else {\n      inputs.push(input);\n    }\n  });\n  return inputs;\n}\n\nfunction normalizeSchema(configurationSchema) {\n  each(configurationSchema.properties, (prop, name) => {\n    if (name === \"password\" || name === \"passwd\") {\n      prop.type = \"password\";\n    }\n\n    if (name.endsWith(\"File\")) {\n      prop.type = \"file\";\n    }\n\n    if (prop.type === \"boolean\") {\n      prop.type = \"checkbox\";\n    }\n\n    if (prop.type === \"string\") {\n      prop.type = \"text\";\n    }\n\n    if (!isEmpty(prop.enum)) {\n      prop.type = \"select\";\n      prop.options = map(prop.enum, value => ({ value, name: value }));\n    }\n\n    if (!isEmpty(prop.extendedEnum)) {\n      prop.type = \"select\";\n      prop.options = prop.extendedEnum;\n    }\n\n    prop.required = includes(configurationSchema.required, name);\n    prop.extra = includes(configurationSchema.extra_options, name);\n  });\n\n  configurationSchema.order = configurationSchema.order || [];\n}\n\nfunction setDefaultValueToFields(configurationSchema, options = {}) {\n  const properties = configurationSchema.properties;\n  Object.keys(properties).forEach(key => {\n    const property = properties[key];\n    // set default value for checkboxes\n    if (!isUndefined(property.default) && property.type === \"checkbox\") {\n      options[key] = property.default;\n    }\n    // set default or first value when value has predefined options\n    if (property.type === \"select\") {\n      const optionValues = map(property.options, option => option.value);\n      options[key] = includes(optionValues, property.default) ? property.default : optionValues[0];\n    }\n  });\n}\n\nfunction getFields(type = {}, target = { options: {} }) {\n  const configurationSchema = type.configuration_schema;\n  normalizeSchema(configurationSchema);\n  const hasTargetObject = Object.keys(target.options).length > 0;\n  if (!hasTargetObject) {\n    setDefaultValueToFields(configurationSchema, target.options);\n  }\n\n  const isNewTarget = !target.id;\n  const inputs = [\n    {\n      name: \"name\",\n      title: \"Name\",\n      type: \"text\",\n      required: true,\n      initialValue: target.name,\n      contentAfter: React.createElement(\"hr\"),\n      placeholder: `My ${type.name}`,\n      autoFocus: isNewTarget,\n    },\n    ...orderedInputs(configurationSchema.properties, configurationSchema.order, target.options),\n  ];\n\n  return inputs;\n}\n\nfunction updateTargetWithValues(target, values) {\n  target.name = values.name;\n  Object.keys(values).forEach(key => {\n    if (key !== \"name\") {\n      target.options[key] = values[key];\n    }\n  });\n}\n\nfunction getBase64(file) {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.readAsDataURL(file);\n    reader.onload = () => resolve(reader.result.substr(reader.result.indexOf(\",\") + 1));\n    reader.onerror = error => reject(error);\n  });\n}\n\nfunction hasFilledExtraField(type, target) {\n  const extraOptions = get(type, \"configuration_schema.extra_options\", []);\n  return some(extraOptions, optionName => {\n    const defaultOptionValue = get(type, [\"configuration_schema\", \"properties\", optionName, \"default\"]);\n    const targetOptionValue = get(target, [\"options\", optionName]);\n    return !isNil(targetOptionValue) && targetOptionValue !== defaultOptionValue;\n  });\n}\n\nexport default {\n  getFields,\n  updateTargetWithValues,\n  getBase64,\n  hasFilledExtraField,\n};\n"
  },
  {
    "path": "client/app/components/dynamic-form/fields/AceEditorField.jsx",
    "content": "import React from \"react\";\nimport AceEditorInput from \"@/components/AceEditorInput\";\n\nexport default function AceEditorField({ form, field, ...otherProps }) {\n  return <AceEditorInput {...otherProps} />;\n}\n"
  },
  {
    "path": "client/app/components/dynamic-form/fields/CheckboxField.jsx",
    "content": "import React from \"react\";\nimport Checkbox from \"antd/lib/checkbox\";\nimport getFieldLabel from \"../getFieldLabel\";\n\nexport default function CheckboxField({ form, field, ...otherProps }) {\n  const fieldLabel = getFieldLabel(field);\n  return <Checkbox {...otherProps}>{fieldLabel}</Checkbox>;\n}\n"
  },
  {
    "path": "client/app/components/dynamic-form/fields/ContentField.jsx",
    "content": "export default function ContentField({ field }) {\n  return field.content;\n}\n"
  },
  {
    "path": "client/app/components/dynamic-form/fields/FileField.jsx",
    "content": "import React from \"react\";\nimport Button from \"antd/lib/button\";\nimport Upload from \"antd/lib/upload\";\nimport UploadOutlinedIcon from \"@ant-design/icons/UploadOutlined\";\n\nexport default function FileField({ form, field, ...otherProps }) {\n  const { name, initialValue } = field;\n  const { getFieldValue } = form;\n  const disabled = getFieldValue(name) !== undefined && getFieldValue(name) !== initialValue;\n\n  return (\n    <Upload {...otherProps} beforeUpload={() => false}>\n      <Button disabled={disabled}>\n        <UploadOutlinedIcon /> Click to upload\n      </Button>\n    </Upload>\n  );\n}\n"
  },
  {
    "path": "client/app/components/dynamic-form/fields/InputField.jsx",
    "content": "import React from \"react\";\nimport Input from \"antd/lib/input\";\n\nexport default function InputField({ form, field, ...otherProps }) {\n  return <Input {...otherProps} />;\n}\n"
  },
  {
    "path": "client/app/components/dynamic-form/fields/NumberField.jsx",
    "content": "import React from \"react\";\nimport InputNumber from \"antd/lib/input-number\";\n\nexport default function NumberField({ form, field, ...otherProps }) {\n  return <InputNumber {...otherProps} />;\n}\n"
  },
  {
    "path": "client/app/components/dynamic-form/fields/SelectField.jsx",
    "content": "import React from \"react\";\nimport Select from \"antd/lib/select\";\n\nexport default function SelectField({ form, field, ...otherProps }) {\n  const { readOnly } = field;\n  return (\n    <Select\n      {...otherProps}\n      optionFilterProp=\"children\"\n      loading={field.loading || false}\n      mode={field.mode}\n      getPopupContainer={trigger => trigger.parentNode}>\n      {field.options &&\n        field.options.map(option => (\n          <Select.Option key={`${option.value}`} value={option.value} disabled={readOnly}>\n            {option.name || option.value}\n          </Select.Option>\n        ))}\n    </Select>\n  );\n}\n"
  },
  {
    "path": "client/app/components/dynamic-form/fields/TextAreaField.jsx",
    "content": "import React from \"react\";\nimport Input from \"antd/lib/input\";\n\nexport default function TextAreaField({ form, field, ...otherProps }) {\n  return <Input.TextArea {...otherProps} />;\n}\n"
  },
  {
    "path": "client/app/components/dynamic-form/fields/index.js",
    "content": "export { default as AceEditorField } from \"./AceEditorField\";\nexport { default as CheckboxField } from \"./CheckboxField\";\nexport { default as ContentField } from \"./ContentField\";\nexport { default as FileField } from \"./FileField\";\nexport { default as InputField } from \"./InputField\";\nexport { default as NumberField } from \"./NumberField\";\nexport { default as SelectField } from \"./SelectField\";\nexport { default as TextAreaField } from \"./TextAreaField\";\n"
  },
  {
    "path": "client/app/components/dynamic-form/getFieldLabel.js",
    "content": "import { toHuman } from \"@/lib/utils\";\n\nexport default function getFieldLabel(field) {\n  const { title, name } = field;\n  return title || toHuman(name);\n}\n"
  },
  {
    "path": "client/app/components/dynamic-parameters/DateParameter.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { getDynamicDateFromString } from \"@/services/parameters/DateParameter\";\nimport DynamicDatePicker from \"@/components/dynamic-parameters/DynamicDatePicker\";\n\nconst DYNAMIC_DATE_OPTIONS = [\n  {\n    name: \"Today/Now\",\n    value: getDynamicDateFromString(\"d_now\"),\n    label: () =>\n      getDynamicDateFromString(\"d_now\")\n        .value()\n        .format(\"MMM D\"),\n  },\n  {\n    name: \"Yesterday\",\n    value: getDynamicDateFromString(\"d_yesterday\"),\n    label: () =>\n      getDynamicDateFromString(\"d_yesterday\")\n        .value()\n        .format(\"MMM D\"),\n  },\n];\n\nfunction DateParameter(props) {\n  return (\n    <DynamicDatePicker\n      dynamicButtonOptions={{ options: DYNAMIC_DATE_OPTIONS }}\n      {...props}\n      dateOptions={{ \"aria-label\": \"Parameter date value\" }}\n    />\n  );\n}\n\nDateParameter.propTypes = {\n  type: PropTypes.string,\n  className: PropTypes.string,\n  value: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n  parameter: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n  onSelect: PropTypes.func,\n};\n\nDateParameter.defaultProps = {\n  type: \"\",\n  className: \"\",\n  value: null,\n  parameter: null,\n  onSelect: () => {},\n};\n\nexport default DateParameter;\n"
  },
  {
    "path": "client/app/components/dynamic-parameters/DateRangeParameter.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { includes } from \"lodash\";\nimport { getDynamicDateRangeFromString } from \"@/services/parameters/DateRangeParameter\";\nimport DynamicDateRangePicker from \"@/components/dynamic-parameters/DynamicDateRangePicker\";\n\nconst DYNAMIC_DATE_OPTIONS = [\n  {\n    name: \"This week\",\n    value: getDynamicDateRangeFromString(\"d_this_week\"),\n    label: () =>\n      getDynamicDateRangeFromString(\"d_this_week\").value()[0].format(\"MMM D\") +\n      \" - \" +\n      getDynamicDateRangeFromString(\"d_this_week\").value()[1].format(\"MMM D\"),\n  },\n  {\n    name: \"This month\",\n    value: getDynamicDateRangeFromString(\"d_this_month\"),\n    label: () => getDynamicDateRangeFromString(\"d_this_month\").value()[0].format(\"MMMM\"),\n  },\n  {\n    name: \"This year\",\n    value: getDynamicDateRangeFromString(\"d_this_year\"),\n    label: () => getDynamicDateRangeFromString(\"d_this_year\").value()[0].format(\"YYYY\"),\n  },\n  {\n    name: \"Last week\",\n    value: getDynamicDateRangeFromString(\"d_last_week\"),\n    label: () =>\n      getDynamicDateRangeFromString(\"d_last_week\").value()[0].format(\"MMM D\") +\n      \" - \" +\n      getDynamicDateRangeFromString(\"d_last_week\").value()[1].format(\"MMM D\"),\n  },\n  {\n    name: \"Last month\",\n    value: getDynamicDateRangeFromString(\"d_last_month\"),\n    label: () => getDynamicDateRangeFromString(\"d_last_month\").value()[0].format(\"MMMM\"),\n  },\n  {\n    name: \"Last year\",\n    value: getDynamicDateRangeFromString(\"d_last_year\"),\n    label: () => getDynamicDateRangeFromString(\"d_last_year\").value()[0].format(\"YYYY\"),\n  },\n  {\n    name: \"Last 7 days\",\n    value: getDynamicDateRangeFromString(\"d_last_7_days\"),\n    label: () => getDynamicDateRangeFromString(\"d_last_7_days\").value()[0].format(\"MMM D\") + \" - Today\",\n  },\n  {\n    name: \"Last 14 days\",\n    value: getDynamicDateRangeFromString(\"d_last_14_days\"),\n    label: () => getDynamicDateRangeFromString(\"d_last_14_days\").value()[0].format(\"MMM D\") + \" - Today\",\n  },\n  {\n    name: \"Last 30 days\",\n    value: getDynamicDateRangeFromString(\"d_last_30_days\"),\n    label: () => getDynamicDateRangeFromString(\"d_last_30_days\").value()[0].format(\"MMM D\") + \" - Today\",\n  },\n  {\n    name: \"Last 60 days\",\n    value: getDynamicDateRangeFromString(\"d_last_60_days\"),\n    label: () => getDynamicDateRangeFromString(\"d_last_60_days\").value()[0].format(\"MMM D\") + \" - Today\",\n  },\n  {\n    name: \"Last 90 days\",\n    value: getDynamicDateRangeFromString(\"d_last_90_days\"),\n    label: () => getDynamicDateRangeFromString(\"d_last_90_days\").value()[0].format(\"MMM D\") + \" - Today\",\n  },\n  {\n    name: \"Last 12 months\",\n    value: getDynamicDateRangeFromString(\"d_last_12_months\"),\n    label: null,\n  },\n  {\n    name: \"Last 2 years\",\n    value: getDynamicDateRangeFromString(\"d_last_2_years\"),\n    label: null,\n  },\n  {\n    name: \"Last 3 years\",\n    value: getDynamicDateRangeFromString(\"d_last_3_years\"),\n    label: null,\n  },\n  {\n    name: \"Last 10 years\",\n    value: getDynamicDateRangeFromString(\"d_last_10_years\"),\n    label: null,\n  },\n];\n\nconst DYNAMIC_DATETIME_OPTIONS = [\n  {\n    name: \"Today\",\n    value: getDynamicDateRangeFromString(\"d_today\"),\n    label: () => getDynamicDateRangeFromString(\"d_today\").value()[0].format(\"MMM D\"),\n  },\n  {\n    name: \"Yesterday\",\n    value: getDynamicDateRangeFromString(\"d_yesterday\"),\n    label: () => getDynamicDateRangeFromString(\"d_yesterday\").value()[0].format(\"MMM D\"),\n  },\n  ...DYNAMIC_DATE_OPTIONS,\n];\n\nfunction DateRangeParameter(props) {\n  const options = includes(props.type, \"datetime-range\") ? DYNAMIC_DATETIME_OPTIONS : DYNAMIC_DATE_OPTIONS;\n  return <DynamicDateRangePicker {...props} dynamicButtonOptions={{ options }} />;\n}\n\nDateRangeParameter.propTypes = {\n  type: PropTypes.string,\n  className: PropTypes.string,\n  value: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n  parameter: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n  onSelect: PropTypes.func,\n};\n\nDateRangeParameter.defaultProps = {\n  type: \"\",\n  className: \"\",\n  value: null,\n  parameter: null,\n  onSelect: () => {},\n};\n\nexport default DateRangeParameter;\n"
  },
  {
    "path": "client/app/components/dynamic-parameters/DynamicButton.jsx",
    "content": "import React, { useRef } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { isFunction, get, findIndex } from \"lodash\";\nimport Dropdown from \"antd/lib/dropdown\";\nimport Menu from \"antd/lib/menu\";\nimport Typography from \"antd/lib/typography\";\nimport { DynamicDateType } from \"@/services/parameters/DateParameter\";\nimport { DynamicDateRangeType } from \"@/services/parameters/DateRangeParameter\";\n\nimport ArrowLeftOutlinedIcon from \"@ant-design/icons/ArrowLeftOutlined\";\nimport ThunderboltTwoToneIcon from \"@ant-design/icons/ThunderboltTwoTone\";\nimport ThunderboltOutlinedIcon from \"@ant-design/icons/ThunderboltOutlined\";\n\nimport \"./DynamicButton.less\";\n\nconst { Text } = Typography;\n\nfunction DynamicButton({ options, selectedDynamicValue, onSelect, enabled, staticValueLabel }) {\n  const menu = (\n    <Menu\n      className=\"dynamic-menu\"\n      onClick={({ key }) => onSelect(get(options, key, \"static\"))}\n      selectedKeys={[`${findIndex(options, { value: selectedDynamicValue })}`]}\n      data-test=\"DynamicButtonMenu\">\n      {options.map((option, index) => (\n        // eslint-disable-next-line react/no-array-index-key\n        <Menu.Item key={index}>\n          {option.name} {option.label && <em>{isFunction(option.label) ? option.label() : option.label}</em>}\n        </Menu.Item>\n      ))}\n      {enabled && <Menu.Divider />}\n      {enabled && (\n        <Menu.Item>\n          <ArrowLeftOutlinedIcon />\n          <Text type=\"secondary\">{staticValueLabel}</Text>\n        </Menu.Item>\n      )}\n    </Menu>\n  );\n\n  const containerRef = useRef(null);\n\n  return (\n    <div ref={containerRef}>\n      <div role=\"presentation\" onClick={e => e.stopPropagation()}>\n        <Dropdown.Button\n          overlay={menu}\n          className=\"dynamic-button\"\n          placement=\"bottomRight\"\n          trigger={[\"click\"]}\n          icon={\n            enabled ? (\n              <ThunderboltTwoToneIcon className=\"dynamic-icon\" />\n            ) : (\n              <ThunderboltOutlinedIcon className=\"dynamic-icon\" />\n            )\n          }\n          getPopupContainer={() => containerRef.current}\n          data-test=\"DynamicButton\"\n        />\n      </div>\n    </div>\n  );\n}\n\nDynamicButton.propTypes = {\n  options: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types\n  selectedDynamicValue: PropTypes.oneOfType([DynamicDateType, DynamicDateRangeType]),\n  onSelect: PropTypes.func,\n  enabled: PropTypes.bool,\n  staticValueLabel: PropTypes.string,\n};\n\nDynamicButton.defaultProps = {\n  options: [],\n  selectedDynamicValue: null,\n  onSelect: () => {},\n  enabled: false,\n  staticValueLabel: \"Back to Static Value\",\n};\n\nexport default DynamicButton;\n"
  },
  {
    "path": "client/app/components/dynamic-parameters/DynamicButton.less",
    "content": ".dynamic-button {\n  height: 100%;\n  position: absolute !important;\n  right: 1px;\n  top: 0;\n\n  .ant-dropdown-trigger {\n    height: 100%;\n  }\n\n  button {\n    border: none;\n    padding: 0;\n    box-shadow: none;\n    background-color: transparent !important;\n  }\n\n  &:after {\n    content: \"\";\n    position: absolute;\n    width: 1px;\n    height: 19px;\n    left: 0;\n    top: 8px;\n    border-left: 1px dotted rgba(0, 0, 0, 0.12);\n  }\n}\n\n.dynamic-menu {\n  width: 187px;\n\n  em {\n    color: #ccc;\n    font-size: 11px;\n  }\n}\n\n.dynamic-icon {\n  display: flex !important;\n  align-items: center;\n  justify-content: center;\n}\n"
  },
  {
    "path": "client/app/components/dynamic-parameters/DynamicDatePicker.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport classNames from \"classnames\";\nimport moment from \"moment\";\nimport { includes } from \"lodash\";\nimport { isDynamicDate } from \"@/services/parameters/DateParameter\";\nimport DateInput from \"@/components/DateInput\";\nimport DateTimeInput from \"@/components/DateTimeInput\";\nimport DynamicButton from \"@/components/dynamic-parameters/DynamicButton\";\n\nimport \"./DynamicParameters.less\";\n\nclass DynamicDatePicker extends React.Component {\n  static propTypes = {\n    type: PropTypes.string,\n    className: PropTypes.string,\n    value: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n    parameter: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n    onSelect: PropTypes.func,\n    dynamicButtonOptions: PropTypes.shape({\n      staticValueLabel: PropTypes.string,\n      options: PropTypes.arrayOf(\n        PropTypes.shape({\n          name: PropTypes.string,\n          value: PropTypes.object,\n          label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),\n        })\n      ),\n    }),\n    dateOptions: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n  };\n\n  static defaultProps = {\n    type: \"\",\n    className: \"\",\n    value: null,\n    parameter: null,\n    dynamicButtonOptions: {\n      options: [],\n    },\n    onSelect: () => {},\n  };\n\n  constructor(props) {\n    super(props);\n    this.dateComponentRef = React.createRef();\n  }\n\n  onDynamicValueSelect = dynamicValue => {\n    const { onSelect, parameter } = this.props;\n    if (dynamicValue === \"static\") {\n      const parameterValue = parameter.getExecutionValue();\n      if (parameterValue) {\n        onSelect(moment(parameterValue));\n      } else {\n        onSelect(null);\n      }\n    } else {\n      onSelect(dynamicValue.value);\n    }\n    // give focus to the DatePicker to get keyboard shortcuts to work\n    this.dateComponentRef.current.focus();\n  };\n\n  render() {\n    const { type, value, className, dateOptions, dynamicButtonOptions, onSelect } = this.props;\n    const hasDynamicValue = isDynamicDate(value);\n    const isDateTime = includes(type, \"datetime\");\n\n    const additionalAttributes = {};\n\n    let DateComponent = DateInput;\n    if (isDateTime) {\n      DateComponent = DateTimeInput;\n      if (includes(type, \"with-seconds\")) {\n        additionalAttributes.withSeconds = true;\n      }\n    }\n\n    if (moment.isMoment(value) || value === null) {\n      additionalAttributes.value = value;\n    }\n\n    if (hasDynamicValue) {\n      const dynamicDate = value;\n      additionalAttributes.placeholder = dynamicDate && dynamicDate.name;\n      additionalAttributes.value = null;\n    }\n\n    return (\n      <div className={classNames(\"date-parameter\", className)}>\n        <DateComponent\n          {...dateOptions}\n          ref={this.dateComponentRef}\n          className={classNames(\"redash-datepicker\", type, { \"dynamic-value\": hasDynamicValue })}\n          onSelect={onSelect}\n          suffixIcon={null}\n          {...additionalAttributes}\n        />\n        <DynamicButton\n          options={dynamicButtonOptions.options}\n          staticValueLabel={dynamicButtonOptions.staticValueLabel}\n          selectedDynamicValue={hasDynamicValue ? value : null}\n          enabled={hasDynamicValue}\n          onSelect={this.onDynamicValueSelect}\n        />\n      </div>\n    );\n  }\n}\n\nexport default DynamicDatePicker;\n"
  },
  {
    "path": "client/app/components/dynamic-parameters/DynamicDateRangePicker.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport classNames from \"classnames\";\nimport moment from \"moment\";\nimport { includes, isArray, isObject } from \"lodash\";\nimport { isDynamicDateRange } from \"@/services/parameters/DateRangeParameter\";\nimport DateRangeInput from \"@/components/DateRangeInput\";\nimport DateTimeRangeInput from \"@/components/DateTimeRangeInput\";\nimport DynamicButton from \"@/components/dynamic-parameters/DynamicButton\";\n\nimport \"./DynamicParameters.less\";\n\nfunction isValidDateRangeValue(value) {\n  return isArray(value) && value.length === 2 && moment.isMoment(value[0]) && moment.isMoment(value[1]);\n}\n\nclass DynamicDateRangePicker extends React.Component {\n  static propTypes = {\n    type: PropTypes.oneOf([\"date-range\", \"datetime-range\", \"datetime-range-with-seconds\"]).isRequired,\n    className: PropTypes.string,\n    value: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n    parameter: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n    onSelect: PropTypes.func,\n    dynamicButtonOptions: PropTypes.shape({\n      staticValueLabel: PropTypes.string,\n      options: PropTypes.arrayOf(\n        PropTypes.shape({\n          name: PropTypes.string,\n          value: PropTypes.object,\n          label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),\n        })\n      ),\n    }),\n    dateRangeOptions: PropTypes.any, // eslint-disable-line react/forbid-prop-types\n  };\n\n  static defaultProps = {\n    type: \"date-range\",\n    className: \"\",\n    value: null,\n    parameter: null,\n    dynamicButtonOptions: {\n      options: [],\n    },\n    onSelect: () => {},\n  };\n\n  constructor(props) {\n    super(props);\n    this.dateRangeComponentRef = React.createRef();\n  }\n\n  onDynamicValueSelect = dynamicValue => {\n    const { onSelect, parameter } = this.props;\n    if (dynamicValue === \"static\") {\n      const parameterValue = parameter.getExecutionValue();\n      if (isObject(parameterValue) && parameterValue.start && parameterValue.end) {\n        onSelect([moment(parameterValue.start), moment(parameterValue.end)]);\n      } else {\n        onSelect(null);\n      }\n    } else {\n      onSelect(dynamicValue.value);\n    }\n    // give focus to the DatePicker to get keyboard shortcuts to work\n    this.dateRangeComponentRef.current.focus();\n  };\n\n  render() {\n    const { type, value, onSelect, className, dynamicButtonOptions, dateRangeOptions, parameter, ...rest } = this.props;\n    const isDateTimeRange = includes(type, \"datetime-range\");\n    const hasDynamicValue = isDynamicDateRange(value);\n\n    const additionalAttributes = {};\n\n    let DateRangeComponent = DateRangeInput;\n    if (isDateTimeRange) {\n      DateRangeComponent = DateTimeRangeInput;\n      if (includes(type, \"with-seconds\")) {\n        additionalAttributes.withSeconds = true;\n      }\n    }\n\n    if (isValidDateRangeValue(value) || value === null) {\n      additionalAttributes.value = value;\n    }\n\n    if (hasDynamicValue) {\n      additionalAttributes.placeholder = [value && value.name];\n      additionalAttributes.value = null;\n    }\n\n    return (\n      <div {...rest} className={classNames(\"date-range-parameter\", className)}>\n        <DateRangeComponent\n          {...dateRangeOptions}\n          ref={this.dateRangeComponentRef}\n          className={classNames(\"redash-datepicker date-range-input\", type, { \"dynamic-value\": hasDynamicValue })}\n          onSelect={onSelect}\n          suffixIcon={null}\n          {...additionalAttributes}\n        />\n        <DynamicButton\n          options={dynamicButtonOptions.options}\n          staticValueLabel={dynamicButtonOptions.staticValueLabel}\n          selectedDynamicValue={hasDynamicValue ? value : null}\n          enabled={hasDynamicValue}\n          onSelect={this.onDynamicValueSelect}\n        />\n      </div>\n    );\n  }\n}\n\nexport default DynamicDateRangePicker;\n"
  },
  {
    "path": "client/app/components/dynamic-parameters/DynamicParameters.less",
    "content": "@import (reference, less) \"~@/assets/less/inc/variables\";\n\n.date-range-parameter,\n.date-parameter {\n  position: relative;\n}\n\n.redash-datepicker {\n  padding-right: 35px !important;\n\n  &.date-range {\n    width: 294px;\n  }\n  &.datetime-range {\n    width: 352px;\n  }\n  &.datetime-range-with-seconds {\n    width: 382px;\n  }\n  &.dynamic-value {\n    width: 195px;\n  }\n\n  &.ant-picker-range .ant-picker-clear {\n    right: 35px !important;\n    background: transparent;\n  }\n\n  &.date-range-input {\n    transition: width 100ms ease-in-out;\n  }\n\n  &.dynamic-value {\n    & ::placeholder {\n      color: @input-color !important;\n    }\n\n    &.date-range-input {\n      .ant-picker-active-bar {\n        opacity: 0;\n      }\n\n      .ant-picker-separator,\n      .ant-picker-range-separator {\n        display: none;\n      }\n\n      .ant-picker-input:not(:first-child) {\n        width: 0;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/empty-state/EmptyState.d.ts",
    "content": "import React from \"react\";\n\ntype DefaultStepKey = \"dataSources\" | \"queries\" | \"alerts\" | \"dashboards\" | \"users\";\nexport type StepKey<K> = DefaultStepKey | K;\n\nexport interface StepItem<K> {\n  key: StepKey<K>;\n  node: React.ReactNode;\n}\n\nexport interface EmptyStateHelpMessageProps {\n  helpTriggerType: string;\n}\n\nexport declare const EmptyStateHelpMessage: React.FunctionComponent<EmptyStateHelpMessageProps>;\n\nexport interface EmptyStateProps<K = unknown> {\n  header?: string;\n  icon?: string;\n  description: string;\n  illustration: string;\n  illustrationPath?: string;\n  helpMessage?: React.ReactNode;\n  closable?: boolean;\n  onClose?: () => void;\n\n  onboardingMode?: boolean;\n  showAlertStep?: boolean;\n  showDashboardStep?: boolean;\n  showDataSourceStep?: boolean;\n  showInviteStep?: boolean;\n\n  getStepsItems?: (items: Array<StepItem<K>>) => Array<StepItem<K>>;\n}\n\ndeclare class EmptyState<R> extends React.Component<EmptyStateProps<R>> {}\n\nexport default EmptyState;\n\nexport interface StepProps {\n  show: boolean;\n  completed: boolean;\n  url?: string;\n  urlTarget?: string;\n  urlText?: React.ReactNode;\n  text?: React.ReactNode;\n  onClick?: () => void;\n}\n\nexport declare const Step: React.FunctionComponent<StepProps>;\n"
  },
  {
    "path": "client/app/components/empty-state/EmptyState.jsx",
    "content": "import { keys, some } from \"lodash\";\nimport React, { useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport classNames from \"classnames\";\nimport CloseOutlinedIcon from \"@ant-design/icons/CloseOutlined\";\nimport Link from \"@/components/Link\";\nimport PlainButton from \"@/components/PlainButton\";\nimport CreateDashboardDialog from \"@/components/dashboards/CreateDashboardDialog\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport { currentUser } from \"@/services/auth\";\nimport organizationStatus from \"@/services/organizationStatus\";\n\nimport \"./empty-state.less\";\n\nexport function Step({ show, completed, text, url, urlText, onClick }) {\n  if (!show) {\n    return null;\n  }\n\n  const commonProps = { children: urlText, onClick };\n\n  return (\n    <li className={classNames({ done: completed })}>\n      {url ? <Link href={url} {...commonProps} /> : <PlainButton type=\"link\" {...commonProps} />} {text}\n    </li>\n  );\n}\n\nStep.propTypes = {\n  show: PropTypes.bool.isRequired,\n  completed: PropTypes.bool.isRequired,\n  text: PropTypes.node,\n  url: PropTypes.string,\n  urlTarget: PropTypes.string,\n  urlText: PropTypes.node,\n  onClick: PropTypes.func,\n};\n\nStep.defaultProps = {\n  url: null,\n  urlTarget: null,\n  urlText: null,\n  text: null,\n  onClick: null,\n};\n\nexport function EmptyStateHelpMessage({ helpTriggerType }) {\n  return (\n    <p>\n      Need more support?{\" \"}\n      <HelpTrigger className=\"f-14\" type={helpTriggerType} showTooltip={false}>\n        See our Help\n      </HelpTrigger>\n    </p>\n  );\n}\n\nEmptyStateHelpMessage.propTypes = {\n  helpTriggerType: PropTypes.string.isRequired,\n};\n\nfunction EmptyState({\n  icon,\n  header,\n  description,\n  illustration,\n  helpMessage,\n  closable,\n  onClose,\n  onboardingMode,\n  showAlertStep,\n  showDashboardStep,\n  showDataSourceStep,\n  showInviteStep,\n  getStepsItems,\n  illustrationPath,\n}) {\n  const isAvailable = {\n    dataSource: showDataSourceStep,\n    query: true,\n    alert: showAlertStep,\n    dashboard: showDashboardStep,\n    inviteUsers: showInviteStep,\n  };\n\n  const isCompleted = {\n    dataSource: organizationStatus.objectCounters.data_sources > 0,\n    query: organizationStatus.objectCounters.queries > 0,\n    alert: organizationStatus.objectCounters.alerts > 0,\n    dashboard: organizationStatus.objectCounters.dashboards > 0,\n    inviteUsers: organizationStatus.objectCounters.users > 1,\n  };\n\n  const showCreateDashboardDialog = useCallback(() => {\n    CreateDashboardDialog.showModal();\n  }, []);\n\n  // Show if `onboardingMode=false` or any requested step not completed\n  const shouldShow = !onboardingMode || some(keys(isAvailable), (step) => isAvailable[step] && !isCompleted[step]);\n\n  if (!shouldShow) {\n    return null;\n  }\n\n  const renderDataSourcesStep = () => {\n    if (currentUser.isAdmin) {\n      return (\n        <Step\n          key=\"dataSources\"\n          show={isAvailable.dataSource}\n          completed={isCompleted.dataSource}\n          url=\"data_sources/new\"\n          urlText=\"Connect a Data Source\"\n        />\n      );\n    }\n\n    return (\n      <Step\n        key=\"dataSources\"\n        show={isAvailable.dataSource}\n        completed={isCompleted.dataSource}\n        text=\"Ask an account admin to connect a data source\"\n      />\n    );\n  };\n\n  const defaultStepsItems = [\n    {\n      key: \"dataSources\",\n      node: renderDataSourcesStep(),\n    },\n    {\n      key: \"queries\",\n      node: (\n        <Step\n          key=\"queries\"\n          show={isAvailable.query}\n          completed={isCompleted.query}\n          url=\"queries/new\"\n          urlText=\"Create your first Query\"\n        />\n      ),\n    },\n    {\n      key: \"alerts\",\n      node: (\n        <Step\n          key=\"alerts\"\n          show={isAvailable.alert}\n          completed={isCompleted.alert}\n          url=\"alerts/new\"\n          urlText=\"Create your first Alert\"\n        />\n      ),\n    },\n    {\n      key: \"dashboards\",\n      node: (\n        <Step\n          key=\"dashboards\"\n          show={isAvailable.dashboard}\n          completed={isCompleted.dashboard}\n          onClick={showCreateDashboardDialog}\n          urlText=\"Create your first Dashboard\"\n        />\n      ),\n    },\n    {\n      key: \"users\",\n      node: (\n        <Step\n          key=\"users\"\n          show={isAvailable.inviteUsers}\n          completed={isCompleted.inviteUsers}\n          url=\"users/new\"\n          urlText=\"Invite your team members\"\n        />\n      ),\n    },\n  ];\n\n  const stepsItems = getStepsItems ? getStepsItems(defaultStepsItems) : defaultStepsItems;\n  const imageSource = illustrationPath ? illustrationPath : \"/static/images/illustrations/\" + illustration + \".svg\";\n\n  return (\n    <div className=\"empty-state-wrapper\">\n      <div className=\"empty-state bg-white tiled\">\n        <div className=\"empty-state__summary\">\n          {header && <h4>{header}</h4>}\n          <h2>\n            <i className={icon} aria-hidden=\"true\" />\n          </h2>\n          <p>{description}</p>\n          <img src={imageSource} alt={illustration + \" Illustration\"} width=\"75%\" />\n        </div>\n        <div className=\"empty-state__steps\">\n          <h4>Let&apos;s get started</h4>\n          <ol>{stepsItems.map((item) => item.node)}</ol>\n          {helpMessage}\n        </div>\n      </div>\n      {closable && (\n        <PlainButton className=\"close-button\" aria-label=\"Close\" onClick={onClose}>\n          <CloseOutlinedIcon />\n        </PlainButton>\n      )}\n    </div>\n  );\n}\n\nEmptyState.propTypes = {\n  icon: PropTypes.string,\n  header: PropTypes.string,\n  description: PropTypes.string.isRequired,\n  illustration: PropTypes.string.isRequired,\n  illustrationPath: PropTypes.string,\n  helpMessage: PropTypes.node,\n  closable: PropTypes.bool,\n  onClose: PropTypes.func,\n\n  onboardingMode: PropTypes.bool,\n  showAlertStep: PropTypes.bool,\n  showDashboardStep: PropTypes.bool,\n  showDataSourceStep: PropTypes.bool,\n  showInviteStep: PropTypes.bool,\n\n  getStepItems: PropTypes.func,\n};\n\nEmptyState.defaultProps = {\n  icon: null,\n  header: null,\n  helpMessage: null,\n  closable: false,\n  onClose: () => {},\n\n  onboardingMode: false,\n  showAlertStep: false,\n  showDashboardStep: false,\n  showDataSourceStep: true,\n  showInviteStep: false,\n};\n\nexport default EmptyState;\n"
  },
  {
    "path": "client/app/components/empty-state/empty-state.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\";\n\n// Empty states\n.empty-state {\n  width: 100%;\n  margin: 0 auto 10px;\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n  font-size: 14px;\n  line-height: 21px;\n\n  .empty-state__summary,\n  .empty-state__steps {\n    width: 48%;\n    padding: 35px;\n    padding-bottom: 25px;\n  }\n\n  .empty-state__steps {\n    padding-left: 0;\n  }\n\n  .empty-state__summary {\n    align-self: flex-start;\n    text-align: center;\n    background: rgba(102, 136, 153, 0.025);\n\n    p {\n      margin-bottom: 0;\n    }\n  }\n\n  ol {\n    margin-bottom: 15px;\n    padding: 17px;\n  }\n\n  li.done {\n    text-decoration: line-through;\n  }\n\n  h2 {\n    margin: 0 0 15px;\n  }\n\n  h4 {\n    margin-top: 0;\n    margin-bottom: 15px;\n  }\n\n  a:hover {\n    cursor: pointer;\n  }\n\n  @media (max-width: 767px) {\n    flex-direction: column;\n\n    .empty-state__summary {\n      margin-bottom: 25px;\n      padding-bottom: 15px;\n    }\n\n    .empty-state__summary,\n    .empty-state__steps {\n      width: 100%;\n    }\n\n    .empty-state__steps {\n      padding-left: 35px;\n      padding-top: 15px;\n    }\n  }\n}\n\n// close button\n.empty-state-wrapper {\n  position: relative;\n\n  .close-button {\n    position: absolute;\n    top: 15px;\n    right: 25px;\n    font-size: 15px;\n    color: @text-color-secondary;\n    cursor: pointer;\n    transition: color @animation-duration-slow;\n\n    &:hover,\n    &:focus {\n      color: @text-color;\n    }\n\n    &:active {\n      filter: contrast(200%);\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/groups/CreateGroupDialog.jsx",
    "content": "import React from \"react\";\nimport Modal from \"antd/lib/modal\";\nimport Input from \"antd/lib/input\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\n\nclass CreateGroupDialog extends React.Component {\n  static propTypes = {\n    dialog: DialogPropType.isRequired,\n  };\n\n  state = {\n    name: \"\",\n  };\n\n  save = () => {\n    this.props.dialog.close({\n      name: this.state.name,\n    });\n  };\n\n  render() {\n    const { dialog } = this.props;\n    return (\n      <Modal {...dialog.props} title=\"Create a New Group\" okText=\"Create\" onOk={() => this.save()}>\n        <Input\n          className=\"form-control\"\n          defaultValue={this.state.name}\n          onChange={event => this.setState({ name: event.target.value })}\n          onPressEnter={() => this.save()}\n          placeholder=\"Group Name\"\n          aria-label=\"Group name\"\n          autoFocus\n        />\n      </Modal>\n    );\n  }\n}\n\nexport default wrapDialog(CreateGroupDialog);\n"
  },
  {
    "path": "client/app/components/groups/DeleteGroupButton.jsx",
    "content": "import { isString } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport Modal from \"antd/lib/modal\";\nimport Tooltip from \"@/components/Tooltip\";\nimport notification from \"@/services/notification\";\nimport Group from \"@/services/group\";\n\nfunction deleteGroup(event, group, onGroupDeleted) {\n  Modal.confirm({\n    title: \"Delete Group\",\n    content: \"Are you sure you want to delete this group?\",\n    okText: \"Yes\",\n    okType: \"danger\",\n    cancelText: \"No\",\n    onOk: () => {\n      Group.delete(group).then(() => {\n        notification.success(\"Group deleted successfully.\");\n        onGroupDeleted();\n      });\n    },\n  });\n}\n\nexport default function DeleteGroupButton({ group, title, onClick, children, ...props }) {\n  if (!group) {\n    return null;\n  }\n  const button = (\n    <Button {...props} type=\"danger\" onClick={event => deleteGroup(event, group, onClick)}>\n      {children}\n    </Button>\n  );\n\n  if (isString(title) && title !== \"\") {\n    return (\n      <Tooltip placement=\"top\" title={title} mouseLeaveDelay={0}>\n        {button}\n      </Tooltip>\n    );\n  }\n\n  return button;\n}\n\nDeleteGroupButton.propTypes = {\n  group: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  title: PropTypes.string,\n  onClick: PropTypes.func,\n  children: PropTypes.node,\n};\n\nDeleteGroupButton.defaultProps = {\n  group: null,\n  title: null,\n  onClick: () => {},\n  children: null,\n};\n"
  },
  {
    "path": "client/app/components/groups/DetailsPageSidebar.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport Divider from \"antd/lib/divider\";\n\nimport * as Sidebar from \"@/components/items-list/components/Sidebar\";\nimport { ControllerType } from \"@/components/items-list/ItemsList\";\nimport DeleteGroupButton from \"./DeleteGroupButton\";\n\nimport { currentUser } from \"@/services/auth\";\n\nexport default function DetailsPageSidebar({\n  controller,\n  group,\n  items,\n  canAddMembers,\n  onAddMembersClick,\n  canAddDataSources,\n  onAddDataSourcesClick,\n  onGroupDeleted,\n}) {\n  const canRemove = group && currentUser.isAdmin && group.type !== \"builtin\";\n\n  return (\n    <React.Fragment>\n      <Sidebar.Menu items={items} selected={controller.params.currentPage} />\n      {canAddMembers && (\n        <Button className=\"w-100 m-t-5\" type=\"primary\" onClick={onAddMembersClick}>\n          <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n          Add Members\n        </Button>\n      )}\n      {canAddDataSources && (\n        <Button className=\"w-100 m-t-5\" type=\"primary\" onClick={onAddDataSourcesClick}>\n          <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n          Add Data Sources\n        </Button>\n      )}\n      {canRemove && (\n        <React.Fragment>\n          <Divider dashed className=\"m-t-10 m-b-10\" />\n          <DeleteGroupButton className=\"w-100\" group={group} onClick={onGroupDeleted}>\n            Delete Group\n          </DeleteGroupButton>\n        </React.Fragment>\n      )}\n    </React.Fragment>\n  );\n}\n\nDetailsPageSidebar.propTypes = {\n  controller: ControllerType.isRequired,\n  group: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n\n  canAddMembers: PropTypes.bool,\n  onAddMembersClick: PropTypes.func,\n\n  canAddDataSources: PropTypes.bool,\n  onAddDataSourcesClick: PropTypes.func,\n\n  onGroupDeleted: PropTypes.func,\n};\n\nDetailsPageSidebar.defaultProps = {\n  group: null,\n\n  canAddMembers: false,\n  onAddMembersClick: null,\n\n  canAddDataSources: false,\n  onAddDataSourcesClick: null,\n\n  onGroupDeleted: null,\n};\n"
  },
  {
    "path": "client/app/components/groups/GroupName.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport EditInPlace from \"@/components/EditInPlace\";\nimport { currentUser } from \"@/services/auth\";\nimport Group from \"@/services/group\";\n\nfunction updateGroupName(group, name, onChange) {\n  group.name = name;\n  Group.save(group);\n  onChange();\n}\n\nexport default function GroupName({ group, onChange, ...props }) {\n  if (!group) {\n    return null;\n  }\n\n  const canEdit = currentUser.isAdmin && group.type !== \"builtin\";\n\n  return (\n    <h3 {...props}>\n      <EditInPlace\n        className=\"edit-in-place\"\n        isEditable={canEdit}\n        ignoreBlanks\n        onDone={name => updateGroupName(group, name, onChange)}\n        value={group.name}\n      />\n    </h3>\n  );\n}\n\nGroupName.propTypes = {\n  group: PropTypes.shape({\n    name: PropTypes.string.isRequired,\n  }),\n  onChange: PropTypes.func,\n};\n\nGroupName.defaultProps = {\n  group: null,\n  onChange: () => {},\n};\n"
  },
  {
    "path": "client/app/components/groups/ListItemAddon.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Tooltip from \"@/components/Tooltip\";\n\nexport default function ListItemAddon({ isSelected, isStaged, alreadyInGroup, deselectedIcon }) {\n  if (isStaged) {\n    return (\n      <>\n        <i className=\"fa fa-remove\" aria-hidden=\"true\" />\n        <span className=\"sr-only\">Remove</span>\n      </>\n    );\n  }\n  if (alreadyInGroup) {\n    return (\n      <Tooltip title=\"Already selected\">\n        {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}\n        <span tabIndex={0}>\n          <i className=\"fa fa-check\" aria-hidden=\"true\" />\n          <span className=\"sr-only\">Already selected</span>\n        </span>\n      </Tooltip>\n    );\n  }\n  return isSelected ? (\n    <>\n      <i className=\"fa fa-check\" aria-hidden=\"true\" />\n      <span className=\"sr-only\">Selected</span>\n    </>\n  ) : (\n    <>\n      <i className={`fa ${deselectedIcon}`} aria-hidden=\"true\" />\n      <span className=\"sr-only\">Select</span>\n    </>\n  );\n}\n\nListItemAddon.propTypes = {\n  isSelected: PropTypes.bool,\n  isStaged: PropTypes.bool,\n  alreadyInGroup: PropTypes.bool,\n  deselectedIcon: PropTypes.string,\n};\n\nListItemAddon.defaultProps = {\n  isSelected: false,\n  isStaged: false,\n  alreadyInGroup: false,\n  deselectedIcon: \"fa-angle-double-right\",\n};\n"
  },
  {
    "path": "client/app/components/items-list/ItemsList.tsx",
    "content": "import { omit, debounce } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport hoistNonReactStatics from \"hoist-non-react-statics\";\nimport { clientConfig } from \"@/services/auth\";\nimport { AxiosError } from \"axios\";\n\nexport interface PaginationOptions {\n  page?: number;\n  itemsPerPage?: number;\n}\n\nexport interface SearchOptions {\n  isServerSideFTS?: boolean;\n}\n\nexport interface Controller<I, P = any> {\n  params: P; // TODO: Find out what params is (except merging with props)\n\n  isLoaded: boolean;\n  isEmpty: boolean;\n\n  // search\n  searchTerm?: string;\n  updateSearch: (searchTerm: string, searchOptions?: SearchOptions) => void;\n\n  // tags\n  selectedTags: string[];\n  updateSelectedTags: (selectedTags: string[]) => void;\n\n  // sorting\n  orderByField?: string;\n  orderByReverse: boolean;\n  toggleSorting: (orderByField: string) => void;\n  setSorting: (orderByField: string, orderByReverse: boolean) => void;\n\n  // pagination\n  page: number;\n  itemsPerPage: number;\n  totalItemsCount: number;\n  pageSizeOptions: number[];\n  pageItems: I[];\n  updatePagination: (options: PaginationOptions) => void; // ({ page: number, itemsPerPage: number }) => void\n\n  handleError: (error: any) => void; // TODO: Find out if error is string or object or Exception.\n}\n\nexport const ControllerType = PropTypes.shape({\n  // values of props declared by wrapped component and some additional props from items list\n  params: PropTypes.object.isRequired,\n\n  isLoaded: PropTypes.bool.isRequired,\n  isEmpty: PropTypes.bool.isRequired,\n\n  // search\n  searchTerm: PropTypes.string,\n  updateSearch: PropTypes.func.isRequired, // (searchTerm: string) => void\n\n  // tags\n  selectedTags: PropTypes.array.isRequired,\n  updateSelectedTags: PropTypes.func.isRequired, // (selectedTags: array of tags) => void\n\n  // sorting\n  orderByField: PropTypes.string,\n  orderByReverse: PropTypes.bool.isRequired,\n  toggleSorting: PropTypes.func.isRequired, // (orderByField: string) => void\n\n  // pagination\n  page: PropTypes.number.isRequired,\n  itemsPerPage: PropTypes.number.isRequired,\n  totalItemsCount: PropTypes.number.isRequired,\n  pageSizeOptions: PropTypes.arrayOf(PropTypes.number).isRequired,\n  pageItems: PropTypes.array.isRequired,\n  updatePagination: PropTypes.func.isRequired, // ({ page: number, itemsPerPage: number }) => void\n\n  handleError: PropTypes.func.isRequired, // (error) => void\n});\n\nexport type GenericItemSourceError = AxiosError | Error;\n\nexport interface ItemsListWrapperProps {\n  onError?: (error: AxiosError | Error) => void;\n  children: React.ReactNode;\n}\n\ninterface ItemsListWrapperState<I, P = any> extends Controller<I, P> {\n  totalCount?: number;\n  update: () => void;\n}\n\ntype ItemsSource = any; // TODO: Type ItemsSource\ntype StateStorage = any; // TODO: Type StateStore\n\nexport interface ItemsListWrappedComponentProps<I, P = any> {\n  controller: Controller<I, P>;\n}\n\nexport function wrap<I, P = any>(\n  WrappedComponent: React.ComponentType<ItemsListWrappedComponentProps<I>>,\n  createItemsSource: () => ItemsSource,\n  createStateStorage: ( { ...props }) => StateStorage\n) {\n  class ItemsListWrapper extends React.Component<ItemsListWrapperProps, ItemsListWrapperState<I, P>> {\n    private _itemsSource: ItemsSource;\n\n    static propTypes = {\n      onError: PropTypes.func,\n      children: PropTypes.node,\n    };\n\n    static defaultProps = {\n      onError: (error: GenericItemSourceError) => {\n        // Allow calling chain to roll up, and then throw the error in global context\n        setTimeout(() => {\n          throw error;\n        });\n      },\n      children: null,\n    };\n\n    constructor(props: ItemsListWrapperProps) {\n      super(props);\n\n      const stateStorage = createStateStorage({ ...props });\n      const itemsSource = createItemsSource();\n      this._itemsSource = itemsSource;\n\n      itemsSource.setState({ ...stateStorage.getState(), validate: false });\n      itemsSource.getCallbackContext = () => this.state;\n\n      itemsSource.onBeforeUpdate = () => {\n        const state = itemsSource.getState();\n        stateStorage.setState(state);\n        this.setState(this.getState({ ...state, isLoaded: false }));\n      };\n\n      itemsSource.onAfterUpdate = () => {\n        const state = itemsSource.getState();\n        this.setState(this.getState({ ...state, isLoaded: true }));\n      };\n\n      itemsSource.onError = (error: GenericItemSourceError) =>\n        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n        this.props.onError!(error);\n\n      const initialState = this.getState({ ...itemsSource.getState(), isLoaded: false });\n      const { updatePagination, toggleSorting, setSorting, updateSearch, updateSelectedTags, update, handleError } = itemsSource;\n\n      let isRunningUpdateSearch = false;\n      let pendingUpdateSearchParams: any[] | null = null;\n      const debouncedUpdateSearch = debounce(async (...params) => {\n        // Avoid running multiple updateSerch concurrently.\n        // If an updateSearch is already running, we save the params for the latest call.\n        // When the current updateSearch is finished, we call debouncedUpdateSearch again with the saved params.\n        if (isRunningUpdateSearch) {\n          pendingUpdateSearchParams = params;\n          return;\n        }\n        isRunningUpdateSearch = true;\n        await updateSearch(...params);\n        isRunningUpdateSearch = false;\n        if (pendingUpdateSearchParams) {\n          const pendingParams = pendingUpdateSearchParams;\n          pendingUpdateSearchParams = null;\n          debouncedUpdateSearch(...pendingParams);\n        }\n      }, 200);\n\n      this.state = {\n        ...initialState,\n        toggleSorting, // eslint-disable-line react/no-unused-state\n        setSorting, // eslint-disable-line react/no-unused-state\n        updateSearch: debouncedUpdateSearch, // eslint-disable-line react/no-unused-state\n        updateSelectedTags, // eslint-disable-line react/no-unused-state\n        updatePagination, // eslint-disable-line react/no-unused-state\n        update, // eslint-disable-line react/no-unused-state\n        handleError, // eslint-disable-line react/no-unused-state\n      };\n    }\n\n    componentDidMount() {\n      this.state.update();\n    }\n\n    componentWillUnmount() {\n      // eslint-disable-next-line @typescript-eslint/no-empty-function\n      this._itemsSource.onBeforeUpdate = () => {};\n      // eslint-disable-next-line @typescript-eslint/no-empty-function\n      this._itemsSource.onAfterUpdate = () => {};\n      // eslint-disable-next-line @typescript-eslint/no-empty-function\n      this._itemsSource.onError = () => {};\n    }\n\n    // eslint-disable-next-line class-methods-use-this\n    getState({\n      isLoaded,\n      totalCount,\n      pageItems,\n      params,\n      ...rest\n    }: ItemsListWrapperState<I, P>): ItemsListWrapperState<I, P> {\n      return {\n        ...rest,\n\n        params: {\n          ...params, // custom params from items source\n          ...omit(this.props, [\"onError\", \"children\"]), // add all props except of own ones\n        },\n\n        isLoaded,\n        isEmpty: !isLoaded || totalCount === 0,\n        totalItemsCount: totalCount || 0,\n        pageSizeOptions: (clientConfig as any).pageSizeOptions, // TODO: Type auth.js\n        pageItems: pageItems || [],\n      };\n    }\n\n    render() {\n      // don't pass own props to wrapped component\n      const { children, onError, ...props } = this.props;\n      return (\n        <WrappedComponent {...props} controller={this.state}>\n          {children}\n        </WrappedComponent>\n      );\n    }\n  }\n\n  // Copy static methods from `WrappedComponent`\n  hoistNonReactStatics(ItemsListWrapper, WrappedComponent);\n\n  return ItemsListWrapper;\n}\n"
  },
  {
    "path": "client/app/components/items-list/classes/ItemsFetcher.js",
    "content": "import { identity, isFunction, isNil, isString } from \"lodash\";\n\nclass ItemsFetcher {\n  _getRequest(state, context) {\n    return this._originalGetRequest({}, context);\n  }\n\n  _processResults({ results, count }, state, context) {\n    return {\n      results: this._originalProcessResults(results, context),\n      count,\n    };\n  }\n\n  constructor({ getRequest, doRequest, processResults }) {\n    this._originalGetRequest = isFunction(getRequest) ? getRequest : identity;\n    this._originalDoRequest = doRequest;\n    this._originalProcessResults = isFunction(processResults) ? processResults : identity;\n  }\n\n  fetch(changes, state, context) {\n    const request = this._getRequest(state, context);\n    return this._originalDoRequest(request, context).then(data => this._processResults(data, state, context));\n  }\n}\n\n// For endpoints that return just an array with items; sorting and pagination\n// is performed on client\nexport class PlainListFetcher extends ItemsFetcher {\n  _allItems = [];\n\n  _getRequest({ searchTerm, selectedTags }, context) {\n    return this._originalGetRequest(\n      {\n        q: isString(searchTerm) && searchTerm !== \"\" ? searchTerm : undefined,\n        tags: selectedTags,\n      },\n      context\n    );\n  }\n\n  _processResults(data, { paginator, sorter }, context) {\n    this._allItems = this._originalProcessResults(data, context);\n    this._allItems = sorter.sort(this._allItems);\n    return {\n      results: paginator.getItemsForPage(this._allItems),\n      count: this._allItems.length,\n      allResults: this._allItems,\n    };\n  }\n\n  fetch(changes, state, context) {\n    // For plain lists we need to reload items from server only if tags or search changes.\n    if (isNil(changes) || changes.tags || changes.sorting) {\n      return super.fetch(changes, state, context);\n    }\n    // Sorting and pagination could be updated using previously fetched items.\n    const { paginator, sorter } = state;\n    if (changes.sorting) {\n      this._allItems = sorter.sort(this._allItems);\n    }\n    return Promise.resolve({\n      results: paginator.getItemsForPage(this._allItems),\n      count: this._allItems.length,\n      allResults: this._allItems,\n    });\n  }\n}\n\n// For endpoints that support server-side pagination (return object with\n// items for current page and total items count)\nexport class PaginatedListFetcher extends ItemsFetcher {\n  _getRequest({ paginator, sorter, searchTerm, selectedTags }, context) {\n    return this._originalGetRequest(\n      {\n        page: paginator.page,\n        page_size: paginator.itemsPerPage,\n        order: sorter.compiled,\n        q: isString(searchTerm) && searchTerm !== \"\" ? searchTerm : undefined,\n        tags: selectedTags,\n      },\n      context\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/components/items-list/classes/ItemsSource.d.ts",
    "content": "export interface ItemsSourceOptions<I = any> extends Partial<ItemsSourceState> {\n  getRequest?: (params: any, context: any) => any; // TODO: Add stricter types\n  doRequest?: () => any; // TODO: Add stricter type\n  processResults?: () => any; // TODO: Add stricter type\n  isPlainList?: boolean;\n  sortByIteratees?: { [fieldName: string]: (a: I) => number };\n}\n\nexport interface GetResourceContext extends ItemsSourceState {\n  params: {\n    currentPage: number;\n    // TODO: Add more context parameters\n  };\n}\n\nexport type GetResourceRequest = any; // TODO: Add stricter type\n\nexport interface ItemsPage<INPUT = any> {\n  count: number;\n  page: number;\n  page_size: number;\n  results: INPUT[];\n}\n\nexport interface ResourceItemsSourceOptions<INPUT = any, ITEM = any> extends ItemsSourceOptions {\n  getResource: (context: GetResourceContext) => (request: GetResourceRequest) => Promise<INPUT[]>;\n  getItemProcessor?: () => (input: INPUT) => ITEM;\n}\n\nexport type ItemsSourceState<ITEM = any> = {\n  page: number;\n  itemsPerPage: number;\n  orderByField: string;\n  orderByReverse: boolean;\n  searchTerm: string;\n  selectedTags: string[];\n  totalCount: number;\n  pageItems: ITEM[];\n  allItems: ITEM[] | undefined;\n  params: {\n    pageTitle?: string;\n  } & { [key: string]: string | number };\n};\n\ndeclare class ItemsSource {\n  constructor(options: ItemsSourceOptions);\n}\n\ndeclare class ResourceItemsSource<I> {\n  constructor(options: ResourceItemsSourceOptions<I>);\n}\n"
  },
  {
    "path": "client/app/components/items-list/classes/ItemsSource.js",
    "content": "import { isFunction, identity, map, extend } from \"lodash\";\nimport Paginator from \"./Paginator\";\nimport Sorter from \"./Sorter\";\nimport { PlainListFetcher, PaginatedListFetcher } from \"./ItemsFetcher\";\n\nexport class ItemsSource {\n  onBeforeUpdate = null;\n\n  onAfterUpdate = null;\n\n  onError = null;\n\n  sortByIteratees = undefined;\n\n  getCallbackContext = () => null;\n\n  _beforeUpdate() {\n    if (isFunction(this.onBeforeUpdate)) {\n      return Promise.resolve(this.onBeforeUpdate(this.getState(), this.getCallbackContext()));\n    }\n    return Promise.resolve();\n  }\n\n  _afterUpdate() {\n    if (isFunction(this.onAfterUpdate)) {\n      return Promise.resolve(this.onAfterUpdate(this.getState(), this.getCallbackContext()));\n    }\n    return Promise.resolve();\n  }\n\n  // changes: object with flags or null (full refresh requested)\n  _changed(changes) {\n    const state = {\n      paginator: this._paginator,\n      sorter: this._sorter,\n      searchTerm: this._searchTerm,\n      selectedTags: this._selectedTags,\n    };\n    const customParams = {};\n    const context = {\n      ...this.getCallbackContext(),\n      setCustomParams: (params) => {\n        extend(customParams, params);\n      },\n    };\n    return this._beforeUpdate().then(() => {\n      const fetchToken = Math.random().toString(36).substr(2);\n      this._currentFetchToken = fetchToken;\n      return this._fetcher\n        .fetch(changes, state, context)\n        .then(({ results, count, allResults }) => {\n          if (this._currentFetchToken === fetchToken) {\n            this._pageItems = results;\n            this._allItems = allResults || null;\n            this._paginator.setTotalCount(count);\n            this._params = { ...this._params, ...customParams };\n            return this._afterUpdate();\n          }\n        })\n        .catch((error) => this.handleError(error));\n    });\n  }\n\n  constructor({\n    getRequest,\n    doRequest,\n    processResults,\n    isPlainList = false,\n    sortByIteratees = undefined,\n    ...defaultState\n  }) {\n    if (!isFunction(getRequest)) {\n      getRequest = identity;\n    }\n\n    this._fetcher = isPlainList\n      ? new PlainListFetcher({ getRequest, doRequest, processResults })\n      : new PaginatedListFetcher({ getRequest, doRequest, processResults });\n\n    this.sortByIteratees = sortByIteratees;\n\n    this.setState(defaultState);\n    this._pageItems = [];\n\n    this._params = {};\n  }\n\n  getState() {\n    return {\n      page: this._paginator.page,\n      itemsPerPage: this._paginator.itemsPerPage,\n      orderByField: this._sorter.field,\n      orderByReverse: this._sorter.reverse,\n      searchTerm: this._searchTerm,\n      selectedTags: this._selectedTags,\n      totalCount: this._paginator.totalCount,\n      pageItems: this._pageItems,\n      allItems: this._allItems,\n      params: this._params,\n    };\n  }\n\n  setState(state) {\n    this._paginator = new Paginator(state);\n    this._sorter = new Sorter(state, this.sortByIteratees);\n\n    this._searchTerm = state.searchTerm || \"\";\n    this._selectedTags = state.selectedTags || [];\n\n    this._savedOrderByField = this._sorter.field;\n  }\n\n  updatePagination = ({ page, itemsPerPage }) => {\n    const { page: prevPage, itemsPerPage: prevItemsPerPage } = this._paginator;\n    this._paginator.setItemsPerPage(itemsPerPage);\n    this._paginator.setPage(page);\n    this._changed({\n      pagination: {\n        page: prevPage !== this._paginator.page, // page changed flag\n        itemsPerPage: prevItemsPerPage !== this._paginator.itemsPerPage, // items per page changed flags\n      },\n    });\n  };\n\n  toggleSorting = (orderByField) => {\n    this._sorter.toggleField(orderByField);\n    this._savedOrderByField = this._sorter.field;\n    this._changed({ sorting: true });\n  };\n\n  setSorting = (orderByField, orderByReverse) => {\n    this._sorter.setField(orderByField);\n    this._sorter.setReverse(orderByReverse);\n    this._savedOrderByField = this._sorter.field;\n    this._changed({ sorting: true });\n  };\n\n  updateSearch = (searchTerm, options) => {\n    // here we update state directly, but later `fetchData` will update it properly\n    this._searchTerm = searchTerm;\n    // in search mode ignore the ordering and use the ranking order\n    // provided by the server-side FTS backend instead, unless it was\n    // requested by the user by actively ordering in search mode\n    if (searchTerm === \"\" || !options?.isServerSideFTS) {\n      this._sorter.setField(this._savedOrderByField); // restore ordering\n    } else {\n      this._sorter.setField(null);\n    }\n    this._paginator.setPage(1);\n    return this._changed({ search: true, pagination: { page: true } });\n  };\n\n  updateSelectedTags = (selectedTags) => {\n    this._selectedTags = selectedTags;\n    this._paginator.setPage(1);\n    this._changed({ tags: true, pagination: { page: true } });\n  };\n\n  update = () => this._changed();\n\n  handleError = (error) => {\n    if (isFunction(this.onError)) {\n      this.onError(error);\n    }\n  };\n}\n\nexport class ResourceItemsSource extends ItemsSource {\n  constructor({ getResource, getItemProcessor, ...rest }) {\n    getItemProcessor = isFunction(getItemProcessor) ? getItemProcessor : () => null;\n    super({\n      ...rest,\n      doRequest: (request, context) => {\n        const resource = getResource(context)(request);\n        return resource;\n      },\n      processResults: (results, context) => {\n        let processItem = getItemProcessor(context);\n        processItem = isFunction(processItem) ? processItem : identity;\n        return map(results, (item) => processItem(item, context));\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "client/app/components/items-list/classes/Paginator.js",
    "content": "import { isUndefined } from \"lodash\";\n\nexport default class Paginator {\n  page = 1;\n\n  itemsPerPage = 20;\n\n  totalCount = 0;\n\n  get totalPages() {\n    return Math.ceil(this.totalCount / this.itemsPerPage);\n  }\n\n  setPage(value, validate = true) {\n    if (isUndefined(value)) {\n      return;\n    }\n    value = parseInt(value, 10) || 1;\n    if (validate) {\n      this.page = value >= 1 && value <= this.totalPages ? value : 1;\n    } else {\n      this.page = value >= 1 ? value : 1;\n    }\n  }\n\n  setItemsPerPage(value, validate = true) {\n    if (isUndefined(value)) {\n      return;\n    }\n    value = parseInt(value, 10) || 20;\n    this.itemsPerPage = value >= 1 ? value : 1;\n    if (validate) {\n      this.setPage(this.page, validate);\n    }\n  }\n\n  setTotalCount(value, validate = true) {\n    if (isUndefined(value)) {\n      return;\n    }\n    value = parseInt(value, 10) || 0;\n    this.totalCount = value;\n    if (validate) {\n      this.setPage(this.page, validate);\n    }\n  }\n\n  constructor({ page, itemsPerPage, totalCount, validate = true } = {}) {\n    this.setItemsPerPage(itemsPerPage, validate);\n    this.setTotalCount(totalCount, validate);\n    this.setPage(page, validate);\n  }\n\n  getItemsForPage(items) {\n    const first = this.itemsPerPage * (this.page - 1);\n    const last = first + this.itemsPerPage;\n\n    return items.slice(first, last);\n  }\n}\n"
  },
  {
    "path": "client/app/components/items-list/classes/Sorter.js",
    "content": "import { isString, sortBy } from \"lodash\";\n\nconst ORDER_BY_REVERSE = \"-\";\n\nexport function compile(field, reverse) {\n  if (!field) {\n    return null;\n  }\n  return reverse ? ORDER_BY_REVERSE + field : field;\n}\n\nexport function parse(compiled) {\n  compiled = isString(compiled) ? compiled : \"\";\n  const reverse = compiled.startsWith(ORDER_BY_REVERSE);\n  if (reverse) {\n    compiled = compiled.substring(1);\n  }\n  const field = compiled !== \"\" ? compiled : null;\n  return { field, reverse };\n}\n\nexport default class Sorter {\n  field = null;\n\n  reverse = false;\n\n  sortByIteratees = null;\n\n  get compiled() {\n    return compile(this.field, this.reverse);\n  }\n\n  set compiled(value) {\n    const { field, reverse } = parse(value);\n    this.field = field;\n    this.reverse = reverse;\n  }\n\n  setField(value) {\n    this.field = isString(value) && value !== \"\" ? value : null;\n  }\n\n  setReverse(value) {\n    this.reverse = !!value; // cast to boolean\n  }\n\n  constructor({ orderByField, orderByReverse } = {}, sortByIteratees = undefined) {\n    this.setField(orderByField);\n    this.setReverse(orderByReverse);\n    this.sortByIteratees = sortByIteratees;\n  }\n\n  toggleField(field) {\n    if (!isString(field) || field === \"\") {\n      return;\n    }\n    if (field === this.field) {\n      this.reverse = !this.reverse;\n    } else {\n      this.field = field;\n      this.reverse = false;\n    }\n  }\n\n  sort(items) {\n    if (this.field) {\n      const customIteratee = this.sortByIteratees && this.sortByIteratees[this.field];\n      items = sortBy(items, customIteratee ? [customIteratee] : this.field);\n      if (this.reverse) {\n        items.reverse();\n      }\n      return items;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/items-list/classes/StateStorage.js",
    "content": "import { defaults } from \"lodash\";\nimport { clientConfig } from \"@/services/auth\";\nimport location from \"@/services/location\";\nimport { parse as parseOrderBy, compile as compileOrderBy } from \"./Sorter\";\n\nexport class StateStorage {\n  constructor(state = {}) {\n    this._state = { ...state };\n  }\n\n  getState() {\n    return defaults(this._state, {\n      page: 1,\n      itemsPerPage: clientConfig.pageSize,\n      orderByField: \"created_at\",\n      orderByReverse: false,\n      searchTerm: \"\",\n      tags: [],\n    });\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  setState() {}\n}\n\nexport class UrlStateStorage extends StateStorage {\n  getState() {\n    const defaultState = super.getState();\n    const params = location.search;\n\n    const searchTerm = params.q || \"\";\n\n    // in search mode order by should be explicitly specified in url, otherwise use default\n    const defaultOrderBy =\n      searchTerm !== \"\" ? \"\" : compileOrderBy(defaultState.orderByField, defaultState.orderByReverse);\n\n    const { field: orderByField, reverse: orderByReverse } = parseOrderBy(params.order || defaultOrderBy);\n\n    return {\n      page: parseInt(params.page, 10) || defaultState.page,\n      itemsPerPage: parseInt(params.page_size, 10) || defaultState.itemsPerPage,\n      orderByField,\n      orderByReverse,\n      searchTerm,\n    };\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  setState({ page, itemsPerPage, orderByField, orderByReverse, searchTerm }) {\n    location.setSearch(\n      {\n        page,\n        page_size: itemsPerPage,\n        order: compileOrderBy(orderByField, orderByReverse),\n        q: searchTerm !== \"\" ? searchTerm : null,\n      },\n      true\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/components/items-list/components/EmptyState.jsx",
    "content": "import React from \"react\";\nimport BigMessage from \"@/components/BigMessage\";\n\n// Default \"list empty\" message for list pages\nexport default function EmptyState(props) {\n  return (\n    <div className=\"text-center\">\n      <BigMessage icon=\"fa-search\" message=\"Sorry, we couldn't find anything.\" {...props} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "client/app/components/items-list/components/ItemsTable.jsx",
    "content": "import { isFunction, map, filter, extend, omit, identity, range, isEmpty } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport classNames from \"classnames\";\nimport Table from \"antd/lib/table\";\nimport Skeleton from \"antd/lib/skeleton\";\nimport FavoritesControl from \"@/components/FavoritesControl\";\nimport TimeAgo from \"@/components/TimeAgo\";\nimport { durationHumanize, formatDate, formatDateTime } from \"@/lib/utils\";\n\n// `this` refers to previous function in the chain (`Columns.***`).\n// Adds `sorter: true` field to column definition\nfunction sortable(...args) {\n  return extend(this(...args), { sorter: true });\n}\n\nexport const Columns = {\n  favorites(overrides) {\n    return extend(\n      {\n        width: \"1%\",\n        render: (text, item) => <FavoritesControl item={item} />,\n      },\n      overrides\n    );\n  },\n  avatar(overrides, formatTitle) {\n    formatTitle = isFunction(formatTitle) ? formatTitle : identity;\n    return extend(\n      {\n        width: \"1%\",\n        render: (user, item) => (\n          <img\n            src={item.user.profile_image_url}\n            className=\"profile__image_thumb\"\n            alt={formatTitle(user.name, item)}\n            title={formatTitle(user.name, item)}\n          />\n        ),\n      },\n      overrides\n    );\n  },\n  date(overrides) {\n    return extend(\n      {\n        render: (text) => formatDate(text),\n      },\n      overrides\n    );\n  },\n  dateTime(overrides) {\n    return extend(\n      {\n        render: (text) => formatDateTime(text),\n      },\n      overrides\n    );\n  },\n  duration(overrides) {\n    return extend(\n      {\n        width: \"1%\",\n        className: \"text-nowrap\",\n        render: (text) => durationHumanize(text),\n      },\n      overrides\n    );\n  },\n  timeAgo(overrides, timeAgoCustomProps = undefined) {\n    return extend(\n      {\n        render: (value) => <TimeAgo date={value} {...timeAgoCustomProps} />,\n      },\n      overrides\n    );\n  },\n  custom(render, overrides) {\n    return extend(\n      {\n        render,\n      },\n      overrides\n    );\n  },\n};\n\nColumns.date.sortable = sortable;\nColumns.dateTime.sortable = sortable;\nColumns.duration.sortable = sortable;\nColumns.timeAgo.sortable = sortable;\nColumns.custom.sortable = sortable;\n\nexport default class ItemsTable extends React.Component {\n  static propTypes = {\n    loading: PropTypes.bool,\n    // eslint-disable-next-line react/forbid-prop-types\n    items: PropTypes.arrayOf(PropTypes.object),\n    columns: PropTypes.arrayOf(\n      PropTypes.shape({\n        field: PropTypes.string, // data field\n        orderByField: PropTypes.string, // field to order by (defaults to `field`)\n        render: PropTypes.func, // (prop, item) => text | node; `prop` is `item[field]`\n        isAvailable: PropTypes.func, // return `true` to show column and `false` to hide; if omitted: show column\n      })\n    ),\n    showHeader: PropTypes.bool,\n    onRowClick: PropTypes.func, // (event, item) => void\n\n    orderByField: PropTypes.string,\n    orderByReverse: PropTypes.bool,\n    toggleSorting: PropTypes.func,\n    setSorting: PropTypes.func,\n    \"data-test\": PropTypes.string,\n    rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),\n  };\n\n  static defaultProps = {\n    loading: false,\n    items: [],\n    columns: [],\n    showHeader: true,\n    onRowClick: null,\n\n    orderByField: null,\n    orderByReverse: false,\n    toggleSorting: () => {},\n  };\n\n  prepareColumns() {\n    const { orderByField, orderByReverse } = this.props;\n    const orderByDirection = orderByReverse ? \"descend\" : \"ascend\";\n\n    return map(\n      map(\n        filter(this.props.columns, (column) => (isFunction(column.isAvailable) ? column.isAvailable() : true)),\n        (column) => extend(column, { orderByField: column.orderByField || column.field })\n      ),\n      (column, index) => {\n        // Wrap render function to pass correct arguments\n        const render = isFunction(column.render) ? (text, row) => column.render(text, row.item) : identity;\n\n        return extend(omit(column, [\"field\", \"orderByField\", \"render\"]), {\n          key: \"column\" + index,\n          dataIndex: [\"item\", column.field],\n          defaultSortOrder: column.orderByField === orderByField ? orderByDirection : null,\n          render,\n        });\n      }\n    );\n  }\n\n  getRowKey = (record) => {\n    const { rowKey } = this.props;\n    if (rowKey) {\n      if (isFunction(rowKey)) {\n        return rowKey(record.item);\n      }\n      return record.item[rowKey];\n    }\n    return record.key;\n  };\n\n  render() {\n    const tableDataProps = {\n      columns: this.prepareColumns(),\n      dataSource: map(this.props.items, (item, index) => ({ key: \"row\" + index, item })),\n    };\n\n    // Bind events only if `onRowClick` specified\n    const onTableRow = isFunction(this.props.onRowClick)\n      ? (row) => ({\n          onClick: (event) => {\n            this.props.onRowClick(event, row.item);\n          },\n        })\n      : null;\n\n    const onChange = (pagination, filters, sorter, extra) => {\n      const action = extra?.action;\n      if (action === \"sort\") {\n        const propsColumn = this.props.columns.find((column) => column.field === sorter.field[1]);\n        if (!propsColumn.sorter) {\n          return;\n        }\n        let orderByField = propsColumn.orderByField;\n        const orderByReverse = sorter.order === \"descend\";\n\n        if (orderByReverse === undefined) {\n          orderByField = null;\n        }\n        if (this.props.setSorting) {\n          this.props.setSorting(orderByField, orderByReverse);\n        } else {\n          this.props.toggleSorting(orderByField);\n        }\n      }\n    };\n\n    const { showHeader } = this.props;\n    if (this.props.loading) {\n      if (isEmpty(tableDataProps.dataSource)) {\n        tableDataProps.columns = tableDataProps.columns.map((column) => ({\n          ...column,\n          sorter: false,\n          render: () => <Skeleton active paragraph={false} />,\n        }));\n        tableDataProps.dataSource = range(10).map((key) => ({ key: `${key}` }));\n      } else {\n        tableDataProps.loading = { indicator: null };\n      }\n    }\n\n    return (\n      <Table\n        className={classNames(\"table-data\", { \"ant-table-headerless\": !showHeader })}\n        showHeader={showHeader}\n        rowKey={this.getRowKey}\n        pagination={false}\n        onRow={onTableRow}\n        onChange={onChange}\n        data-test={this.props[\"data-test\"]}\n        {...tableDataProps}\n      />\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/components/items-list/components/LoadingState.jsx",
    "content": "import React from \"react\";\nimport BigMessage from \"@/components/BigMessage\";\n\n// Default \"loading\" message for list pages\nexport default function LoadingState(props) {\n  return (\n    <div className=\"text-center\">\n      <BigMessage icon=\"fa-spinner fa-2x fa-pulse\" message=\"Loading...\" {...props} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "client/app/components/items-list/components/Sidebar.jsx",
    "content": "import { isFunction, isString, filter, map } from \"lodash\";\nimport React, { useState, useCallback, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Input from \"antd/lib/input\";\nimport AntdMenu from \"antd/lib/menu\";\nimport Link from \"@/components/Link\";\nimport TagsList from \"@/components/TagsList\";\n\n/*\n    SearchInput\n */\n\nexport function SearchInput({ placeholder, value, showIcon, onChange, label }) {\n  const [currentValue, setCurrentValue] = useState(value);\n\n  useEffect(() => {\n    setCurrentValue(value);\n  }, [value]);\n\n  const onInputChange = useCallback(\n    event => {\n      const newValue = event.target.value;\n      setCurrentValue(newValue);\n      onChange(newValue);\n    },\n    [onChange]\n  );\n\n  const InputControl = showIcon ? Input.Search : Input;\n  return (\n    <div className=\"m-b-10\">\n      <InputControl\n        className=\"form-control\"\n        placeholder={placeholder}\n        value={currentValue}\n        aria-label={label}\n        onChange={onInputChange}\n      />\n    </div>\n  );\n}\n\nSearchInput.propTypes = {\n  value: PropTypes.string.isRequired,\n  placeholder: PropTypes.string,\n  showIcon: PropTypes.bool,\n  onChange: PropTypes.func.isRequired,\n  label: PropTypes.string,\n};\n\nSearchInput.defaultProps = {\n  placeholder: \"Search...\",\n  showIcon: false,\n  label: \"Search\",\n};\n\n/*\n    Menu\n */\n\nexport function Menu({ items, selected }) {\n  items = filter(items, item => (isFunction(item.isAvailable) ? item.isAvailable() : true));\n  if (items.length === 0) {\n    return null;\n  }\n  return (\n    <div className=\"m-b-10 tags-list tiled\">\n      <AntdMenu className=\"invert-stripe-position\" mode=\"inline\" selectable={false} selectedKeys={[selected]}>\n        {map(items, item => (\n          <AntdMenu.Item key={item.key} className=\"m-0\">\n            <Link href={item.href}>\n              {isString(item.icon) && item.icon !== \"\" && (\n                <span className=\"btn-favorite m-r-5\">\n                  <i className={item.icon} aria-hidden=\"true\" />\n                </span>\n              )}\n              {isFunction(item.icon) && (item.icon(item) || null)}\n              {item.title}\n            </Link>\n          </AntdMenu.Item>\n        ))}\n      </AntdMenu>\n    </div>\n  );\n}\n\nMenu.propTypes = {\n  items: PropTypes.arrayOf(\n    PropTypes.shape({\n      key: PropTypes.string.isRequired,\n      href: PropTypes.string.isRequired,\n      title: PropTypes.string.isRequired,\n      icon: PropTypes.func, // function to render icon\n      isAvailable: PropTypes.func, // return `true` to show item and `false` to hide; if omitted: show item\n    })\n  ),\n  selected: PropTypes.string,\n};\n\nMenu.defaultProps = {\n  items: [],\n  selected: null,\n};\n\n/*\n    MenuIcon\n */\n\nexport function MenuIcon({ icon }) {\n  return (\n    <span className=\"btn-favorite m-r-5\">\n      <i className={icon} aria-hidden=\"true\" />\n    </span>\n  );\n}\n\nMenuIcon.propTypes = {\n  icon: PropTypes.string.isRequired,\n};\n\n/*\n    ProfileImage\n */\n\nexport function ProfileImage({ user }) {\n  if (!isString(user.profile_image_url) || user.profile_image_url === \"\") {\n    return null;\n  }\n  return <img src={user.profile_image_url} className=\"profile__image--sidebar m-r-5\" width=\"13\" alt={user.name} />;\n}\n\nProfileImage.propTypes = {\n  user: PropTypes.shape({\n    profile_image_url: PropTypes.string,\n    name: PropTypes.string,\n  }).isRequired,\n};\n\n/*\n    Tags\n */\n\nexport function Tags({ url, onChange, showUnselectAll }) {\n  if (url === \"\") {\n    return null;\n  }\n  return (\n    <div className=\"m-b-10\">\n      <TagsList tagsUrl={url} onUpdate={onChange} showUnselectAll={showUnselectAll} />\n    </div>\n  );\n}\n\nTags.propTypes = {\n  url: PropTypes.string.isRequired,\n  onChange: PropTypes.func.isRequired,\n  showUnselectAll: PropTypes.bool,\n  unselectAllButtonTitle: PropTypes.string,\n};\n"
  },
  {
    "path": "client/app/components/items-list/hooks/useItemsListExtraActions.js",
    "content": "import { filter, includes, intersection } from \"lodash\";\nimport React, { useState, useMemo, useEffect, useCallback } from \"react\";\nimport Checkbox from \"antd/lib/checkbox\";\nimport { Columns } from \"../components/ItemsTable\";\n\nexport default function useItemsListExtraActions(controller, listColumns, ExtraActionsComponent) {\n  const [actionsState, setActionsState] = useState({ isAvailable: false });\n  const [selectedItems, setSelectedItems] = useState([]);\n\n  // clear selection when page changes\n  useEffect(() => {\n    setSelectedItems([]);\n  }, [controller.pageItems, actionsState.isAvailable]);\n\n  const areAllItemsSelected = useMemo(() => {\n    const allItems = controller.pageItems;\n    if (allItems.length === 0 || selectedItems.length === 0) {\n      return false;\n    }\n    return intersection(selectedItems, allItems).length === allItems.length;\n  }, [selectedItems, controller.pageItems]);\n\n  const toggleAllItems = useCallback(() => {\n    if (areAllItemsSelected) {\n      setSelectedItems([]);\n    } else {\n      setSelectedItems(controller.pageItems);\n    }\n  }, [areAllItemsSelected, controller.pageItems]);\n\n  const toggleItem = useCallback(\n    item => {\n      if (includes(selectedItems, item)) {\n        setSelectedItems(filter(selectedItems, s => s !== item));\n      } else {\n        setSelectedItems([...selectedItems, item]);\n      }\n    },\n    [selectedItems]\n  );\n\n  const checkboxColumn = useMemo(\n    () =>\n      Columns.custom(\n        (text, item) => <Checkbox checked={includes(selectedItems, item)} onChange={() => toggleItem(item)} />,\n        {\n          title: () => <Checkbox checked={areAllItemsSelected} onChange={toggleAllItems} />,\n          field: \"id\",\n          width: \"1%\",\n        }\n      ),\n    [selectedItems, areAllItemsSelected, toggleAllItems, toggleItem]\n  );\n\n  const Component = useCallback(\n    function ItemsListExtraActionsComponentWrapper(props) {\n      // this check mostly needed to avoid eslint exhaustive deps warning\n      if (!ExtraActionsComponent) {\n        return null;\n      }\n\n      return <ExtraActionsComponent onStateChange={setActionsState} {...props} />;\n    },\n    [ExtraActionsComponent]\n  );\n\n  return useMemo(\n    () => ({\n      areExtraActionsAvailable: actionsState.isAvailable,\n      listColumns: actionsState.isAvailable ? [checkboxColumn, ...listColumns] : listColumns,\n      Component,\n      selectedItems,\n      setSelectedItems,\n    }),\n    [actionsState, listColumns, checkboxColumn, selectedItems, Component]\n  );\n}\n"
  },
  {
    "path": "client/app/components/layouts/ContentWithSidebar.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport classNames from \"classnames\";\n\nimport \"./content-with-sidebar.less\";\n\nconst propTypes = {\n  className: PropTypes.string,\n  children: PropTypes.node,\n};\n\nconst defaultProps = {\n  className: null,\n  children: null,\n};\n\n// Sidebar\n\nfunction Sidebar({ className, children, ...props }) {\n  return (\n    <div className={classNames(\"layout-sidebar\", className)} {...props}>\n      <div>{children}</div>\n    </div>\n  );\n}\n\nSidebar.propTypes = propTypes;\nSidebar.defaultProps = defaultProps;\n\n// Content\n\nfunction Content({ className, children, ...props }) {\n  return (\n    <div className={classNames(\"layout-content\", className)} {...props}>\n      <div>{children}</div>\n    </div>\n  );\n}\n\nContent.propTypes = propTypes;\nContent.defaultProps = defaultProps;\n\n// Layout\n\nexport default function Layout({ children, className = undefined, ...props }) {\n  return (\n    <div className={classNames(\"layout-with-sidebar\", className)} {...props}>\n      {children}\n    </div>\n  );\n}\n\nLayout.propTypes = propTypes;\nLayout.defaultProps = defaultProps;\n\nLayout.Sidebar = Sidebar;\nLayout.Content = Content;\n"
  },
  {
    "path": "client/app/components/layouts/content-with-sidebar.less",
    "content": ".layout-with-sidebar {\n  @spacing: 15px;\n  position: relative;\n\n  display: flex;\n  align-items: stretch;\n  justify-content: stretch;\n  flex-direction: row;\n  margin: 0;\n\n  > .layout-content {\n    flex: 1 0 auto;\n    width: 75%;\n    order: 1;\n    margin: 0;\n    padding: 0 0 0 @spacing\n  }\n\n  > .layout-sidebar {\n    flex: 0 0 auto;\n    width: 25%;\n    max-width: 350px;\n    order: 0;\n    margin: 0;\n  }\n\n  @media (max-width: 990px) {\n    flex-direction: column;\n\n    > .layout-content {\n      width: 100%;\n      order: 1;\n      margin: 0;\n      padding: 0;\n    }\n\n    > .layout-sidebar {\n      width: 100%;\n      max-width: none;\n      order: 0;\n      margin: 0 0 @spacing 0;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/proptypes.js",
    "content": "import PropTypes from \"prop-types\";\nimport { wrap } from \"lodash\";\nimport moment from \"moment\";\n\nexport const DataSource = PropTypes.shape({\n  syntax: PropTypes.string,\n  options: PropTypes.shape({\n    doc: PropTypes.string,\n    doc_url: PropTypes.string,\n  }),\n  type_name: PropTypes.string,\n});\n\nexport const Table = PropTypes.shape({\n  columns: PropTypes.arrayOf(PropTypes.string).isRequired,\n});\n\nexport const Schema = PropTypes.arrayOf(Table);\n\nexport const RefreshScheduleType = PropTypes.shape({\n  interval: PropTypes.number,\n  time: PropTypes.string,\n  day_of_week: PropTypes.string,\n  until: PropTypes.string,\n});\n\nexport const RefreshScheduleDefault = {\n  interval: null,\n  time: null,\n  day_of_week: null,\n  until: null,\n};\n\nexport const UserProfile = PropTypes.shape({\n  id: PropTypes.number.isRequired,\n  name: PropTypes.string.isRequired,\n  email: PropTypes.string.isRequired,\n  profileImageUrl: PropTypes.string,\n  apiKey: PropTypes.string,\n  isDisabled: PropTypes.bool,\n});\n\nexport const Destination = PropTypes.shape({\n  id: PropTypes.number.isRequired,\n  name: PropTypes.string.isRequired,\n  icon: PropTypes.string.isRequired,\n  type: PropTypes.string.isRequired,\n});\n\nexport const Query = PropTypes.shape({\n  id: PropTypes.any.isRequired,\n  name: PropTypes.string.isRequired,\n  description: PropTypes.string,\n  data_source_id: PropTypes.any.isRequired,\n  created_at: PropTypes.string.isRequired,\n  updated_at: PropTypes.string,\n  user: UserProfile,\n  query: PropTypes.string,\n  queryHash: PropTypes.string,\n  is_safe: PropTypes.bool.isRequired,\n  is_draft: PropTypes.bool.isRequired,\n  is_archived: PropTypes.bool.isRequired,\n  api_key: PropTypes.string.isRequired,\n});\n\nexport const AlertOptions = PropTypes.shape({\n  column: PropTypes.string,\n  selector: PropTypes.oneOf([\"first\", \"min\", \"max\"]),\n  op: PropTypes.oneOf([\">\", \">=\", \"<\", \"<=\", \"==\", \"!=\"]),\n  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n  custom_subject: PropTypes.string,\n  custom_body: PropTypes.string,\n});\n\nexport const Alert = PropTypes.shape({\n  id: PropTypes.any,\n  name: PropTypes.string,\n  created_at: PropTypes.string,\n  last_triggered_at: PropTypes.string,\n  updated_at: PropTypes.string,\n  rearm: PropTypes.number,\n  state: PropTypes.oneOf([\"ok\", \"triggered\", \"unknown\"]),\n  user: UserProfile,\n  query: Query,\n  options: PropTypes.shape({\n    column: PropTypes.string,\n    selector: PropTypes.string,\n    op: PropTypes.string,\n    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n  }).isRequired,\n});\n\nfunction checkMoment(isRequired, props, propName, componentName) {\n  const value = props[propName];\n  const isRequiredValid = isRequired && value !== null && value !== undefined && moment.isMoment(value);\n  const isOptionalValid = !isRequired && (value === null || value === undefined || moment.isMoment(value));\n  if (!isRequiredValid && !isOptionalValid) {\n    return new Error(\"Prop `\" + propName + \"` supplied to `\" + componentName + \"` should be a Moment.js instance.\");\n  }\n}\n\nexport const Moment = wrap(false, checkMoment);\nMoment.isRequired = wrap(true, checkMoment);\n"
  },
  {
    "path": "client/app/components/queries/AddToDashboardDialog.jsx",
    "content": "import { isString } from \"lodash\";\nimport React, { useState, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Modal from \"antd/lib/modal\";\nimport Input from \"antd/lib/input\";\nimport List from \"antd/lib/list\";\nimport Link from \"@/components/Link\";\nimport PlainButton from \"@/components/PlainButton\";\nimport CloseOutlinedIcon from \"@ant-design/icons/CloseOutlined\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport { QueryTagsControl } from \"@/components/tags-control/TagsControl\";\nimport { Dashboard } from \"@/services/dashboard\";\nimport notification from \"@/services/notification\";\nimport useSearchResults from \"@/lib/hooks/useSearchResults\";\n\nimport \"./add-to-dashboard-dialog.less\";\n\nfunction AddToDashboardDialog({ dialog, visualization }) {\n  const [searchTerm, setSearchTerm] = useState(\"\");\n\n  const [doSearch, dashboards, isLoading] = useSearchResults(\n    term => {\n      if (isString(term) && term !== \"\") {\n        return Dashboard.query({ q: term })\n          .then(results => results.results)\n          .catch(() => []);\n      }\n      return Promise.resolve([]);\n    },\n    { initialResults: [] }\n  );\n\n  const [selectedDashboard, setSelectedDashboard] = useState(null);\n\n  const [saveInProgress, setSaveInProgress] = useState(false);\n\n  useEffect(() => {\n    doSearch(searchTerm);\n  }, [doSearch, searchTerm]);\n\n  function addWidgetToDashboard() {\n    // Load dashboard with all widgets\n    Dashboard.get(selectedDashboard)\n      .then(dashboard => {\n        dashboard.addWidget(visualization);\n        return dashboard;\n      })\n      .then(dashboard => {\n        dialog.close();\n        const key = `notification-${Math.random()\n          .toString(36)\n          .substr(2, 10)}`;\n        notification.success(\n          \"Widget added to dashboard\",\n          <React.Fragment>\n            <Link href={`${dashboard.url}`} onClick={() => notification.close(key)}>\n              {dashboard.name}\n            </Link>\n            <QueryTagsControl isDraft={dashboard.is_draft} tags={dashboard.tags} />\n          </React.Fragment>,\n          { key }\n        );\n      })\n      .catch(() => {\n        notification.error(\"Widget not added.\");\n      })\n      .finally(() => {\n        setSaveInProgress(false);\n      });\n  }\n\n  const items = selectedDashboard ? [selectedDashboard] : dashboards;\n\n  return (\n    <Modal\n      {...dialog.props}\n      title=\"Add to Dashboard\"\n      okButtonProps={{ disabled: !selectedDashboard || saveInProgress, loading: saveInProgress }}\n      cancelButtonProps={{ disabled: saveInProgress }}\n      onOk={addWidgetToDashboard}>\n      <label htmlFor=\"add-to-dashboard-dialog-dashboard\">Choose the dashboard to add this query to:</label>\n\n      {!selectedDashboard && (\n        <Input\n          id=\"add-to-dashboard-dialog-dashboard\"\n          className=\"w-100\"\n          autoComplete=\"off\"\n          autoFocus\n          placeholder=\"Search a dashboard by name\"\n          value={searchTerm}\n          onChange={event => setSearchTerm(event.target.value)}\n          suffix={\n            <PlainButton className={searchTerm === \"\" ? \"hidden\" : null} onClick={() => setSearchTerm(\"\")}>\n              <CloseOutlinedIcon />\n            </PlainButton>\n          }\n        />\n      )}\n\n      {(items.length > 0 || isLoading) && (\n        <List\n          className={selectedDashboard ? \"add-to-dashboard-dialog-selection\" : \"add-to-dashboard-dialog-search-results\"}\n          bordered\n          itemLayout=\"horizontal\"\n          loading={isLoading}\n          dataSource={items}\n          renderItem={d => (\n            <List.Item\n              key={`dashboard-${d.id}`}\n              actions={\n                selectedDashboard\n                  ? [\n                      <PlainButton onClick={() => setSelectedDashboard(null)}>\n                        <CloseOutlinedIcon />\n                      </PlainButton>,\n                    ]\n                  : []\n              }\n              onClick={selectedDashboard ? null : () => setSelectedDashboard(d)}>\n              <div className=\"add-to-dashboard-dialog-item-content\">\n                {d.name}\n                <QueryTagsControl isDraft={d.is_draft} tags={d.tags} />\n              </div>\n            </List.Item>\n          )}\n        />\n      )}\n    </Modal>\n  );\n}\n\nAddToDashboardDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n  visualization: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n};\n\nexport default wrapDialog(AddToDashboardDialog);\n"
  },
  {
    "path": "client/app/components/queries/ApiKeyDialog/index.jsx",
    "content": "import { extend } from \"lodash\";\nimport React, { useMemo, useState, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Modal from \"antd/lib/modal\";\nimport Input from \"antd/lib/input\";\nimport Button from \"antd/lib/button\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport CodeBlock from \"@/components/CodeBlock\";\nimport { axios } from \"@/services/axios\";\nimport { clientConfig } from \"@/services/auth\";\nimport notification from \"@/services/notification\";\nimport { useUniqueId } from \"@/lib/hooks/useUniqueId\";\n\nimport \"./index.less\";\nimport { policy } from \"@/services/policy\";\n\nfunction ApiKeyDialog({ dialog, ...props }) {\n  const [query, setQuery] = useState(props.query);\n  const [updatingApiKey, setUpdatingApiKey] = useState(false);\n\n  const regenerateQueryApiKey = useCallback(() => {\n    setUpdatingApiKey(true);\n    axios\n      .post(`api/queries/${query.id}/regenerate_api_key`)\n      .then(data => {\n        setUpdatingApiKey(false);\n        setQuery(extend(query.clone(), { api_key: data.api_key }));\n      })\n      .catch(() => {\n        setUpdatingApiKey(false);\n        notification.error(\"Failed to update API key\");\n      });\n  }, [query]);\n\n  const { csvUrl, jsonUrl } = useMemo(\n    () => ({\n      csvUrl: `${clientConfig.basePath}api/queries/${query.id}/results.csv?api_key=${query.api_key}`,\n      jsonUrl: `${clientConfig.basePath}api/queries/${query.id}/results.json?api_key=${query.api_key}`,\n    }),\n    [query.id, query.api_key]\n  );\n\n  const csvResultsLabelId = useUniqueId(\"csv-results-label\");\n  const jsonResultsLabelId = useUniqueId(\"json-results-label\");\n\n  return (\n    <Modal {...dialog.props} width={600} footer={<Button onClick={() => dialog.close(query)}>Close</Button>}>\n      <div className=\"query-api-key-dialog-wrapper\">\n        <h5>API Key</h5>\n        <div className=\"m-b-20\">\n          <Input.Group compact>\n            <Input readOnly value={query.api_key} aria-label=\"Query API Key\" />\n            {policy.canEdit(query) && (\n              <Button disabled={updatingApiKey} loading={updatingApiKey} onClick={regenerateQueryApiKey}>\n                Regenerate\n              </Button>\n            )}\n          </Input.Group>\n        </div>\n\n        <h5>Example API Calls:</h5>\n        <div className=\"m-b-10\">\n          <span id={csvResultsLabelId}>Results in CSV format:</span>\n          <CodeBlock aria-labelledby={csvResultsLabelId} copyable>\n            {csvUrl}\n          </CodeBlock>\n        </div>\n        <div>\n          <span id={jsonResultsLabelId}>Results in JSON format:</span>\n          <CodeBlock aria-labelledby={jsonResultsLabelId} copyable>\n            {jsonUrl}\n          </CodeBlock>\n        </div>\n      </div>\n    </Modal>\n  );\n}\n\nApiKeyDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n  query: PropTypes.shape({\n    id: PropTypes.number.isRequired,\n    api_key: PropTypes.string,\n    can_edit: PropTypes.bool,\n  }).isRequired,\n};\n\nexport default wrapDialog(ApiKeyDialog);\n"
  },
  {
    "path": "client/app/components/queries/ApiKeyDialog/index.less",
    "content": ".query-api-key-dialog-wrapper {\n  .ant-input-group.ant-input-group-compact {\n    display: flex;\n    flex-wrap: nowrap;\n\n    .ant-input {\n      flex-grow: 1;\n      flex-shrink: 1;\n    }\n\n    .ant-btn {\n      flex-grow: 0;\n      flex-shrink: 0;\n      height: auto;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/queries/EmbedQueryDialog.jsx",
    "content": "import { uniqueId } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Alert from \"antd/lib/alert\";\nimport Button from \"antd/lib/button\";\nimport Checkbox from \"antd/lib/checkbox\";\nimport Form from \"antd/lib/form\";\nimport InputNumber from \"antd/lib/input-number\";\nimport Modal from \"antd/lib/modal\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport { clientConfig } from \"@/services/auth\";\nimport CodeBlock from \"@/components/CodeBlock\";\n\nimport \"./EmbedQueryDialog.less\";\n\nclass EmbedQueryDialog extends React.Component {\n  static propTypes = {\n    dialog: DialogPropType.isRequired,\n    query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n    visualization: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  };\n\n  state = {\n    enableChangeIframeSize: false,\n    iframeWidth: 720,\n    iframeHeight: 391,\n  };\n\n  constructor(props) {\n    super(props);\n    const { query, visualization } = props;\n    this.embedUrl = `${clientConfig.basePath}embed/query/${query.id}/visualization/${visualization.id}?api_key=${\n      query.api_key\n    }&${query.getParameters().toUrlParams()}`;\n\n    if (window.snapshotUrlBuilder) {\n      this.snapshotUrl = window.snapshotUrlBuilder(query, visualization);\n    }\n  }\n\n  urlEmbedLabelId = uniqueId(\"url-embed-label\");\n  iframeEmbedLabelId = uniqueId(\"iframe-embed-label\");\n\n  render() {\n    const { query, dialog } = this.props;\n    const { enableChangeIframeSize, iframeWidth, iframeHeight } = this.state;\n\n    return (\n      <Modal\n        {...dialog.props}\n        className=\"embed-query-dialog\"\n        title=\"Embed Query\"\n        footer={<Button onClick={dialog.dismiss}>Close</Button>}>\n        {query.is_safe ? (\n          <React.Fragment>\n            <h5 id={this.urlEmbedLabelId} className=\"m-t-0\">\n              Public URL\n            </h5>\n            <div className=\"m-b-30\">\n              <CodeBlock aria-labelledby={this.urlEmbedLabelId} data-test=\"EmbedIframe\" copyable>\n                {this.embedUrl}\n              </CodeBlock>\n            </div>\n            <h5 id={this.iframeEmbedLabelId} className=\"m-t-0\">\n              IFrame Embed\n            </h5>\n            <div>\n              <CodeBlock aria-labelledby={this.iframeEmbedLabelId} copyable>\n                {`<iframe src=\"${this.embedUrl}\" width=\"${iframeWidth}\" height=\"${iframeHeight}\"></iframe>`}\n              </CodeBlock>\n              <Form className=\"m-t-10\" layout=\"inline\">\n                <Form.Item>\n                  <Checkbox\n                    checked={enableChangeIframeSize}\n                    onChange={e => this.setState({ enableChangeIframeSize: e.target.checked })}\n                  />\n                </Form.Item>\n                <Form.Item label=\"Width\">\n                  <InputNumber\n                    className=\"size-input\"\n                    value={iframeWidth}\n                    onChange={value => this.setState({ iframeWidth: value })}\n                    size=\"small\"\n                    disabled={!enableChangeIframeSize}\n                  />\n                </Form.Item>\n                <Form.Item label=\"Height\">\n                  <InputNumber\n                    className=\"size-input\"\n                    value={iframeHeight}\n                    onChange={value => this.setState({ iframeHeight: value })}\n                    size=\"small\"\n                    disabled={!enableChangeIframeSize}\n                  />\n                </Form.Item>\n              </Form>\n            </div>\n            {this.snapshotUrl && (\n              <React.Fragment>\n                <h5>Image Embed</h5>\n                <CodeBlock copyable>{this.snapshotUrl}</CodeBlock>\n              </React.Fragment>\n            )}\n          </React.Fragment>\n        ) : (\n          <Alert\n            message=\"Currently it is not possible to embed queries that contain text parameters.\"\n            type=\"error\"\n            data-test=\"EmbedErrorAlert\"\n          />\n        )}\n      </Modal>\n    );\n  }\n}\n\nexport default wrapDialog(EmbedQueryDialog);\n"
  },
  {
    "path": "client/app/components/queries/EmbedQueryDialog.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\";\n\n.embed-query-dialog {\n  label {\n    font-weight: normal;\n  }\n\n  .size-input {\n    width: 72px;\n  }\n}\n"
  },
  {
    "path": "client/app/components/queries/QueryEditor/AutoLimitCheckbox.jsx",
    "content": "import React, { useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport recordEvent from \"@/services/recordEvent\";\nimport Checkbox from \"antd/lib/checkbox\";\nimport Tooltip from \"@/components/Tooltip\";\n\nexport default function AutoLimitCheckbox({ available, checked, onChange }) {\n  const handleClick = useCallback(() => {\n    recordEvent(\"checkbox_auto_limit\", \"screen\", \"query_editor\", { state: !checked });\n    onChange(!checked);\n  }, [checked, onChange]);\n\n  let tooltipMessage = null;\n  if (!available) {\n    tooltipMessage = \"Auto limiting is not available for this Data Source type.\";\n  } else {\n    tooltipMessage = \"Auto limit results to first 1000 rows.\";\n  }\n\n  return (\n    <Tooltip placement=\"top\" title={tooltipMessage}>\n      <Checkbox\n        className=\"query-editor-controls-checkbox\"\n        disabled={!available}\n        onClick={handleClick}\n        checked={available && checked}>\n        LIMIT 1000\n      </Checkbox>\n    </Tooltip>\n  );\n}\n\nAutoLimitCheckbox.propTypes = {\n  available: PropTypes.bool,\n  checked: PropTypes.bool.isRequired,\n  onChange: PropTypes.func.isRequired,\n};\n"
  },
  {
    "path": "client/app/components/queries/QueryEditor/AutocompleteToggle.jsx",
    "content": "import React, { useCallback } from \"react\";\nimport Tooltip from \"@/components/Tooltip\";\nimport Button from \"antd/lib/button\";\nimport PropTypes from \"prop-types\";\nimport \"@/redash-font/style.less\";\nimport recordEvent from \"@/services/recordEvent\";\n\nexport default function AutocompleteToggle({ available, enabled, onToggle }) {\n  let tooltipMessage = \"Live Autocomplete Enabled\";\n  let icon = \"icon-flash\";\n  if (!enabled) {\n    tooltipMessage = \"Live Autocomplete Disabled\";\n    icon = \"icon-flash-off\";\n  }\n\n  if (!available) {\n    tooltipMessage = \"Live Autocomplete Not Available (Use Ctrl+Space to Trigger)\";\n    icon = \"icon-flash-off\";\n  }\n\n  const handleClick = useCallback(() => {\n    recordEvent(\"toggle_autocomplete\", \"screen\", \"query_editor\", { state: !enabled });\n    onToggle(!enabled);\n  }, [enabled, onToggle]);\n\n  return (\n    <Tooltip placement=\"top\" title={tooltipMessage}>\n      <Button\n        className=\"query-editor-controls-button m-r-5\"\n        disabled={!available}\n        onClick={handleClick}\n        aria-label={enabled ? \"Disable live autocomplete\" : \"Enable live autocomplete\"}>\n        <i className={\"icon \" + icon} aria-hidden=\"true\" />\n      </Button>\n    </Tooltip>\n  );\n}\n\nAutocompleteToggle.propTypes = {\n  available: PropTypes.bool.isRequired,\n  enabled: PropTypes.bool.isRequired,\n  onToggle: PropTypes.func.isRequired,\n};\n"
  },
  {
    "path": "client/app/components/queries/QueryEditor/QueryEditorControls.jsx",
    "content": "import { isFunction, map, filter, fromPairs, noop } from \"lodash\";\nimport React, { useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Tooltip from \"@/components/Tooltip\";\nimport Button from \"antd/lib/button\";\nimport Select from \"antd/lib/select\";\nimport KeyboardShortcuts, { humanReadableShortcut } from \"@/services/KeyboardShortcuts\";\n\nimport AutocompleteToggle from \"./AutocompleteToggle\";\nimport \"./QueryEditorControls.less\";\nimport AutoLimitCheckbox from \"@/components/queries/QueryEditor/AutoLimitCheckbox\";\n\nexport function ButtonTooltip({ title, shortcut, ...props }) {\n  shortcut = humanReadableShortcut(shortcut, 1); // show only primary shortcut\n  title =\n    title && shortcut ? (\n      <React.Fragment>\n        {title} (<i>{shortcut}</i>)\n      </React.Fragment>\n    ) : (\n      title || shortcut\n    );\n  return <Tooltip placement=\"top\" title={title} {...props} />;\n}\n\nButtonTooltip.propTypes = {\n  title: PropTypes.node,\n  shortcut: PropTypes.string,\n};\n\nButtonTooltip.defaultProps = {\n  title: null,\n  shortcut: null,\n};\n\nexport default function EditorControl({\n  addParameterButtonProps,\n  formatButtonProps,\n  saveButtonProps,\n  executeButtonProps,\n  autocompleteToggleProps,\n  autoLimitCheckboxProps,\n  dataSourceSelectorProps,\n}) {\n  useEffect(() => {\n    const buttons = filter(\n      [addParameterButtonProps, formatButtonProps, saveButtonProps, executeButtonProps],\n      b => b.shortcut && isFunction(b.onClick)\n    );\n    if (buttons.length > 0) {\n      const shortcuts = fromPairs(map(buttons, b => [b.shortcut, b.disabled ? noop : b.onClick]));\n      KeyboardShortcuts.bind(shortcuts);\n      return () => {\n        KeyboardShortcuts.unbind(shortcuts);\n      };\n    }\n  }, [addParameterButtonProps, formatButtonProps, saveButtonProps, executeButtonProps]);\n\n  return (\n    <div className=\"query-editor-controls\">\n      {addParameterButtonProps !== false && (\n        <ButtonTooltip title={addParameterButtonProps.title} shortcut={addParameterButtonProps.shortcut}>\n          <Button\n            className=\"query-editor-controls-button m-r-5\"\n            disabled={addParameterButtonProps.disabled}\n            onClick={addParameterButtonProps.onClick}>\n            {\"{{\"}&nbsp;{\"}}\"}\n          </Button>\n        </ButtonTooltip>\n      )}\n      {formatButtonProps !== false && (\n        <ButtonTooltip title={formatButtonProps.title} shortcut={formatButtonProps.shortcut}>\n          <Button\n            className=\"query-editor-controls-button m-r-5\"\n            disabled={formatButtonProps.disabled}\n            onClick={formatButtonProps.onClick}>\n            <span className=\"zmdi zmdi-format-indent-increase\" />\n            {formatButtonProps.text}\n          </Button>\n        </ButtonTooltip>\n      )}\n      {autocompleteToggleProps !== false && (\n        <AutocompleteToggle\n          available={autocompleteToggleProps.available}\n          enabled={autocompleteToggleProps.enabled}\n          onToggle={autocompleteToggleProps.onToggle}\n        />\n      )}\n      {autoLimitCheckboxProps !== false && <AutoLimitCheckbox {...autoLimitCheckboxProps} />}\n      {dataSourceSelectorProps === false && <span className=\"query-editor-controls-spacer\" />}\n      {dataSourceSelectorProps !== false && (\n        <Select\n          className=\"w-100 flex-fill datasource-small\"\n          disabled={dataSourceSelectorProps.disabled}\n          value={dataSourceSelectorProps.value}\n          onChange={dataSourceSelectorProps.onChange}>\n          {map(dataSourceSelectorProps.options, option => (\n            <Select.Option key={`option-${option.value}`} value={option.value}>\n              {option.label}\n            </Select.Option>\n          ))}\n        </Select>\n      )}\n      {saveButtonProps !== false && (\n        <ButtonTooltip title={saveButtonProps.title} shortcut={saveButtonProps.shortcut}>\n          <Button\n            className=\"query-editor-controls-button m-l-5\"\n            disabled={saveButtonProps.disabled}\n            loading={saveButtonProps.loading}\n            onClick={saveButtonProps.onClick}\n            data-test=\"SaveButton\">\n            {!saveButtonProps.loading && <span className=\"fa fa-floppy-o\" />}\n            {saveButtonProps.text}\n          </Button>\n        </ButtonTooltip>\n      )}\n      {executeButtonProps !== false && (\n        <ButtonTooltip title={executeButtonProps.title} shortcut={executeButtonProps.shortcut}>\n          <Button\n            className=\"query-editor-controls-button m-l-5\"\n            type=\"primary\"\n            disabled={executeButtonProps.disabled}\n            onClick={executeButtonProps.onClick}\n            data-test=\"ExecuteButton\">\n            <span className=\"zmdi zmdi-play\" />\n            {executeButtonProps.text}\n          </Button>\n        </ButtonTooltip>\n      )}\n    </div>\n  );\n}\n\nconst ButtonPropsPropType = PropTypes.oneOfType([\n  PropTypes.bool, // `false` to hide button\n  PropTypes.shape({\n    title: PropTypes.node,\n    disabled: PropTypes.bool,\n    loading: PropTypes.bool,\n    onClick: PropTypes.func,\n    text: PropTypes.node,\n    shortcut: PropTypes.string,\n  }),\n]);\n\nEditorControl.propTypes = {\n  addParameterButtonProps: ButtonPropsPropType,\n  formatButtonProps: ButtonPropsPropType,\n  saveButtonProps: ButtonPropsPropType,\n  executeButtonProps: ButtonPropsPropType,\n  autocompleteToggleProps: PropTypes.oneOfType([\n    PropTypes.bool, // `false` to hide\n    PropTypes.shape({\n      available: PropTypes.bool,\n      enabled: PropTypes.bool,\n      onToggle: PropTypes.func,\n    }),\n  ]),\n  autoLimitCheckboxProps: PropTypes.oneOfType([\n    PropTypes.bool, // `false` to hide\n    PropTypes.shape(AutoLimitCheckbox.propTypes),\n  ]),\n  dataSourceSelectorProps: PropTypes.oneOfType([\n    PropTypes.bool, // `false` to hide\n    PropTypes.shape({\n      disabled: PropTypes.bool,\n      value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n      options: PropTypes.arrayOf(\n        PropTypes.shape({\n          value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n          label: PropTypes.node,\n        })\n      ),\n      onChange: PropTypes.func,\n    }),\n  ]),\n};\n\nEditorControl.defaultProps = {\n  addParameterButtonProps: false,\n  formatButtonProps: false,\n  saveButtonProps: false,\n  executeButtonProps: false,\n  autocompleteToggleProps: false,\n  autoLimitCheckboxProps: false,\n  dataSourceSelectorProps: false,\n};\n"
  },
  {
    "path": "client/app/components/queries/QueryEditor/QueryEditorControls.less",
    "content": ".query-editor-controls {\n  display: flex;\n  flex-wrap: nowrap;\n  align-items: stretch;\n  justify-content: stretch;\n\n  // Styles for a wrapper that `Tooltip` adds for disabled `Button`s\n  span.query-editor-controls-button {\n    display: flex !important;\n    align-items: stretch;\n    justify-content: stretch;\n  }\n\n  .ant-btn {\n    height: auto;\n\n    .fa + span,\n    .zmdi + span {\n      // if button has icon and label - add some space between them\n      margin-left: 5px;\n    }\n  }\n\n  .query-editor-controls-checkbox {\n    display: inline-block;\n    white-space: nowrap;\n    margin: auto 5px;\n  }\n\n  .query-editor-controls-spacer {\n    flex: 1 1 auto;\n    height: 35px; // same as Antd <Select>\n  }\n}\n"
  },
  {
    "path": "client/app/components/queries/QueryEditor/ace.js",
    "content": "import { capitalize, isNil, map, get } from \"lodash\";\nimport AceEditor from \"react-ace\";\nimport ace from \"ace-builds\";\n\nimport \"ace-builds/src-noconflict/ext-language_tools\";\nimport \"ace-builds/src-noconflict/mode-json\";\nimport \"ace-builds/src-noconflict/mode-python\";\nimport \"ace-builds/src-noconflict/mode-sql\";\nimport \"ace-builds/src-noconflict/mode-yaml\";\nimport \"ace-builds/src-noconflict/theme-textmate\";\nimport \"ace-builds/src-noconflict/ext-searchbox\";\n\nconst langTools = ace.acequire(\"ace/ext/language_tools\");\nconst snippetsModule = ace.acequire(\"ace/snippets\");\n\n// By default Ace will try to load snippet files for the different modes and fail.\n// We don't need them, so we use these placeholders until we define our own.\nfunction defineDummySnippets(mode) {\n  ace.define(`ace/snippets/${mode}`, [\"require\", \"exports\", \"module\"], (require, exports) => {\n    exports.snippetText = \"\";\n    exports.scope = mode;\n  });\n}\n\ndefineDummySnippets(\"python\");\ndefineDummySnippets(\"sql\");\ndefineDummySnippets(\"json\");\ndefineDummySnippets(\"yaml\");\n\n// without this line, ace will try to load a non-existent mode-custom.js file\n// for data sources with syntax = \"custom\"\nace.define(\"ace/mode/custom\", [], () => {});\n\nfunction buildTableColumnKeywords(table) {\n  const keywords = [];\n  table.columns.forEach(column => {\n    const columnName = get(column, \"name\");\n    keywords.push({\n      name: `${table.name}.${columnName}`,\n      value: `${table.name}.${columnName}`,\n      score: 100,\n      meta: capitalize(get(column, \"type\", \"Column\")),\n    });\n  });\n  return keywords;\n}\n\nfunction buildKeywordsFromSchema(schema) {\n  const tableKeywords = [];\n  const columnKeywords = {};\n  const tableColumnKeywords = {};\n\n  schema.forEach(table => {\n    tableKeywords.push({\n      name: table.name,\n      value: table.name,\n      score: 100,\n      meta: \"Table\",\n    });\n    tableColumnKeywords[table.name] = buildTableColumnKeywords(table);\n    table.columns.forEach(c => {\n      const columnName = get(c, \"name\", c);\n      columnKeywords[columnName] = capitalize(get(c, \"type\", \"Column\"));\n    });\n  });\n\n  return {\n    table: tableKeywords,\n    column: map(columnKeywords, (v, k) => ({\n      name: k,\n      value: k,\n      score: 50,\n      meta: v,\n    })),\n    tableColumn: tableColumnKeywords,\n  };\n}\n\nconst schemaCompleterKeywords = {};\n\nexport function updateSchemaCompleter(editorKey, schema = null) {\n  schemaCompleterKeywords[editorKey] = isNil(schema) ? null : buildKeywordsFromSchema(schema);\n}\n\nlangTools.setCompleters([\n  langTools.snippetCompleter,\n  langTools.keyWordCompleter,\n  langTools.textCompleter,\n  {\n    identifierRegexps: [/[a-zA-Z_0-9.\\-\\u00A2-\\uFFFF]/],\n    getCompletions: (editor, session, pos, prefix, callback) => {\n      const { table, column, tableColumn } = schemaCompleterKeywords[editor.id] || {\n        table: [],\n        column: [],\n        tableColumn: [],\n      };\n\n      if (prefix.length === 0 || table.length === 0) {\n        callback(null, []);\n        return;\n      }\n\n      if (prefix[prefix.length - 1] === \".\") {\n        const tableName = prefix.substring(0, prefix.length - 1);\n        callback(null, table.concat(tableColumn[tableName]));\n        return;\n      }\n      callback(null, table.concat(column));\n    },\n  },\n]);\n\nexport { AceEditor, langTools, snippetsModule };\n"
  },
  {
    "path": "client/app/components/queries/QueryEditor/index.jsx",
    "content": "import React, { useEffect, useMemo, useState, useCallback, useImperativeHandle } from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport { AceEditor, snippetsModule, updateSchemaCompleter } from \"./ace\";\nimport { srNotify } from \"@/lib/accessibility\";\nimport { SchemaItemType } from \"@/components/queries/SchemaBrowser\";\nimport resizeObserver from \"@/services/resizeObserver\";\nimport QuerySnippet from \"@/services/query-snippet\";\n\nimport QueryEditorControls from \"./QueryEditorControls\";\nimport \"./index.less\";\n\nconst editorProps = { $blockScrolling: Infinity };\n\nconst QueryEditor = React.forwardRef(function(\n  { className, syntax, value, autocompleteEnabled, schema, onChange, onSelectionChange, ...props },\n  ref\n) {\n  const [container, setContainer] = useState(null);\n  const [editorRef, setEditorRef] = useState(null);\n\n  // For some reason, value for AceEditor should be managed in this way - otherwise it goes berserk when selecting text\n  const [currentValue, setCurrentValue] = useState(value);\n\n  useEffect(() => {\n    setCurrentValue(value);\n  }, [value]);\n\n  const handleChange = useCallback(\n    str => {\n      setCurrentValue(str);\n      onChange(str);\n    },\n    [onChange]\n  );\n\n  const editorOptions = useMemo(\n    () => ({\n      behavioursEnabled: true,\n      enableSnippets: true,\n      enableBasicAutocompletion: true,\n      enableLiveAutocompletion: autocompleteEnabled,\n      autoScrollEditorIntoView: true,\n    }),\n    [autocompleteEnabled]\n  );\n\n  useEffect(() => {\n    if (editorRef) {\n      const editorId = editorRef.editor.id;\n      updateSchemaCompleter(editorId, schema);\n      return () => {\n        updateSchemaCompleter(editorId, null);\n      };\n    }\n  }, [schema, editorRef]);\n\n  useEffect(() => {\n    function resize() {\n      if (editorRef) {\n        editorRef.editor.resize();\n      }\n    }\n\n    if (container) {\n      resize();\n      const unwatch = resizeObserver(container, resize);\n      return unwatch;\n    }\n  }, [container, editorRef]);\n\n  const handleSelectionChange = useCallback(\n    selection => {\n      const rawSelectedQueryText = editorRef.editor.session.doc.getTextRange(selection.getRange());\n      const selectedQueryText = rawSelectedQueryText.length > 1 ? rawSelectedQueryText : null;\n      onSelectionChange(selectedQueryText);\n    },\n    [editorRef, onSelectionChange]\n  );\n\n  const initEditor = useCallback(editor => {\n    // Release Cmd/Ctrl+L to the browser\n    editor.commands.bindKey({ win: \"Ctrl+L\", mac: \"Cmd+L\" }, null);\n\n    // Release Cmd/Ctrl+Shift+F for format query action\n    editor.commands.bindKey({ win: \"Ctrl+Shift+F\", mac: \"Cmd+Shift+F\" }, null);\n\n    // Release Ctrl+P for open new parameter dialog\n    editor.commands.bindKey({ win: \"Ctrl+P\", mac: null }, null);\n    // Lineup only mac\n    editor.commands.bindKey({ win: null, mac: \"Ctrl+P\" }, \"golineup\");\n\n    // Esc for exiting\n    editor.commands.bindKey({ win: \"Esc\", mac: \"Esc\" }, () => {\n      editor.blur();\n    });\n\n    let notificationCleanup = null;\n    editor.on(\"focus\", () => {\n      notificationCleanup = srNotify({\n        text: \"You've entered the SQL editor. To exit press the ESC key.\",\n        politeness: \"assertive\",\n      });\n    });\n\n    editor.on(\"blur\", () => {\n      if (notificationCleanup) {\n        notificationCleanup();\n      }\n    });\n\n    // Reset Completer in case dot is pressed\n    editor.commands.on(\"afterExec\", e => {\n      if (e.command.name === \"insertstring\" && e.args === \".\" && editor.completer) {\n        editor.completer.showPopup(editor);\n      }\n    });\n\n    QuerySnippet.query().then(snippets => {\n      const snippetManager = snippetsModule.snippetManager;\n      const m = {\n        snippetText: \"\",\n      };\n      m.snippets = snippetManager.parseSnippetFile(m.snippetText);\n      snippets.forEach(snippet => {\n        m.snippets.push(snippet.getSnippet());\n      });\n      snippetManager.register(m.snippets || [], m.scope);\n    });\n\n    editor.focus();\n  }, []);\n\n  useImperativeHandle(\n    ref,\n    () => ({\n      paste: text => {\n        if (editorRef) {\n          const { editor } = editorRef;\n          editor.session.doc.replace(editor.selection.getRange(), text);\n          const range = editor.selection.getRange();\n          onChange(editor.session.getValue());\n          editor.selection.setRange(range);\n        }\n      },\n      focus: () => {\n        if (editorRef) {\n          editorRef.editor.focus();\n        }\n      },\n    }),\n    [editorRef, onChange]\n  );\n\n  return (\n    <div className={cx(\"query-editor-container\", className)} {...props} ref={setContainer}>\n      <AceEditor\n        ref={setEditorRef}\n        theme=\"textmate\"\n        mode={syntax || \"sql\"}\n        value={currentValue}\n        editorProps={editorProps}\n        width=\"100%\"\n        height=\"100%\"\n        setOptions={editorOptions}\n        showPrintMargin={false}\n        wrapEnabled={false}\n        onLoad={initEditor}\n        onChange={handleChange}\n        onSelectionChange={handleSelectionChange}\n      />\n    </div>\n  );\n});\n\nQueryEditor.propTypes = {\n  className: PropTypes.string,\n  syntax: PropTypes.string,\n  value: PropTypes.string,\n  autocompleteEnabled: PropTypes.bool,\n  schema: PropTypes.arrayOf(SchemaItemType),\n  onChange: PropTypes.func,\n  onSelectionChange: PropTypes.func,\n};\n\nQueryEditor.defaultProps = {\n  className: null,\n  syntax: null,\n  value: null,\n  autocompleteEnabled: true,\n  schema: [],\n  onChange: () => {},\n  onSelectionChange: () => {},\n};\n\nQueryEditor.Controls = QueryEditorControls;\n\nexport default QueryEditor;\n"
  },
  {
    "path": "client/app/components/queries/QueryEditor/index.less",
    "content": ".query-editor-container {\n  margin-bottom: 0;\n  position: relative;\n\n  .ace_editor {\n    position: absolute;\n    left: 0;\n    top: 0;\n    width: 100%;\n    height: 100%;\n    margin: 0;\n  }\n}\n"
  },
  {
    "path": "client/app/components/queries/ScheduleDialog.css",
    "content": ".schedule {\n  width: 449px !important;\n  margin: 0 auto;\n}\n\n.schedule-component {\n  padding: 5px 0px;\n}\n\n.schedule-component > * {\n  display: inline-block;\n}\n\n.schedule-component h5 {\n  margin-right: 10px;\n  width: 87px;\n  text-align: right;\n}\n\n.schedule-component > div > *:not(:last-child) {\n  margin-right: 3px;\n}\n\n.schedule-component datepicker {\n  display: block;\n}\n\n.schedule-phrase {\n  display: inline-block;\n}\n\na.schedule-phrase {\n  cursor: pointer;\n}\n\n.utc {\n  opacity: 0.4;\n  margin-left: 10px;\n}"
  },
  {
    "path": "client/app/components/queries/ScheduleDialog.jsx",
    "content": "import React, { useState } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Modal from \"antd/lib/modal\";\nimport DatePicker from \"antd/lib/date-picker\";\nimport TimePicker from \"antd/lib/time-picker\";\nimport Select from \"antd/lib/select\";\nimport Radio from \"antd/lib/radio\";\nimport { capitalize, clone, isEqual, omitBy, isNil, isEmpty } from \"lodash\";\nimport moment from \"moment\";\nimport { secondsToInterval, durationHumanize, pluralize, IntervalEnum, localizeTime } from \"@/lib/utils\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport { RefreshScheduleType, RefreshScheduleDefault, Moment } from \"../proptypes\";\n\nimport \"./ScheduleDialog.css\";\n\nconst WEEKDAYS_SHORT = moment.weekdaysShort();\nconst WEEKDAYS_FULL = moment.weekdays();\nconst DATE_FORMAT = \"YYYY-MM-DD\";\nconst HOUR_FORMAT = \"HH:mm\";\nconst { Option, OptGroup } = Select;\n\nexport function TimeEditor(props) {\n  const [time, setTime] = useState(props.defaultValue);\n  const showUtc = time && !time.isUTC();\n\n  function onChange(newTime) {\n    setTime(newTime);\n    props.onChange(newTime);\n  }\n\n  return (\n    <React.Fragment>\n      <TimePicker allowClear={false} value={time} format={HOUR_FORMAT} minuteStep={5} onChange={onChange} />\n      {showUtc && (\n        <span className=\"utc\" data-testid=\"utc\">\n          ({moment.utc(time).format(HOUR_FORMAT)} UTC)\n        </span>\n      )}\n    </React.Fragment>\n  );\n}\n\nTimeEditor.propTypes = {\n  defaultValue: Moment,\n  onChange: PropTypes.func.isRequired,\n};\n\nTimeEditor.defaultProps = {\n  defaultValue: null,\n};\n\nclass ScheduleDialog extends React.Component {\n  static propTypes = {\n    schedule: RefreshScheduleType,\n    refreshOptions: PropTypes.arrayOf(PropTypes.number).isRequired,\n    dialog: DialogPropType.isRequired,\n  };\n\n  static defaultProps = {\n    schedule: RefreshScheduleDefault,\n  };\n\n  state = this.getState();\n\n  getState() {\n    const newSchedule = clone(this.props.schedule || ScheduleDialog.defaultProps.schedule);\n    const { time, interval: seconds, day_of_week: day } = newSchedule;\n    const { interval } = secondsToInterval(seconds);\n    const [hour, minute] = time ? localizeTime(time).split(\":\") : [null, null];\n\n    return {\n      hour,\n      minute,\n      seconds,\n      interval,\n      dayOfWeek: day ? WEEKDAYS_SHORT[WEEKDAYS_FULL.indexOf(day)] : null,\n      newSchedule,\n    };\n  }\n\n  get intervals() {\n    const ret = {\n      [IntervalEnum.NEVER]: [],\n    };\n    this.props.refreshOptions.forEach(seconds => {\n      const { count, interval } = secondsToInterval(seconds);\n      if (!(interval in ret)) {\n        ret[interval] = [];\n      }\n      ret[interval].push([count, seconds]);\n    });\n\n    Object.defineProperty(this, \"intervals\", { value: ret }); // memoize\n\n    return ret;\n  }\n\n  set newSchedule(newProps) {\n    this.setState(prevState => ({\n      newSchedule: Object.assign(prevState.newSchedule, newProps),\n    }));\n  }\n\n  setTime = time => {\n    this.newSchedule = {\n      time: moment(time)\n        .utc()\n        .format(HOUR_FORMAT),\n    };\n  };\n\n  setInterval = newSeconds => {\n    const { newSchedule } = this.state;\n    const { interval: newInterval } = secondsToInterval(newSeconds);\n\n    // resets to defaults\n    if (newInterval === IntervalEnum.NEVER) {\n      newSchedule.until = null;\n    }\n    if ([IntervalEnum.NEVER, IntervalEnum.MINUTES, IntervalEnum.HOURS].indexOf(newInterval) !== -1) {\n      newSchedule.time = null;\n    }\n    if (newInterval !== IntervalEnum.WEEKS) {\n      newSchedule.day_of_week = null;\n    }\n    if (\n      (newInterval === IntervalEnum.DAYS || newInterval === IntervalEnum.WEEKS) &&\n      (!this.state.minute || !this.state.hour)\n    ) {\n      newSchedule.time = moment()\n        .hour(\"00\")\n        .minute(\"15\")\n        .utc()\n        .format(HOUR_FORMAT);\n    }\n    if (newInterval === IntervalEnum.WEEKS && !this.state.dayOfWeek) {\n      newSchedule.day_of_week = WEEKDAYS_FULL[0];\n    }\n\n    newSchedule.interval = newSeconds;\n\n    const [hour, minute] = newSchedule.time ? localizeTime(newSchedule.time).split(\":\") : [null, null];\n\n    this.setState({\n      interval: newInterval,\n      seconds: newSeconds,\n      hour,\n      minute,\n      dayOfWeek: newSchedule.day_of_week ? WEEKDAYS_SHORT[WEEKDAYS_FULL.indexOf(newSchedule.day_of_week)] : null,\n    });\n\n    this.newSchedule = newSchedule;\n  };\n\n  setScheduleUntil = (_, date) => {\n    this.newSchedule = { until: date };\n  };\n\n  setWeekday = e => {\n    const dayOfWeek = e.target.value;\n    this.setState({ dayOfWeek });\n    this.newSchedule = {\n      day_of_week: dayOfWeek ? WEEKDAYS_FULL[WEEKDAYS_SHORT.indexOf(dayOfWeek)] : null,\n    };\n  };\n\n  setUntilToggle = e => {\n    const date = e.target.value ? moment().format(DATE_FORMAT) : null;\n    this.setScheduleUntil(null, date);\n  };\n\n  save() {\n    const { newSchedule } = this.state;\n    const hasChanged = () => {\n      const newCompact = omitBy(newSchedule, isNil);\n      const oldCompact = omitBy(this.props.schedule, isNil);\n      return !isEqual(newCompact, oldCompact);\n    };\n\n    // save if changed\n    if (hasChanged()) {\n      if (newSchedule.interval) {\n        this.props.dialog.close(clone(newSchedule));\n      } else {\n        this.props.dialog.close(null);\n      }\n    }\n    this.props.dialog.dismiss();\n  }\n\n  render() {\n    const { dialog } = this.props;\n    const {\n      interval,\n      minute,\n      hour,\n      seconds,\n      newSchedule: { until },\n    } = this.state;\n\n    return (\n      <Modal {...dialog.props} title=\"Refresh Schedule\" className=\"schedule\" onOk={() => this.save()}>\n        <div className=\"schedule-component\">\n          <h5>Refresh every</h5>\n          <div data-testid=\"interval\">\n            <Select className=\"input\" value={seconds} onChange={this.setInterval} dropdownMatchSelectWidth={false}>\n              <Option value={null} key=\"never\">\n                Never\n              </Option>\n              {Object.keys(this.intervals)\n                .filter(int => !isEmpty(this.intervals[int]))\n                .map(int => (\n                  <OptGroup label={capitalize(pluralize(int))} key={int}>\n                    {this.intervals[int].map(([cnt, secs]) => (\n                      <Option value={secs} key={`${int}-${cnt}`}>\n                        {durationHumanize(secs)}\n                      </Option>\n                    ))}\n                  </OptGroup>\n                ))}\n            </Select>\n          </div>\n        </div>\n        {[IntervalEnum.DAYS, IntervalEnum.WEEKS].indexOf(interval) !== -1 ? (\n          <div className=\"schedule-component\">\n            <h5>On time</h5>\n            <div data-testid=\"time\">\n              <TimeEditor\n                defaultValue={\n                  hour\n                    ? moment()\n                        .hour(hour)\n                        .minute(minute)\n                    : null\n                }\n                onChange={this.setTime}\n              />\n            </div>\n          </div>\n        ) : null}\n        {IntervalEnum.WEEKS === interval ? (\n          <div className=\"schedule-component\">\n            <h5>On day</h5>\n            <div data-testid=\"weekday\">\n              <Radio.Group size=\"medium\" defaultValue={this.state.dayOfWeek} onChange={this.setWeekday}>\n                {WEEKDAYS_SHORT.map(day => (\n                  <Radio.Button value={day} key={day} className=\"input\">\n                    {day[0]}\n                  </Radio.Button>\n                ))}\n              </Radio.Group>\n            </div>\n          </div>\n        ) : null}\n        {interval !== IntervalEnum.NEVER ? (\n          <div className=\"schedule-component\">\n            <h5>Ends</h5>\n            <div className=\"ends\" data-testid=\"ends\">\n              <Radio.Group size=\"medium\" value={!!until} onChange={this.setUntilToggle}>\n                <Radio value={false}>Never</Radio>\n                <Radio value>On</Radio>\n              </Radio.Group>\n              {until ? (\n                <DatePicker\n                  size=\"small\"\n                  className=\"datepicker\"\n                  value={moment(until)}\n                  allowClear={false}\n                  format={DATE_FORMAT}\n                  onChange={this.setScheduleUntil}\n                />\n              ) : null}\n            </div>\n          </div>\n        ) : null}\n      </Modal>\n    );\n  }\n}\n\nexport default wrapDialog(ScheduleDialog);\n"
  },
  {
    "path": "client/app/components/queries/ScheduleDialog.test.js",
    "content": "import React from \"react\";\nimport { mount } from \"enzyme\";\nimport moment from \"moment\";\nimport { durationHumanize } from \"@/lib/utils\";\nimport ScheduleDialog, { TimeEditor } from \"./ScheduleDialog\";\nimport RefreshScheduleDefault from \"../proptypes\";\n\nconst defaultProps = {\n  schedule: RefreshScheduleDefault,\n  refreshOptions: [\n    60,\n    300,\n    600, // 1, 5 ,10 mins\n    3600,\n    36000,\n    82800, // 1, 10, 23 hours\n    86400,\n    172800,\n    518400, // 1, 2, 6 days\n    604800,\n    1209600, // 1, 2, 4 weeks\n  ],\n  dialog: {\n    props: {\n      visible: true,\n      onOk: () => {},\n      onCancel: () => {},\n      afterClose: () => {},\n    },\n    close: () => {},\n    dismiss: () => {},\n  },\n};\n\nfunction getWrapper(schedule = {}, { onConfirm, onCancel, ...props } = {}) {\n  onConfirm = onConfirm || (() => {});\n  onCancel = onCancel || (() => {});\n\n  props = {\n    ...defaultProps,\n    ...props,\n    schedule: {\n      ...RefreshScheduleDefault,\n      ...schedule,\n    },\n    dialog: {\n      props: {\n        visible: true,\n        onOk: onConfirm,\n        onCancel,\n        afterClose: () => {},\n      },\n      close: onConfirm,\n      dismiss: onCancel,\n    },\n  };\n\n  return [mount(<ScheduleDialog.Component {...props} />), props];\n}\n\nfunction findByTestID(wrapper, id) {\n  return wrapper.find(`[data-testid=\"${id}\"]`);\n}\n\ndescribe(\"ScheduleDialog\", () => {\n  describe(\"Sets correct schedule settings\", () => {\n    test('Sets to \"Never\"', () => {\n      const [wrapper] = getWrapper();\n      const el = findByTestID(wrapper, \"interval\");\n      expect(el).toMatchSnapshot();\n    });\n\n    test('Sets to \"5 Minutes\"', () => {\n      const [wrapper] = getWrapper({ interval: 300 });\n      const el = findByTestID(wrapper, \"interval\");\n      expect(el).toMatchSnapshot();\n    });\n\n    test('Sets to \"2 Hours\"', () => {\n      const [wrapper] = getWrapper({ interval: 7200 });\n      const el = findByTestID(wrapper, \"interval\");\n      expect(el).toMatchSnapshot();\n    });\n\n    describe('Sets to \"1 Day 22:15\"', () => {\n      const [wrapper] = getWrapper({\n        interval: 86400,\n        time: \"22:15\",\n      });\n\n      test(\"Sets to correct interval\", () => {\n        const el = findByTestID(wrapper, \"interval\");\n        expect(el).toMatchSnapshot();\n      });\n\n      test(\"Sets to correct time\", () => {\n        const el = findByTestID(wrapper, \"time\");\n        expect(el).toMatchSnapshot();\n      });\n    });\n\n    describe(\"TimeEditor\", () => {\n      const defaultValue = moment().hour(5).minute(25); // 05:25\n\n      test(\"UTC set correctly on init\", () => {\n        const editor = mount(<TimeEditor defaultValue={defaultValue} onChange={() => {}} />);\n        const utc = findByTestID(editor, \"utc\");\n\n        // expect utc to be 2h below initial time\n        expect(utc.text()).toBe(\"(03:25 UTC)\");\n      });\n\n      test(\"UTC time should not render\", () => {\n        const utcValue = moment.utc(defaultValue);\n        const editor = mount(<TimeEditor defaultValue={utcValue} onChange={() => {}} />);\n        const utc = findByTestID(editor, \"utc\");\n\n        // expect utc to not render\n        expect(utc.exists()).toBeFalsy();\n      });\n\n      // Disabling this test as the TimePicker wasn't setting values from here after Antd v4\n      // eslint-disable-next-line jest/no-disabled-tests\n      test.skip(\"onChange correct result\", () => {\n        const onChangeCb = jest.fn((time) => time.format(\"HH:mm\"));\n        const editor = mount(<TimeEditor onChange={onChangeCb} />);\n\n        // click TimePicker\n        editor.find(\".ant-picker-input input\").simulate(\"mouseDown\");\n\n        const timePickerPanel = editor.find(\".ant-picker-panel\");\n\n        // select hour \"07\"\n        const hourSelector = timePickerPanel.find(\".ant-picker-time-panel-column\").at(0);\n        hourSelector.find(\"li\").at(7).simulate(\"click\");\n\n        // select minute \"30\"\n        const minuteSelector = timePickerPanel.find(\".ant-picker-time-panel-column\").at(1);\n        minuteSelector.find(\"li\").at(6).simulate(\"click\");\n\n        timePickerPanel.find(\".ant-picker-ok\").find(\"button\").simulate(\"mouseDown\");\n\n        // expect utc to be 2h below initial time\n        const utc = findByTestID(editor, \"utc\");\n        expect(utc.text()).toBe(\"(05:30 UTC)\");\n\n        // expect 07:30 from onChange\n        const onChangeResult = onChangeCb.mock.results[1].value;\n        expect(onChangeResult).toBe(\"07:30\");\n      });\n    });\n\n    describe('Sets to \"2 Weeks 22:15 Tuesday\"', () => {\n      const [wrapper] = getWrapper({\n        interval: 1209600,\n        time: \"22:15\",\n        day_of_week: \"Monday\",\n      });\n\n      test(\"Sets to correct interval\", () => {\n        const el = findByTestID(wrapper, \"interval\");\n        expect(el).toMatchSnapshot();\n      });\n\n      test(\"Sets to correct time\", () => {\n        const el = findByTestID(wrapper, \"time\");\n        expect(el).toMatchSnapshot();\n      });\n\n      test(\"Sets to correct weekday\", () => {\n        const el = findByTestID(wrapper, \"weekday\");\n        expect(el).toMatchSnapshot();\n      });\n    });\n\n    describe(\"Until feature\", () => {\n      test(\"Until not set\", () => {\n        const [wrapper] = getWrapper({ interval: 300 });\n        const el = findByTestID(wrapper, \"ends\");\n        expect(el).toMatchSnapshot();\n      });\n\n      test(\"Until is set\", () => {\n        const [wrapper] = getWrapper({ interval: 300, until: \"2030-01-01\" });\n        const el = findByTestID(wrapper, \"ends\");\n        expect(el).toMatchSnapshot();\n      });\n    });\n\n    describe(\"Supports 30 days interval with no time value\", () => {\n      test(\"Time is none\", () => {\n        const [wrapper] = getWrapper({ interval: 30 * 24 * 3600 });\n        const el = findByTestID(wrapper, \"time\");\n        expect(el).toMatchSnapshot();\n      });\n    });\n  });\n\n  describe(\"Adheres to user permissions\", () => {\n    test(\"Shows correct interval options\", () => {\n      const refreshOptions = [60, 300, 3600, 7200]; // 1 min, 5 min, 1 hour, 2 hours\n      const [wrapper] = getWrapper(null, { refreshOptions });\n\n      // Get the ScheduleDialog component instance and verify its computed intervals\n      const component = wrapper.find(\"ScheduleDialog\").instance();\n      const intervals = component.intervals;\n\n      // Flatten all interval options to [label, seconds] pairs, prepend \"Never\"\n      const allOptions = [\"Never\"];\n      Object.keys(intervals)\n        .filter((key) => intervals[key].length > 0)\n        .forEach((key) => {\n          intervals[key].forEach(([, secs]) => {\n            allOptions.push(durationHumanize(secs));\n          });\n        });\n\n      const expected = [\"Never\", \"1 minute\", \"5 minutes\", \"1 hour\", \"2 hours\"];\n\n      expect(allOptions).toEqual(expected);\n    });\n  });\n\n  describe(\"Modal Confirm/Cancel feature\", () => {\n    const confirmCb = jest.fn().mockName(\"confirmCb\");\n    const closeCb = jest.fn().mockName(\"closeCb\");\n    const initProps = { onConfirm: confirmCb, onCancel: closeCb };\n\n    beforeEach(() => {\n      jest.clearAllMocks();\n    });\n\n    test(\"Query saved on confirm if state changed\", () => {\n      // init\n      const [wrapper, props] = getWrapper(null, initProps);\n\n      // change state\n      const change = { time: \"22:15\" };\n      const newSchedule = Object.assign({}, props.schedule, change);\n      wrapper.setState({ newSchedule });\n\n      // click confirm button\n      wrapper.find(\".ant-modal-footer\").find(\".ant-btn-primary\").simulate(\"click\");\n\n      // expect calls\n      expect(confirmCb).toHaveBeenCalled();\n      expect(closeCb).toHaveBeenCalled();\n    });\n\n    test(\"Query not saved on confirm if state unchanged\", () => {\n      // init\n      const [wrapper] = getWrapper(null, initProps);\n\n      // click confirm button\n      wrapper.find(\".ant-modal-footer\").find(\".ant-btn-primary\").simulate(\"click\");\n\n      // expect calls\n      expect(confirmCb).not.toHaveBeenCalled();\n      expect(closeCb).toHaveBeenCalled();\n    });\n\n    test(\"Cancel closes modal and query unsaved\", () => {\n      // init\n      const [wrapper, props] = getWrapper(null, initProps);\n\n      // change state\n      const change = { time: \"22:15\" };\n      const newSchedule = Object.assign({}, props.schedule, change);\n      wrapper.setState({ newSchedule });\n\n      // click cancel button\n      wrapper.find(\".ant-modal-footer\").find(\"button:not(.ant-btn-primary)\").simulate(\"click\");\n\n      // expect calls\n      expect(confirmCb).not.toHaveBeenCalled();\n      expect(closeCb).toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/components/queries/SchedulePhrase.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Tooltip from \"@/components/Tooltip\";\nimport PlainButton from \"@/components/PlainButton\";\nimport { localizeTime, durationHumanize } from \"@/lib/utils\";\nimport { RefreshScheduleType, RefreshScheduleDefault } from \"../proptypes\";\n\nimport \"./ScheduleDialog.css\";\n\nexport default class SchedulePhrase extends React.Component {\n  static propTypes = {\n    schedule: RefreshScheduleType,\n    isNew: PropTypes.bool.isRequired,\n    isLink: PropTypes.bool,\n    onClick: PropTypes.func,\n  };\n\n  static defaultProps = {\n    schedule: RefreshScheduleDefault,\n    isLink: false,\n    onClick: () => {},\n  };\n\n  get content() {\n    const { interval: seconds } = this.props.schedule || SchedulePhrase.defaultProps.schedule;\n    if (!seconds) {\n      return [\"Never\"];\n    }\n    const humanized = durationHumanize(seconds, {\n      omitSingleValueNumber: true,\n    });\n    const short = `Every ${humanized}`;\n    let full = `Refreshes every ${humanized}`;\n\n    const { time, day_of_week: dayOfWeek } = this.props.schedule;\n    if (time) {\n      full += ` at ${localizeTime(time)}`;\n    }\n    if (dayOfWeek) {\n      full += ` on ${dayOfWeek}`;\n    }\n\n    return [short, full];\n  }\n\n  render() {\n    if (this.props.isNew) {\n      return \"Never\";\n    }\n\n    const [short, full] = this.content;\n    const content = full ? <Tooltip title={full}>{short}</Tooltip> : short;\n\n    return this.props.isLink ? (\n      <PlainButton type=\"link\" className=\"schedule-phrase\" onClick={this.props.onClick} data-test=\"EditSchedule\">\n        {content}\n      </PlainButton>\n    ) : (\n      content\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/components/queries/SchemaBrowser.jsx",
    "content": "import { isNil, map, filter, some, includes, get } from \"lodash\";\nimport cx from \"classnames\";\nimport React, { useState, useCallback, useMemo, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport Input from \"antd/lib/input\";\nimport Button from \"antd/lib/button\";\nimport AutoSizer from \"react-virtualized/dist/commonjs/AutoSizer\";\nimport List from \"react-virtualized/dist/commonjs/List\";\nimport PlainButton from \"@/components/PlainButton\";\nimport Tooltip from \"@/components/Tooltip\";\nimport useDataSourceSchema from \"@/pages/queries/hooks/useDataSourceSchema\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\nimport LoadingState from \"../items-list/components/LoadingState\";\n\nconst SchemaItemColumnType = PropTypes.shape({\n  name: PropTypes.string.isRequired,\n  type: PropTypes.string,\n});\n\nexport const SchemaItemType = PropTypes.shape({\n  name: PropTypes.string.isRequired,\n  size: PropTypes.number,\n  loading: PropTypes.bool,\n  columns: PropTypes.arrayOf(SchemaItemColumnType).isRequired,\n});\n\nconst schemaTableHeight = 22;\nconst schemaColumnHeight = 18;\n\nfunction SchemaItem({ item, expanded, onToggle, onSelect, ...props }) {\n  const handleSelect = useCallback(\n    (event, ...args) => {\n      event.preventDefault();\n      event.stopPropagation();\n      onSelect(...args);\n    },\n    [onSelect]\n  );\n\n  if (!item) {\n    return null;\n  }\n\n  const tableDisplayName = item.displayName || item.name;\n\n  return (\n    <div {...props}>\n      <div className=\"schema-list-item\">\n        <Tooltip\n          title={item.description}\n          mouseEnterDelay={0}\n          mouseLeaveDelay={0}\n          placement=\"rightTop\"\n          trigger={item.description ? \"hover\" : \"\"}\n          overlayStyle={{ whiteSpace: \"pre-line\" }}\n        >\n          <PlainButton className=\"table-name\" onClick={onToggle}>\n            <i className=\"fa fa-table m-r-5\" aria-hidden=\"true\" />\n            <strong>\n              <span title={item.name}>{tableDisplayName}</span>\n              {!isNil(item.size) && <span> ({item.size})</span>}\n            </strong>\n          </PlainButton>\n        </Tooltip>\n        <Tooltip\n          title=\"Insert table name into query text\"\n          mouseEnterDelay={0}\n          mouseLeaveDelay={0}\n          placement=\"topRight\"\n          arrowPointAtCenter\n        >\n          <PlainButton className=\"copy-to-editor\" onClick={(e) => handleSelect(e, item.name)}>\n            <i className=\"fa fa-angle-double-right\" aria-hidden=\"true\" />\n          </PlainButton>\n        </Tooltip>\n      </div>\n      {expanded && (\n        <div className=\"table-open\">\n          {item.loading ? (\n            <div className=\"table-open\">Loading...</div>\n          ) : (\n            map(item.columns, (column) => {\n              const columnName = get(column, \"name\");\n              const columnType = get(column, \"type\");\n              const columnDescription = get(column, \"description\");\n              return (\n                <Tooltip\n                  title={\"Insert column name into query text\" + (columnDescription ? \"\\n\" + columnDescription : \"\")}\n                  mouseEnterDelay={0}\n                  mouseLeaveDelay={0}\n                  placement=\"rightTop\"\n                  overlayStyle={{ whiteSpace: \"pre-line\" }}\n                >\n                  <PlainButton\n                    key={columnName}\n                    className=\"table-open-item\"\n                    onClick={(e) => handleSelect(e, columnName)}\n                  >\n                    <div>\n                      {columnName} {columnType && <span className=\"column-type\">{columnType}</span>}\n                    </div>\n\n                    <div className=\"copy-to-editor\">\n                      <i className=\"fa fa-angle-double-right\" aria-hidden=\"true\" />\n                    </div>\n                  </PlainButton>\n                </Tooltip>\n              );\n            })\n          )}\n        </div>\n      )}\n    </div>\n  );\n}\n\nSchemaItem.propTypes = {\n  item: SchemaItemType,\n  expanded: PropTypes.bool,\n  onToggle: PropTypes.func,\n  onSelect: PropTypes.func,\n};\n\nSchemaItem.defaultProps = {\n  item: null,\n  expanded: false,\n  onToggle: () => {},\n  onSelect: () => {},\n};\n\nfunction SchemaLoadingState() {\n  return (\n    <div className=\"schema-loading-state\">\n      <LoadingState className=\"\" />\n    </div>\n  );\n}\n\nexport function SchemaList({ loading, schema, expandedFlags, onTableExpand, onItemSelect }) {\n  const [listRef, setListRef] = useState(null);\n\n  useEffect(() => {\n    if (listRef) {\n      listRef.recomputeRowHeights();\n    }\n  }, [listRef, schema, expandedFlags]);\n\n  return (\n    <div className=\"schema-browser\">\n      {loading && <SchemaLoadingState />}\n      {!loading && (\n        <AutoSizer>\n          {({ width, height }) => (\n            <List\n              ref={setListRef}\n              width={width}\n              height={height}\n              rowCount={schema.length}\n              rowHeight={({ index }) => {\n                const item = schema[index];\n                const columnsLength = !item.loading ? item.columns.length : 1;\n                let columnCount = expandedFlags[item.name] ? columnsLength : 0;\n                return schemaTableHeight + schemaColumnHeight * columnCount;\n              }}\n              rowRenderer={({ key, index, style }) => {\n                const item = schema[index];\n                return (\n                  <SchemaItem\n                    key={key}\n                    style={style}\n                    item={item}\n                    expanded={expandedFlags[item.name]}\n                    onToggle={() => onTableExpand(item.name)}\n                    onSelect={onItemSelect}\n                  />\n                );\n              }}\n            />\n          )}\n        </AutoSizer>\n      )}\n    </div>\n  );\n}\n\nexport function applyFilterOnSchema(schema, filterString) {\n  const filters = filter(filterString.toLowerCase().split(/\\s+/), (s) => s.length > 0);\n\n  // Empty string: return original schema\n  if (filters.length === 0) {\n    return schema;\n  }\n\n  // Single word: matches table or column\n  if (filters.length === 1) {\n    const nameFilter = filters[0];\n    const columnFilter = filters[0];\n    return filter(\n      schema,\n      (item) =>\n        includes(item.name.toLowerCase(), nameFilter) ||\n        some(item.columns, (column) => includes(get(column, \"name\").toLowerCase(), columnFilter))\n    );\n  }\n\n  // Two (or more) words: first matches table, seconds matches column\n  const nameFilter = filters[0];\n  const columnFilter = filters[1];\n  return filter(\n    map(schema, (item) => {\n      if (includes(item.name.toLowerCase(), nameFilter)) {\n        item = {\n          ...item,\n          columns: filter(item.columns, (column) => includes(get(column, \"name\").toLowerCase(), columnFilter)),\n        };\n        return item.columns.length > 0 ? item : null;\n      }\n    })\n  );\n}\n\nexport default function SchemaBrowser({\n  dataSource,\n  onSchemaUpdate,\n  onItemSelect,\n  options,\n  onOptionsUpdate,\n  ...props\n}) {\n  const [schema, isLoading, refreshSchema] = useDataSourceSchema(dataSource);\n  const [filterString, setFilterString] = useState(\"\");\n  const filteredSchema = useMemo(() => applyFilterOnSchema(schema, filterString), [schema, filterString]);\n  const [handleFilterChange] = useDebouncedCallback(setFilterString, 500);\n  const [expandedFlags, setExpandedFlags] = useState({});\n\n  const handleSchemaUpdate = useImmutableCallback(onSchemaUpdate);\n\n  useEffect(() => {\n    setExpandedFlags({});\n    handleSchemaUpdate(schema);\n  }, [schema, handleSchemaUpdate]);\n\n  if (schema.length === 0 && !isLoading) {\n    return null;\n  }\n\n  function toggleTable(tableName) {\n    setExpandedFlags({\n      ...expandedFlags,\n      [tableName]: !expandedFlags[tableName],\n    });\n  }\n\n  return (\n    <div className=\"schema-container\" {...props}>\n      <div className=\"schema-control\">\n        <Input\n          className=\"m-r-5\"\n          placeholder=\"Search schema...\"\n          aria-label=\"Search schema\"\n          disabled={schema.length === 0}\n          onChange={(event) => handleFilterChange(event.target.value)}\n        />\n\n        <Tooltip title=\"Refresh Schema\">\n          <Button onClick={() => refreshSchema(true)}>\n            <i className={cx(\"zmdi zmdi-refresh\", { \"zmdi-hc-spin\": isLoading })} aria-hidden=\"true\" />\n            <span className=\"sr-only\">{isLoading ? \"Loading, please wait.\" : \"Press to refresh.\"}</span>\n          </Button>\n        </Tooltip>\n      </div>\n      <SchemaList\n        loading={isLoading && schema.length === 0}\n        schema={filteredSchema}\n        expandedFlags={expandedFlags}\n        onTableExpand={toggleTable}\n        onItemSelect={onItemSelect}\n      />\n    </div>\n  );\n}\n\nSchemaBrowser.propTypes = {\n  dataSource: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  onSchemaUpdate: PropTypes.func,\n  onItemSelect: PropTypes.func,\n};\n\nSchemaBrowser.defaultProps = {\n  dataSource: null,\n  onSchemaUpdate: () => {},\n  onItemSelect: () => {},\n};\n"
  },
  {
    "path": "client/app/components/queries/__snapshots__/ScheduleDialog.test.js.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`ScheduleDialog Sets correct schedule settings Sets to \"1 Day 22:15\" Sets to correct interval 1`] = `\n<div\n  data-testid=\"interval\"\n>\n  <Select\n    bordered={true}\n    choiceTransitionName=\"zoom\"\n    className=\"input\"\n    dropdownMatchSelectWidth={false}\n    onChange={[Function]}\n    transitionName=\"slide-up\"\n    value={86400}\n  >\n    <Select\n      choiceTransitionName=\"zoom\"\n      className=\"input\"\n      clearIcon={<CloseCircleFilled />}\n      dropdownClassName=\"\"\n      dropdownMatchSelectWidth={false}\n      inputIcon={[Function]}\n      listHeight={256}\n      listItemHeight={24}\n      menuItemSelectedIcon={null}\n      notFoundContent={\n        <Context.Consumer>\n          [Function]\n        </Context.Consumer>\n      }\n      onChange={[Function]}\n      prefixCls=\"ant-select\"\n      removeIcon={<CloseOutlined />}\n      transitionName=\"slide-up\"\n      value={86400}\n    >\n      <ForwardRef(Select)\n        choiceTransitionName=\"zoom\"\n        className=\"input\"\n        clearIcon={<CloseCircleFilled />}\n        dropdownClassName=\"\"\n        dropdownMatchSelectWidth={false}\n        inputIcon={[Function]}\n        listHeight={256}\n        listItemHeight={24}\n        menuItemSelectedIcon={null}\n        notFoundContent={\n          <Context.Consumer>\n            [Function]\n          </Context.Consumer>\n        }\n        onChange={[Function]}\n        prefixCls=\"ant-select\"\n        removeIcon={<CloseOutlined />}\n        transitionName=\"slide-up\"\n        value={86400}\n      >\n        <div\n          className=\"ant-select input ant-select-single ant-select-show-arrow\"\n          onBlur={[Function]}\n          onFocus={[Function]}\n          onKeyDown={[Function]}\n          onKeyUp={[Function]}\n          onMouseDown={[Function]}\n        >\n          <SelectTrigger\n            containerWidth={null}\n            dropdownClassName=\"\"\n            dropdownMatchSelectWidth={false}\n            empty={false}\n            getTriggerDOMNode={[Function]}\n            popupElement={\n              <OptionList\n                childrenAsData={true}\n                defaultActiveFirstOption={true}\n                flattenOptions={\n                  [\n                    {\n                      \"data\": {\n                        \"children\": \"Never\",\n                        \"key\": \"never\",\n                        \"value\": null,\n                      },\n                      \"groupOption\": false,\n                      \"key\": \"never\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__minute__\",\n                        \"label\": \"Minutes\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 minute\",\n                            \"key\": \"minute-1\",\n                            \"value\": 60,\n                          },\n                          {\n                            \"children\": \"5 minutes\",\n                            \"key\": \"minute-5\",\n                            \"value\": 300,\n                          },\n                          {\n                            \"children\": \"10 minutes\",\n                            \"key\": \"minute-10\",\n                            \"value\": 600,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__minute__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 minute\",\n                        \"key\": \"minute-1\",\n                        \"value\": 60,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"5 minutes\",\n                        \"key\": \"minute-5\",\n                        \"value\": 300,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-5\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"10 minutes\",\n                        \"key\": \"minute-10\",\n                        \"value\": 600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-10\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__hour__\",\n                        \"label\": \"Hours\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 hour\",\n                            \"key\": \"hour-1\",\n                            \"value\": 3600,\n                          },\n                          {\n                            \"children\": \"10 hours\",\n                            \"key\": \"hour-10\",\n                            \"value\": 36000,\n                          },\n                          {\n                            \"children\": \"23 hours\",\n                            \"key\": \"hour-23\",\n                            \"value\": 82800,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__hour__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 hour\",\n                        \"key\": \"hour-1\",\n                        \"value\": 3600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"10 hours\",\n                        \"key\": \"hour-10\",\n                        \"value\": 36000,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-10\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"23 hours\",\n                        \"key\": \"hour-23\",\n                        \"value\": 82800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-23\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__day__\",\n                        \"label\": \"Days\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 day\",\n                            \"key\": \"day-1\",\n                            \"value\": 86400,\n                          },\n                          {\n                            \"children\": \"2 days\",\n                            \"key\": \"day-2\",\n                            \"value\": 172800,\n                          },\n                          {\n                            \"children\": \"6 days\",\n                            \"key\": \"day-6\",\n                            \"value\": 518400,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__day__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 day\",\n                        \"key\": \"day-1\",\n                        \"value\": 86400,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"2 days\",\n                        \"key\": \"day-2\",\n                        \"value\": 172800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-2\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"6 days\",\n                        \"key\": \"day-6\",\n                        \"value\": 518400,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-6\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__week__\",\n                        \"label\": \"Weeks\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 week\",\n                            \"key\": \"week-1\",\n                            \"value\": 604800,\n                          },\n                          {\n                            \"children\": \"2 weeks\",\n                            \"key\": \"week-2\",\n                            \"value\": 1209600,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__week__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 week\",\n                        \"key\": \"week-1\",\n                        \"value\": 604800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"week-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"2 weeks\",\n                        \"key\": \"week-2\",\n                        \"value\": 1209600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"week-2\",\n                    },\n                  ]\n                }\n                height={256}\n                id=\"rc_select_TEST_OR_SSR\"\n                itemHeight={24}\n                menuItemSelectedIcon={null}\n                multiple={false}\n                notFoundContent={\n                  <Context.Consumer>\n                    [Function]\n                  </Context.Consumer>\n                }\n                onActiveValue={[Function]}\n                onMouseEnter={[Function]}\n                onSelect={[Function]}\n                onToggleOpen={[Function]}\n                options={\n                  [\n                    {\n                      \"children\": \"Never\",\n                      \"key\": \"never\",\n                      \"value\": null,\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__minute__\",\n                      \"label\": \"Minutes\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 minute\",\n                          \"key\": \"minute-1\",\n                          \"value\": 60,\n                        },\n                        {\n                          \"children\": \"5 minutes\",\n                          \"key\": \"minute-5\",\n                          \"value\": 300,\n                        },\n                        {\n                          \"children\": \"10 minutes\",\n                          \"key\": \"minute-10\",\n                          \"value\": 600,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__hour__\",\n                      \"label\": \"Hours\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 hour\",\n                          \"key\": \"hour-1\",\n                          \"value\": 3600,\n                        },\n                        {\n                          \"children\": \"10 hours\",\n                          \"key\": \"hour-10\",\n                          \"value\": 36000,\n                        },\n                        {\n                          \"children\": \"23 hours\",\n                          \"key\": \"hour-23\",\n                          \"value\": 82800,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__day__\",\n                      \"label\": \"Days\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 day\",\n                          \"key\": \"day-1\",\n                          \"value\": 86400,\n                        },\n                        {\n                          \"children\": \"2 days\",\n                          \"key\": \"day-2\",\n                          \"value\": 172800,\n                        },\n                        {\n                          \"children\": \"6 days\",\n                          \"key\": \"day-6\",\n                          \"value\": 518400,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__week__\",\n                      \"label\": \"Weeks\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 week\",\n                          \"key\": \"week-1\",\n                          \"value\": 604800,\n                        },\n                        {\n                          \"children\": \"2 weeks\",\n                          \"key\": \"week-2\",\n                          \"value\": 1209600,\n                        },\n                      ],\n                    },\n                  ]\n                }\n                prefixCls=\"ant-select\"\n                searchValue=\"\"\n                values={\n                  Set {\n                    86400,\n                  }\n                }\n                virtual={false}\n              />\n            }\n            prefixCls=\"ant-select\"\n            transitionName=\"slide-up\"\n          >\n            <Trigger\n              action={[]}\n              afterPopupVisibleChange={[Function]}\n              autoDestroy={false}\n              blurDelay={0.15}\n              builtinPlacements={\n                {\n                  \"bottomLeft\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tl\",\n                      \"bl\",\n                    ],\n                  },\n                  \"bottomRight\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tr\",\n                      \"br\",\n                    ],\n                  },\n                  \"topLeft\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"bl\",\n                      \"tl\",\n                    ],\n                  },\n                  \"topRight\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"br\",\n                      \"tr\",\n                    ],\n                  },\n                }\n              }\n              defaultPopupVisible={false}\n              destroyPopupOnHide={false}\n              focusDelay={0}\n              getDocument={[Function]}\n              getPopupClassNameFromAlign={[Function]}\n              getTriggerDOMNode={[Function]}\n              hideAction={[]}\n              mask={false}\n              maskClosable={true}\n              mouseEnterDelay={0}\n              mouseLeaveDelay={0.1}\n              onPopupAlign={[Function]}\n              onPopupVisibleChange={[Function]}\n              popup={\n                <div>\n                  <OptionList\n                    childrenAsData={true}\n                    defaultActiveFirstOption={true}\n                    flattenOptions={\n                      [\n                        {\n                          \"data\": {\n                            \"children\": \"Never\",\n                            \"key\": \"never\",\n                            \"value\": null,\n                          },\n                          \"groupOption\": false,\n                          \"key\": \"never\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__minute__\",\n                            \"label\": \"Minutes\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 minute\",\n                                \"key\": \"minute-1\",\n                                \"value\": 60,\n                              },\n                              {\n                                \"children\": \"5 minutes\",\n                                \"key\": \"minute-5\",\n                                \"value\": 300,\n                              },\n                              {\n                                \"children\": \"10 minutes\",\n                                \"key\": \"minute-10\",\n                                \"value\": 600,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__minute__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 minute\",\n                            \"key\": \"minute-1\",\n                            \"value\": 60,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"5 minutes\",\n                            \"key\": \"minute-5\",\n                            \"value\": 300,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-5\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"10 minutes\",\n                            \"key\": \"minute-10\",\n                            \"value\": 600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-10\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__hour__\",\n                            \"label\": \"Hours\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 hour\",\n                                \"key\": \"hour-1\",\n                                \"value\": 3600,\n                              },\n                              {\n                                \"children\": \"10 hours\",\n                                \"key\": \"hour-10\",\n                                \"value\": 36000,\n                              },\n                              {\n                                \"children\": \"23 hours\",\n                                \"key\": \"hour-23\",\n                                \"value\": 82800,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__hour__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 hour\",\n                            \"key\": \"hour-1\",\n                            \"value\": 3600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"10 hours\",\n                            \"key\": \"hour-10\",\n                            \"value\": 36000,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-10\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"23 hours\",\n                            \"key\": \"hour-23\",\n                            \"value\": 82800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-23\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__day__\",\n                            \"label\": \"Days\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 day\",\n                                \"key\": \"day-1\",\n                                \"value\": 86400,\n                              },\n                              {\n                                \"children\": \"2 days\",\n                                \"key\": \"day-2\",\n                                \"value\": 172800,\n                              },\n                              {\n                                \"children\": \"6 days\",\n                                \"key\": \"day-6\",\n                                \"value\": 518400,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__day__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 day\",\n                            \"key\": \"day-1\",\n                            \"value\": 86400,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"2 days\",\n                            \"key\": \"day-2\",\n                            \"value\": 172800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-2\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"6 days\",\n                            \"key\": \"day-6\",\n                            \"value\": 518400,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-6\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__week__\",\n                            \"label\": \"Weeks\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 week\",\n                                \"key\": \"week-1\",\n                                \"value\": 604800,\n                              },\n                              {\n                                \"children\": \"2 weeks\",\n                                \"key\": \"week-2\",\n                                \"value\": 1209600,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__week__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 week\",\n                            \"key\": \"week-1\",\n                            \"value\": 604800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"week-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"2 weeks\",\n                            \"key\": \"week-2\",\n                            \"value\": 1209600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"week-2\",\n                        },\n                      ]\n                    }\n                    height={256}\n                    id=\"rc_select_TEST_OR_SSR\"\n                    itemHeight={24}\n                    menuItemSelectedIcon={null}\n                    multiple={false}\n                    notFoundContent={\n                      <Context.Consumer>\n                        [Function]\n                      </Context.Consumer>\n                    }\n                    onActiveValue={[Function]}\n                    onMouseEnter={[Function]}\n                    onSelect={[Function]}\n                    onToggleOpen={[Function]}\n                    options={\n                      [\n                        {\n                          \"children\": \"Never\",\n                          \"key\": \"never\",\n                          \"value\": null,\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__minute__\",\n                          \"label\": \"Minutes\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 minute\",\n                              \"key\": \"minute-1\",\n                              \"value\": 60,\n                            },\n                            {\n                              \"children\": \"5 minutes\",\n                              \"key\": \"minute-5\",\n                              \"value\": 300,\n                            },\n                            {\n                              \"children\": \"10 minutes\",\n                              \"key\": \"minute-10\",\n                              \"value\": 600,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__hour__\",\n                          \"label\": \"Hours\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 hour\",\n                              \"key\": \"hour-1\",\n                              \"value\": 3600,\n                            },\n                            {\n                              \"children\": \"10 hours\",\n                              \"key\": \"hour-10\",\n                              \"value\": 36000,\n                            },\n                            {\n                              \"children\": \"23 hours\",\n                              \"key\": \"hour-23\",\n                              \"value\": 82800,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__day__\",\n                          \"label\": \"Days\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 day\",\n                              \"key\": \"day-1\",\n                              \"value\": 86400,\n                            },\n                            {\n                              \"children\": \"2 days\",\n                              \"key\": \"day-2\",\n                              \"value\": 172800,\n                            },\n                            {\n                              \"children\": \"6 days\",\n                              \"key\": \"day-6\",\n                              \"value\": 518400,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__week__\",\n                          \"label\": \"Weeks\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 week\",\n                              \"key\": \"week-1\",\n                              \"value\": 604800,\n                            },\n                            {\n                              \"children\": \"2 weeks\",\n                              \"key\": \"week-2\",\n                              \"value\": 1209600,\n                            },\n                          ],\n                        },\n                      ]\n                    }\n                    prefixCls=\"ant-select\"\n                    searchValue=\"\"\n                    values={\n                      Set {\n                        86400,\n                      }\n                    }\n                    virtual={false}\n                  />\n                </div>\n              }\n              popupAlign={{}}\n              popupClassName=\"\"\n              popupPlacement=\"bottomLeft\"\n              popupStyle={\n                {\n                  \"minWidth\": null,\n                }\n              }\n              popupTransitionName=\"slide-up\"\n              prefixCls=\"ant-select-dropdown\"\n              showAction={[]}\n            >\n              <Selector\n                accessibilityIndex={0}\n                activeValue={null}\n                choiceTransitionName=\"zoom\"\n                className=\"input\"\n                clearIcon={<CloseCircleFilled />}\n                domRef={\n                  {\n                    \"current\": <div\n                      class=\"ant-select-selector\"\n                    >\n                      <span\n                        class=\"ant-select-selection-search\"\n                      >\n                        <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autocomplete=\"off\"\n                          class=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          readonly=\"\"\n                          role=\"combobox\"\n                          style=\"opacity: 0;\"\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />\n                      </span>\n                      <span\n                        class=\"ant-select-selection-item\"\n                        title=\"1 day\"\n                      >\n                        1 day\n                      </span>\n                    </div>,\n                  }\n                }\n                dropdownClassName=\"\"\n                dropdownMatchSelectWidth={false}\n                id=\"rc_select_TEST_OR_SSR\"\n                inputElement={null}\n                inputIcon={[Function]}\n                key=\"trigger\"\n                listHeight={256}\n                listItemHeight={24}\n                menuItemSelectedIcon={null}\n                multiple={false}\n                notFoundContent={\n                  <Context.Consumer>\n                    [Function]\n                  </Context.Consumer>\n                }\n                onChange={[Function]}\n                onSearch={[Function]}\n                onSearchSubmit={[Function]}\n                onSelect={[Function]}\n                onToggleOpen={[Function]}\n                prefixCls=\"ant-select\"\n                removeIcon={<CloseOutlined />}\n                searchValue=\"\"\n                showSearch={false}\n                tokenWithEnter={false}\n                transitionName=\"slide-up\"\n                value={86400}\n                values={\n                  [\n                    {\n                      \"disabled\": undefined,\n                      \"key\": 86400,\n                      \"label\": \"1 day\",\n                      \"value\": 86400,\n                    },\n                  ]\n                }\n              >\n                <div\n                  className=\"ant-select-selector\"\n                  onClick={[Function]}\n                  onMouseDown={[Function]}\n                >\n                  <SingleSelector\n                    accessibilityIndex={0}\n                    activeValue={null}\n                    choiceTransitionName=\"zoom\"\n                    className=\"input\"\n                    clearIcon={<CloseCircleFilled />}\n                    domRef={\n                      {\n                        \"current\": <div\n                          class=\"ant-select-selector\"\n                        >\n                          <span\n                            class=\"ant-select-selection-search\"\n                          >\n                            <input\n                              aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                              aria-autocomplete=\"list\"\n                              aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                              aria-haspopup=\"listbox\"\n                              aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                              autocomplete=\"off\"\n                              class=\"ant-select-selection-search-input\"\n                              id=\"rc_select_TEST_OR_SSR\"\n                              readonly=\"\"\n                              role=\"combobox\"\n                              style=\"opacity: 0;\"\n                              type=\"search\"\n                              unselectable=\"on\"\n                              value=\"\"\n                            />\n                          </span>\n                          <span\n                            class=\"ant-select-selection-item\"\n                            title=\"1 day\"\n                          >\n                            1 day\n                          </span>\n                        </div>,\n                      }\n                    }\n                    dropdownClassName=\"\"\n                    dropdownMatchSelectWidth={false}\n                    id=\"rc_select_TEST_OR_SSR\"\n                    inputElement={null}\n                    inputIcon={[Function]}\n                    inputRef={\n                      {\n                        \"current\": <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autocomplete=\"off\"\n                          class=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          readonly=\"\"\n                          role=\"combobox\"\n                          style=\"opacity: 0;\"\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />,\n                      }\n                    }\n                    listHeight={256}\n                    listItemHeight={24}\n                    menuItemSelectedIcon={null}\n                    multiple={false}\n                    notFoundContent={\n                      <Context.Consumer>\n                        [Function]\n                      </Context.Consumer>\n                    }\n                    onChange={[Function]}\n                    onInputChange={[Function]}\n                    onInputCompositionEnd={[Function]}\n                    onInputCompositionStart={[Function]}\n                    onInputKeyDown={[Function]}\n                    onInputMouseDown={[Function]}\n                    onInputPaste={[Function]}\n                    onSearch={[Function]}\n                    onSearchSubmit={[Function]}\n                    onSelect={[Function]}\n                    onToggleOpen={[Function]}\n                    prefixCls=\"ant-select\"\n                    removeIcon={<CloseOutlined />}\n                    searchValue=\"\"\n                    showSearch={false}\n                    tokenWithEnter={false}\n                    transitionName=\"slide-up\"\n                    value={86400}\n                    values={\n                      [\n                        {\n                          \"disabled\": undefined,\n                          \"key\": 86400,\n                          \"label\": \"1 day\",\n                          \"value\": 86400,\n                        },\n                      ]\n                    }\n                  >\n                    <span\n                      className=\"ant-select-selection-search\"\n                    >\n                      <Input\n                        accessibilityIndex={0}\n                        attrs={{}}\n                        editable={false}\n                        id=\"rc_select_TEST_OR_SSR\"\n                        inputElement={null}\n                        onChange={[Function]}\n                        onCompositionEnd={[Function]}\n                        onCompositionStart={[Function]}\n                        onKeyDown={[Function]}\n                        onMouseDown={[Function]}\n                        onPaste={[Function]}\n                        prefixCls=\"ant-select\"\n                        value=\"\"\n                      >\n                        <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autoComplete=\"off\"\n                          className=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          onChange={[Function]}\n                          onCompositionEnd={[Function]}\n                          onCompositionStart={[Function]}\n                          onKeyDown={[Function]}\n                          onMouseDown={[Function]}\n                          onPaste={[Function]}\n                          readOnly={true}\n                          role=\"combobox\"\n                          style={\n                            {\n                              \"opacity\": 0,\n                            }\n                          }\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />\n                      </Input>\n                    </span>\n                    <span\n                      className=\"ant-select-selection-item\"\n                      title=\"1 day\"\n                    >\n                      1 day\n                    </span>\n                  </SingleSelector>\n                </div>\n              </Selector>\n            </Trigger>\n          </SelectTrigger>\n          <TransBtn\n            className=\"ant-select-arrow\"\n            customizeIcon={[Function]}\n            customizeIconProps={\n              {\n                \"focused\": false,\n                \"loading\": undefined,\n                \"open\": undefined,\n                \"searchValue\": \"\",\n                \"showSearch\": false,\n              }\n            }\n          >\n            <span\n              aria-hidden={true}\n              className=\"ant-select-arrow\"\n              onMouseDown={[Function]}\n              style={\n                {\n                  \"WebkitUserSelect\": \"none\",\n                  \"userSelect\": \"none\",\n                }\n              }\n              unselectable=\"on\"\n            >\n              <DownOutlined\n                className=\"ant-select-suffix\"\n              >\n                <AntdIcon\n                  className=\"ant-select-suffix\"\n                  icon={\n                    {\n                      \"icon\": {\n                        \"attrs\": {\n                          \"focusable\": \"false\",\n                          \"viewBox\": \"64 64 896 896\",\n                        },\n                        \"children\": [\n                          {\n                            \"attrs\": {\n                              \"d\": \"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\",\n                            },\n                            \"tag\": \"path\",\n                          },\n                        ],\n                        \"tag\": \"svg\",\n                      },\n                      \"name\": \"down\",\n                      \"theme\": \"outlined\",\n                    }\n                  }\n                >\n                  <span\n                    aria-label=\"down\"\n                    className=\"anticon anticon-down ant-select-suffix\"\n                    role=\"img\"\n                  >\n                    <IconReact\n                      icon={\n                        {\n                          \"icon\": {\n                            \"attrs\": {\n                              \"focusable\": \"false\",\n                              \"viewBox\": \"64 64 896 896\",\n                            },\n                            \"children\": [\n                              {\n                                \"attrs\": {\n                                  \"d\": \"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\",\n                                },\n                                \"tag\": \"path\",\n                              },\n                            ],\n                            \"tag\": \"svg\",\n                          },\n                          \"name\": \"down\",\n                          \"theme\": \"outlined\",\n                        }\n                      }\n                    >\n                      <svg\n                        aria-hidden=\"true\"\n                        data-icon=\"down\"\n                        fill=\"currentColor\"\n                        focusable=\"false\"\n                        height=\"1em\"\n                        key=\"svg-down\"\n                        viewBox=\"64 64 896 896\"\n                        width=\"1em\"\n                      >\n                        <path\n                          d=\"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\"\n                          key=\"svg-down-svg-0\"\n                        />\n                      </svg>\n                    </IconReact>\n                  </span>\n                </AntdIcon>\n              </DownOutlined>\n            </span>\n          </TransBtn>\n        </div>\n      </ForwardRef(Select)>\n    </Select>\n  </Select>\n</div>\n`;\n\nexports[`ScheduleDialog Sets correct schedule settings Sets to \"1 Day 22:15\" Sets to correct time 1`] = `\n<div\n  data-testid=\"time\"\n>\n  <TimeEditor\n    defaultValue={\"1999-12-31T22:15:00.000Z\"}\n    onChange={[Function]}\n  >\n    <TimePicker\n      allowClear={false}\n      format=\"HH:mm\"\n      minuteStep={5}\n      onChange={[Function]}\n      value={\"1999-12-31T22:15:00.000Z\"}\n    >\n      <TimePicker\n        allowClear={false}\n        format=\"HH:mm\"\n        minuteStep={5}\n        onChange={[Function]}\n        value={\"1999-12-31T22:15:00.000Z\"}\n      >\n        <LocaleReceiver\n          componentName=\"DatePicker\"\n          defaultLocale={[Function]}\n        >\n          <Picker\n            allowClear={false}\n            className=\"\"\n            clearIcon={<CloseCircleFilled />}\n            components={\n              {\n                \"button\": [Function],\n                \"rangeItem\": [Function],\n              }\n            }\n            format=\"HH:mm\"\n            generateConfig={\n              {\n                \"addDate\": [Function],\n                \"addMonth\": [Function],\n                \"addYear\": [Function],\n                \"getDate\": [Function],\n                \"getHour\": [Function],\n                \"getMinute\": [Function],\n                \"getMonth\": [Function],\n                \"getNow\": [Function],\n                \"getSecond\": [Function],\n                \"getWeekDay\": [Function],\n                \"getYear\": [Function],\n                \"isAfter\": [Function],\n                \"isValidate\": [Function],\n                \"locale\": {\n                  \"format\": [Function],\n                  \"getShortMonths\": [Function],\n                  \"getShortWeekDays\": [Function],\n                  \"getWeek\": [Function],\n                  \"getWeekFirstDay\": [Function],\n                  \"parse\": [Function],\n                },\n                \"setDate\": [Function],\n                \"setHour\": [Function],\n                \"setMinute\": [Function],\n                \"setMonth\": [Function],\n                \"setSecond\": [Function],\n                \"setYear\": [Function],\n              }\n            }\n            locale={\n              {\n                \"backToToday\": \"Back to today\",\n                \"clear\": \"Clear\",\n                \"dateFormat\": \"M/D/YYYY\",\n                \"dateSelect\": \"select date\",\n                \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                \"dayFormat\": \"D\",\n                \"decadeSelect\": \"Choose a decade\",\n                \"locale\": \"en_US\",\n                \"month\": \"Month\",\n                \"monthBeforeYear\": true,\n                \"monthPlaceholder\": \"Select month\",\n                \"monthSelect\": \"Choose a month\",\n                \"nextCentury\": \"Next century\",\n                \"nextDecade\": \"Next decade\",\n                \"nextMonth\": \"Next month (PageDown)\",\n                \"nextYear\": \"Next year (Control + right)\",\n                \"now\": \"Now\",\n                \"ok\": \"Ok\",\n                \"placeholder\": \"Select date\",\n                \"previousCentury\": \"Last century\",\n                \"previousDecade\": \"Last decade\",\n                \"previousMonth\": \"Previous month (PageUp)\",\n                \"previousYear\": \"Last year (Control + left)\",\n                \"quarterPlaceholder\": \"Select quarter\",\n                \"rangeMonthPlaceholder\": [\n                  \"Start month\",\n                  \"End month\",\n                ],\n                \"rangePlaceholder\": [\n                  \"Start date\",\n                  \"End date\",\n                ],\n                \"rangeWeekPlaceholder\": [\n                  \"Start week\",\n                  \"End week\",\n                ],\n                \"rangeYearPlaceholder\": [\n                  \"Start year\",\n                  \"End year\",\n                ],\n                \"timeSelect\": \"select time\",\n                \"today\": \"Today\",\n                \"weekPlaceholder\": \"Select week\",\n                \"weekSelect\": \"Choose a week\",\n                \"year\": \"Year\",\n                \"yearFormat\": \"YYYY\",\n                \"yearPlaceholder\": \"Select year\",\n                \"yearSelect\": \"Choose a year\",\n              }\n            }\n            minuteStep={5}\n            nextIcon={\n              <span\n                className=\"ant-picker-next-icon\"\n              />\n            }\n            onChange={[Function]}\n            picker=\"time\"\n            placeholder=\"Select time\"\n            prefixCls=\"ant-picker\"\n            prevIcon={\n              <span\n                className=\"ant-picker-prev-icon\"\n              />\n            }\n            showSecond={false}\n            showToday={true}\n            suffixIcon={<ClockCircleOutlined />}\n            superNextIcon={\n              <span\n                className=\"ant-picker-super-next-icon\"\n              />\n            }\n            superPrevIcon={\n              <span\n                className=\"ant-picker-super-prev-icon\"\n              />\n            }\n            transitionName=\"slide-up\"\n            value={\"1999-12-31T22:15:00.000Z\"}\n          >\n            <InnerPicker\n              allowClear={false}\n              className=\"\"\n              clearIcon={<CloseCircleFilled />}\n              components={\n                {\n                  \"button\": [Function],\n                  \"rangeItem\": [Function],\n                }\n              }\n              format=\"HH:mm\"\n              generateConfig={\n                {\n                  \"addDate\": [Function],\n                  \"addMonth\": [Function],\n                  \"addYear\": [Function],\n                  \"getDate\": [Function],\n                  \"getHour\": [Function],\n                  \"getMinute\": [Function],\n                  \"getMonth\": [Function],\n                  \"getNow\": [Function],\n                  \"getSecond\": [Function],\n                  \"getWeekDay\": [Function],\n                  \"getYear\": [Function],\n                  \"isAfter\": [Function],\n                  \"isValidate\": [Function],\n                  \"locale\": {\n                    \"format\": [Function],\n                    \"getShortMonths\": [Function],\n                    \"getShortWeekDays\": [Function],\n                    \"getWeek\": [Function],\n                    \"getWeekFirstDay\": [Function],\n                    \"parse\": [Function],\n                  },\n                  \"setDate\": [Function],\n                  \"setHour\": [Function],\n                  \"setMinute\": [Function],\n                  \"setMonth\": [Function],\n                  \"setSecond\": [Function],\n                  \"setYear\": [Function],\n                }\n              }\n              locale={\n                {\n                  \"backToToday\": \"Back to today\",\n                  \"clear\": \"Clear\",\n                  \"dateFormat\": \"M/D/YYYY\",\n                  \"dateSelect\": \"select date\",\n                  \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                  \"dayFormat\": \"D\",\n                  \"decadeSelect\": \"Choose a decade\",\n                  \"locale\": \"en_US\",\n                  \"month\": \"Month\",\n                  \"monthBeforeYear\": true,\n                  \"monthPlaceholder\": \"Select month\",\n                  \"monthSelect\": \"Choose a month\",\n                  \"nextCentury\": \"Next century\",\n                  \"nextDecade\": \"Next decade\",\n                  \"nextMonth\": \"Next month (PageDown)\",\n                  \"nextYear\": \"Next year (Control + right)\",\n                  \"now\": \"Now\",\n                  \"ok\": \"Ok\",\n                  \"placeholder\": \"Select date\",\n                  \"previousCentury\": \"Last century\",\n                  \"previousDecade\": \"Last decade\",\n                  \"previousMonth\": \"Previous month (PageUp)\",\n                  \"previousYear\": \"Last year (Control + left)\",\n                  \"quarterPlaceholder\": \"Select quarter\",\n                  \"rangeMonthPlaceholder\": [\n                    \"Start month\",\n                    \"End month\",\n                  ],\n                  \"rangePlaceholder\": [\n                    \"Start date\",\n                    \"End date\",\n                  ],\n                  \"rangeWeekPlaceholder\": [\n                    \"Start week\",\n                    \"End week\",\n                  ],\n                  \"rangeYearPlaceholder\": [\n                    \"Start year\",\n                    \"End year\",\n                  ],\n                  \"timeSelect\": \"select time\",\n                  \"today\": \"Today\",\n                  \"weekPlaceholder\": \"Select week\",\n                  \"weekSelect\": \"Choose a week\",\n                  \"year\": \"Year\",\n                  \"yearFormat\": \"YYYY\",\n                  \"yearPlaceholder\": \"Select year\",\n                  \"yearSelect\": \"Choose a year\",\n                }\n              }\n              minuteStep={5}\n              nextIcon={\n                <span\n                  className=\"ant-picker-next-icon\"\n                />\n              }\n              onChange={[Function]}\n              picker=\"time\"\n              pickerRef={\n                {\n                  \"current\": {\n                    \"blur\": [Function],\n                    \"focus\": [Function],\n                  },\n                }\n              }\n              placeholder=\"Select time\"\n              prefixCls=\"ant-picker\"\n              prevIcon={\n                <span\n                  className=\"ant-picker-prev-icon\"\n                />\n              }\n              showSecond={false}\n              showToday={true}\n              suffixIcon={<ClockCircleOutlined />}\n              superNextIcon={\n                <span\n                  className=\"ant-picker-super-next-icon\"\n                />\n              }\n              superPrevIcon={\n                <span\n                  className=\"ant-picker-super-prev-icon\"\n                />\n              }\n              transitionName=\"slide-up\"\n              value={\"1999-12-31T22:15:00.000Z\"}\n            >\n              <PickerTrigger\n                popupElement={\n                  <div\n                    className=\"ant-picker-panel-container\"\n                    onMouseDown={[Function]}\n                  >\n                    <PickerPanel\n                      allowClear={false}\n                      className=\"ant-picker-panel-focused\"\n                      clearIcon={<CloseCircleFilled />}\n                      components={\n                        {\n                          \"button\": [Function],\n                          \"rangeItem\": [Function],\n                        }\n                      }\n                      format=\"HH:mm\"\n                      generateConfig={\n                        {\n                          \"addDate\": [Function],\n                          \"addMonth\": [Function],\n                          \"addYear\": [Function],\n                          \"getDate\": [Function],\n                          \"getHour\": [Function],\n                          \"getMinute\": [Function],\n                          \"getMonth\": [Function],\n                          \"getNow\": [Function],\n                          \"getSecond\": [Function],\n                          \"getWeekDay\": [Function],\n                          \"getYear\": [Function],\n                          \"isAfter\": [Function],\n                          \"isValidate\": [Function],\n                          \"locale\": {\n                            \"format\": [Function],\n                            \"getShortMonths\": [Function],\n                            \"getShortWeekDays\": [Function],\n                            \"getWeek\": [Function],\n                            \"getWeekFirstDay\": [Function],\n                            \"parse\": [Function],\n                          },\n                          \"setDate\": [Function],\n                          \"setHour\": [Function],\n                          \"setMinute\": [Function],\n                          \"setMonth\": [Function],\n                          \"setSecond\": [Function],\n                          \"setYear\": [Function],\n                        }\n                      }\n                      locale={\n                        {\n                          \"backToToday\": \"Back to today\",\n                          \"clear\": \"Clear\",\n                          \"dateFormat\": \"M/D/YYYY\",\n                          \"dateSelect\": \"select date\",\n                          \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                          \"dayFormat\": \"D\",\n                          \"decadeSelect\": \"Choose a decade\",\n                          \"locale\": \"en_US\",\n                          \"month\": \"Month\",\n                          \"monthBeforeYear\": true,\n                          \"monthPlaceholder\": \"Select month\",\n                          \"monthSelect\": \"Choose a month\",\n                          \"nextCentury\": \"Next century\",\n                          \"nextDecade\": \"Next decade\",\n                          \"nextMonth\": \"Next month (PageDown)\",\n                          \"nextYear\": \"Next year (Control + right)\",\n                          \"now\": \"Now\",\n                          \"ok\": \"Ok\",\n                          \"placeholder\": \"Select date\",\n                          \"previousCentury\": \"Last century\",\n                          \"previousDecade\": \"Last decade\",\n                          \"previousMonth\": \"Previous month (PageUp)\",\n                          \"previousYear\": \"Last year (Control + left)\",\n                          \"quarterPlaceholder\": \"Select quarter\",\n                          \"rangeMonthPlaceholder\": [\n                            \"Start month\",\n                            \"End month\",\n                          ],\n                          \"rangePlaceholder\": [\n                            \"Start date\",\n                            \"End date\",\n                          ],\n                          \"rangeWeekPlaceholder\": [\n                            \"Start week\",\n                            \"End week\",\n                          ],\n                          \"rangeYearPlaceholder\": [\n                            \"Start year\",\n                            \"End year\",\n                          ],\n                          \"timeSelect\": \"select time\",\n                          \"today\": \"Today\",\n                          \"weekPlaceholder\": \"Select week\",\n                          \"weekSelect\": \"Choose a week\",\n                          \"year\": \"Year\",\n                          \"yearFormat\": \"YYYY\",\n                          \"yearPlaceholder\": \"Select year\",\n                          \"yearSelect\": \"Choose a year\",\n                        }\n                      }\n                      minuteStep={5}\n                      nextIcon={\n                        <span\n                          className=\"ant-picker-next-icon\"\n                        />\n                      }\n                      onChange={[Function]}\n                      picker=\"time\"\n                      pickerRef={\n                        {\n                          \"current\": {\n                            \"blur\": [Function],\n                            \"focus\": [Function],\n                          },\n                        }\n                      }\n                      placeholder=\"Select time\"\n                      prefixCls=\"ant-picker\"\n                      prevIcon={\n                        <span\n                          className=\"ant-picker-prev-icon\"\n                        />\n                      }\n                      showSecond={false}\n                      showToday={true}\n                      suffixIcon={<ClockCircleOutlined />}\n                      superNextIcon={\n                        <span\n                          className=\"ant-picker-super-next-icon\"\n                        />\n                      }\n                      superPrevIcon={\n                        <span\n                          className=\"ant-picker-super-prev-icon\"\n                        />\n                      }\n                      tabIndex={-1}\n                      transitionName=\"slide-up\"\n                      value={\"1999-12-31T22:15:00.000Z\"}\n                    />\n                  </div>\n                }\n                popupPlacement=\"bottomLeft\"\n                prefixCls=\"ant-picker\"\n                transitionName=\"slide-up\"\n                visible={false}\n              >\n                <Trigger\n                  action={[]}\n                  afterPopupVisibleChange={[Function]}\n                  autoDestroy={false}\n                  blurDelay={0.15}\n                  builtinPlacements={\n                    {\n                      \"bottomLeft\": {\n                        \"offset\": [\n                          0,\n                          4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 1,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"tl\",\n                          \"bl\",\n                        ],\n                      },\n                      \"bottomRight\": {\n                        \"offset\": [\n                          0,\n                          4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 1,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"tr\",\n                          \"br\",\n                        ],\n                      },\n                      \"topLeft\": {\n                        \"offset\": [\n                          0,\n                          -4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 0,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"bl\",\n                          \"tl\",\n                        ],\n                      },\n                      \"topRight\": {\n                        \"offset\": [\n                          0,\n                          -4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 0,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"br\",\n                          \"tr\",\n                        ],\n                      },\n                    }\n                  }\n                  defaultPopupVisible={false}\n                  destroyPopupOnHide={false}\n                  focusDelay={0}\n                  getDocument={[Function]}\n                  getPopupClassNameFromAlign={[Function]}\n                  hideAction={[]}\n                  mask={false}\n                  maskClosable={true}\n                  mouseEnterDelay={0}\n                  mouseLeaveDelay={0.1}\n                  onPopupAlign={[Function]}\n                  onPopupVisibleChange={[Function]}\n                  popup={\n                    <div\n                      className=\"ant-picker-panel-container\"\n                      onMouseDown={[Function]}\n                    >\n                      <PickerPanel\n                        allowClear={false}\n                        className=\"ant-picker-panel-focused\"\n                        clearIcon={<CloseCircleFilled />}\n                        components={\n                          {\n                            \"button\": [Function],\n                            \"rangeItem\": [Function],\n                          }\n                        }\n                        format=\"HH:mm\"\n                        generateConfig={\n                          {\n                            \"addDate\": [Function],\n                            \"addMonth\": [Function],\n                            \"addYear\": [Function],\n                            \"getDate\": [Function],\n                            \"getHour\": [Function],\n                            \"getMinute\": [Function],\n                            \"getMonth\": [Function],\n                            \"getNow\": [Function],\n                            \"getSecond\": [Function],\n                            \"getWeekDay\": [Function],\n                            \"getYear\": [Function],\n                            \"isAfter\": [Function],\n                            \"isValidate\": [Function],\n                            \"locale\": {\n                              \"format\": [Function],\n                              \"getShortMonths\": [Function],\n                              \"getShortWeekDays\": [Function],\n                              \"getWeek\": [Function],\n                              \"getWeekFirstDay\": [Function],\n                              \"parse\": [Function],\n                            },\n                            \"setDate\": [Function],\n                            \"setHour\": [Function],\n                            \"setMinute\": [Function],\n                            \"setMonth\": [Function],\n                            \"setSecond\": [Function],\n                            \"setYear\": [Function],\n                          }\n                        }\n                        locale={\n                          {\n                            \"backToToday\": \"Back to today\",\n                            \"clear\": \"Clear\",\n                            \"dateFormat\": \"M/D/YYYY\",\n                            \"dateSelect\": \"select date\",\n                            \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                            \"dayFormat\": \"D\",\n                            \"decadeSelect\": \"Choose a decade\",\n                            \"locale\": \"en_US\",\n                            \"month\": \"Month\",\n                            \"monthBeforeYear\": true,\n                            \"monthPlaceholder\": \"Select month\",\n                            \"monthSelect\": \"Choose a month\",\n                            \"nextCentury\": \"Next century\",\n                            \"nextDecade\": \"Next decade\",\n                            \"nextMonth\": \"Next month (PageDown)\",\n                            \"nextYear\": \"Next year (Control + right)\",\n                            \"now\": \"Now\",\n                            \"ok\": \"Ok\",\n                            \"placeholder\": \"Select date\",\n                            \"previousCentury\": \"Last century\",\n                            \"previousDecade\": \"Last decade\",\n                            \"previousMonth\": \"Previous month (PageUp)\",\n                            \"previousYear\": \"Last year (Control + left)\",\n                            \"quarterPlaceholder\": \"Select quarter\",\n                            \"rangeMonthPlaceholder\": [\n                              \"Start month\",\n                              \"End month\",\n                            ],\n                            \"rangePlaceholder\": [\n                              \"Start date\",\n                              \"End date\",\n                            ],\n                            \"rangeWeekPlaceholder\": [\n                              \"Start week\",\n                              \"End week\",\n                            ],\n                            \"rangeYearPlaceholder\": [\n                              \"Start year\",\n                              \"End year\",\n                            ],\n                            \"timeSelect\": \"select time\",\n                            \"today\": \"Today\",\n                            \"weekPlaceholder\": \"Select week\",\n                            \"weekSelect\": \"Choose a week\",\n                            \"year\": \"Year\",\n                            \"yearFormat\": \"YYYY\",\n                            \"yearPlaceholder\": \"Select year\",\n                            \"yearSelect\": \"Choose a year\",\n                          }\n                        }\n                        minuteStep={5}\n                        nextIcon={\n                          <span\n                            className=\"ant-picker-next-icon\"\n                          />\n                        }\n                        onChange={[Function]}\n                        picker=\"time\"\n                        pickerRef={\n                          {\n                            \"current\": {\n                              \"blur\": [Function],\n                              \"focus\": [Function],\n                            },\n                          }\n                        }\n                        placeholder=\"Select time\"\n                        prefixCls=\"ant-picker\"\n                        prevIcon={\n                          <span\n                            className=\"ant-picker-prev-icon\"\n                          />\n                        }\n                        showSecond={false}\n                        showToday={true}\n                        suffixIcon={<ClockCircleOutlined />}\n                        superNextIcon={\n                          <span\n                            className=\"ant-picker-super-next-icon\"\n                          />\n                        }\n                        superPrevIcon={\n                          <span\n                            className=\"ant-picker-super-prev-icon\"\n                          />\n                        }\n                        tabIndex={-1}\n                        transitionName=\"slide-up\"\n                        value={\"1999-12-31T22:15:00.000Z\"}\n                      />\n                    </div>\n                  }\n                  popupAlign={{}}\n                  popupClassName=\"\"\n                  popupPlacement=\"bottomLeft\"\n                  popupStyle={{}}\n                  popupTransitionName=\"slide-up\"\n                  popupVisible={false}\n                  prefixCls=\"ant-picker-dropdown\"\n                  showAction={[]}\n                >\n                  <div\n                    className=\"ant-picker\"\n                    key=\"trigger\"\n                    onMouseUp={[Function]}\n                  >\n                    <div\n                      className=\"ant-picker-input\"\n                    >\n                      <input\n                        autoComplete=\"off\"\n                        onBlur={[Function]}\n                        onChange={[Function]}\n                        onFocus={[Function]}\n                        onKeyDown={[Function]}\n                        onMouseDown={[Function]}\n                        placeholder=\"Select time\"\n                        readOnly={true}\n                        size={10}\n                        title=\"00:15\"\n                        value=\"00:15\"\n                      />\n                      <span\n                        className=\"ant-picker-suffix\"\n                      >\n                        <ClockCircleOutlined>\n                          <AntdIcon\n                            icon={\n                              {\n                                \"icon\": {\n                                  \"attrs\": {\n                                    \"focusable\": \"false\",\n                                    \"viewBox\": \"64 64 896 896\",\n                                  },\n                                  \"children\": [\n                                    {\n                                      \"attrs\": {\n                                        \"d\": \"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\n                                      },\n                                      \"tag\": \"path\",\n                                    },\n                                    {\n                                      \"attrs\": {\n                                        \"d\": \"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z\",\n                                      },\n                                      \"tag\": \"path\",\n                                    },\n                                  ],\n                                  \"tag\": \"svg\",\n                                },\n                                \"name\": \"clock-circle\",\n                                \"theme\": \"outlined\",\n                              }\n                            }\n                          >\n                            <span\n                              aria-label=\"clock-circle\"\n                              className=\"anticon anticon-clock-circle\"\n                              role=\"img\"\n                            >\n                              <IconReact\n                                icon={\n                                  {\n                                    \"icon\": {\n                                      \"attrs\": {\n                                        \"focusable\": \"false\",\n                                        \"viewBox\": \"64 64 896 896\",\n                                      },\n                                      \"children\": [\n                                        {\n                                          \"attrs\": {\n                                            \"d\": \"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\n                                          },\n                                          \"tag\": \"path\",\n                                        },\n                                        {\n                                          \"attrs\": {\n                                            \"d\": \"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z\",\n                                          },\n                                          \"tag\": \"path\",\n                                        },\n                                      ],\n                                      \"tag\": \"svg\",\n                                    },\n                                    \"name\": \"clock-circle\",\n                                    \"theme\": \"outlined\",\n                                  }\n                                }\n                              >\n                                <svg\n                                  aria-hidden=\"true\"\n                                  data-icon=\"clock-circle\"\n                                  fill=\"currentColor\"\n                                  focusable=\"false\"\n                                  height=\"1em\"\n                                  key=\"svg-clock-circle\"\n                                  viewBox=\"64 64 896 896\"\n                                  width=\"1em\"\n                                >\n                                  <path\n                                    d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"\n                                    key=\"svg-clock-circle-svg-0\"\n                                  />\n                                  <path\n                                    d=\"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z\"\n                                    key=\"svg-clock-circle-svg-1\"\n                                  />\n                                </svg>\n                              </IconReact>\n                            </span>\n                          </AntdIcon>\n                        </ClockCircleOutlined>\n                      </span>\n                    </div>\n                  </div>\n                </Trigger>\n              </PickerTrigger>\n            </InnerPicker>\n          </Picker>\n        </LocaleReceiver>\n      </TimePicker>\n    </TimePicker>\n    <span\n      className=\"utc\"\n      data-testid=\"utc\"\n    >\n      (\n      22:15\n       UTC)\n    </span>\n  </TimeEditor>\n</div>\n`;\n\nexports[`ScheduleDialog Sets correct schedule settings Sets to \"2 Hours\" 1`] = `\n<div\n  data-testid=\"interval\"\n>\n  <Select\n    bordered={true}\n    choiceTransitionName=\"zoom\"\n    className=\"input\"\n    dropdownMatchSelectWidth={false}\n    onChange={[Function]}\n    transitionName=\"slide-up\"\n    value={7200}\n  >\n    <Select\n      choiceTransitionName=\"zoom\"\n      className=\"input\"\n      clearIcon={<CloseCircleFilled />}\n      dropdownClassName=\"\"\n      dropdownMatchSelectWidth={false}\n      inputIcon={[Function]}\n      listHeight={256}\n      listItemHeight={24}\n      menuItemSelectedIcon={null}\n      notFoundContent={\n        <Context.Consumer>\n          [Function]\n        </Context.Consumer>\n      }\n      onChange={[Function]}\n      prefixCls=\"ant-select\"\n      removeIcon={<CloseOutlined />}\n      transitionName=\"slide-up\"\n      value={7200}\n    >\n      <ForwardRef(Select)\n        choiceTransitionName=\"zoom\"\n        className=\"input\"\n        clearIcon={<CloseCircleFilled />}\n        dropdownClassName=\"\"\n        dropdownMatchSelectWidth={false}\n        inputIcon={[Function]}\n        listHeight={256}\n        listItemHeight={24}\n        menuItemSelectedIcon={null}\n        notFoundContent={\n          <Context.Consumer>\n            [Function]\n          </Context.Consumer>\n        }\n        onChange={[Function]}\n        prefixCls=\"ant-select\"\n        removeIcon={<CloseOutlined />}\n        transitionName=\"slide-up\"\n        value={7200}\n      >\n        <div\n          className=\"ant-select input ant-select-single ant-select-show-arrow\"\n          onBlur={[Function]}\n          onFocus={[Function]}\n          onKeyDown={[Function]}\n          onKeyUp={[Function]}\n          onMouseDown={[Function]}\n        >\n          <SelectTrigger\n            containerWidth={null}\n            dropdownClassName=\"\"\n            dropdownMatchSelectWidth={false}\n            empty={false}\n            getTriggerDOMNode={[Function]}\n            popupElement={\n              <OptionList\n                childrenAsData={true}\n                defaultActiveFirstOption={true}\n                flattenOptions={\n                  [\n                    {\n                      \"data\": {\n                        \"children\": \"Never\",\n                        \"key\": \"never\",\n                        \"value\": null,\n                      },\n                      \"groupOption\": false,\n                      \"key\": \"never\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__minute__\",\n                        \"label\": \"Minutes\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 minute\",\n                            \"key\": \"minute-1\",\n                            \"value\": 60,\n                          },\n                          {\n                            \"children\": \"5 minutes\",\n                            \"key\": \"minute-5\",\n                            \"value\": 300,\n                          },\n                          {\n                            \"children\": \"10 minutes\",\n                            \"key\": \"minute-10\",\n                            \"value\": 600,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__minute__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 minute\",\n                        \"key\": \"minute-1\",\n                        \"value\": 60,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"5 minutes\",\n                        \"key\": \"minute-5\",\n                        \"value\": 300,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-5\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"10 minutes\",\n                        \"key\": \"minute-10\",\n                        \"value\": 600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-10\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__hour__\",\n                        \"label\": \"Hours\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 hour\",\n                            \"key\": \"hour-1\",\n                            \"value\": 3600,\n                          },\n                          {\n                            \"children\": \"10 hours\",\n                            \"key\": \"hour-10\",\n                            \"value\": 36000,\n                          },\n                          {\n                            \"children\": \"23 hours\",\n                            \"key\": \"hour-23\",\n                            \"value\": 82800,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__hour__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 hour\",\n                        \"key\": \"hour-1\",\n                        \"value\": 3600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"10 hours\",\n                        \"key\": \"hour-10\",\n                        \"value\": 36000,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-10\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"23 hours\",\n                        \"key\": \"hour-23\",\n                        \"value\": 82800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-23\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__day__\",\n                        \"label\": \"Days\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 day\",\n                            \"key\": \"day-1\",\n                            \"value\": 86400,\n                          },\n                          {\n                            \"children\": \"2 days\",\n                            \"key\": \"day-2\",\n                            \"value\": 172800,\n                          },\n                          {\n                            \"children\": \"6 days\",\n                            \"key\": \"day-6\",\n                            \"value\": 518400,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__day__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 day\",\n                        \"key\": \"day-1\",\n                        \"value\": 86400,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"2 days\",\n                        \"key\": \"day-2\",\n                        \"value\": 172800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-2\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"6 days\",\n                        \"key\": \"day-6\",\n                        \"value\": 518400,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-6\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__week__\",\n                        \"label\": \"Weeks\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 week\",\n                            \"key\": \"week-1\",\n                            \"value\": 604800,\n                          },\n                          {\n                            \"children\": \"2 weeks\",\n                            \"key\": \"week-2\",\n                            \"value\": 1209600,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__week__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 week\",\n                        \"key\": \"week-1\",\n                        \"value\": 604800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"week-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"2 weeks\",\n                        \"key\": \"week-2\",\n                        \"value\": 1209600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"week-2\",\n                    },\n                  ]\n                }\n                height={256}\n                id=\"rc_select_TEST_OR_SSR\"\n                itemHeight={24}\n                menuItemSelectedIcon={null}\n                multiple={false}\n                notFoundContent={\n                  <Context.Consumer>\n                    [Function]\n                  </Context.Consumer>\n                }\n                onActiveValue={[Function]}\n                onMouseEnter={[Function]}\n                onSelect={[Function]}\n                onToggleOpen={[Function]}\n                options={\n                  [\n                    {\n                      \"children\": \"Never\",\n                      \"key\": \"never\",\n                      \"value\": null,\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__minute__\",\n                      \"label\": \"Minutes\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 minute\",\n                          \"key\": \"minute-1\",\n                          \"value\": 60,\n                        },\n                        {\n                          \"children\": \"5 minutes\",\n                          \"key\": \"minute-5\",\n                          \"value\": 300,\n                        },\n                        {\n                          \"children\": \"10 minutes\",\n                          \"key\": \"minute-10\",\n                          \"value\": 600,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__hour__\",\n                      \"label\": \"Hours\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 hour\",\n                          \"key\": \"hour-1\",\n                          \"value\": 3600,\n                        },\n                        {\n                          \"children\": \"10 hours\",\n                          \"key\": \"hour-10\",\n                          \"value\": 36000,\n                        },\n                        {\n                          \"children\": \"23 hours\",\n                          \"key\": \"hour-23\",\n                          \"value\": 82800,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__day__\",\n                      \"label\": \"Days\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 day\",\n                          \"key\": \"day-1\",\n                          \"value\": 86400,\n                        },\n                        {\n                          \"children\": \"2 days\",\n                          \"key\": \"day-2\",\n                          \"value\": 172800,\n                        },\n                        {\n                          \"children\": \"6 days\",\n                          \"key\": \"day-6\",\n                          \"value\": 518400,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__week__\",\n                      \"label\": \"Weeks\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 week\",\n                          \"key\": \"week-1\",\n                          \"value\": 604800,\n                        },\n                        {\n                          \"children\": \"2 weeks\",\n                          \"key\": \"week-2\",\n                          \"value\": 1209600,\n                        },\n                      ],\n                    },\n                  ]\n                }\n                prefixCls=\"ant-select\"\n                searchValue=\"\"\n                values={\n                  Set {\n                    7200,\n                  }\n                }\n                virtual={false}\n              />\n            }\n            prefixCls=\"ant-select\"\n            transitionName=\"slide-up\"\n          >\n            <Trigger\n              action={[]}\n              afterPopupVisibleChange={[Function]}\n              autoDestroy={false}\n              blurDelay={0.15}\n              builtinPlacements={\n                {\n                  \"bottomLeft\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tl\",\n                      \"bl\",\n                    ],\n                  },\n                  \"bottomRight\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tr\",\n                      \"br\",\n                    ],\n                  },\n                  \"topLeft\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"bl\",\n                      \"tl\",\n                    ],\n                  },\n                  \"topRight\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"br\",\n                      \"tr\",\n                    ],\n                  },\n                }\n              }\n              defaultPopupVisible={false}\n              destroyPopupOnHide={false}\n              focusDelay={0}\n              getDocument={[Function]}\n              getPopupClassNameFromAlign={[Function]}\n              getTriggerDOMNode={[Function]}\n              hideAction={[]}\n              mask={false}\n              maskClosable={true}\n              mouseEnterDelay={0}\n              mouseLeaveDelay={0.1}\n              onPopupAlign={[Function]}\n              onPopupVisibleChange={[Function]}\n              popup={\n                <div>\n                  <OptionList\n                    childrenAsData={true}\n                    defaultActiveFirstOption={true}\n                    flattenOptions={\n                      [\n                        {\n                          \"data\": {\n                            \"children\": \"Never\",\n                            \"key\": \"never\",\n                            \"value\": null,\n                          },\n                          \"groupOption\": false,\n                          \"key\": \"never\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__minute__\",\n                            \"label\": \"Minutes\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 minute\",\n                                \"key\": \"minute-1\",\n                                \"value\": 60,\n                              },\n                              {\n                                \"children\": \"5 minutes\",\n                                \"key\": \"minute-5\",\n                                \"value\": 300,\n                              },\n                              {\n                                \"children\": \"10 minutes\",\n                                \"key\": \"minute-10\",\n                                \"value\": 600,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__minute__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 minute\",\n                            \"key\": \"minute-1\",\n                            \"value\": 60,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"5 minutes\",\n                            \"key\": \"minute-5\",\n                            \"value\": 300,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-5\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"10 minutes\",\n                            \"key\": \"minute-10\",\n                            \"value\": 600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-10\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__hour__\",\n                            \"label\": \"Hours\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 hour\",\n                                \"key\": \"hour-1\",\n                                \"value\": 3600,\n                              },\n                              {\n                                \"children\": \"10 hours\",\n                                \"key\": \"hour-10\",\n                                \"value\": 36000,\n                              },\n                              {\n                                \"children\": \"23 hours\",\n                                \"key\": \"hour-23\",\n                                \"value\": 82800,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__hour__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 hour\",\n                            \"key\": \"hour-1\",\n                            \"value\": 3600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"10 hours\",\n                            \"key\": \"hour-10\",\n                            \"value\": 36000,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-10\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"23 hours\",\n                            \"key\": \"hour-23\",\n                            \"value\": 82800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-23\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__day__\",\n                            \"label\": \"Days\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 day\",\n                                \"key\": \"day-1\",\n                                \"value\": 86400,\n                              },\n                              {\n                                \"children\": \"2 days\",\n                                \"key\": \"day-2\",\n                                \"value\": 172800,\n                              },\n                              {\n                                \"children\": \"6 days\",\n                                \"key\": \"day-6\",\n                                \"value\": 518400,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__day__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 day\",\n                            \"key\": \"day-1\",\n                            \"value\": 86400,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"2 days\",\n                            \"key\": \"day-2\",\n                            \"value\": 172800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-2\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"6 days\",\n                            \"key\": \"day-6\",\n                            \"value\": 518400,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-6\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__week__\",\n                            \"label\": \"Weeks\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 week\",\n                                \"key\": \"week-1\",\n                                \"value\": 604800,\n                              },\n                              {\n                                \"children\": \"2 weeks\",\n                                \"key\": \"week-2\",\n                                \"value\": 1209600,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__week__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 week\",\n                            \"key\": \"week-1\",\n                            \"value\": 604800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"week-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"2 weeks\",\n                            \"key\": \"week-2\",\n                            \"value\": 1209600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"week-2\",\n                        },\n                      ]\n                    }\n                    height={256}\n                    id=\"rc_select_TEST_OR_SSR\"\n                    itemHeight={24}\n                    menuItemSelectedIcon={null}\n                    multiple={false}\n                    notFoundContent={\n                      <Context.Consumer>\n                        [Function]\n                      </Context.Consumer>\n                    }\n                    onActiveValue={[Function]}\n                    onMouseEnter={[Function]}\n                    onSelect={[Function]}\n                    onToggleOpen={[Function]}\n                    options={\n                      [\n                        {\n                          \"children\": \"Never\",\n                          \"key\": \"never\",\n                          \"value\": null,\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__minute__\",\n                          \"label\": \"Minutes\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 minute\",\n                              \"key\": \"minute-1\",\n                              \"value\": 60,\n                            },\n                            {\n                              \"children\": \"5 minutes\",\n                              \"key\": \"minute-5\",\n                              \"value\": 300,\n                            },\n                            {\n                              \"children\": \"10 minutes\",\n                              \"key\": \"minute-10\",\n                              \"value\": 600,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__hour__\",\n                          \"label\": \"Hours\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 hour\",\n                              \"key\": \"hour-1\",\n                              \"value\": 3600,\n                            },\n                            {\n                              \"children\": \"10 hours\",\n                              \"key\": \"hour-10\",\n                              \"value\": 36000,\n                            },\n                            {\n                              \"children\": \"23 hours\",\n                              \"key\": \"hour-23\",\n                              \"value\": 82800,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__day__\",\n                          \"label\": \"Days\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 day\",\n                              \"key\": \"day-1\",\n                              \"value\": 86400,\n                            },\n                            {\n                              \"children\": \"2 days\",\n                              \"key\": \"day-2\",\n                              \"value\": 172800,\n                            },\n                            {\n                              \"children\": \"6 days\",\n                              \"key\": \"day-6\",\n                              \"value\": 518400,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__week__\",\n                          \"label\": \"Weeks\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 week\",\n                              \"key\": \"week-1\",\n                              \"value\": 604800,\n                            },\n                            {\n                              \"children\": \"2 weeks\",\n                              \"key\": \"week-2\",\n                              \"value\": 1209600,\n                            },\n                          ],\n                        },\n                      ]\n                    }\n                    prefixCls=\"ant-select\"\n                    searchValue=\"\"\n                    values={\n                      Set {\n                        7200,\n                      }\n                    }\n                    virtual={false}\n                  />\n                </div>\n              }\n              popupAlign={{}}\n              popupClassName=\"\"\n              popupPlacement=\"bottomLeft\"\n              popupStyle={\n                {\n                  \"minWidth\": null,\n                }\n              }\n              popupTransitionName=\"slide-up\"\n              prefixCls=\"ant-select-dropdown\"\n              showAction={[]}\n            >\n              <Selector\n                accessibilityIndex={0}\n                activeValue={null}\n                choiceTransitionName=\"zoom\"\n                className=\"input\"\n                clearIcon={<CloseCircleFilled />}\n                domRef={\n                  {\n                    \"current\": <div\n                      class=\"ant-select-selector\"\n                    >\n                      <span\n                        class=\"ant-select-selection-search\"\n                      >\n                        <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autocomplete=\"off\"\n                          class=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          readonly=\"\"\n                          role=\"combobox\"\n                          style=\"opacity: 0;\"\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />\n                      </span>\n                      <span\n                        class=\"ant-select-selection-item\"\n                        title=\"7200\"\n                      >\n                        7200\n                      </span>\n                    </div>,\n                  }\n                }\n                dropdownClassName=\"\"\n                dropdownMatchSelectWidth={false}\n                id=\"rc_select_TEST_OR_SSR\"\n                inputElement={null}\n                inputIcon={[Function]}\n                key=\"trigger\"\n                listHeight={256}\n                listItemHeight={24}\n                menuItemSelectedIcon={null}\n                multiple={false}\n                notFoundContent={\n                  <Context.Consumer>\n                    [Function]\n                  </Context.Consumer>\n                }\n                onChange={[Function]}\n                onSearch={[Function]}\n                onSearchSubmit={[Function]}\n                onSelect={[Function]}\n                onToggleOpen={[Function]}\n                prefixCls=\"ant-select\"\n                removeIcon={<CloseOutlined />}\n                searchValue=\"\"\n                showSearch={false}\n                tokenWithEnter={false}\n                transitionName=\"slide-up\"\n                value={7200}\n                values={\n                  [\n                    {\n                      \"disabled\": undefined,\n                      \"key\": 7200,\n                      \"label\": 7200,\n                      \"value\": 7200,\n                    },\n                  ]\n                }\n              >\n                <div\n                  className=\"ant-select-selector\"\n                  onClick={[Function]}\n                  onMouseDown={[Function]}\n                >\n                  <SingleSelector\n                    accessibilityIndex={0}\n                    activeValue={null}\n                    choiceTransitionName=\"zoom\"\n                    className=\"input\"\n                    clearIcon={<CloseCircleFilled />}\n                    domRef={\n                      {\n                        \"current\": <div\n                          class=\"ant-select-selector\"\n                        >\n                          <span\n                            class=\"ant-select-selection-search\"\n                          >\n                            <input\n                              aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                              aria-autocomplete=\"list\"\n                              aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                              aria-haspopup=\"listbox\"\n                              aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                              autocomplete=\"off\"\n                              class=\"ant-select-selection-search-input\"\n                              id=\"rc_select_TEST_OR_SSR\"\n                              readonly=\"\"\n                              role=\"combobox\"\n                              style=\"opacity: 0;\"\n                              type=\"search\"\n                              unselectable=\"on\"\n                              value=\"\"\n                            />\n                          </span>\n                          <span\n                            class=\"ant-select-selection-item\"\n                            title=\"7200\"\n                          >\n                            7200\n                          </span>\n                        </div>,\n                      }\n                    }\n                    dropdownClassName=\"\"\n                    dropdownMatchSelectWidth={false}\n                    id=\"rc_select_TEST_OR_SSR\"\n                    inputElement={null}\n                    inputIcon={[Function]}\n                    inputRef={\n                      {\n                        \"current\": <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autocomplete=\"off\"\n                          class=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          readonly=\"\"\n                          role=\"combobox\"\n                          style=\"opacity: 0;\"\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />,\n                      }\n                    }\n                    listHeight={256}\n                    listItemHeight={24}\n                    menuItemSelectedIcon={null}\n                    multiple={false}\n                    notFoundContent={\n                      <Context.Consumer>\n                        [Function]\n                      </Context.Consumer>\n                    }\n                    onChange={[Function]}\n                    onInputChange={[Function]}\n                    onInputCompositionEnd={[Function]}\n                    onInputCompositionStart={[Function]}\n                    onInputKeyDown={[Function]}\n                    onInputMouseDown={[Function]}\n                    onInputPaste={[Function]}\n                    onSearch={[Function]}\n                    onSearchSubmit={[Function]}\n                    onSelect={[Function]}\n                    onToggleOpen={[Function]}\n                    prefixCls=\"ant-select\"\n                    removeIcon={<CloseOutlined />}\n                    searchValue=\"\"\n                    showSearch={false}\n                    tokenWithEnter={false}\n                    transitionName=\"slide-up\"\n                    value={7200}\n                    values={\n                      [\n                        {\n                          \"disabled\": undefined,\n                          \"key\": 7200,\n                          \"label\": 7200,\n                          \"value\": 7200,\n                        },\n                      ]\n                    }\n                  >\n                    <span\n                      className=\"ant-select-selection-search\"\n                    >\n                      <Input\n                        accessibilityIndex={0}\n                        attrs={{}}\n                        editable={false}\n                        id=\"rc_select_TEST_OR_SSR\"\n                        inputElement={null}\n                        onChange={[Function]}\n                        onCompositionEnd={[Function]}\n                        onCompositionStart={[Function]}\n                        onKeyDown={[Function]}\n                        onMouseDown={[Function]}\n                        onPaste={[Function]}\n                        prefixCls=\"ant-select\"\n                        value=\"\"\n                      >\n                        <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autoComplete=\"off\"\n                          className=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          onChange={[Function]}\n                          onCompositionEnd={[Function]}\n                          onCompositionStart={[Function]}\n                          onKeyDown={[Function]}\n                          onMouseDown={[Function]}\n                          onPaste={[Function]}\n                          readOnly={true}\n                          role=\"combobox\"\n                          style={\n                            {\n                              \"opacity\": 0,\n                            }\n                          }\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />\n                      </Input>\n                    </span>\n                    <span\n                      className=\"ant-select-selection-item\"\n                      title=\"7200\"\n                    >\n                      7200\n                    </span>\n                  </SingleSelector>\n                </div>\n              </Selector>\n            </Trigger>\n          </SelectTrigger>\n          <TransBtn\n            className=\"ant-select-arrow\"\n            customizeIcon={[Function]}\n            customizeIconProps={\n              {\n                \"focused\": false,\n                \"loading\": undefined,\n                \"open\": undefined,\n                \"searchValue\": \"\",\n                \"showSearch\": false,\n              }\n            }\n          >\n            <span\n              aria-hidden={true}\n              className=\"ant-select-arrow\"\n              onMouseDown={[Function]}\n              style={\n                {\n                  \"WebkitUserSelect\": \"none\",\n                  \"userSelect\": \"none\",\n                }\n              }\n              unselectable=\"on\"\n            >\n              <DownOutlined\n                className=\"ant-select-suffix\"\n              >\n                <AntdIcon\n                  className=\"ant-select-suffix\"\n                  icon={\n                    {\n                      \"icon\": {\n                        \"attrs\": {\n                          \"focusable\": \"false\",\n                          \"viewBox\": \"64 64 896 896\",\n                        },\n                        \"children\": [\n                          {\n                            \"attrs\": {\n                              \"d\": \"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\",\n                            },\n                            \"tag\": \"path\",\n                          },\n                        ],\n                        \"tag\": \"svg\",\n                      },\n                      \"name\": \"down\",\n                      \"theme\": \"outlined\",\n                    }\n                  }\n                >\n                  <span\n                    aria-label=\"down\"\n                    className=\"anticon anticon-down ant-select-suffix\"\n                    role=\"img\"\n                  >\n                    <IconReact\n                      icon={\n                        {\n                          \"icon\": {\n                            \"attrs\": {\n                              \"focusable\": \"false\",\n                              \"viewBox\": \"64 64 896 896\",\n                            },\n                            \"children\": [\n                              {\n                                \"attrs\": {\n                                  \"d\": \"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\",\n                                },\n                                \"tag\": \"path\",\n                              },\n                            ],\n                            \"tag\": \"svg\",\n                          },\n                          \"name\": \"down\",\n                          \"theme\": \"outlined\",\n                        }\n                      }\n                    >\n                      <svg\n                        aria-hidden=\"true\"\n                        data-icon=\"down\"\n                        fill=\"currentColor\"\n                        focusable=\"false\"\n                        height=\"1em\"\n                        key=\"svg-down\"\n                        viewBox=\"64 64 896 896\"\n                        width=\"1em\"\n                      >\n                        <path\n                          d=\"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\"\n                          key=\"svg-down-svg-0\"\n                        />\n                      </svg>\n                    </IconReact>\n                  </span>\n                </AntdIcon>\n              </DownOutlined>\n            </span>\n          </TransBtn>\n        </div>\n      </ForwardRef(Select)>\n    </Select>\n  </Select>\n</div>\n`;\n\nexports[`ScheduleDialog Sets correct schedule settings Sets to \"2 Weeks 22:15 Tuesday\" Sets to correct interval 1`] = `\n<div\n  data-testid=\"interval\"\n>\n  <Select\n    bordered={true}\n    choiceTransitionName=\"zoom\"\n    className=\"input\"\n    dropdownMatchSelectWidth={false}\n    onChange={[Function]}\n    transitionName=\"slide-up\"\n    value={1209600}\n  >\n    <Select\n      choiceTransitionName=\"zoom\"\n      className=\"input\"\n      clearIcon={<CloseCircleFilled />}\n      dropdownClassName=\"\"\n      dropdownMatchSelectWidth={false}\n      inputIcon={[Function]}\n      listHeight={256}\n      listItemHeight={24}\n      menuItemSelectedIcon={null}\n      notFoundContent={\n        <Context.Consumer>\n          [Function]\n        </Context.Consumer>\n      }\n      onChange={[Function]}\n      prefixCls=\"ant-select\"\n      removeIcon={<CloseOutlined />}\n      transitionName=\"slide-up\"\n      value={1209600}\n    >\n      <ForwardRef(Select)\n        choiceTransitionName=\"zoom\"\n        className=\"input\"\n        clearIcon={<CloseCircleFilled />}\n        dropdownClassName=\"\"\n        dropdownMatchSelectWidth={false}\n        inputIcon={[Function]}\n        listHeight={256}\n        listItemHeight={24}\n        menuItemSelectedIcon={null}\n        notFoundContent={\n          <Context.Consumer>\n            [Function]\n          </Context.Consumer>\n        }\n        onChange={[Function]}\n        prefixCls=\"ant-select\"\n        removeIcon={<CloseOutlined />}\n        transitionName=\"slide-up\"\n        value={1209600}\n      >\n        <div\n          className=\"ant-select input ant-select-single ant-select-show-arrow\"\n          onBlur={[Function]}\n          onFocus={[Function]}\n          onKeyDown={[Function]}\n          onKeyUp={[Function]}\n          onMouseDown={[Function]}\n        >\n          <SelectTrigger\n            containerWidth={null}\n            dropdownClassName=\"\"\n            dropdownMatchSelectWidth={false}\n            empty={false}\n            getTriggerDOMNode={[Function]}\n            popupElement={\n              <OptionList\n                childrenAsData={true}\n                defaultActiveFirstOption={true}\n                flattenOptions={\n                  [\n                    {\n                      \"data\": {\n                        \"children\": \"Never\",\n                        \"key\": \"never\",\n                        \"value\": null,\n                      },\n                      \"groupOption\": false,\n                      \"key\": \"never\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__minute__\",\n                        \"label\": \"Minutes\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 minute\",\n                            \"key\": \"minute-1\",\n                            \"value\": 60,\n                          },\n                          {\n                            \"children\": \"5 minutes\",\n                            \"key\": \"minute-5\",\n                            \"value\": 300,\n                          },\n                          {\n                            \"children\": \"10 minutes\",\n                            \"key\": \"minute-10\",\n                            \"value\": 600,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__minute__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 minute\",\n                        \"key\": \"minute-1\",\n                        \"value\": 60,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"5 minutes\",\n                        \"key\": \"minute-5\",\n                        \"value\": 300,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-5\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"10 minutes\",\n                        \"key\": \"minute-10\",\n                        \"value\": 600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-10\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__hour__\",\n                        \"label\": \"Hours\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 hour\",\n                            \"key\": \"hour-1\",\n                            \"value\": 3600,\n                          },\n                          {\n                            \"children\": \"10 hours\",\n                            \"key\": \"hour-10\",\n                            \"value\": 36000,\n                          },\n                          {\n                            \"children\": \"23 hours\",\n                            \"key\": \"hour-23\",\n                            \"value\": 82800,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__hour__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 hour\",\n                        \"key\": \"hour-1\",\n                        \"value\": 3600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"10 hours\",\n                        \"key\": \"hour-10\",\n                        \"value\": 36000,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-10\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"23 hours\",\n                        \"key\": \"hour-23\",\n                        \"value\": 82800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-23\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__day__\",\n                        \"label\": \"Days\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 day\",\n                            \"key\": \"day-1\",\n                            \"value\": 86400,\n                          },\n                          {\n                            \"children\": \"2 days\",\n                            \"key\": \"day-2\",\n                            \"value\": 172800,\n                          },\n                          {\n                            \"children\": \"6 days\",\n                            \"key\": \"day-6\",\n                            \"value\": 518400,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__day__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 day\",\n                        \"key\": \"day-1\",\n                        \"value\": 86400,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"2 days\",\n                        \"key\": \"day-2\",\n                        \"value\": 172800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-2\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"6 days\",\n                        \"key\": \"day-6\",\n                        \"value\": 518400,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-6\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__week__\",\n                        \"label\": \"Weeks\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 week\",\n                            \"key\": \"week-1\",\n                            \"value\": 604800,\n                          },\n                          {\n                            \"children\": \"2 weeks\",\n                            \"key\": \"week-2\",\n                            \"value\": 1209600,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__week__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 week\",\n                        \"key\": \"week-1\",\n                        \"value\": 604800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"week-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"2 weeks\",\n                        \"key\": \"week-2\",\n                        \"value\": 1209600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"week-2\",\n                    },\n                  ]\n                }\n                height={256}\n                id=\"rc_select_TEST_OR_SSR\"\n                itemHeight={24}\n                menuItemSelectedIcon={null}\n                multiple={false}\n                notFoundContent={\n                  <Context.Consumer>\n                    [Function]\n                  </Context.Consumer>\n                }\n                onActiveValue={[Function]}\n                onMouseEnter={[Function]}\n                onSelect={[Function]}\n                onToggleOpen={[Function]}\n                options={\n                  [\n                    {\n                      \"children\": \"Never\",\n                      \"key\": \"never\",\n                      \"value\": null,\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__minute__\",\n                      \"label\": \"Minutes\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 minute\",\n                          \"key\": \"minute-1\",\n                          \"value\": 60,\n                        },\n                        {\n                          \"children\": \"5 minutes\",\n                          \"key\": \"minute-5\",\n                          \"value\": 300,\n                        },\n                        {\n                          \"children\": \"10 minutes\",\n                          \"key\": \"minute-10\",\n                          \"value\": 600,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__hour__\",\n                      \"label\": \"Hours\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 hour\",\n                          \"key\": \"hour-1\",\n                          \"value\": 3600,\n                        },\n                        {\n                          \"children\": \"10 hours\",\n                          \"key\": \"hour-10\",\n                          \"value\": 36000,\n                        },\n                        {\n                          \"children\": \"23 hours\",\n                          \"key\": \"hour-23\",\n                          \"value\": 82800,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__day__\",\n                      \"label\": \"Days\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 day\",\n                          \"key\": \"day-1\",\n                          \"value\": 86400,\n                        },\n                        {\n                          \"children\": \"2 days\",\n                          \"key\": \"day-2\",\n                          \"value\": 172800,\n                        },\n                        {\n                          \"children\": \"6 days\",\n                          \"key\": \"day-6\",\n                          \"value\": 518400,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__week__\",\n                      \"label\": \"Weeks\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 week\",\n                          \"key\": \"week-1\",\n                          \"value\": 604800,\n                        },\n                        {\n                          \"children\": \"2 weeks\",\n                          \"key\": \"week-2\",\n                          \"value\": 1209600,\n                        },\n                      ],\n                    },\n                  ]\n                }\n                prefixCls=\"ant-select\"\n                searchValue=\"\"\n                values={\n                  Set {\n                    1209600,\n                  }\n                }\n                virtual={false}\n              />\n            }\n            prefixCls=\"ant-select\"\n            transitionName=\"slide-up\"\n          >\n            <Trigger\n              action={[]}\n              afterPopupVisibleChange={[Function]}\n              autoDestroy={false}\n              blurDelay={0.15}\n              builtinPlacements={\n                {\n                  \"bottomLeft\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tl\",\n                      \"bl\",\n                    ],\n                  },\n                  \"bottomRight\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tr\",\n                      \"br\",\n                    ],\n                  },\n                  \"topLeft\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"bl\",\n                      \"tl\",\n                    ],\n                  },\n                  \"topRight\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"br\",\n                      \"tr\",\n                    ],\n                  },\n                }\n              }\n              defaultPopupVisible={false}\n              destroyPopupOnHide={false}\n              focusDelay={0}\n              getDocument={[Function]}\n              getPopupClassNameFromAlign={[Function]}\n              getTriggerDOMNode={[Function]}\n              hideAction={[]}\n              mask={false}\n              maskClosable={true}\n              mouseEnterDelay={0}\n              mouseLeaveDelay={0.1}\n              onPopupAlign={[Function]}\n              onPopupVisibleChange={[Function]}\n              popup={\n                <div>\n                  <OptionList\n                    childrenAsData={true}\n                    defaultActiveFirstOption={true}\n                    flattenOptions={\n                      [\n                        {\n                          \"data\": {\n                            \"children\": \"Never\",\n                            \"key\": \"never\",\n                            \"value\": null,\n                          },\n                          \"groupOption\": false,\n                          \"key\": \"never\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__minute__\",\n                            \"label\": \"Minutes\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 minute\",\n                                \"key\": \"minute-1\",\n                                \"value\": 60,\n                              },\n                              {\n                                \"children\": \"5 minutes\",\n                                \"key\": \"minute-5\",\n                                \"value\": 300,\n                              },\n                              {\n                                \"children\": \"10 minutes\",\n                                \"key\": \"minute-10\",\n                                \"value\": 600,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__minute__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 minute\",\n                            \"key\": \"minute-1\",\n                            \"value\": 60,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"5 minutes\",\n                            \"key\": \"minute-5\",\n                            \"value\": 300,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-5\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"10 minutes\",\n                            \"key\": \"minute-10\",\n                            \"value\": 600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-10\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__hour__\",\n                            \"label\": \"Hours\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 hour\",\n                                \"key\": \"hour-1\",\n                                \"value\": 3600,\n                              },\n                              {\n                                \"children\": \"10 hours\",\n                                \"key\": \"hour-10\",\n                                \"value\": 36000,\n                              },\n                              {\n                                \"children\": \"23 hours\",\n                                \"key\": \"hour-23\",\n                                \"value\": 82800,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__hour__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 hour\",\n                            \"key\": \"hour-1\",\n                            \"value\": 3600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"10 hours\",\n                            \"key\": \"hour-10\",\n                            \"value\": 36000,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-10\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"23 hours\",\n                            \"key\": \"hour-23\",\n                            \"value\": 82800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-23\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__day__\",\n                            \"label\": \"Days\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 day\",\n                                \"key\": \"day-1\",\n                                \"value\": 86400,\n                              },\n                              {\n                                \"children\": \"2 days\",\n                                \"key\": \"day-2\",\n                                \"value\": 172800,\n                              },\n                              {\n                                \"children\": \"6 days\",\n                                \"key\": \"day-6\",\n                                \"value\": 518400,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__day__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 day\",\n                            \"key\": \"day-1\",\n                            \"value\": 86400,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"2 days\",\n                            \"key\": \"day-2\",\n                            \"value\": 172800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-2\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"6 days\",\n                            \"key\": \"day-6\",\n                            \"value\": 518400,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-6\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__week__\",\n                            \"label\": \"Weeks\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 week\",\n                                \"key\": \"week-1\",\n                                \"value\": 604800,\n                              },\n                              {\n                                \"children\": \"2 weeks\",\n                                \"key\": \"week-2\",\n                                \"value\": 1209600,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__week__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 week\",\n                            \"key\": \"week-1\",\n                            \"value\": 604800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"week-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"2 weeks\",\n                            \"key\": \"week-2\",\n                            \"value\": 1209600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"week-2\",\n                        },\n                      ]\n                    }\n                    height={256}\n                    id=\"rc_select_TEST_OR_SSR\"\n                    itemHeight={24}\n                    menuItemSelectedIcon={null}\n                    multiple={false}\n                    notFoundContent={\n                      <Context.Consumer>\n                        [Function]\n                      </Context.Consumer>\n                    }\n                    onActiveValue={[Function]}\n                    onMouseEnter={[Function]}\n                    onSelect={[Function]}\n                    onToggleOpen={[Function]}\n                    options={\n                      [\n                        {\n                          \"children\": \"Never\",\n                          \"key\": \"never\",\n                          \"value\": null,\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__minute__\",\n                          \"label\": \"Minutes\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 minute\",\n                              \"key\": \"minute-1\",\n                              \"value\": 60,\n                            },\n                            {\n                              \"children\": \"5 minutes\",\n                              \"key\": \"minute-5\",\n                              \"value\": 300,\n                            },\n                            {\n                              \"children\": \"10 minutes\",\n                              \"key\": \"minute-10\",\n                              \"value\": 600,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__hour__\",\n                          \"label\": \"Hours\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 hour\",\n                              \"key\": \"hour-1\",\n                              \"value\": 3600,\n                            },\n                            {\n                              \"children\": \"10 hours\",\n                              \"key\": \"hour-10\",\n                              \"value\": 36000,\n                            },\n                            {\n                              \"children\": \"23 hours\",\n                              \"key\": \"hour-23\",\n                              \"value\": 82800,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__day__\",\n                          \"label\": \"Days\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 day\",\n                              \"key\": \"day-1\",\n                              \"value\": 86400,\n                            },\n                            {\n                              \"children\": \"2 days\",\n                              \"key\": \"day-2\",\n                              \"value\": 172800,\n                            },\n                            {\n                              \"children\": \"6 days\",\n                              \"key\": \"day-6\",\n                              \"value\": 518400,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__week__\",\n                          \"label\": \"Weeks\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 week\",\n                              \"key\": \"week-1\",\n                              \"value\": 604800,\n                            },\n                            {\n                              \"children\": \"2 weeks\",\n                              \"key\": \"week-2\",\n                              \"value\": 1209600,\n                            },\n                          ],\n                        },\n                      ]\n                    }\n                    prefixCls=\"ant-select\"\n                    searchValue=\"\"\n                    values={\n                      Set {\n                        1209600,\n                      }\n                    }\n                    virtual={false}\n                  />\n                </div>\n              }\n              popupAlign={{}}\n              popupClassName=\"\"\n              popupPlacement=\"bottomLeft\"\n              popupStyle={\n                {\n                  \"minWidth\": null,\n                }\n              }\n              popupTransitionName=\"slide-up\"\n              prefixCls=\"ant-select-dropdown\"\n              showAction={[]}\n            >\n              <Selector\n                accessibilityIndex={0}\n                activeValue={null}\n                choiceTransitionName=\"zoom\"\n                className=\"input\"\n                clearIcon={<CloseCircleFilled />}\n                domRef={\n                  {\n                    \"current\": <div\n                      class=\"ant-select-selector\"\n                    >\n                      <span\n                        class=\"ant-select-selection-search\"\n                      >\n                        <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autocomplete=\"off\"\n                          class=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          readonly=\"\"\n                          role=\"combobox\"\n                          style=\"opacity: 0;\"\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />\n                      </span>\n                      <span\n                        class=\"ant-select-selection-item\"\n                        title=\"2 weeks\"\n                      >\n                        2 weeks\n                      </span>\n                    </div>,\n                  }\n                }\n                dropdownClassName=\"\"\n                dropdownMatchSelectWidth={false}\n                id=\"rc_select_TEST_OR_SSR\"\n                inputElement={null}\n                inputIcon={[Function]}\n                key=\"trigger\"\n                listHeight={256}\n                listItemHeight={24}\n                menuItemSelectedIcon={null}\n                multiple={false}\n                notFoundContent={\n                  <Context.Consumer>\n                    [Function]\n                  </Context.Consumer>\n                }\n                onChange={[Function]}\n                onSearch={[Function]}\n                onSearchSubmit={[Function]}\n                onSelect={[Function]}\n                onToggleOpen={[Function]}\n                prefixCls=\"ant-select\"\n                removeIcon={<CloseOutlined />}\n                searchValue=\"\"\n                showSearch={false}\n                tokenWithEnter={false}\n                transitionName=\"slide-up\"\n                value={1209600}\n                values={\n                  [\n                    {\n                      \"disabled\": undefined,\n                      \"key\": 1209600,\n                      \"label\": \"2 weeks\",\n                      \"value\": 1209600,\n                    },\n                  ]\n                }\n              >\n                <div\n                  className=\"ant-select-selector\"\n                  onClick={[Function]}\n                  onMouseDown={[Function]}\n                >\n                  <SingleSelector\n                    accessibilityIndex={0}\n                    activeValue={null}\n                    choiceTransitionName=\"zoom\"\n                    className=\"input\"\n                    clearIcon={<CloseCircleFilled />}\n                    domRef={\n                      {\n                        \"current\": <div\n                          class=\"ant-select-selector\"\n                        >\n                          <span\n                            class=\"ant-select-selection-search\"\n                          >\n                            <input\n                              aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                              aria-autocomplete=\"list\"\n                              aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                              aria-haspopup=\"listbox\"\n                              aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                              autocomplete=\"off\"\n                              class=\"ant-select-selection-search-input\"\n                              id=\"rc_select_TEST_OR_SSR\"\n                              readonly=\"\"\n                              role=\"combobox\"\n                              style=\"opacity: 0;\"\n                              type=\"search\"\n                              unselectable=\"on\"\n                              value=\"\"\n                            />\n                          </span>\n                          <span\n                            class=\"ant-select-selection-item\"\n                            title=\"2 weeks\"\n                          >\n                            2 weeks\n                          </span>\n                        </div>,\n                      }\n                    }\n                    dropdownClassName=\"\"\n                    dropdownMatchSelectWidth={false}\n                    id=\"rc_select_TEST_OR_SSR\"\n                    inputElement={null}\n                    inputIcon={[Function]}\n                    inputRef={\n                      {\n                        \"current\": <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autocomplete=\"off\"\n                          class=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          readonly=\"\"\n                          role=\"combobox\"\n                          style=\"opacity: 0;\"\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />,\n                      }\n                    }\n                    listHeight={256}\n                    listItemHeight={24}\n                    menuItemSelectedIcon={null}\n                    multiple={false}\n                    notFoundContent={\n                      <Context.Consumer>\n                        [Function]\n                      </Context.Consumer>\n                    }\n                    onChange={[Function]}\n                    onInputChange={[Function]}\n                    onInputCompositionEnd={[Function]}\n                    onInputCompositionStart={[Function]}\n                    onInputKeyDown={[Function]}\n                    onInputMouseDown={[Function]}\n                    onInputPaste={[Function]}\n                    onSearch={[Function]}\n                    onSearchSubmit={[Function]}\n                    onSelect={[Function]}\n                    onToggleOpen={[Function]}\n                    prefixCls=\"ant-select\"\n                    removeIcon={<CloseOutlined />}\n                    searchValue=\"\"\n                    showSearch={false}\n                    tokenWithEnter={false}\n                    transitionName=\"slide-up\"\n                    value={1209600}\n                    values={\n                      [\n                        {\n                          \"disabled\": undefined,\n                          \"key\": 1209600,\n                          \"label\": \"2 weeks\",\n                          \"value\": 1209600,\n                        },\n                      ]\n                    }\n                  >\n                    <span\n                      className=\"ant-select-selection-search\"\n                    >\n                      <Input\n                        accessibilityIndex={0}\n                        attrs={{}}\n                        editable={false}\n                        id=\"rc_select_TEST_OR_SSR\"\n                        inputElement={null}\n                        onChange={[Function]}\n                        onCompositionEnd={[Function]}\n                        onCompositionStart={[Function]}\n                        onKeyDown={[Function]}\n                        onMouseDown={[Function]}\n                        onPaste={[Function]}\n                        prefixCls=\"ant-select\"\n                        value=\"\"\n                      >\n                        <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autoComplete=\"off\"\n                          className=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          onChange={[Function]}\n                          onCompositionEnd={[Function]}\n                          onCompositionStart={[Function]}\n                          onKeyDown={[Function]}\n                          onMouseDown={[Function]}\n                          onPaste={[Function]}\n                          readOnly={true}\n                          role=\"combobox\"\n                          style={\n                            {\n                              \"opacity\": 0,\n                            }\n                          }\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />\n                      </Input>\n                    </span>\n                    <span\n                      className=\"ant-select-selection-item\"\n                      title=\"2 weeks\"\n                    >\n                      2 weeks\n                    </span>\n                  </SingleSelector>\n                </div>\n              </Selector>\n            </Trigger>\n          </SelectTrigger>\n          <TransBtn\n            className=\"ant-select-arrow\"\n            customizeIcon={[Function]}\n            customizeIconProps={\n              {\n                \"focused\": false,\n                \"loading\": undefined,\n                \"open\": undefined,\n                \"searchValue\": \"\",\n                \"showSearch\": false,\n              }\n            }\n          >\n            <span\n              aria-hidden={true}\n              className=\"ant-select-arrow\"\n              onMouseDown={[Function]}\n              style={\n                {\n                  \"WebkitUserSelect\": \"none\",\n                  \"userSelect\": \"none\",\n                }\n              }\n              unselectable=\"on\"\n            >\n              <DownOutlined\n                className=\"ant-select-suffix\"\n              >\n                <AntdIcon\n                  className=\"ant-select-suffix\"\n                  icon={\n                    {\n                      \"icon\": {\n                        \"attrs\": {\n                          \"focusable\": \"false\",\n                          \"viewBox\": \"64 64 896 896\",\n                        },\n                        \"children\": [\n                          {\n                            \"attrs\": {\n                              \"d\": \"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\",\n                            },\n                            \"tag\": \"path\",\n                          },\n                        ],\n                        \"tag\": \"svg\",\n                      },\n                      \"name\": \"down\",\n                      \"theme\": \"outlined\",\n                    }\n                  }\n                >\n                  <span\n                    aria-label=\"down\"\n                    className=\"anticon anticon-down ant-select-suffix\"\n                    role=\"img\"\n                  >\n                    <IconReact\n                      icon={\n                        {\n                          \"icon\": {\n                            \"attrs\": {\n                              \"focusable\": \"false\",\n                              \"viewBox\": \"64 64 896 896\",\n                            },\n                            \"children\": [\n                              {\n                                \"attrs\": {\n                                  \"d\": \"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\",\n                                },\n                                \"tag\": \"path\",\n                              },\n                            ],\n                            \"tag\": \"svg\",\n                          },\n                          \"name\": \"down\",\n                          \"theme\": \"outlined\",\n                        }\n                      }\n                    >\n                      <svg\n                        aria-hidden=\"true\"\n                        data-icon=\"down\"\n                        fill=\"currentColor\"\n                        focusable=\"false\"\n                        height=\"1em\"\n                        key=\"svg-down\"\n                        viewBox=\"64 64 896 896\"\n                        width=\"1em\"\n                      >\n                        <path\n                          d=\"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\"\n                          key=\"svg-down-svg-0\"\n                        />\n                      </svg>\n                    </IconReact>\n                  </span>\n                </AntdIcon>\n              </DownOutlined>\n            </span>\n          </TransBtn>\n        </div>\n      </ForwardRef(Select)>\n    </Select>\n  </Select>\n</div>\n`;\n\nexports[`ScheduleDialog Sets correct schedule settings Sets to \"2 Weeks 22:15 Tuesday\" Sets to correct time 1`] = `\n<div\n  data-testid=\"time\"\n>\n  <TimeEditor\n    defaultValue={\"1999-12-31T22:15:00.000Z\"}\n    onChange={[Function]}\n  >\n    <TimePicker\n      allowClear={false}\n      format=\"HH:mm\"\n      minuteStep={5}\n      onChange={[Function]}\n      value={\"1999-12-31T22:15:00.000Z\"}\n    >\n      <TimePicker\n        allowClear={false}\n        format=\"HH:mm\"\n        minuteStep={5}\n        onChange={[Function]}\n        value={\"1999-12-31T22:15:00.000Z\"}\n      >\n        <LocaleReceiver\n          componentName=\"DatePicker\"\n          defaultLocale={[Function]}\n        >\n          <Picker\n            allowClear={false}\n            className=\"\"\n            clearIcon={<CloseCircleFilled />}\n            components={\n              {\n                \"button\": [Function],\n                \"rangeItem\": [Function],\n              }\n            }\n            format=\"HH:mm\"\n            generateConfig={\n              {\n                \"addDate\": [Function],\n                \"addMonth\": [Function],\n                \"addYear\": [Function],\n                \"getDate\": [Function],\n                \"getHour\": [Function],\n                \"getMinute\": [Function],\n                \"getMonth\": [Function],\n                \"getNow\": [Function],\n                \"getSecond\": [Function],\n                \"getWeekDay\": [Function],\n                \"getYear\": [Function],\n                \"isAfter\": [Function],\n                \"isValidate\": [Function],\n                \"locale\": {\n                  \"format\": [Function],\n                  \"getShortMonths\": [Function],\n                  \"getShortWeekDays\": [Function],\n                  \"getWeek\": [Function],\n                  \"getWeekFirstDay\": [Function],\n                  \"parse\": [Function],\n                },\n                \"setDate\": [Function],\n                \"setHour\": [Function],\n                \"setMinute\": [Function],\n                \"setMonth\": [Function],\n                \"setSecond\": [Function],\n                \"setYear\": [Function],\n              }\n            }\n            locale={\n              {\n                \"backToToday\": \"Back to today\",\n                \"clear\": \"Clear\",\n                \"dateFormat\": \"M/D/YYYY\",\n                \"dateSelect\": \"select date\",\n                \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                \"dayFormat\": \"D\",\n                \"decadeSelect\": \"Choose a decade\",\n                \"locale\": \"en_US\",\n                \"month\": \"Month\",\n                \"monthBeforeYear\": true,\n                \"monthPlaceholder\": \"Select month\",\n                \"monthSelect\": \"Choose a month\",\n                \"nextCentury\": \"Next century\",\n                \"nextDecade\": \"Next decade\",\n                \"nextMonth\": \"Next month (PageDown)\",\n                \"nextYear\": \"Next year (Control + right)\",\n                \"now\": \"Now\",\n                \"ok\": \"Ok\",\n                \"placeholder\": \"Select date\",\n                \"previousCentury\": \"Last century\",\n                \"previousDecade\": \"Last decade\",\n                \"previousMonth\": \"Previous month (PageUp)\",\n                \"previousYear\": \"Last year (Control + left)\",\n                \"quarterPlaceholder\": \"Select quarter\",\n                \"rangeMonthPlaceholder\": [\n                  \"Start month\",\n                  \"End month\",\n                ],\n                \"rangePlaceholder\": [\n                  \"Start date\",\n                  \"End date\",\n                ],\n                \"rangeWeekPlaceholder\": [\n                  \"Start week\",\n                  \"End week\",\n                ],\n                \"rangeYearPlaceholder\": [\n                  \"Start year\",\n                  \"End year\",\n                ],\n                \"timeSelect\": \"select time\",\n                \"today\": \"Today\",\n                \"weekPlaceholder\": \"Select week\",\n                \"weekSelect\": \"Choose a week\",\n                \"year\": \"Year\",\n                \"yearFormat\": \"YYYY\",\n                \"yearPlaceholder\": \"Select year\",\n                \"yearSelect\": \"Choose a year\",\n              }\n            }\n            minuteStep={5}\n            nextIcon={\n              <span\n                className=\"ant-picker-next-icon\"\n              />\n            }\n            onChange={[Function]}\n            picker=\"time\"\n            placeholder=\"Select time\"\n            prefixCls=\"ant-picker\"\n            prevIcon={\n              <span\n                className=\"ant-picker-prev-icon\"\n              />\n            }\n            showSecond={false}\n            showToday={true}\n            suffixIcon={<ClockCircleOutlined />}\n            superNextIcon={\n              <span\n                className=\"ant-picker-super-next-icon\"\n              />\n            }\n            superPrevIcon={\n              <span\n                className=\"ant-picker-super-prev-icon\"\n              />\n            }\n            transitionName=\"slide-up\"\n            value={\"1999-12-31T22:15:00.000Z\"}\n          >\n            <InnerPicker\n              allowClear={false}\n              className=\"\"\n              clearIcon={<CloseCircleFilled />}\n              components={\n                {\n                  \"button\": [Function],\n                  \"rangeItem\": [Function],\n                }\n              }\n              format=\"HH:mm\"\n              generateConfig={\n                {\n                  \"addDate\": [Function],\n                  \"addMonth\": [Function],\n                  \"addYear\": [Function],\n                  \"getDate\": [Function],\n                  \"getHour\": [Function],\n                  \"getMinute\": [Function],\n                  \"getMonth\": [Function],\n                  \"getNow\": [Function],\n                  \"getSecond\": [Function],\n                  \"getWeekDay\": [Function],\n                  \"getYear\": [Function],\n                  \"isAfter\": [Function],\n                  \"isValidate\": [Function],\n                  \"locale\": {\n                    \"format\": [Function],\n                    \"getShortMonths\": [Function],\n                    \"getShortWeekDays\": [Function],\n                    \"getWeek\": [Function],\n                    \"getWeekFirstDay\": [Function],\n                    \"parse\": [Function],\n                  },\n                  \"setDate\": [Function],\n                  \"setHour\": [Function],\n                  \"setMinute\": [Function],\n                  \"setMonth\": [Function],\n                  \"setSecond\": [Function],\n                  \"setYear\": [Function],\n                }\n              }\n              locale={\n                {\n                  \"backToToday\": \"Back to today\",\n                  \"clear\": \"Clear\",\n                  \"dateFormat\": \"M/D/YYYY\",\n                  \"dateSelect\": \"select date\",\n                  \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                  \"dayFormat\": \"D\",\n                  \"decadeSelect\": \"Choose a decade\",\n                  \"locale\": \"en_US\",\n                  \"month\": \"Month\",\n                  \"monthBeforeYear\": true,\n                  \"monthPlaceholder\": \"Select month\",\n                  \"monthSelect\": \"Choose a month\",\n                  \"nextCentury\": \"Next century\",\n                  \"nextDecade\": \"Next decade\",\n                  \"nextMonth\": \"Next month (PageDown)\",\n                  \"nextYear\": \"Next year (Control + right)\",\n                  \"now\": \"Now\",\n                  \"ok\": \"Ok\",\n                  \"placeholder\": \"Select date\",\n                  \"previousCentury\": \"Last century\",\n                  \"previousDecade\": \"Last decade\",\n                  \"previousMonth\": \"Previous month (PageUp)\",\n                  \"previousYear\": \"Last year (Control + left)\",\n                  \"quarterPlaceholder\": \"Select quarter\",\n                  \"rangeMonthPlaceholder\": [\n                    \"Start month\",\n                    \"End month\",\n                  ],\n                  \"rangePlaceholder\": [\n                    \"Start date\",\n                    \"End date\",\n                  ],\n                  \"rangeWeekPlaceholder\": [\n                    \"Start week\",\n                    \"End week\",\n                  ],\n                  \"rangeYearPlaceholder\": [\n                    \"Start year\",\n                    \"End year\",\n                  ],\n                  \"timeSelect\": \"select time\",\n                  \"today\": \"Today\",\n                  \"weekPlaceholder\": \"Select week\",\n                  \"weekSelect\": \"Choose a week\",\n                  \"year\": \"Year\",\n                  \"yearFormat\": \"YYYY\",\n                  \"yearPlaceholder\": \"Select year\",\n                  \"yearSelect\": \"Choose a year\",\n                }\n              }\n              minuteStep={5}\n              nextIcon={\n                <span\n                  className=\"ant-picker-next-icon\"\n                />\n              }\n              onChange={[Function]}\n              picker=\"time\"\n              pickerRef={\n                {\n                  \"current\": {\n                    \"blur\": [Function],\n                    \"focus\": [Function],\n                  },\n                }\n              }\n              placeholder=\"Select time\"\n              prefixCls=\"ant-picker\"\n              prevIcon={\n                <span\n                  className=\"ant-picker-prev-icon\"\n                />\n              }\n              showSecond={false}\n              showToday={true}\n              suffixIcon={<ClockCircleOutlined />}\n              superNextIcon={\n                <span\n                  className=\"ant-picker-super-next-icon\"\n                />\n              }\n              superPrevIcon={\n                <span\n                  className=\"ant-picker-super-prev-icon\"\n                />\n              }\n              transitionName=\"slide-up\"\n              value={\"1999-12-31T22:15:00.000Z\"}\n            >\n              <PickerTrigger\n                popupElement={\n                  <div\n                    className=\"ant-picker-panel-container\"\n                    onMouseDown={[Function]}\n                  >\n                    <PickerPanel\n                      allowClear={false}\n                      className=\"ant-picker-panel-focused\"\n                      clearIcon={<CloseCircleFilled />}\n                      components={\n                        {\n                          \"button\": [Function],\n                          \"rangeItem\": [Function],\n                        }\n                      }\n                      format=\"HH:mm\"\n                      generateConfig={\n                        {\n                          \"addDate\": [Function],\n                          \"addMonth\": [Function],\n                          \"addYear\": [Function],\n                          \"getDate\": [Function],\n                          \"getHour\": [Function],\n                          \"getMinute\": [Function],\n                          \"getMonth\": [Function],\n                          \"getNow\": [Function],\n                          \"getSecond\": [Function],\n                          \"getWeekDay\": [Function],\n                          \"getYear\": [Function],\n                          \"isAfter\": [Function],\n                          \"isValidate\": [Function],\n                          \"locale\": {\n                            \"format\": [Function],\n                            \"getShortMonths\": [Function],\n                            \"getShortWeekDays\": [Function],\n                            \"getWeek\": [Function],\n                            \"getWeekFirstDay\": [Function],\n                            \"parse\": [Function],\n                          },\n                          \"setDate\": [Function],\n                          \"setHour\": [Function],\n                          \"setMinute\": [Function],\n                          \"setMonth\": [Function],\n                          \"setSecond\": [Function],\n                          \"setYear\": [Function],\n                        }\n                      }\n                      locale={\n                        {\n                          \"backToToday\": \"Back to today\",\n                          \"clear\": \"Clear\",\n                          \"dateFormat\": \"M/D/YYYY\",\n                          \"dateSelect\": \"select date\",\n                          \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                          \"dayFormat\": \"D\",\n                          \"decadeSelect\": \"Choose a decade\",\n                          \"locale\": \"en_US\",\n                          \"month\": \"Month\",\n                          \"monthBeforeYear\": true,\n                          \"monthPlaceholder\": \"Select month\",\n                          \"monthSelect\": \"Choose a month\",\n                          \"nextCentury\": \"Next century\",\n                          \"nextDecade\": \"Next decade\",\n                          \"nextMonth\": \"Next month (PageDown)\",\n                          \"nextYear\": \"Next year (Control + right)\",\n                          \"now\": \"Now\",\n                          \"ok\": \"Ok\",\n                          \"placeholder\": \"Select date\",\n                          \"previousCentury\": \"Last century\",\n                          \"previousDecade\": \"Last decade\",\n                          \"previousMonth\": \"Previous month (PageUp)\",\n                          \"previousYear\": \"Last year (Control + left)\",\n                          \"quarterPlaceholder\": \"Select quarter\",\n                          \"rangeMonthPlaceholder\": [\n                            \"Start month\",\n                            \"End month\",\n                          ],\n                          \"rangePlaceholder\": [\n                            \"Start date\",\n                            \"End date\",\n                          ],\n                          \"rangeWeekPlaceholder\": [\n                            \"Start week\",\n                            \"End week\",\n                          ],\n                          \"rangeYearPlaceholder\": [\n                            \"Start year\",\n                            \"End year\",\n                          ],\n                          \"timeSelect\": \"select time\",\n                          \"today\": \"Today\",\n                          \"weekPlaceholder\": \"Select week\",\n                          \"weekSelect\": \"Choose a week\",\n                          \"year\": \"Year\",\n                          \"yearFormat\": \"YYYY\",\n                          \"yearPlaceholder\": \"Select year\",\n                          \"yearSelect\": \"Choose a year\",\n                        }\n                      }\n                      minuteStep={5}\n                      nextIcon={\n                        <span\n                          className=\"ant-picker-next-icon\"\n                        />\n                      }\n                      onChange={[Function]}\n                      picker=\"time\"\n                      pickerRef={\n                        {\n                          \"current\": {\n                            \"blur\": [Function],\n                            \"focus\": [Function],\n                          },\n                        }\n                      }\n                      placeholder=\"Select time\"\n                      prefixCls=\"ant-picker\"\n                      prevIcon={\n                        <span\n                          className=\"ant-picker-prev-icon\"\n                        />\n                      }\n                      showSecond={false}\n                      showToday={true}\n                      suffixIcon={<ClockCircleOutlined />}\n                      superNextIcon={\n                        <span\n                          className=\"ant-picker-super-next-icon\"\n                        />\n                      }\n                      superPrevIcon={\n                        <span\n                          className=\"ant-picker-super-prev-icon\"\n                        />\n                      }\n                      tabIndex={-1}\n                      transitionName=\"slide-up\"\n                      value={\"1999-12-31T22:15:00.000Z\"}\n                    />\n                  </div>\n                }\n                popupPlacement=\"bottomLeft\"\n                prefixCls=\"ant-picker\"\n                transitionName=\"slide-up\"\n                visible={false}\n              >\n                <Trigger\n                  action={[]}\n                  afterPopupVisibleChange={[Function]}\n                  autoDestroy={false}\n                  blurDelay={0.15}\n                  builtinPlacements={\n                    {\n                      \"bottomLeft\": {\n                        \"offset\": [\n                          0,\n                          4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 1,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"tl\",\n                          \"bl\",\n                        ],\n                      },\n                      \"bottomRight\": {\n                        \"offset\": [\n                          0,\n                          4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 1,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"tr\",\n                          \"br\",\n                        ],\n                      },\n                      \"topLeft\": {\n                        \"offset\": [\n                          0,\n                          -4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 0,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"bl\",\n                          \"tl\",\n                        ],\n                      },\n                      \"topRight\": {\n                        \"offset\": [\n                          0,\n                          -4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 0,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"br\",\n                          \"tr\",\n                        ],\n                      },\n                    }\n                  }\n                  defaultPopupVisible={false}\n                  destroyPopupOnHide={false}\n                  focusDelay={0}\n                  getDocument={[Function]}\n                  getPopupClassNameFromAlign={[Function]}\n                  hideAction={[]}\n                  mask={false}\n                  maskClosable={true}\n                  mouseEnterDelay={0}\n                  mouseLeaveDelay={0.1}\n                  onPopupAlign={[Function]}\n                  onPopupVisibleChange={[Function]}\n                  popup={\n                    <div\n                      className=\"ant-picker-panel-container\"\n                      onMouseDown={[Function]}\n                    >\n                      <PickerPanel\n                        allowClear={false}\n                        className=\"ant-picker-panel-focused\"\n                        clearIcon={<CloseCircleFilled />}\n                        components={\n                          {\n                            \"button\": [Function],\n                            \"rangeItem\": [Function],\n                          }\n                        }\n                        format=\"HH:mm\"\n                        generateConfig={\n                          {\n                            \"addDate\": [Function],\n                            \"addMonth\": [Function],\n                            \"addYear\": [Function],\n                            \"getDate\": [Function],\n                            \"getHour\": [Function],\n                            \"getMinute\": [Function],\n                            \"getMonth\": [Function],\n                            \"getNow\": [Function],\n                            \"getSecond\": [Function],\n                            \"getWeekDay\": [Function],\n                            \"getYear\": [Function],\n                            \"isAfter\": [Function],\n                            \"isValidate\": [Function],\n                            \"locale\": {\n                              \"format\": [Function],\n                              \"getShortMonths\": [Function],\n                              \"getShortWeekDays\": [Function],\n                              \"getWeek\": [Function],\n                              \"getWeekFirstDay\": [Function],\n                              \"parse\": [Function],\n                            },\n                            \"setDate\": [Function],\n                            \"setHour\": [Function],\n                            \"setMinute\": [Function],\n                            \"setMonth\": [Function],\n                            \"setSecond\": [Function],\n                            \"setYear\": [Function],\n                          }\n                        }\n                        locale={\n                          {\n                            \"backToToday\": \"Back to today\",\n                            \"clear\": \"Clear\",\n                            \"dateFormat\": \"M/D/YYYY\",\n                            \"dateSelect\": \"select date\",\n                            \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                            \"dayFormat\": \"D\",\n                            \"decadeSelect\": \"Choose a decade\",\n                            \"locale\": \"en_US\",\n                            \"month\": \"Month\",\n                            \"monthBeforeYear\": true,\n                            \"monthPlaceholder\": \"Select month\",\n                            \"monthSelect\": \"Choose a month\",\n                            \"nextCentury\": \"Next century\",\n                            \"nextDecade\": \"Next decade\",\n                            \"nextMonth\": \"Next month (PageDown)\",\n                            \"nextYear\": \"Next year (Control + right)\",\n                            \"now\": \"Now\",\n                            \"ok\": \"Ok\",\n                            \"placeholder\": \"Select date\",\n                            \"previousCentury\": \"Last century\",\n                            \"previousDecade\": \"Last decade\",\n                            \"previousMonth\": \"Previous month (PageUp)\",\n                            \"previousYear\": \"Last year (Control + left)\",\n                            \"quarterPlaceholder\": \"Select quarter\",\n                            \"rangeMonthPlaceholder\": [\n                              \"Start month\",\n                              \"End month\",\n                            ],\n                            \"rangePlaceholder\": [\n                              \"Start date\",\n                              \"End date\",\n                            ],\n                            \"rangeWeekPlaceholder\": [\n                              \"Start week\",\n                              \"End week\",\n                            ],\n                            \"rangeYearPlaceholder\": [\n                              \"Start year\",\n                              \"End year\",\n                            ],\n                            \"timeSelect\": \"select time\",\n                            \"today\": \"Today\",\n                            \"weekPlaceholder\": \"Select week\",\n                            \"weekSelect\": \"Choose a week\",\n                            \"year\": \"Year\",\n                            \"yearFormat\": \"YYYY\",\n                            \"yearPlaceholder\": \"Select year\",\n                            \"yearSelect\": \"Choose a year\",\n                          }\n                        }\n                        minuteStep={5}\n                        nextIcon={\n                          <span\n                            className=\"ant-picker-next-icon\"\n                          />\n                        }\n                        onChange={[Function]}\n                        picker=\"time\"\n                        pickerRef={\n                          {\n                            \"current\": {\n                              \"blur\": [Function],\n                              \"focus\": [Function],\n                            },\n                          }\n                        }\n                        placeholder=\"Select time\"\n                        prefixCls=\"ant-picker\"\n                        prevIcon={\n                          <span\n                            className=\"ant-picker-prev-icon\"\n                          />\n                        }\n                        showSecond={false}\n                        showToday={true}\n                        suffixIcon={<ClockCircleOutlined />}\n                        superNextIcon={\n                          <span\n                            className=\"ant-picker-super-next-icon\"\n                          />\n                        }\n                        superPrevIcon={\n                          <span\n                            className=\"ant-picker-super-prev-icon\"\n                          />\n                        }\n                        tabIndex={-1}\n                        transitionName=\"slide-up\"\n                        value={\"1999-12-31T22:15:00.000Z\"}\n                      />\n                    </div>\n                  }\n                  popupAlign={{}}\n                  popupClassName=\"\"\n                  popupPlacement=\"bottomLeft\"\n                  popupStyle={{}}\n                  popupTransitionName=\"slide-up\"\n                  popupVisible={false}\n                  prefixCls=\"ant-picker-dropdown\"\n                  showAction={[]}\n                >\n                  <div\n                    className=\"ant-picker\"\n                    key=\"trigger\"\n                    onMouseUp={[Function]}\n                  >\n                    <div\n                      className=\"ant-picker-input\"\n                    >\n                      <input\n                        autoComplete=\"off\"\n                        onBlur={[Function]}\n                        onChange={[Function]}\n                        onFocus={[Function]}\n                        onKeyDown={[Function]}\n                        onMouseDown={[Function]}\n                        placeholder=\"Select time\"\n                        readOnly={true}\n                        size={10}\n                        title=\"00:15\"\n                        value=\"00:15\"\n                      />\n                      <span\n                        className=\"ant-picker-suffix\"\n                      >\n                        <ClockCircleOutlined>\n                          <AntdIcon\n                            icon={\n                              {\n                                \"icon\": {\n                                  \"attrs\": {\n                                    \"focusable\": \"false\",\n                                    \"viewBox\": \"64 64 896 896\",\n                                  },\n                                  \"children\": [\n                                    {\n                                      \"attrs\": {\n                                        \"d\": \"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\n                                      },\n                                      \"tag\": \"path\",\n                                    },\n                                    {\n                                      \"attrs\": {\n                                        \"d\": \"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z\",\n                                      },\n                                      \"tag\": \"path\",\n                                    },\n                                  ],\n                                  \"tag\": \"svg\",\n                                },\n                                \"name\": \"clock-circle\",\n                                \"theme\": \"outlined\",\n                              }\n                            }\n                          >\n                            <span\n                              aria-label=\"clock-circle\"\n                              className=\"anticon anticon-clock-circle\"\n                              role=\"img\"\n                            >\n                              <IconReact\n                                icon={\n                                  {\n                                    \"icon\": {\n                                      \"attrs\": {\n                                        \"focusable\": \"false\",\n                                        \"viewBox\": \"64 64 896 896\",\n                                      },\n                                      \"children\": [\n                                        {\n                                          \"attrs\": {\n                                            \"d\": \"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\n                                          },\n                                          \"tag\": \"path\",\n                                        },\n                                        {\n                                          \"attrs\": {\n                                            \"d\": \"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z\",\n                                          },\n                                          \"tag\": \"path\",\n                                        },\n                                      ],\n                                      \"tag\": \"svg\",\n                                    },\n                                    \"name\": \"clock-circle\",\n                                    \"theme\": \"outlined\",\n                                  }\n                                }\n                              >\n                                <svg\n                                  aria-hidden=\"true\"\n                                  data-icon=\"clock-circle\"\n                                  fill=\"currentColor\"\n                                  focusable=\"false\"\n                                  height=\"1em\"\n                                  key=\"svg-clock-circle\"\n                                  viewBox=\"64 64 896 896\"\n                                  width=\"1em\"\n                                >\n                                  <path\n                                    d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"\n                                    key=\"svg-clock-circle-svg-0\"\n                                  />\n                                  <path\n                                    d=\"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z\"\n                                    key=\"svg-clock-circle-svg-1\"\n                                  />\n                                </svg>\n                              </IconReact>\n                            </span>\n                          </AntdIcon>\n                        </ClockCircleOutlined>\n                      </span>\n                    </div>\n                  </div>\n                </Trigger>\n              </PickerTrigger>\n            </InnerPicker>\n          </Picker>\n        </LocaleReceiver>\n      </TimePicker>\n    </TimePicker>\n    <span\n      className=\"utc\"\n      data-testid=\"utc\"\n    >\n      (\n      22:15\n       UTC)\n    </span>\n  </TimeEditor>\n</div>\n`;\n\nexports[`ScheduleDialog Sets correct schedule settings Sets to \"2 Weeks 22:15 Tuesday\" Sets to correct weekday 1`] = `\n<div\n  data-testid=\"weekday\"\n>\n  <ForwardRef\n    defaultValue=\"Mon\"\n    onChange={[Function]}\n    size=\"medium\"\n  >\n    <div\n      className=\"ant-radio-group ant-radio-group-outline ant-radio-group-medium\"\n    >\n      <ForwardRef(RadioButton)\n        className=\"input\"\n        key=\"Sun\"\n        value=\"Sun\"\n      >\n        <Radio\n          checked={false}\n          className=\"input\"\n          prefixCls=\"ant-radio-button\"\n          type=\"radio\"\n          value=\"Sun\"\n        >\n          <label\n            className=\"input ant-radio-button-wrapper\"\n          >\n            <Checkbox\n              checked={false}\n              className=\"\"\n              defaultChecked={false}\n              onBlur={[Function]}\n              onChange={[Function]}\n              onFocus={[Function]}\n              onKeyDown={[Function]}\n              onKeyPress={[Function]}\n              onKeyUp={[Function]}\n              prefixCls=\"ant-radio-button\"\n              style={{}}\n              type=\"radio\"\n              value=\"Sun\"\n            >\n              <span\n                className=\"ant-radio-button\"\n                style={{}}\n              >\n                <input\n                  checked={false}\n                  className=\"ant-radio-button-input\"\n                  onBlur={[Function]}\n                  onChange={[Function]}\n                  onFocus={[Function]}\n                  onKeyDown={[Function]}\n                  onKeyPress={[Function]}\n                  onKeyUp={[Function]}\n                  type=\"radio\"\n                  value=\"Sun\"\n                />\n                <span\n                  className=\"ant-radio-button-inner\"\n                />\n              </span>\n            </Checkbox>\n            <span>\n              S\n            </span>\n          </label>\n        </Radio>\n      </ForwardRef(RadioButton)>\n      <ForwardRef(RadioButton)\n        className=\"input\"\n        key=\"Mon\"\n        value=\"Mon\"\n      >\n        <Radio\n          checked={true}\n          className=\"input\"\n          prefixCls=\"ant-radio-button\"\n          type=\"radio\"\n          value=\"Mon\"\n        >\n          <label\n            className=\"input ant-radio-button-wrapper ant-radio-button-wrapper-checked\"\n          >\n            <Checkbox\n              checked={true}\n              className=\"\"\n              defaultChecked={false}\n              onBlur={[Function]}\n              onChange={[Function]}\n              onFocus={[Function]}\n              onKeyDown={[Function]}\n              onKeyPress={[Function]}\n              onKeyUp={[Function]}\n              prefixCls=\"ant-radio-button\"\n              style={{}}\n              type=\"radio\"\n              value=\"Mon\"\n            >\n              <span\n                className=\"ant-radio-button ant-radio-button-checked\"\n                style={{}}\n              >\n                <input\n                  checked={true}\n                  className=\"ant-radio-button-input\"\n                  onBlur={[Function]}\n                  onChange={[Function]}\n                  onFocus={[Function]}\n                  onKeyDown={[Function]}\n                  onKeyPress={[Function]}\n                  onKeyUp={[Function]}\n                  type=\"radio\"\n                  value=\"Mon\"\n                />\n                <span\n                  className=\"ant-radio-button-inner\"\n                />\n              </span>\n            </Checkbox>\n            <span>\n              M\n            </span>\n          </label>\n        </Radio>\n      </ForwardRef(RadioButton)>\n      <ForwardRef(RadioButton)\n        className=\"input\"\n        key=\"Tue\"\n        value=\"Tue\"\n      >\n        <Radio\n          checked={false}\n          className=\"input\"\n          prefixCls=\"ant-radio-button\"\n          type=\"radio\"\n          value=\"Tue\"\n        >\n          <label\n            className=\"input ant-radio-button-wrapper\"\n          >\n            <Checkbox\n              checked={false}\n              className=\"\"\n              defaultChecked={false}\n              onBlur={[Function]}\n              onChange={[Function]}\n              onFocus={[Function]}\n              onKeyDown={[Function]}\n              onKeyPress={[Function]}\n              onKeyUp={[Function]}\n              prefixCls=\"ant-radio-button\"\n              style={{}}\n              type=\"radio\"\n              value=\"Tue\"\n            >\n              <span\n                className=\"ant-radio-button\"\n                style={{}}\n              >\n                <input\n                  checked={false}\n                  className=\"ant-radio-button-input\"\n                  onBlur={[Function]}\n                  onChange={[Function]}\n                  onFocus={[Function]}\n                  onKeyDown={[Function]}\n                  onKeyPress={[Function]}\n                  onKeyUp={[Function]}\n                  type=\"radio\"\n                  value=\"Tue\"\n                />\n                <span\n                  className=\"ant-radio-button-inner\"\n                />\n              </span>\n            </Checkbox>\n            <span>\n              T\n            </span>\n          </label>\n        </Radio>\n      </ForwardRef(RadioButton)>\n      <ForwardRef(RadioButton)\n        className=\"input\"\n        key=\"Wed\"\n        value=\"Wed\"\n      >\n        <Radio\n          checked={false}\n          className=\"input\"\n          prefixCls=\"ant-radio-button\"\n          type=\"radio\"\n          value=\"Wed\"\n        >\n          <label\n            className=\"input ant-radio-button-wrapper\"\n          >\n            <Checkbox\n              checked={false}\n              className=\"\"\n              defaultChecked={false}\n              onBlur={[Function]}\n              onChange={[Function]}\n              onFocus={[Function]}\n              onKeyDown={[Function]}\n              onKeyPress={[Function]}\n              onKeyUp={[Function]}\n              prefixCls=\"ant-radio-button\"\n              style={{}}\n              type=\"radio\"\n              value=\"Wed\"\n            >\n              <span\n                className=\"ant-radio-button\"\n                style={{}}\n              >\n                <input\n                  checked={false}\n                  className=\"ant-radio-button-input\"\n                  onBlur={[Function]}\n                  onChange={[Function]}\n                  onFocus={[Function]}\n                  onKeyDown={[Function]}\n                  onKeyPress={[Function]}\n                  onKeyUp={[Function]}\n                  type=\"radio\"\n                  value=\"Wed\"\n                />\n                <span\n                  className=\"ant-radio-button-inner\"\n                />\n              </span>\n            </Checkbox>\n            <span>\n              W\n            </span>\n          </label>\n        </Radio>\n      </ForwardRef(RadioButton)>\n      <ForwardRef(RadioButton)\n        className=\"input\"\n        key=\"Thu\"\n        value=\"Thu\"\n      >\n        <Radio\n          checked={false}\n          className=\"input\"\n          prefixCls=\"ant-radio-button\"\n          type=\"radio\"\n          value=\"Thu\"\n        >\n          <label\n            className=\"input ant-radio-button-wrapper\"\n          >\n            <Checkbox\n              checked={false}\n              className=\"\"\n              defaultChecked={false}\n              onBlur={[Function]}\n              onChange={[Function]}\n              onFocus={[Function]}\n              onKeyDown={[Function]}\n              onKeyPress={[Function]}\n              onKeyUp={[Function]}\n              prefixCls=\"ant-radio-button\"\n              style={{}}\n              type=\"radio\"\n              value=\"Thu\"\n            >\n              <span\n                className=\"ant-radio-button\"\n                style={{}}\n              >\n                <input\n                  checked={false}\n                  className=\"ant-radio-button-input\"\n                  onBlur={[Function]}\n                  onChange={[Function]}\n                  onFocus={[Function]}\n                  onKeyDown={[Function]}\n                  onKeyPress={[Function]}\n                  onKeyUp={[Function]}\n                  type=\"radio\"\n                  value=\"Thu\"\n                />\n                <span\n                  className=\"ant-radio-button-inner\"\n                />\n              </span>\n            </Checkbox>\n            <span>\n              T\n            </span>\n          </label>\n        </Radio>\n      </ForwardRef(RadioButton)>\n      <ForwardRef(RadioButton)\n        className=\"input\"\n        key=\"Fri\"\n        value=\"Fri\"\n      >\n        <Radio\n          checked={false}\n          className=\"input\"\n          prefixCls=\"ant-radio-button\"\n          type=\"radio\"\n          value=\"Fri\"\n        >\n          <label\n            className=\"input ant-radio-button-wrapper\"\n          >\n            <Checkbox\n              checked={false}\n              className=\"\"\n              defaultChecked={false}\n              onBlur={[Function]}\n              onChange={[Function]}\n              onFocus={[Function]}\n              onKeyDown={[Function]}\n              onKeyPress={[Function]}\n              onKeyUp={[Function]}\n              prefixCls=\"ant-radio-button\"\n              style={{}}\n              type=\"radio\"\n              value=\"Fri\"\n            >\n              <span\n                className=\"ant-radio-button\"\n                style={{}}\n              >\n                <input\n                  checked={false}\n                  className=\"ant-radio-button-input\"\n                  onBlur={[Function]}\n                  onChange={[Function]}\n                  onFocus={[Function]}\n                  onKeyDown={[Function]}\n                  onKeyPress={[Function]}\n                  onKeyUp={[Function]}\n                  type=\"radio\"\n                  value=\"Fri\"\n                />\n                <span\n                  className=\"ant-radio-button-inner\"\n                />\n              </span>\n            </Checkbox>\n            <span>\n              F\n            </span>\n          </label>\n        </Radio>\n      </ForwardRef(RadioButton)>\n      <ForwardRef(RadioButton)\n        className=\"input\"\n        key=\"Sat\"\n        value=\"Sat\"\n      >\n        <Radio\n          checked={false}\n          className=\"input\"\n          prefixCls=\"ant-radio-button\"\n          type=\"radio\"\n          value=\"Sat\"\n        >\n          <label\n            className=\"input ant-radio-button-wrapper\"\n          >\n            <Checkbox\n              checked={false}\n              className=\"\"\n              defaultChecked={false}\n              onBlur={[Function]}\n              onChange={[Function]}\n              onFocus={[Function]}\n              onKeyDown={[Function]}\n              onKeyPress={[Function]}\n              onKeyUp={[Function]}\n              prefixCls=\"ant-radio-button\"\n              style={{}}\n              type=\"radio\"\n              value=\"Sat\"\n            >\n              <span\n                className=\"ant-radio-button\"\n                style={{}}\n              >\n                <input\n                  checked={false}\n                  className=\"ant-radio-button-input\"\n                  onBlur={[Function]}\n                  onChange={[Function]}\n                  onFocus={[Function]}\n                  onKeyDown={[Function]}\n                  onKeyPress={[Function]}\n                  onKeyUp={[Function]}\n                  type=\"radio\"\n                  value=\"Sat\"\n                />\n                <span\n                  className=\"ant-radio-button-inner\"\n                />\n              </span>\n            </Checkbox>\n            <span>\n              S\n            </span>\n          </label>\n        </Radio>\n      </ForwardRef(RadioButton)>\n    </div>\n  </ForwardRef>\n</div>\n`;\n\nexports[`ScheduleDialog Sets correct schedule settings Sets to \"5 Minutes\" 1`] = `\n<div\n  data-testid=\"interval\"\n>\n  <Select\n    bordered={true}\n    choiceTransitionName=\"zoom\"\n    className=\"input\"\n    dropdownMatchSelectWidth={false}\n    onChange={[Function]}\n    transitionName=\"slide-up\"\n    value={300}\n  >\n    <Select\n      choiceTransitionName=\"zoom\"\n      className=\"input\"\n      clearIcon={<CloseCircleFilled />}\n      dropdownClassName=\"\"\n      dropdownMatchSelectWidth={false}\n      inputIcon={[Function]}\n      listHeight={256}\n      listItemHeight={24}\n      menuItemSelectedIcon={null}\n      notFoundContent={\n        <Context.Consumer>\n          [Function]\n        </Context.Consumer>\n      }\n      onChange={[Function]}\n      prefixCls=\"ant-select\"\n      removeIcon={<CloseOutlined />}\n      transitionName=\"slide-up\"\n      value={300}\n    >\n      <ForwardRef(Select)\n        choiceTransitionName=\"zoom\"\n        className=\"input\"\n        clearIcon={<CloseCircleFilled />}\n        dropdownClassName=\"\"\n        dropdownMatchSelectWidth={false}\n        inputIcon={[Function]}\n        listHeight={256}\n        listItemHeight={24}\n        menuItemSelectedIcon={null}\n        notFoundContent={\n          <Context.Consumer>\n            [Function]\n          </Context.Consumer>\n        }\n        onChange={[Function]}\n        prefixCls=\"ant-select\"\n        removeIcon={<CloseOutlined />}\n        transitionName=\"slide-up\"\n        value={300}\n      >\n        <div\n          className=\"ant-select input ant-select-single ant-select-show-arrow\"\n          onBlur={[Function]}\n          onFocus={[Function]}\n          onKeyDown={[Function]}\n          onKeyUp={[Function]}\n          onMouseDown={[Function]}\n        >\n          <SelectTrigger\n            containerWidth={null}\n            dropdownClassName=\"\"\n            dropdownMatchSelectWidth={false}\n            empty={false}\n            getTriggerDOMNode={[Function]}\n            popupElement={\n              <OptionList\n                childrenAsData={true}\n                defaultActiveFirstOption={true}\n                flattenOptions={\n                  [\n                    {\n                      \"data\": {\n                        \"children\": \"Never\",\n                        \"key\": \"never\",\n                        \"value\": null,\n                      },\n                      \"groupOption\": false,\n                      \"key\": \"never\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__minute__\",\n                        \"label\": \"Minutes\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 minute\",\n                            \"key\": \"minute-1\",\n                            \"value\": 60,\n                          },\n                          {\n                            \"children\": \"5 minutes\",\n                            \"key\": \"minute-5\",\n                            \"value\": 300,\n                          },\n                          {\n                            \"children\": \"10 minutes\",\n                            \"key\": \"minute-10\",\n                            \"value\": 600,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__minute__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 minute\",\n                        \"key\": \"minute-1\",\n                        \"value\": 60,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"5 minutes\",\n                        \"key\": \"minute-5\",\n                        \"value\": 300,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-5\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"10 minutes\",\n                        \"key\": \"minute-10\",\n                        \"value\": 600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-10\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__hour__\",\n                        \"label\": \"Hours\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 hour\",\n                            \"key\": \"hour-1\",\n                            \"value\": 3600,\n                          },\n                          {\n                            \"children\": \"10 hours\",\n                            \"key\": \"hour-10\",\n                            \"value\": 36000,\n                          },\n                          {\n                            \"children\": \"23 hours\",\n                            \"key\": \"hour-23\",\n                            \"value\": 82800,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__hour__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 hour\",\n                        \"key\": \"hour-1\",\n                        \"value\": 3600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"10 hours\",\n                        \"key\": \"hour-10\",\n                        \"value\": 36000,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-10\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"23 hours\",\n                        \"key\": \"hour-23\",\n                        \"value\": 82800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-23\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__day__\",\n                        \"label\": \"Days\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 day\",\n                            \"key\": \"day-1\",\n                            \"value\": 86400,\n                          },\n                          {\n                            \"children\": \"2 days\",\n                            \"key\": \"day-2\",\n                            \"value\": 172800,\n                          },\n                          {\n                            \"children\": \"6 days\",\n                            \"key\": \"day-6\",\n                            \"value\": 518400,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__day__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 day\",\n                        \"key\": \"day-1\",\n                        \"value\": 86400,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"2 days\",\n                        \"key\": \"day-2\",\n                        \"value\": 172800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-2\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"6 days\",\n                        \"key\": \"day-6\",\n                        \"value\": 518400,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-6\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__week__\",\n                        \"label\": \"Weeks\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 week\",\n                            \"key\": \"week-1\",\n                            \"value\": 604800,\n                          },\n                          {\n                            \"children\": \"2 weeks\",\n                            \"key\": \"week-2\",\n                            \"value\": 1209600,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__week__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 week\",\n                        \"key\": \"week-1\",\n                        \"value\": 604800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"week-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"2 weeks\",\n                        \"key\": \"week-2\",\n                        \"value\": 1209600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"week-2\",\n                    },\n                  ]\n                }\n                height={256}\n                id=\"rc_select_TEST_OR_SSR\"\n                itemHeight={24}\n                menuItemSelectedIcon={null}\n                multiple={false}\n                notFoundContent={\n                  <Context.Consumer>\n                    [Function]\n                  </Context.Consumer>\n                }\n                onActiveValue={[Function]}\n                onMouseEnter={[Function]}\n                onSelect={[Function]}\n                onToggleOpen={[Function]}\n                options={\n                  [\n                    {\n                      \"children\": \"Never\",\n                      \"key\": \"never\",\n                      \"value\": null,\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__minute__\",\n                      \"label\": \"Minutes\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 minute\",\n                          \"key\": \"minute-1\",\n                          \"value\": 60,\n                        },\n                        {\n                          \"children\": \"5 minutes\",\n                          \"key\": \"minute-5\",\n                          \"value\": 300,\n                        },\n                        {\n                          \"children\": \"10 minutes\",\n                          \"key\": \"minute-10\",\n                          \"value\": 600,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__hour__\",\n                      \"label\": \"Hours\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 hour\",\n                          \"key\": \"hour-1\",\n                          \"value\": 3600,\n                        },\n                        {\n                          \"children\": \"10 hours\",\n                          \"key\": \"hour-10\",\n                          \"value\": 36000,\n                        },\n                        {\n                          \"children\": \"23 hours\",\n                          \"key\": \"hour-23\",\n                          \"value\": 82800,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__day__\",\n                      \"label\": \"Days\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 day\",\n                          \"key\": \"day-1\",\n                          \"value\": 86400,\n                        },\n                        {\n                          \"children\": \"2 days\",\n                          \"key\": \"day-2\",\n                          \"value\": 172800,\n                        },\n                        {\n                          \"children\": \"6 days\",\n                          \"key\": \"day-6\",\n                          \"value\": 518400,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__week__\",\n                      \"label\": \"Weeks\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 week\",\n                          \"key\": \"week-1\",\n                          \"value\": 604800,\n                        },\n                        {\n                          \"children\": \"2 weeks\",\n                          \"key\": \"week-2\",\n                          \"value\": 1209600,\n                        },\n                      ],\n                    },\n                  ]\n                }\n                prefixCls=\"ant-select\"\n                searchValue=\"\"\n                values={\n                  Set {\n                    300,\n                  }\n                }\n                virtual={false}\n              />\n            }\n            prefixCls=\"ant-select\"\n            transitionName=\"slide-up\"\n          >\n            <Trigger\n              action={[]}\n              afterPopupVisibleChange={[Function]}\n              autoDestroy={false}\n              blurDelay={0.15}\n              builtinPlacements={\n                {\n                  \"bottomLeft\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tl\",\n                      \"bl\",\n                    ],\n                  },\n                  \"bottomRight\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tr\",\n                      \"br\",\n                    ],\n                  },\n                  \"topLeft\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"bl\",\n                      \"tl\",\n                    ],\n                  },\n                  \"topRight\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"br\",\n                      \"tr\",\n                    ],\n                  },\n                }\n              }\n              defaultPopupVisible={false}\n              destroyPopupOnHide={false}\n              focusDelay={0}\n              getDocument={[Function]}\n              getPopupClassNameFromAlign={[Function]}\n              getTriggerDOMNode={[Function]}\n              hideAction={[]}\n              mask={false}\n              maskClosable={true}\n              mouseEnterDelay={0}\n              mouseLeaveDelay={0.1}\n              onPopupAlign={[Function]}\n              onPopupVisibleChange={[Function]}\n              popup={\n                <div>\n                  <OptionList\n                    childrenAsData={true}\n                    defaultActiveFirstOption={true}\n                    flattenOptions={\n                      [\n                        {\n                          \"data\": {\n                            \"children\": \"Never\",\n                            \"key\": \"never\",\n                            \"value\": null,\n                          },\n                          \"groupOption\": false,\n                          \"key\": \"never\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__minute__\",\n                            \"label\": \"Minutes\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 minute\",\n                                \"key\": \"minute-1\",\n                                \"value\": 60,\n                              },\n                              {\n                                \"children\": \"5 minutes\",\n                                \"key\": \"minute-5\",\n                                \"value\": 300,\n                              },\n                              {\n                                \"children\": \"10 minutes\",\n                                \"key\": \"minute-10\",\n                                \"value\": 600,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__minute__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 minute\",\n                            \"key\": \"minute-1\",\n                            \"value\": 60,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"5 minutes\",\n                            \"key\": \"minute-5\",\n                            \"value\": 300,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-5\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"10 minutes\",\n                            \"key\": \"minute-10\",\n                            \"value\": 600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-10\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__hour__\",\n                            \"label\": \"Hours\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 hour\",\n                                \"key\": \"hour-1\",\n                                \"value\": 3600,\n                              },\n                              {\n                                \"children\": \"10 hours\",\n                                \"key\": \"hour-10\",\n                                \"value\": 36000,\n                              },\n                              {\n                                \"children\": \"23 hours\",\n                                \"key\": \"hour-23\",\n                                \"value\": 82800,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__hour__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 hour\",\n                            \"key\": \"hour-1\",\n                            \"value\": 3600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"10 hours\",\n                            \"key\": \"hour-10\",\n                            \"value\": 36000,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-10\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"23 hours\",\n                            \"key\": \"hour-23\",\n                            \"value\": 82800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-23\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__day__\",\n                            \"label\": \"Days\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 day\",\n                                \"key\": \"day-1\",\n                                \"value\": 86400,\n                              },\n                              {\n                                \"children\": \"2 days\",\n                                \"key\": \"day-2\",\n                                \"value\": 172800,\n                              },\n                              {\n                                \"children\": \"6 days\",\n                                \"key\": \"day-6\",\n                                \"value\": 518400,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__day__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 day\",\n                            \"key\": \"day-1\",\n                            \"value\": 86400,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"2 days\",\n                            \"key\": \"day-2\",\n                            \"value\": 172800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-2\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"6 days\",\n                            \"key\": \"day-6\",\n                            \"value\": 518400,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-6\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__week__\",\n                            \"label\": \"Weeks\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 week\",\n                                \"key\": \"week-1\",\n                                \"value\": 604800,\n                              },\n                              {\n                                \"children\": \"2 weeks\",\n                                \"key\": \"week-2\",\n                                \"value\": 1209600,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__week__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 week\",\n                            \"key\": \"week-1\",\n                            \"value\": 604800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"week-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"2 weeks\",\n                            \"key\": \"week-2\",\n                            \"value\": 1209600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"week-2\",\n                        },\n                      ]\n                    }\n                    height={256}\n                    id=\"rc_select_TEST_OR_SSR\"\n                    itemHeight={24}\n                    menuItemSelectedIcon={null}\n                    multiple={false}\n                    notFoundContent={\n                      <Context.Consumer>\n                        [Function]\n                      </Context.Consumer>\n                    }\n                    onActiveValue={[Function]}\n                    onMouseEnter={[Function]}\n                    onSelect={[Function]}\n                    onToggleOpen={[Function]}\n                    options={\n                      [\n                        {\n                          \"children\": \"Never\",\n                          \"key\": \"never\",\n                          \"value\": null,\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__minute__\",\n                          \"label\": \"Minutes\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 minute\",\n                              \"key\": \"minute-1\",\n                              \"value\": 60,\n                            },\n                            {\n                              \"children\": \"5 minutes\",\n                              \"key\": \"minute-5\",\n                              \"value\": 300,\n                            },\n                            {\n                              \"children\": \"10 minutes\",\n                              \"key\": \"minute-10\",\n                              \"value\": 600,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__hour__\",\n                          \"label\": \"Hours\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 hour\",\n                              \"key\": \"hour-1\",\n                              \"value\": 3600,\n                            },\n                            {\n                              \"children\": \"10 hours\",\n                              \"key\": \"hour-10\",\n                              \"value\": 36000,\n                            },\n                            {\n                              \"children\": \"23 hours\",\n                              \"key\": \"hour-23\",\n                              \"value\": 82800,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__day__\",\n                          \"label\": \"Days\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 day\",\n                              \"key\": \"day-1\",\n                              \"value\": 86400,\n                            },\n                            {\n                              \"children\": \"2 days\",\n                              \"key\": \"day-2\",\n                              \"value\": 172800,\n                            },\n                            {\n                              \"children\": \"6 days\",\n                              \"key\": \"day-6\",\n                              \"value\": 518400,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__week__\",\n                          \"label\": \"Weeks\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 week\",\n                              \"key\": \"week-1\",\n                              \"value\": 604800,\n                            },\n                            {\n                              \"children\": \"2 weeks\",\n                              \"key\": \"week-2\",\n                              \"value\": 1209600,\n                            },\n                          ],\n                        },\n                      ]\n                    }\n                    prefixCls=\"ant-select\"\n                    searchValue=\"\"\n                    values={\n                      Set {\n                        300,\n                      }\n                    }\n                    virtual={false}\n                  />\n                </div>\n              }\n              popupAlign={{}}\n              popupClassName=\"\"\n              popupPlacement=\"bottomLeft\"\n              popupStyle={\n                {\n                  \"minWidth\": null,\n                }\n              }\n              popupTransitionName=\"slide-up\"\n              prefixCls=\"ant-select-dropdown\"\n              showAction={[]}\n            >\n              <Selector\n                accessibilityIndex={0}\n                activeValue={null}\n                choiceTransitionName=\"zoom\"\n                className=\"input\"\n                clearIcon={<CloseCircleFilled />}\n                domRef={\n                  {\n                    \"current\": <div\n                      class=\"ant-select-selector\"\n                    >\n                      <span\n                        class=\"ant-select-selection-search\"\n                      >\n                        <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autocomplete=\"off\"\n                          class=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          readonly=\"\"\n                          role=\"combobox\"\n                          style=\"opacity: 0;\"\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />\n                      </span>\n                      <span\n                        class=\"ant-select-selection-item\"\n                        title=\"5 minutes\"\n                      >\n                        5 minutes\n                      </span>\n                    </div>,\n                  }\n                }\n                dropdownClassName=\"\"\n                dropdownMatchSelectWidth={false}\n                id=\"rc_select_TEST_OR_SSR\"\n                inputElement={null}\n                inputIcon={[Function]}\n                key=\"trigger\"\n                listHeight={256}\n                listItemHeight={24}\n                menuItemSelectedIcon={null}\n                multiple={false}\n                notFoundContent={\n                  <Context.Consumer>\n                    [Function]\n                  </Context.Consumer>\n                }\n                onChange={[Function]}\n                onSearch={[Function]}\n                onSearchSubmit={[Function]}\n                onSelect={[Function]}\n                onToggleOpen={[Function]}\n                prefixCls=\"ant-select\"\n                removeIcon={<CloseOutlined />}\n                searchValue=\"\"\n                showSearch={false}\n                tokenWithEnter={false}\n                transitionName=\"slide-up\"\n                value={300}\n                values={\n                  [\n                    {\n                      \"disabled\": undefined,\n                      \"key\": 300,\n                      \"label\": \"5 minutes\",\n                      \"value\": 300,\n                    },\n                  ]\n                }\n              >\n                <div\n                  className=\"ant-select-selector\"\n                  onClick={[Function]}\n                  onMouseDown={[Function]}\n                >\n                  <SingleSelector\n                    accessibilityIndex={0}\n                    activeValue={null}\n                    choiceTransitionName=\"zoom\"\n                    className=\"input\"\n                    clearIcon={<CloseCircleFilled />}\n                    domRef={\n                      {\n                        \"current\": <div\n                          class=\"ant-select-selector\"\n                        >\n                          <span\n                            class=\"ant-select-selection-search\"\n                          >\n                            <input\n                              aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                              aria-autocomplete=\"list\"\n                              aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                              aria-haspopup=\"listbox\"\n                              aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                              autocomplete=\"off\"\n                              class=\"ant-select-selection-search-input\"\n                              id=\"rc_select_TEST_OR_SSR\"\n                              readonly=\"\"\n                              role=\"combobox\"\n                              style=\"opacity: 0;\"\n                              type=\"search\"\n                              unselectable=\"on\"\n                              value=\"\"\n                            />\n                          </span>\n                          <span\n                            class=\"ant-select-selection-item\"\n                            title=\"5 minutes\"\n                          >\n                            5 minutes\n                          </span>\n                        </div>,\n                      }\n                    }\n                    dropdownClassName=\"\"\n                    dropdownMatchSelectWidth={false}\n                    id=\"rc_select_TEST_OR_SSR\"\n                    inputElement={null}\n                    inputIcon={[Function]}\n                    inputRef={\n                      {\n                        \"current\": <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autocomplete=\"off\"\n                          class=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          readonly=\"\"\n                          role=\"combobox\"\n                          style=\"opacity: 0;\"\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />,\n                      }\n                    }\n                    listHeight={256}\n                    listItemHeight={24}\n                    menuItemSelectedIcon={null}\n                    multiple={false}\n                    notFoundContent={\n                      <Context.Consumer>\n                        [Function]\n                      </Context.Consumer>\n                    }\n                    onChange={[Function]}\n                    onInputChange={[Function]}\n                    onInputCompositionEnd={[Function]}\n                    onInputCompositionStart={[Function]}\n                    onInputKeyDown={[Function]}\n                    onInputMouseDown={[Function]}\n                    onInputPaste={[Function]}\n                    onSearch={[Function]}\n                    onSearchSubmit={[Function]}\n                    onSelect={[Function]}\n                    onToggleOpen={[Function]}\n                    prefixCls=\"ant-select\"\n                    removeIcon={<CloseOutlined />}\n                    searchValue=\"\"\n                    showSearch={false}\n                    tokenWithEnter={false}\n                    transitionName=\"slide-up\"\n                    value={300}\n                    values={\n                      [\n                        {\n                          \"disabled\": undefined,\n                          \"key\": 300,\n                          \"label\": \"5 minutes\",\n                          \"value\": 300,\n                        },\n                      ]\n                    }\n                  >\n                    <span\n                      className=\"ant-select-selection-search\"\n                    >\n                      <Input\n                        accessibilityIndex={0}\n                        attrs={{}}\n                        editable={false}\n                        id=\"rc_select_TEST_OR_SSR\"\n                        inputElement={null}\n                        onChange={[Function]}\n                        onCompositionEnd={[Function]}\n                        onCompositionStart={[Function]}\n                        onKeyDown={[Function]}\n                        onMouseDown={[Function]}\n                        onPaste={[Function]}\n                        prefixCls=\"ant-select\"\n                        value=\"\"\n                      >\n                        <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autoComplete=\"off\"\n                          className=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          onChange={[Function]}\n                          onCompositionEnd={[Function]}\n                          onCompositionStart={[Function]}\n                          onKeyDown={[Function]}\n                          onMouseDown={[Function]}\n                          onPaste={[Function]}\n                          readOnly={true}\n                          role=\"combobox\"\n                          style={\n                            {\n                              \"opacity\": 0,\n                            }\n                          }\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />\n                      </Input>\n                    </span>\n                    <span\n                      className=\"ant-select-selection-item\"\n                      title=\"5 minutes\"\n                    >\n                      5 minutes\n                    </span>\n                  </SingleSelector>\n                </div>\n              </Selector>\n            </Trigger>\n          </SelectTrigger>\n          <TransBtn\n            className=\"ant-select-arrow\"\n            customizeIcon={[Function]}\n            customizeIconProps={\n              {\n                \"focused\": false,\n                \"loading\": undefined,\n                \"open\": undefined,\n                \"searchValue\": \"\",\n                \"showSearch\": false,\n              }\n            }\n          >\n            <span\n              aria-hidden={true}\n              className=\"ant-select-arrow\"\n              onMouseDown={[Function]}\n              style={\n                {\n                  \"WebkitUserSelect\": \"none\",\n                  \"userSelect\": \"none\",\n                }\n              }\n              unselectable=\"on\"\n            >\n              <DownOutlined\n                className=\"ant-select-suffix\"\n              >\n                <AntdIcon\n                  className=\"ant-select-suffix\"\n                  icon={\n                    {\n                      \"icon\": {\n                        \"attrs\": {\n                          \"focusable\": \"false\",\n                          \"viewBox\": \"64 64 896 896\",\n                        },\n                        \"children\": [\n                          {\n                            \"attrs\": {\n                              \"d\": \"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\",\n                            },\n                            \"tag\": \"path\",\n                          },\n                        ],\n                        \"tag\": \"svg\",\n                      },\n                      \"name\": \"down\",\n                      \"theme\": \"outlined\",\n                    }\n                  }\n                >\n                  <span\n                    aria-label=\"down\"\n                    className=\"anticon anticon-down ant-select-suffix\"\n                    role=\"img\"\n                  >\n                    <IconReact\n                      icon={\n                        {\n                          \"icon\": {\n                            \"attrs\": {\n                              \"focusable\": \"false\",\n                              \"viewBox\": \"64 64 896 896\",\n                            },\n                            \"children\": [\n                              {\n                                \"attrs\": {\n                                  \"d\": \"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\",\n                                },\n                                \"tag\": \"path\",\n                              },\n                            ],\n                            \"tag\": \"svg\",\n                          },\n                          \"name\": \"down\",\n                          \"theme\": \"outlined\",\n                        }\n                      }\n                    >\n                      <svg\n                        aria-hidden=\"true\"\n                        data-icon=\"down\"\n                        fill=\"currentColor\"\n                        focusable=\"false\"\n                        height=\"1em\"\n                        key=\"svg-down\"\n                        viewBox=\"64 64 896 896\"\n                        width=\"1em\"\n                      >\n                        <path\n                          d=\"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\"\n                          key=\"svg-down-svg-0\"\n                        />\n                      </svg>\n                    </IconReact>\n                  </span>\n                </AntdIcon>\n              </DownOutlined>\n            </span>\n          </TransBtn>\n        </div>\n      </ForwardRef(Select)>\n    </Select>\n  </Select>\n</div>\n`;\n\nexports[`ScheduleDialog Sets correct schedule settings Sets to \"Never\" 1`] = `\n<div\n  data-testid=\"interval\"\n>\n  <Select\n    bordered={true}\n    choiceTransitionName=\"zoom\"\n    className=\"input\"\n    dropdownMatchSelectWidth={false}\n    onChange={[Function]}\n    transitionName=\"slide-up\"\n  >\n    <Select\n      choiceTransitionName=\"zoom\"\n      className=\"input\"\n      clearIcon={<CloseCircleFilled />}\n      dropdownClassName=\"\"\n      dropdownMatchSelectWidth={false}\n      inputIcon={[Function]}\n      listHeight={256}\n      listItemHeight={24}\n      menuItemSelectedIcon={null}\n      notFoundContent={\n        <Context.Consumer>\n          [Function]\n        </Context.Consumer>\n      }\n      onChange={[Function]}\n      prefixCls=\"ant-select\"\n      removeIcon={<CloseOutlined />}\n      transitionName=\"slide-up\"\n    >\n      <ForwardRef(Select)\n        choiceTransitionName=\"zoom\"\n        className=\"input\"\n        clearIcon={<CloseCircleFilled />}\n        dropdownClassName=\"\"\n        dropdownMatchSelectWidth={false}\n        inputIcon={[Function]}\n        listHeight={256}\n        listItemHeight={24}\n        menuItemSelectedIcon={null}\n        notFoundContent={\n          <Context.Consumer>\n            [Function]\n          </Context.Consumer>\n        }\n        onChange={[Function]}\n        prefixCls=\"ant-select\"\n        removeIcon={<CloseOutlined />}\n        transitionName=\"slide-up\"\n      >\n        <div\n          className=\"ant-select input ant-select-single ant-select-show-arrow\"\n          onBlur={[Function]}\n          onFocus={[Function]}\n          onKeyDown={[Function]}\n          onKeyUp={[Function]}\n          onMouseDown={[Function]}\n        >\n          <SelectTrigger\n            containerWidth={null}\n            dropdownClassName=\"\"\n            dropdownMatchSelectWidth={false}\n            empty={false}\n            getTriggerDOMNode={[Function]}\n            popupElement={\n              <OptionList\n                childrenAsData={true}\n                defaultActiveFirstOption={true}\n                flattenOptions={\n                  [\n                    {\n                      \"data\": {\n                        \"children\": \"Never\",\n                        \"key\": \"never\",\n                        \"value\": null,\n                      },\n                      \"groupOption\": false,\n                      \"key\": \"never\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__minute__\",\n                        \"label\": \"Minutes\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 minute\",\n                            \"key\": \"minute-1\",\n                            \"value\": 60,\n                          },\n                          {\n                            \"children\": \"5 minutes\",\n                            \"key\": \"minute-5\",\n                            \"value\": 300,\n                          },\n                          {\n                            \"children\": \"10 minutes\",\n                            \"key\": \"minute-10\",\n                            \"value\": 600,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__minute__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 minute\",\n                        \"key\": \"minute-1\",\n                        \"value\": 60,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"5 minutes\",\n                        \"key\": \"minute-5\",\n                        \"value\": 300,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-5\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"10 minutes\",\n                        \"key\": \"minute-10\",\n                        \"value\": 600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"minute-10\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__hour__\",\n                        \"label\": \"Hours\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 hour\",\n                            \"key\": \"hour-1\",\n                            \"value\": 3600,\n                          },\n                          {\n                            \"children\": \"10 hours\",\n                            \"key\": \"hour-10\",\n                            \"value\": 36000,\n                          },\n                          {\n                            \"children\": \"23 hours\",\n                            \"key\": \"hour-23\",\n                            \"value\": 82800,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__hour__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 hour\",\n                        \"key\": \"hour-1\",\n                        \"value\": 3600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"10 hours\",\n                        \"key\": \"hour-10\",\n                        \"value\": 36000,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-10\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"23 hours\",\n                        \"key\": \"hour-23\",\n                        \"value\": 82800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"hour-23\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__day__\",\n                        \"label\": \"Days\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 day\",\n                            \"key\": \"day-1\",\n                            \"value\": 86400,\n                          },\n                          {\n                            \"children\": \"2 days\",\n                            \"key\": \"day-2\",\n                            \"value\": 172800,\n                          },\n                          {\n                            \"children\": \"6 days\",\n                            \"key\": \"day-6\",\n                            \"value\": 518400,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__day__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 day\",\n                        \"key\": \"day-1\",\n                        \"value\": 86400,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"2 days\",\n                        \"key\": \"day-2\",\n                        \"value\": 172800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-2\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"6 days\",\n                        \"key\": \"day-6\",\n                        \"value\": 518400,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"day-6\",\n                    },\n                    {\n                      \"data\": {\n                        \"key\": \"__RC_SELECT_GRP__week__\",\n                        \"label\": \"Weeks\",\n                        \"options\": [\n                          {\n                            \"children\": \"1 week\",\n                            \"key\": \"week-1\",\n                            \"value\": 604800,\n                          },\n                          {\n                            \"children\": \"2 weeks\",\n                            \"key\": \"week-2\",\n                            \"value\": 1209600,\n                          },\n                        ],\n                      },\n                      \"group\": true,\n                      \"key\": \"__RC_SELECT_GRP__week__\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"1 week\",\n                        \"key\": \"week-1\",\n                        \"value\": 604800,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"week-1\",\n                    },\n                    {\n                      \"data\": {\n                        \"children\": \"2 weeks\",\n                        \"key\": \"week-2\",\n                        \"value\": 1209600,\n                      },\n                      \"groupOption\": true,\n                      \"key\": \"week-2\",\n                    },\n                  ]\n                }\n                height={256}\n                id=\"rc_select_TEST_OR_SSR\"\n                itemHeight={24}\n                menuItemSelectedIcon={null}\n                multiple={false}\n                notFoundContent={\n                  <Context.Consumer>\n                    [Function]\n                  </Context.Consumer>\n                }\n                onActiveValue={[Function]}\n                onMouseEnter={[Function]}\n                onSelect={[Function]}\n                onToggleOpen={[Function]}\n                options={\n                  [\n                    {\n                      \"children\": \"Never\",\n                      \"key\": \"never\",\n                      \"value\": null,\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__minute__\",\n                      \"label\": \"Minutes\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 minute\",\n                          \"key\": \"minute-1\",\n                          \"value\": 60,\n                        },\n                        {\n                          \"children\": \"5 minutes\",\n                          \"key\": \"minute-5\",\n                          \"value\": 300,\n                        },\n                        {\n                          \"children\": \"10 minutes\",\n                          \"key\": \"minute-10\",\n                          \"value\": 600,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__hour__\",\n                      \"label\": \"Hours\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 hour\",\n                          \"key\": \"hour-1\",\n                          \"value\": 3600,\n                        },\n                        {\n                          \"children\": \"10 hours\",\n                          \"key\": \"hour-10\",\n                          \"value\": 36000,\n                        },\n                        {\n                          \"children\": \"23 hours\",\n                          \"key\": \"hour-23\",\n                          \"value\": 82800,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__day__\",\n                      \"label\": \"Days\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 day\",\n                          \"key\": \"day-1\",\n                          \"value\": 86400,\n                        },\n                        {\n                          \"children\": \"2 days\",\n                          \"key\": \"day-2\",\n                          \"value\": 172800,\n                        },\n                        {\n                          \"children\": \"6 days\",\n                          \"key\": \"day-6\",\n                          \"value\": 518400,\n                        },\n                      ],\n                    },\n                    {\n                      \"key\": \"__RC_SELECT_GRP__week__\",\n                      \"label\": \"Weeks\",\n                      \"options\": [\n                        {\n                          \"children\": \"1 week\",\n                          \"key\": \"week-1\",\n                          \"value\": 604800,\n                        },\n                        {\n                          \"children\": \"2 weeks\",\n                          \"key\": \"week-2\",\n                          \"value\": 1209600,\n                        },\n                      ],\n                    },\n                  ]\n                }\n                prefixCls=\"ant-select\"\n                searchValue=\"\"\n                values={Set {}}\n                virtual={false}\n              />\n            }\n            prefixCls=\"ant-select\"\n            transitionName=\"slide-up\"\n          >\n            <Trigger\n              action={[]}\n              afterPopupVisibleChange={[Function]}\n              autoDestroy={false}\n              blurDelay={0.15}\n              builtinPlacements={\n                {\n                  \"bottomLeft\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tl\",\n                      \"bl\",\n                    ],\n                  },\n                  \"bottomRight\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tr\",\n                      \"br\",\n                    ],\n                  },\n                  \"topLeft\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"bl\",\n                      \"tl\",\n                    ],\n                  },\n                  \"topRight\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"br\",\n                      \"tr\",\n                    ],\n                  },\n                }\n              }\n              defaultPopupVisible={false}\n              destroyPopupOnHide={false}\n              focusDelay={0}\n              getDocument={[Function]}\n              getPopupClassNameFromAlign={[Function]}\n              getTriggerDOMNode={[Function]}\n              hideAction={[]}\n              mask={false}\n              maskClosable={true}\n              mouseEnterDelay={0}\n              mouseLeaveDelay={0.1}\n              onPopupAlign={[Function]}\n              onPopupVisibleChange={[Function]}\n              popup={\n                <div>\n                  <OptionList\n                    childrenAsData={true}\n                    defaultActiveFirstOption={true}\n                    flattenOptions={\n                      [\n                        {\n                          \"data\": {\n                            \"children\": \"Never\",\n                            \"key\": \"never\",\n                            \"value\": null,\n                          },\n                          \"groupOption\": false,\n                          \"key\": \"never\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__minute__\",\n                            \"label\": \"Minutes\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 minute\",\n                                \"key\": \"minute-1\",\n                                \"value\": 60,\n                              },\n                              {\n                                \"children\": \"5 minutes\",\n                                \"key\": \"minute-5\",\n                                \"value\": 300,\n                              },\n                              {\n                                \"children\": \"10 minutes\",\n                                \"key\": \"minute-10\",\n                                \"value\": 600,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__minute__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 minute\",\n                            \"key\": \"minute-1\",\n                            \"value\": 60,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"5 minutes\",\n                            \"key\": \"minute-5\",\n                            \"value\": 300,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-5\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"10 minutes\",\n                            \"key\": \"minute-10\",\n                            \"value\": 600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"minute-10\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__hour__\",\n                            \"label\": \"Hours\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 hour\",\n                                \"key\": \"hour-1\",\n                                \"value\": 3600,\n                              },\n                              {\n                                \"children\": \"10 hours\",\n                                \"key\": \"hour-10\",\n                                \"value\": 36000,\n                              },\n                              {\n                                \"children\": \"23 hours\",\n                                \"key\": \"hour-23\",\n                                \"value\": 82800,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__hour__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 hour\",\n                            \"key\": \"hour-1\",\n                            \"value\": 3600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"10 hours\",\n                            \"key\": \"hour-10\",\n                            \"value\": 36000,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-10\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"23 hours\",\n                            \"key\": \"hour-23\",\n                            \"value\": 82800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"hour-23\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__day__\",\n                            \"label\": \"Days\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 day\",\n                                \"key\": \"day-1\",\n                                \"value\": 86400,\n                              },\n                              {\n                                \"children\": \"2 days\",\n                                \"key\": \"day-2\",\n                                \"value\": 172800,\n                              },\n                              {\n                                \"children\": \"6 days\",\n                                \"key\": \"day-6\",\n                                \"value\": 518400,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__day__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 day\",\n                            \"key\": \"day-1\",\n                            \"value\": 86400,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"2 days\",\n                            \"key\": \"day-2\",\n                            \"value\": 172800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-2\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"6 days\",\n                            \"key\": \"day-6\",\n                            \"value\": 518400,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"day-6\",\n                        },\n                        {\n                          \"data\": {\n                            \"key\": \"__RC_SELECT_GRP__week__\",\n                            \"label\": \"Weeks\",\n                            \"options\": [\n                              {\n                                \"children\": \"1 week\",\n                                \"key\": \"week-1\",\n                                \"value\": 604800,\n                              },\n                              {\n                                \"children\": \"2 weeks\",\n                                \"key\": \"week-2\",\n                                \"value\": 1209600,\n                              },\n                            ],\n                          },\n                          \"group\": true,\n                          \"key\": \"__RC_SELECT_GRP__week__\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"1 week\",\n                            \"key\": \"week-1\",\n                            \"value\": 604800,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"week-1\",\n                        },\n                        {\n                          \"data\": {\n                            \"children\": \"2 weeks\",\n                            \"key\": \"week-2\",\n                            \"value\": 1209600,\n                          },\n                          \"groupOption\": true,\n                          \"key\": \"week-2\",\n                        },\n                      ]\n                    }\n                    height={256}\n                    id=\"rc_select_TEST_OR_SSR\"\n                    itemHeight={24}\n                    menuItemSelectedIcon={null}\n                    multiple={false}\n                    notFoundContent={\n                      <Context.Consumer>\n                        [Function]\n                      </Context.Consumer>\n                    }\n                    onActiveValue={[Function]}\n                    onMouseEnter={[Function]}\n                    onSelect={[Function]}\n                    onToggleOpen={[Function]}\n                    options={\n                      [\n                        {\n                          \"children\": \"Never\",\n                          \"key\": \"never\",\n                          \"value\": null,\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__minute__\",\n                          \"label\": \"Minutes\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 minute\",\n                              \"key\": \"minute-1\",\n                              \"value\": 60,\n                            },\n                            {\n                              \"children\": \"5 minutes\",\n                              \"key\": \"minute-5\",\n                              \"value\": 300,\n                            },\n                            {\n                              \"children\": \"10 minutes\",\n                              \"key\": \"minute-10\",\n                              \"value\": 600,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__hour__\",\n                          \"label\": \"Hours\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 hour\",\n                              \"key\": \"hour-1\",\n                              \"value\": 3600,\n                            },\n                            {\n                              \"children\": \"10 hours\",\n                              \"key\": \"hour-10\",\n                              \"value\": 36000,\n                            },\n                            {\n                              \"children\": \"23 hours\",\n                              \"key\": \"hour-23\",\n                              \"value\": 82800,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__day__\",\n                          \"label\": \"Days\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 day\",\n                              \"key\": \"day-1\",\n                              \"value\": 86400,\n                            },\n                            {\n                              \"children\": \"2 days\",\n                              \"key\": \"day-2\",\n                              \"value\": 172800,\n                            },\n                            {\n                              \"children\": \"6 days\",\n                              \"key\": \"day-6\",\n                              \"value\": 518400,\n                            },\n                          ],\n                        },\n                        {\n                          \"key\": \"__RC_SELECT_GRP__week__\",\n                          \"label\": \"Weeks\",\n                          \"options\": [\n                            {\n                              \"children\": \"1 week\",\n                              \"key\": \"week-1\",\n                              \"value\": 604800,\n                            },\n                            {\n                              \"children\": \"2 weeks\",\n                              \"key\": \"week-2\",\n                              \"value\": 1209600,\n                            },\n                          ],\n                        },\n                      ]\n                    }\n                    prefixCls=\"ant-select\"\n                    searchValue=\"\"\n                    values={Set {}}\n                    virtual={false}\n                  />\n                </div>\n              }\n              popupAlign={{}}\n              popupClassName=\"\"\n              popupPlacement=\"bottomLeft\"\n              popupStyle={\n                {\n                  \"minWidth\": null,\n                }\n              }\n              popupTransitionName=\"slide-up\"\n              prefixCls=\"ant-select-dropdown\"\n              showAction={[]}\n            >\n              <Selector\n                accessibilityIndex={0}\n                activeValue={null}\n                choiceTransitionName=\"zoom\"\n                className=\"input\"\n                clearIcon={<CloseCircleFilled />}\n                domRef={\n                  {\n                    \"current\": <div\n                      class=\"ant-select-selector\"\n                    >\n                      <span\n                        class=\"ant-select-selection-search\"\n                      >\n                        <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autocomplete=\"off\"\n                          class=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          readonly=\"\"\n                          role=\"combobox\"\n                          style=\"opacity: 0;\"\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />\n                      </span>\n                      <span\n                        class=\"ant-select-selection-placeholder\"\n                      />\n                    </div>,\n                  }\n                }\n                dropdownClassName=\"\"\n                dropdownMatchSelectWidth={false}\n                id=\"rc_select_TEST_OR_SSR\"\n                inputElement={null}\n                inputIcon={[Function]}\n                key=\"trigger\"\n                listHeight={256}\n                listItemHeight={24}\n                menuItemSelectedIcon={null}\n                multiple={false}\n                notFoundContent={\n                  <Context.Consumer>\n                    [Function]\n                  </Context.Consumer>\n                }\n                onChange={[Function]}\n                onSearch={[Function]}\n                onSearchSubmit={[Function]}\n                onSelect={[Function]}\n                onToggleOpen={[Function]}\n                prefixCls=\"ant-select\"\n                removeIcon={<CloseOutlined />}\n                searchValue=\"\"\n                showSearch={false}\n                tokenWithEnter={false}\n                transitionName=\"slide-up\"\n                values={[]}\n              >\n                <div\n                  className=\"ant-select-selector\"\n                  onClick={[Function]}\n                  onMouseDown={[Function]}\n                >\n                  <SingleSelector\n                    accessibilityIndex={0}\n                    activeValue={null}\n                    choiceTransitionName=\"zoom\"\n                    className=\"input\"\n                    clearIcon={<CloseCircleFilled />}\n                    domRef={\n                      {\n                        \"current\": <div\n                          class=\"ant-select-selector\"\n                        >\n                          <span\n                            class=\"ant-select-selection-search\"\n                          >\n                            <input\n                              aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                              aria-autocomplete=\"list\"\n                              aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                              aria-haspopup=\"listbox\"\n                              aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                              autocomplete=\"off\"\n                              class=\"ant-select-selection-search-input\"\n                              id=\"rc_select_TEST_OR_SSR\"\n                              readonly=\"\"\n                              role=\"combobox\"\n                              style=\"opacity: 0;\"\n                              type=\"search\"\n                              unselectable=\"on\"\n                              value=\"\"\n                            />\n                          </span>\n                          <span\n                            class=\"ant-select-selection-placeholder\"\n                          />\n                        </div>,\n                      }\n                    }\n                    dropdownClassName=\"\"\n                    dropdownMatchSelectWidth={false}\n                    id=\"rc_select_TEST_OR_SSR\"\n                    inputElement={null}\n                    inputIcon={[Function]}\n                    inputRef={\n                      {\n                        \"current\": <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autocomplete=\"off\"\n                          class=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          readonly=\"\"\n                          role=\"combobox\"\n                          style=\"opacity: 0;\"\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />,\n                      }\n                    }\n                    listHeight={256}\n                    listItemHeight={24}\n                    menuItemSelectedIcon={null}\n                    multiple={false}\n                    notFoundContent={\n                      <Context.Consumer>\n                        [Function]\n                      </Context.Consumer>\n                    }\n                    onChange={[Function]}\n                    onInputChange={[Function]}\n                    onInputCompositionEnd={[Function]}\n                    onInputCompositionStart={[Function]}\n                    onInputKeyDown={[Function]}\n                    onInputMouseDown={[Function]}\n                    onInputPaste={[Function]}\n                    onSearch={[Function]}\n                    onSearchSubmit={[Function]}\n                    onSelect={[Function]}\n                    onToggleOpen={[Function]}\n                    prefixCls=\"ant-select\"\n                    removeIcon={<CloseOutlined />}\n                    searchValue=\"\"\n                    showSearch={false}\n                    tokenWithEnter={false}\n                    transitionName=\"slide-up\"\n                    values={[]}\n                  >\n                    <span\n                      className=\"ant-select-selection-search\"\n                    >\n                      <Input\n                        accessibilityIndex={0}\n                        attrs={{}}\n                        editable={false}\n                        id=\"rc_select_TEST_OR_SSR\"\n                        inputElement={null}\n                        onChange={[Function]}\n                        onCompositionEnd={[Function]}\n                        onCompositionStart={[Function]}\n                        onKeyDown={[Function]}\n                        onMouseDown={[Function]}\n                        onPaste={[Function]}\n                        prefixCls=\"ant-select\"\n                        value=\"\"\n                      >\n                        <input\n                          aria-activedescendant=\"rc_select_TEST_OR_SSR_list_0\"\n                          aria-autocomplete=\"list\"\n                          aria-controls=\"rc_select_TEST_OR_SSR_list\"\n                          aria-haspopup=\"listbox\"\n                          aria-owns=\"rc_select_TEST_OR_SSR_list\"\n                          autoComplete=\"off\"\n                          className=\"ant-select-selection-search-input\"\n                          id=\"rc_select_TEST_OR_SSR\"\n                          onChange={[Function]}\n                          onCompositionEnd={[Function]}\n                          onCompositionStart={[Function]}\n                          onKeyDown={[Function]}\n                          onMouseDown={[Function]}\n                          onPaste={[Function]}\n                          readOnly={true}\n                          role=\"combobox\"\n                          style={\n                            {\n                              \"opacity\": 0,\n                            }\n                          }\n                          type=\"search\"\n                          unselectable=\"on\"\n                          value=\"\"\n                        />\n                      </Input>\n                    </span>\n                    <span\n                      className=\"ant-select-selection-placeholder\"\n                    />\n                  </SingleSelector>\n                </div>\n              </Selector>\n            </Trigger>\n          </SelectTrigger>\n          <TransBtn\n            className=\"ant-select-arrow\"\n            customizeIcon={[Function]}\n            customizeIconProps={\n              {\n                \"focused\": false,\n                \"loading\": undefined,\n                \"open\": undefined,\n                \"searchValue\": \"\",\n                \"showSearch\": false,\n              }\n            }\n          >\n            <span\n              aria-hidden={true}\n              className=\"ant-select-arrow\"\n              onMouseDown={[Function]}\n              style={\n                {\n                  \"WebkitUserSelect\": \"none\",\n                  \"userSelect\": \"none\",\n                }\n              }\n              unselectable=\"on\"\n            >\n              <DownOutlined\n                className=\"ant-select-suffix\"\n              >\n                <AntdIcon\n                  className=\"ant-select-suffix\"\n                  icon={\n                    {\n                      \"icon\": {\n                        \"attrs\": {\n                          \"focusable\": \"false\",\n                          \"viewBox\": \"64 64 896 896\",\n                        },\n                        \"children\": [\n                          {\n                            \"attrs\": {\n                              \"d\": \"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\",\n                            },\n                            \"tag\": \"path\",\n                          },\n                        ],\n                        \"tag\": \"svg\",\n                      },\n                      \"name\": \"down\",\n                      \"theme\": \"outlined\",\n                    }\n                  }\n                >\n                  <span\n                    aria-label=\"down\"\n                    className=\"anticon anticon-down ant-select-suffix\"\n                    role=\"img\"\n                  >\n                    <IconReact\n                      icon={\n                        {\n                          \"icon\": {\n                            \"attrs\": {\n                              \"focusable\": \"false\",\n                              \"viewBox\": \"64 64 896 896\",\n                            },\n                            \"children\": [\n                              {\n                                \"attrs\": {\n                                  \"d\": \"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\",\n                                },\n                                \"tag\": \"path\",\n                              },\n                            ],\n                            \"tag\": \"svg\",\n                          },\n                          \"name\": \"down\",\n                          \"theme\": \"outlined\",\n                        }\n                      }\n                    >\n                      <svg\n                        aria-hidden=\"true\"\n                        data-icon=\"down\"\n                        fill=\"currentColor\"\n                        focusable=\"false\"\n                        height=\"1em\"\n                        key=\"svg-down\"\n                        viewBox=\"64 64 896 896\"\n                        width=\"1em\"\n                      >\n                        <path\n                          d=\"M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z\"\n                          key=\"svg-down-svg-0\"\n                        />\n                      </svg>\n                    </IconReact>\n                  </span>\n                </AntdIcon>\n              </DownOutlined>\n            </span>\n          </TransBtn>\n        </div>\n      </ForwardRef(Select)>\n    </Select>\n  </Select>\n</div>\n`;\n\nexports[`ScheduleDialog Sets correct schedule settings Supports 30 days interval with no time value Time is none 1`] = `\n<div\n  data-testid=\"time\"\n>\n  <TimeEditor\n    defaultValue={null}\n    onChange={[Function]}\n  >\n    <TimePicker\n      allowClear={false}\n      format=\"HH:mm\"\n      minuteStep={5}\n      onChange={[Function]}\n      value={null}\n    >\n      <TimePicker\n        allowClear={false}\n        format=\"HH:mm\"\n        minuteStep={5}\n        onChange={[Function]}\n        value={null}\n      >\n        <LocaleReceiver\n          componentName=\"DatePicker\"\n          defaultLocale={[Function]}\n        >\n          <Picker\n            allowClear={false}\n            className=\"\"\n            clearIcon={<CloseCircleFilled />}\n            components={\n              {\n                \"button\": [Function],\n                \"rangeItem\": [Function],\n              }\n            }\n            format=\"HH:mm\"\n            generateConfig={\n              {\n                \"addDate\": [Function],\n                \"addMonth\": [Function],\n                \"addYear\": [Function],\n                \"getDate\": [Function],\n                \"getHour\": [Function],\n                \"getMinute\": [Function],\n                \"getMonth\": [Function],\n                \"getNow\": [Function],\n                \"getSecond\": [Function],\n                \"getWeekDay\": [Function],\n                \"getYear\": [Function],\n                \"isAfter\": [Function],\n                \"isValidate\": [Function],\n                \"locale\": {\n                  \"format\": [Function],\n                  \"getShortMonths\": [Function],\n                  \"getShortWeekDays\": [Function],\n                  \"getWeek\": [Function],\n                  \"getWeekFirstDay\": [Function],\n                  \"parse\": [Function],\n                },\n                \"setDate\": [Function],\n                \"setHour\": [Function],\n                \"setMinute\": [Function],\n                \"setMonth\": [Function],\n                \"setSecond\": [Function],\n                \"setYear\": [Function],\n              }\n            }\n            locale={\n              {\n                \"backToToday\": \"Back to today\",\n                \"clear\": \"Clear\",\n                \"dateFormat\": \"M/D/YYYY\",\n                \"dateSelect\": \"select date\",\n                \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                \"dayFormat\": \"D\",\n                \"decadeSelect\": \"Choose a decade\",\n                \"locale\": \"en_US\",\n                \"month\": \"Month\",\n                \"monthBeforeYear\": true,\n                \"monthPlaceholder\": \"Select month\",\n                \"monthSelect\": \"Choose a month\",\n                \"nextCentury\": \"Next century\",\n                \"nextDecade\": \"Next decade\",\n                \"nextMonth\": \"Next month (PageDown)\",\n                \"nextYear\": \"Next year (Control + right)\",\n                \"now\": \"Now\",\n                \"ok\": \"Ok\",\n                \"placeholder\": \"Select date\",\n                \"previousCentury\": \"Last century\",\n                \"previousDecade\": \"Last decade\",\n                \"previousMonth\": \"Previous month (PageUp)\",\n                \"previousYear\": \"Last year (Control + left)\",\n                \"quarterPlaceholder\": \"Select quarter\",\n                \"rangeMonthPlaceholder\": [\n                  \"Start month\",\n                  \"End month\",\n                ],\n                \"rangePlaceholder\": [\n                  \"Start date\",\n                  \"End date\",\n                ],\n                \"rangeWeekPlaceholder\": [\n                  \"Start week\",\n                  \"End week\",\n                ],\n                \"rangeYearPlaceholder\": [\n                  \"Start year\",\n                  \"End year\",\n                ],\n                \"timeSelect\": \"select time\",\n                \"today\": \"Today\",\n                \"weekPlaceholder\": \"Select week\",\n                \"weekSelect\": \"Choose a week\",\n                \"year\": \"Year\",\n                \"yearFormat\": \"YYYY\",\n                \"yearPlaceholder\": \"Select year\",\n                \"yearSelect\": \"Choose a year\",\n              }\n            }\n            minuteStep={5}\n            nextIcon={\n              <span\n                className=\"ant-picker-next-icon\"\n              />\n            }\n            onChange={[Function]}\n            picker=\"time\"\n            placeholder=\"Select time\"\n            prefixCls=\"ant-picker\"\n            prevIcon={\n              <span\n                className=\"ant-picker-prev-icon\"\n              />\n            }\n            showSecond={false}\n            showToday={true}\n            suffixIcon={<ClockCircleOutlined />}\n            superNextIcon={\n              <span\n                className=\"ant-picker-super-next-icon\"\n              />\n            }\n            superPrevIcon={\n              <span\n                className=\"ant-picker-super-prev-icon\"\n              />\n            }\n            transitionName=\"slide-up\"\n            value={null}\n          >\n            <InnerPicker\n              allowClear={false}\n              className=\"\"\n              clearIcon={<CloseCircleFilled />}\n              components={\n                {\n                  \"button\": [Function],\n                  \"rangeItem\": [Function],\n                }\n              }\n              format=\"HH:mm\"\n              generateConfig={\n                {\n                  \"addDate\": [Function],\n                  \"addMonth\": [Function],\n                  \"addYear\": [Function],\n                  \"getDate\": [Function],\n                  \"getHour\": [Function],\n                  \"getMinute\": [Function],\n                  \"getMonth\": [Function],\n                  \"getNow\": [Function],\n                  \"getSecond\": [Function],\n                  \"getWeekDay\": [Function],\n                  \"getYear\": [Function],\n                  \"isAfter\": [Function],\n                  \"isValidate\": [Function],\n                  \"locale\": {\n                    \"format\": [Function],\n                    \"getShortMonths\": [Function],\n                    \"getShortWeekDays\": [Function],\n                    \"getWeek\": [Function],\n                    \"getWeekFirstDay\": [Function],\n                    \"parse\": [Function],\n                  },\n                  \"setDate\": [Function],\n                  \"setHour\": [Function],\n                  \"setMinute\": [Function],\n                  \"setMonth\": [Function],\n                  \"setSecond\": [Function],\n                  \"setYear\": [Function],\n                }\n              }\n              locale={\n                {\n                  \"backToToday\": \"Back to today\",\n                  \"clear\": \"Clear\",\n                  \"dateFormat\": \"M/D/YYYY\",\n                  \"dateSelect\": \"select date\",\n                  \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                  \"dayFormat\": \"D\",\n                  \"decadeSelect\": \"Choose a decade\",\n                  \"locale\": \"en_US\",\n                  \"month\": \"Month\",\n                  \"monthBeforeYear\": true,\n                  \"monthPlaceholder\": \"Select month\",\n                  \"monthSelect\": \"Choose a month\",\n                  \"nextCentury\": \"Next century\",\n                  \"nextDecade\": \"Next decade\",\n                  \"nextMonth\": \"Next month (PageDown)\",\n                  \"nextYear\": \"Next year (Control + right)\",\n                  \"now\": \"Now\",\n                  \"ok\": \"Ok\",\n                  \"placeholder\": \"Select date\",\n                  \"previousCentury\": \"Last century\",\n                  \"previousDecade\": \"Last decade\",\n                  \"previousMonth\": \"Previous month (PageUp)\",\n                  \"previousYear\": \"Last year (Control + left)\",\n                  \"quarterPlaceholder\": \"Select quarter\",\n                  \"rangeMonthPlaceholder\": [\n                    \"Start month\",\n                    \"End month\",\n                  ],\n                  \"rangePlaceholder\": [\n                    \"Start date\",\n                    \"End date\",\n                  ],\n                  \"rangeWeekPlaceholder\": [\n                    \"Start week\",\n                    \"End week\",\n                  ],\n                  \"rangeYearPlaceholder\": [\n                    \"Start year\",\n                    \"End year\",\n                  ],\n                  \"timeSelect\": \"select time\",\n                  \"today\": \"Today\",\n                  \"weekPlaceholder\": \"Select week\",\n                  \"weekSelect\": \"Choose a week\",\n                  \"year\": \"Year\",\n                  \"yearFormat\": \"YYYY\",\n                  \"yearPlaceholder\": \"Select year\",\n                  \"yearSelect\": \"Choose a year\",\n                }\n              }\n              minuteStep={5}\n              nextIcon={\n                <span\n                  className=\"ant-picker-next-icon\"\n                />\n              }\n              onChange={[Function]}\n              picker=\"time\"\n              pickerRef={\n                {\n                  \"current\": {\n                    \"blur\": [Function],\n                    \"focus\": [Function],\n                  },\n                }\n              }\n              placeholder=\"Select time\"\n              prefixCls=\"ant-picker\"\n              prevIcon={\n                <span\n                  className=\"ant-picker-prev-icon\"\n                />\n              }\n              showSecond={false}\n              showToday={true}\n              suffixIcon={<ClockCircleOutlined />}\n              superNextIcon={\n                <span\n                  className=\"ant-picker-super-next-icon\"\n                />\n              }\n              superPrevIcon={\n                <span\n                  className=\"ant-picker-super-prev-icon\"\n                />\n              }\n              transitionName=\"slide-up\"\n              value={null}\n            >\n              <PickerTrigger\n                popupElement={\n                  <div\n                    className=\"ant-picker-panel-container\"\n                    onMouseDown={[Function]}\n                  >\n                    <PickerPanel\n                      allowClear={false}\n                      className=\"ant-picker-panel-focused\"\n                      clearIcon={<CloseCircleFilled />}\n                      components={\n                        {\n                          \"button\": [Function],\n                          \"rangeItem\": [Function],\n                        }\n                      }\n                      format=\"HH:mm\"\n                      generateConfig={\n                        {\n                          \"addDate\": [Function],\n                          \"addMonth\": [Function],\n                          \"addYear\": [Function],\n                          \"getDate\": [Function],\n                          \"getHour\": [Function],\n                          \"getMinute\": [Function],\n                          \"getMonth\": [Function],\n                          \"getNow\": [Function],\n                          \"getSecond\": [Function],\n                          \"getWeekDay\": [Function],\n                          \"getYear\": [Function],\n                          \"isAfter\": [Function],\n                          \"isValidate\": [Function],\n                          \"locale\": {\n                            \"format\": [Function],\n                            \"getShortMonths\": [Function],\n                            \"getShortWeekDays\": [Function],\n                            \"getWeek\": [Function],\n                            \"getWeekFirstDay\": [Function],\n                            \"parse\": [Function],\n                          },\n                          \"setDate\": [Function],\n                          \"setHour\": [Function],\n                          \"setMinute\": [Function],\n                          \"setMonth\": [Function],\n                          \"setSecond\": [Function],\n                          \"setYear\": [Function],\n                        }\n                      }\n                      locale={\n                        {\n                          \"backToToday\": \"Back to today\",\n                          \"clear\": \"Clear\",\n                          \"dateFormat\": \"M/D/YYYY\",\n                          \"dateSelect\": \"select date\",\n                          \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                          \"dayFormat\": \"D\",\n                          \"decadeSelect\": \"Choose a decade\",\n                          \"locale\": \"en_US\",\n                          \"month\": \"Month\",\n                          \"monthBeforeYear\": true,\n                          \"monthPlaceholder\": \"Select month\",\n                          \"monthSelect\": \"Choose a month\",\n                          \"nextCentury\": \"Next century\",\n                          \"nextDecade\": \"Next decade\",\n                          \"nextMonth\": \"Next month (PageDown)\",\n                          \"nextYear\": \"Next year (Control + right)\",\n                          \"now\": \"Now\",\n                          \"ok\": \"Ok\",\n                          \"placeholder\": \"Select date\",\n                          \"previousCentury\": \"Last century\",\n                          \"previousDecade\": \"Last decade\",\n                          \"previousMonth\": \"Previous month (PageUp)\",\n                          \"previousYear\": \"Last year (Control + left)\",\n                          \"quarterPlaceholder\": \"Select quarter\",\n                          \"rangeMonthPlaceholder\": [\n                            \"Start month\",\n                            \"End month\",\n                          ],\n                          \"rangePlaceholder\": [\n                            \"Start date\",\n                            \"End date\",\n                          ],\n                          \"rangeWeekPlaceholder\": [\n                            \"Start week\",\n                            \"End week\",\n                          ],\n                          \"rangeYearPlaceholder\": [\n                            \"Start year\",\n                            \"End year\",\n                          ],\n                          \"timeSelect\": \"select time\",\n                          \"today\": \"Today\",\n                          \"weekPlaceholder\": \"Select week\",\n                          \"weekSelect\": \"Choose a week\",\n                          \"year\": \"Year\",\n                          \"yearFormat\": \"YYYY\",\n                          \"yearPlaceholder\": \"Select year\",\n                          \"yearSelect\": \"Choose a year\",\n                        }\n                      }\n                      minuteStep={5}\n                      nextIcon={\n                        <span\n                          className=\"ant-picker-next-icon\"\n                        />\n                      }\n                      onChange={[Function]}\n                      picker=\"time\"\n                      pickerRef={\n                        {\n                          \"current\": {\n                            \"blur\": [Function],\n                            \"focus\": [Function],\n                          },\n                        }\n                      }\n                      placeholder=\"Select time\"\n                      prefixCls=\"ant-picker\"\n                      prevIcon={\n                        <span\n                          className=\"ant-picker-prev-icon\"\n                        />\n                      }\n                      showSecond={false}\n                      showToday={true}\n                      suffixIcon={<ClockCircleOutlined />}\n                      superNextIcon={\n                        <span\n                          className=\"ant-picker-super-next-icon\"\n                        />\n                      }\n                      superPrevIcon={\n                        <span\n                          className=\"ant-picker-super-prev-icon\"\n                        />\n                      }\n                      tabIndex={-1}\n                      transitionName=\"slide-up\"\n                      value={null}\n                    />\n                  </div>\n                }\n                popupPlacement=\"bottomLeft\"\n                prefixCls=\"ant-picker\"\n                transitionName=\"slide-up\"\n                visible={false}\n              >\n                <Trigger\n                  action={[]}\n                  afterPopupVisibleChange={[Function]}\n                  autoDestroy={false}\n                  blurDelay={0.15}\n                  builtinPlacements={\n                    {\n                      \"bottomLeft\": {\n                        \"offset\": [\n                          0,\n                          4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 1,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"tl\",\n                          \"bl\",\n                        ],\n                      },\n                      \"bottomRight\": {\n                        \"offset\": [\n                          0,\n                          4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 1,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"tr\",\n                          \"br\",\n                        ],\n                      },\n                      \"topLeft\": {\n                        \"offset\": [\n                          0,\n                          -4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 0,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"bl\",\n                          \"tl\",\n                        ],\n                      },\n                      \"topRight\": {\n                        \"offset\": [\n                          0,\n                          -4,\n                        ],\n                        \"overflow\": {\n                          \"adjustX\": 0,\n                          \"adjustY\": 1,\n                        },\n                        \"points\": [\n                          \"br\",\n                          \"tr\",\n                        ],\n                      },\n                    }\n                  }\n                  defaultPopupVisible={false}\n                  destroyPopupOnHide={false}\n                  focusDelay={0}\n                  getDocument={[Function]}\n                  getPopupClassNameFromAlign={[Function]}\n                  hideAction={[]}\n                  mask={false}\n                  maskClosable={true}\n                  mouseEnterDelay={0}\n                  mouseLeaveDelay={0.1}\n                  onPopupAlign={[Function]}\n                  onPopupVisibleChange={[Function]}\n                  popup={\n                    <div\n                      className=\"ant-picker-panel-container\"\n                      onMouseDown={[Function]}\n                    >\n                      <PickerPanel\n                        allowClear={false}\n                        className=\"ant-picker-panel-focused\"\n                        clearIcon={<CloseCircleFilled />}\n                        components={\n                          {\n                            \"button\": [Function],\n                            \"rangeItem\": [Function],\n                          }\n                        }\n                        format=\"HH:mm\"\n                        generateConfig={\n                          {\n                            \"addDate\": [Function],\n                            \"addMonth\": [Function],\n                            \"addYear\": [Function],\n                            \"getDate\": [Function],\n                            \"getHour\": [Function],\n                            \"getMinute\": [Function],\n                            \"getMonth\": [Function],\n                            \"getNow\": [Function],\n                            \"getSecond\": [Function],\n                            \"getWeekDay\": [Function],\n                            \"getYear\": [Function],\n                            \"isAfter\": [Function],\n                            \"isValidate\": [Function],\n                            \"locale\": {\n                              \"format\": [Function],\n                              \"getShortMonths\": [Function],\n                              \"getShortWeekDays\": [Function],\n                              \"getWeek\": [Function],\n                              \"getWeekFirstDay\": [Function],\n                              \"parse\": [Function],\n                            },\n                            \"setDate\": [Function],\n                            \"setHour\": [Function],\n                            \"setMinute\": [Function],\n                            \"setMonth\": [Function],\n                            \"setSecond\": [Function],\n                            \"setYear\": [Function],\n                          }\n                        }\n                        locale={\n                          {\n                            \"backToToday\": \"Back to today\",\n                            \"clear\": \"Clear\",\n                            \"dateFormat\": \"M/D/YYYY\",\n                            \"dateSelect\": \"select date\",\n                            \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                            \"dayFormat\": \"D\",\n                            \"decadeSelect\": \"Choose a decade\",\n                            \"locale\": \"en_US\",\n                            \"month\": \"Month\",\n                            \"monthBeforeYear\": true,\n                            \"monthPlaceholder\": \"Select month\",\n                            \"monthSelect\": \"Choose a month\",\n                            \"nextCentury\": \"Next century\",\n                            \"nextDecade\": \"Next decade\",\n                            \"nextMonth\": \"Next month (PageDown)\",\n                            \"nextYear\": \"Next year (Control + right)\",\n                            \"now\": \"Now\",\n                            \"ok\": \"Ok\",\n                            \"placeholder\": \"Select date\",\n                            \"previousCentury\": \"Last century\",\n                            \"previousDecade\": \"Last decade\",\n                            \"previousMonth\": \"Previous month (PageUp)\",\n                            \"previousYear\": \"Last year (Control + left)\",\n                            \"quarterPlaceholder\": \"Select quarter\",\n                            \"rangeMonthPlaceholder\": [\n                              \"Start month\",\n                              \"End month\",\n                            ],\n                            \"rangePlaceholder\": [\n                              \"Start date\",\n                              \"End date\",\n                            ],\n                            \"rangeWeekPlaceholder\": [\n                              \"Start week\",\n                              \"End week\",\n                            ],\n                            \"rangeYearPlaceholder\": [\n                              \"Start year\",\n                              \"End year\",\n                            ],\n                            \"timeSelect\": \"select time\",\n                            \"today\": \"Today\",\n                            \"weekPlaceholder\": \"Select week\",\n                            \"weekSelect\": \"Choose a week\",\n                            \"year\": \"Year\",\n                            \"yearFormat\": \"YYYY\",\n                            \"yearPlaceholder\": \"Select year\",\n                            \"yearSelect\": \"Choose a year\",\n                          }\n                        }\n                        minuteStep={5}\n                        nextIcon={\n                          <span\n                            className=\"ant-picker-next-icon\"\n                          />\n                        }\n                        onChange={[Function]}\n                        picker=\"time\"\n                        pickerRef={\n                          {\n                            \"current\": {\n                              \"blur\": [Function],\n                              \"focus\": [Function],\n                            },\n                          }\n                        }\n                        placeholder=\"Select time\"\n                        prefixCls=\"ant-picker\"\n                        prevIcon={\n                          <span\n                            className=\"ant-picker-prev-icon\"\n                          />\n                        }\n                        showSecond={false}\n                        showToday={true}\n                        suffixIcon={<ClockCircleOutlined />}\n                        superNextIcon={\n                          <span\n                            className=\"ant-picker-super-next-icon\"\n                          />\n                        }\n                        superPrevIcon={\n                          <span\n                            className=\"ant-picker-super-prev-icon\"\n                          />\n                        }\n                        tabIndex={-1}\n                        transitionName=\"slide-up\"\n                        value={null}\n                      />\n                    </div>\n                  }\n                  popupAlign={{}}\n                  popupClassName=\"\"\n                  popupPlacement=\"bottomLeft\"\n                  popupStyle={{}}\n                  popupTransitionName=\"slide-up\"\n                  popupVisible={false}\n                  prefixCls=\"ant-picker-dropdown\"\n                  showAction={[]}\n                >\n                  <div\n                    className=\"ant-picker\"\n                    key=\"trigger\"\n                    onMouseUp={[Function]}\n                  >\n                    <div\n                      className=\"ant-picker-input\"\n                    >\n                      <input\n                        autoComplete=\"off\"\n                        onBlur={[Function]}\n                        onChange={[Function]}\n                        onFocus={[Function]}\n                        onKeyDown={[Function]}\n                        onMouseDown={[Function]}\n                        placeholder=\"Select time\"\n                        readOnly={true}\n                        size={10}\n                        title=\"\"\n                        value=\"\"\n                      />\n                      <span\n                        className=\"ant-picker-suffix\"\n                      >\n                        <ClockCircleOutlined>\n                          <AntdIcon\n                            icon={\n                              {\n                                \"icon\": {\n                                  \"attrs\": {\n                                    \"focusable\": \"false\",\n                                    \"viewBox\": \"64 64 896 896\",\n                                  },\n                                  \"children\": [\n                                    {\n                                      \"attrs\": {\n                                        \"d\": \"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\n                                      },\n                                      \"tag\": \"path\",\n                                    },\n                                    {\n                                      \"attrs\": {\n                                        \"d\": \"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z\",\n                                      },\n                                      \"tag\": \"path\",\n                                    },\n                                  ],\n                                  \"tag\": \"svg\",\n                                },\n                                \"name\": \"clock-circle\",\n                                \"theme\": \"outlined\",\n                              }\n                            }\n                          >\n                            <span\n                              aria-label=\"clock-circle\"\n                              className=\"anticon anticon-clock-circle\"\n                              role=\"img\"\n                            >\n                              <IconReact\n                                icon={\n                                  {\n                                    \"icon\": {\n                                      \"attrs\": {\n                                        \"focusable\": \"false\",\n                                        \"viewBox\": \"64 64 896 896\",\n                                      },\n                                      \"children\": [\n                                        {\n                                          \"attrs\": {\n                                            \"d\": \"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\",\n                                          },\n                                          \"tag\": \"path\",\n                                        },\n                                        {\n                                          \"attrs\": {\n                                            \"d\": \"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z\",\n                                          },\n                                          \"tag\": \"path\",\n                                        },\n                                      ],\n                                      \"tag\": \"svg\",\n                                    },\n                                    \"name\": \"clock-circle\",\n                                    \"theme\": \"outlined\",\n                                  }\n                                }\n                              >\n                                <svg\n                                  aria-hidden=\"true\"\n                                  data-icon=\"clock-circle\"\n                                  fill=\"currentColor\"\n                                  focusable=\"false\"\n                                  height=\"1em\"\n                                  key=\"svg-clock-circle\"\n                                  viewBox=\"64 64 896 896\"\n                                  width=\"1em\"\n                                >\n                                  <path\n                                    d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z\"\n                                    key=\"svg-clock-circle-svg-0\"\n                                  />\n                                  <path\n                                    d=\"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z\"\n                                    key=\"svg-clock-circle-svg-1\"\n                                  />\n                                </svg>\n                              </IconReact>\n                            </span>\n                          </AntdIcon>\n                        </ClockCircleOutlined>\n                      </span>\n                    </div>\n                  </div>\n                </Trigger>\n              </PickerTrigger>\n            </InnerPicker>\n          </Picker>\n        </LocaleReceiver>\n      </TimePicker>\n    </TimePicker>\n  </TimeEditor>\n</div>\n`;\n\nexports[`ScheduleDialog Sets correct schedule settings Until feature Until is set 1`] = `\n<div\n  className=\"ends\"\n  data-testid=\"ends\"\n>\n  <ForwardRef\n    onChange={[Function]}\n    size=\"medium\"\n    value={true}\n  >\n    <div\n      className=\"ant-radio-group ant-radio-group-outline ant-radio-group-medium\"\n    >\n      <Radio\n        type=\"radio\"\n        value={false}\n      >\n        <label\n          className=\"ant-radio-wrapper\"\n        >\n          <Checkbox\n            checked={false}\n            className=\"\"\n            defaultChecked={false}\n            onBlur={[Function]}\n            onChange={[Function]}\n            onFocus={[Function]}\n            onKeyDown={[Function]}\n            onKeyPress={[Function]}\n            onKeyUp={[Function]}\n            prefixCls=\"ant-radio\"\n            style={{}}\n            type=\"radio\"\n            value={false}\n          >\n            <span\n              className=\"ant-radio\"\n              style={{}}\n            >\n              <input\n                checked={false}\n                className=\"ant-radio-input\"\n                onBlur={[Function]}\n                onChange={[Function]}\n                onFocus={[Function]}\n                onKeyDown={[Function]}\n                onKeyPress={[Function]}\n                onKeyUp={[Function]}\n                type=\"radio\"\n                value={false}\n              />\n              <span\n                className=\"ant-radio-inner\"\n              />\n            </span>\n          </Checkbox>\n          <span>\n            Never\n          </span>\n        </label>\n      </Radio>\n      <Radio\n        type=\"radio\"\n        value={true}\n      >\n        <label\n          className=\"ant-radio-wrapper ant-radio-wrapper-checked\"\n        >\n          <Checkbox\n            checked={true}\n            className=\"\"\n            defaultChecked={false}\n            onBlur={[Function]}\n            onChange={[Function]}\n            onFocus={[Function]}\n            onKeyDown={[Function]}\n            onKeyPress={[Function]}\n            onKeyUp={[Function]}\n            prefixCls=\"ant-radio\"\n            style={{}}\n            type=\"radio\"\n            value={true}\n          >\n            <span\n              className=\"ant-radio ant-radio-checked\"\n              style={{}}\n            >\n              <input\n                checked={true}\n                className=\"ant-radio-input\"\n                onBlur={[Function]}\n                onChange={[Function]}\n                onFocus={[Function]}\n                onKeyDown={[Function]}\n                onKeyPress={[Function]}\n                onKeyUp={[Function]}\n                type=\"radio\"\n                value={true}\n              />\n              <span\n                className=\"ant-radio-inner\"\n              />\n            </span>\n          </Checkbox>\n          <span>\n            On\n          </span>\n        </label>\n      </Radio>\n    </div>\n  </ForwardRef>\n  <Picker\n    allowClear={false}\n    className=\"datepicker\"\n    format=\"YYYY-MM-DD\"\n    onChange={[Function]}\n    size=\"small\"\n    value={\"2029-12-31T22:00:00.000Z\"}\n  >\n    <LocaleReceiver\n      componentName=\"DatePicker\"\n      defaultLocale={[Function]}\n    >\n      <Picker\n        allowClear={false}\n        className=\"datepicker ant-picker-small\"\n        clearIcon={<CloseCircleFilled />}\n        components={\n          {\n            \"button\": [Function],\n            \"rangeItem\": [Function],\n          }\n        }\n        format=\"YYYY-MM-DD\"\n        generateConfig={\n          {\n            \"addDate\": [Function],\n            \"addMonth\": [Function],\n            \"addYear\": [Function],\n            \"getDate\": [Function],\n            \"getHour\": [Function],\n            \"getMinute\": [Function],\n            \"getMonth\": [Function],\n            \"getNow\": [Function],\n            \"getSecond\": [Function],\n            \"getWeekDay\": [Function],\n            \"getYear\": [Function],\n            \"isAfter\": [Function],\n            \"isValidate\": [Function],\n            \"locale\": {\n              \"format\": [Function],\n              \"getShortMonths\": [Function],\n              \"getShortWeekDays\": [Function],\n              \"getWeek\": [Function],\n              \"getWeekFirstDay\": [Function],\n              \"parse\": [Function],\n            },\n            \"setDate\": [Function],\n            \"setHour\": [Function],\n            \"setMinute\": [Function],\n            \"setMonth\": [Function],\n            \"setSecond\": [Function],\n            \"setYear\": [Function],\n          }\n        }\n        locale={\n          {\n            \"backToToday\": \"Back to today\",\n            \"clear\": \"Clear\",\n            \"dateFormat\": \"M/D/YYYY\",\n            \"dateSelect\": \"select date\",\n            \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n            \"dayFormat\": \"D\",\n            \"decadeSelect\": \"Choose a decade\",\n            \"locale\": \"en_US\",\n            \"month\": \"Month\",\n            \"monthBeforeYear\": true,\n            \"monthPlaceholder\": \"Select month\",\n            \"monthSelect\": \"Choose a month\",\n            \"nextCentury\": \"Next century\",\n            \"nextDecade\": \"Next decade\",\n            \"nextMonth\": \"Next month (PageDown)\",\n            \"nextYear\": \"Next year (Control + right)\",\n            \"now\": \"Now\",\n            \"ok\": \"Ok\",\n            \"placeholder\": \"Select date\",\n            \"previousCentury\": \"Last century\",\n            \"previousDecade\": \"Last decade\",\n            \"previousMonth\": \"Previous month (PageUp)\",\n            \"previousYear\": \"Last year (Control + left)\",\n            \"quarterPlaceholder\": \"Select quarter\",\n            \"rangeMonthPlaceholder\": [\n              \"Start month\",\n              \"End month\",\n            ],\n            \"rangePlaceholder\": [\n              \"Start date\",\n              \"End date\",\n            ],\n            \"rangeWeekPlaceholder\": [\n              \"Start week\",\n              \"End week\",\n            ],\n            \"rangeYearPlaceholder\": [\n              \"Start year\",\n              \"End year\",\n            ],\n            \"timeSelect\": \"select time\",\n            \"today\": \"Today\",\n            \"weekPlaceholder\": \"Select week\",\n            \"weekSelect\": \"Choose a week\",\n            \"year\": \"Year\",\n            \"yearFormat\": \"YYYY\",\n            \"yearPlaceholder\": \"Select year\",\n            \"yearSelect\": \"Choose a year\",\n          }\n        }\n        nextIcon={\n          <span\n            className=\"ant-picker-next-icon\"\n          />\n        }\n        onChange={[Function]}\n        placeholder=\"Select date\"\n        prefixCls=\"ant-picker\"\n        prevIcon={\n          <span\n            className=\"ant-picker-prev-icon\"\n          />\n        }\n        showToday={true}\n        suffixIcon={<CalendarOutlined />}\n        superNextIcon={\n          <span\n            className=\"ant-picker-super-next-icon\"\n          />\n        }\n        superPrevIcon={\n          <span\n            className=\"ant-picker-super-prev-icon\"\n          />\n        }\n        transitionName=\"slide-up\"\n        value={\"2029-12-31T22:00:00.000Z\"}\n      >\n        <InnerPicker\n          allowClear={false}\n          className=\"datepicker ant-picker-small\"\n          clearIcon={<CloseCircleFilled />}\n          components={\n            {\n              \"button\": [Function],\n              \"rangeItem\": [Function],\n            }\n          }\n          format=\"YYYY-MM-DD\"\n          generateConfig={\n            {\n              \"addDate\": [Function],\n              \"addMonth\": [Function],\n              \"addYear\": [Function],\n              \"getDate\": [Function],\n              \"getHour\": [Function],\n              \"getMinute\": [Function],\n              \"getMonth\": [Function],\n              \"getNow\": [Function],\n              \"getSecond\": [Function],\n              \"getWeekDay\": [Function],\n              \"getYear\": [Function],\n              \"isAfter\": [Function],\n              \"isValidate\": [Function],\n              \"locale\": {\n                \"format\": [Function],\n                \"getShortMonths\": [Function],\n                \"getShortWeekDays\": [Function],\n                \"getWeek\": [Function],\n                \"getWeekFirstDay\": [Function],\n                \"parse\": [Function],\n              },\n              \"setDate\": [Function],\n              \"setHour\": [Function],\n              \"setMinute\": [Function],\n              \"setMonth\": [Function],\n              \"setSecond\": [Function],\n              \"setYear\": [Function],\n            }\n          }\n          locale={\n            {\n              \"backToToday\": \"Back to today\",\n              \"clear\": \"Clear\",\n              \"dateFormat\": \"M/D/YYYY\",\n              \"dateSelect\": \"select date\",\n              \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n              \"dayFormat\": \"D\",\n              \"decadeSelect\": \"Choose a decade\",\n              \"locale\": \"en_US\",\n              \"month\": \"Month\",\n              \"monthBeforeYear\": true,\n              \"monthPlaceholder\": \"Select month\",\n              \"monthSelect\": \"Choose a month\",\n              \"nextCentury\": \"Next century\",\n              \"nextDecade\": \"Next decade\",\n              \"nextMonth\": \"Next month (PageDown)\",\n              \"nextYear\": \"Next year (Control + right)\",\n              \"now\": \"Now\",\n              \"ok\": \"Ok\",\n              \"placeholder\": \"Select date\",\n              \"previousCentury\": \"Last century\",\n              \"previousDecade\": \"Last decade\",\n              \"previousMonth\": \"Previous month (PageUp)\",\n              \"previousYear\": \"Last year (Control + left)\",\n              \"quarterPlaceholder\": \"Select quarter\",\n              \"rangeMonthPlaceholder\": [\n                \"Start month\",\n                \"End month\",\n              ],\n              \"rangePlaceholder\": [\n                \"Start date\",\n                \"End date\",\n              ],\n              \"rangeWeekPlaceholder\": [\n                \"Start week\",\n                \"End week\",\n              ],\n              \"rangeYearPlaceholder\": [\n                \"Start year\",\n                \"End year\",\n              ],\n              \"timeSelect\": \"select time\",\n              \"today\": \"Today\",\n              \"weekPlaceholder\": \"Select week\",\n              \"weekSelect\": \"Choose a week\",\n              \"year\": \"Year\",\n              \"yearFormat\": \"YYYY\",\n              \"yearPlaceholder\": \"Select year\",\n              \"yearSelect\": \"Choose a year\",\n            }\n          }\n          nextIcon={\n            <span\n              className=\"ant-picker-next-icon\"\n            />\n          }\n          onChange={[Function]}\n          pickerRef={\n            {\n              \"current\": {\n                \"blur\": [Function],\n                \"focus\": [Function],\n              },\n            }\n          }\n          placeholder=\"Select date\"\n          prefixCls=\"ant-picker\"\n          prevIcon={\n            <span\n              className=\"ant-picker-prev-icon\"\n            />\n          }\n          showToday={true}\n          suffixIcon={<CalendarOutlined />}\n          superNextIcon={\n            <span\n              className=\"ant-picker-super-next-icon\"\n            />\n          }\n          superPrevIcon={\n            <span\n              className=\"ant-picker-super-prev-icon\"\n            />\n          }\n          transitionName=\"slide-up\"\n          value={\"2029-12-31T22:00:00.000Z\"}\n        >\n          <PickerTrigger\n            popupElement={\n              <div\n                className=\"ant-picker-panel-container\"\n                onMouseDown={[Function]}\n              >\n                <PickerPanel\n                  allowClear={false}\n                  className=\"ant-picker-panel-focused\"\n                  clearIcon={<CloseCircleFilled />}\n                  components={\n                    {\n                      \"button\": [Function],\n                      \"rangeItem\": [Function],\n                    }\n                  }\n                  format=\"YYYY-MM-DD\"\n                  generateConfig={\n                    {\n                      \"addDate\": [Function],\n                      \"addMonth\": [Function],\n                      \"addYear\": [Function],\n                      \"getDate\": [Function],\n                      \"getHour\": [Function],\n                      \"getMinute\": [Function],\n                      \"getMonth\": [Function],\n                      \"getNow\": [Function],\n                      \"getSecond\": [Function],\n                      \"getWeekDay\": [Function],\n                      \"getYear\": [Function],\n                      \"isAfter\": [Function],\n                      \"isValidate\": [Function],\n                      \"locale\": {\n                        \"format\": [Function],\n                        \"getShortMonths\": [Function],\n                        \"getShortWeekDays\": [Function],\n                        \"getWeek\": [Function],\n                        \"getWeekFirstDay\": [Function],\n                        \"parse\": [Function],\n                      },\n                      \"setDate\": [Function],\n                      \"setHour\": [Function],\n                      \"setMinute\": [Function],\n                      \"setMonth\": [Function],\n                      \"setSecond\": [Function],\n                      \"setYear\": [Function],\n                    }\n                  }\n                  locale={\n                    {\n                      \"backToToday\": \"Back to today\",\n                      \"clear\": \"Clear\",\n                      \"dateFormat\": \"M/D/YYYY\",\n                      \"dateSelect\": \"select date\",\n                      \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                      \"dayFormat\": \"D\",\n                      \"decadeSelect\": \"Choose a decade\",\n                      \"locale\": \"en_US\",\n                      \"month\": \"Month\",\n                      \"monthBeforeYear\": true,\n                      \"monthPlaceholder\": \"Select month\",\n                      \"monthSelect\": \"Choose a month\",\n                      \"nextCentury\": \"Next century\",\n                      \"nextDecade\": \"Next decade\",\n                      \"nextMonth\": \"Next month (PageDown)\",\n                      \"nextYear\": \"Next year (Control + right)\",\n                      \"now\": \"Now\",\n                      \"ok\": \"Ok\",\n                      \"placeholder\": \"Select date\",\n                      \"previousCentury\": \"Last century\",\n                      \"previousDecade\": \"Last decade\",\n                      \"previousMonth\": \"Previous month (PageUp)\",\n                      \"previousYear\": \"Last year (Control + left)\",\n                      \"quarterPlaceholder\": \"Select quarter\",\n                      \"rangeMonthPlaceholder\": [\n                        \"Start month\",\n                        \"End month\",\n                      ],\n                      \"rangePlaceholder\": [\n                        \"Start date\",\n                        \"End date\",\n                      ],\n                      \"rangeWeekPlaceholder\": [\n                        \"Start week\",\n                        \"End week\",\n                      ],\n                      \"rangeYearPlaceholder\": [\n                        \"Start year\",\n                        \"End year\",\n                      ],\n                      \"timeSelect\": \"select time\",\n                      \"today\": \"Today\",\n                      \"weekPlaceholder\": \"Select week\",\n                      \"weekSelect\": \"Choose a week\",\n                      \"year\": \"Year\",\n                      \"yearFormat\": \"YYYY\",\n                      \"yearPlaceholder\": \"Select year\",\n                      \"yearSelect\": \"Choose a year\",\n                    }\n                  }\n                  nextIcon={\n                    <span\n                      className=\"ant-picker-next-icon\"\n                    />\n                  }\n                  onChange={[Function]}\n                  pickerRef={\n                    {\n                      \"current\": {\n                        \"blur\": [Function],\n                        \"focus\": [Function],\n                      },\n                    }\n                  }\n                  placeholder=\"Select date\"\n                  prefixCls=\"ant-picker\"\n                  prevIcon={\n                    <span\n                      className=\"ant-picker-prev-icon\"\n                    />\n                  }\n                  showToday={true}\n                  suffixIcon={<CalendarOutlined />}\n                  superNextIcon={\n                    <span\n                      className=\"ant-picker-super-next-icon\"\n                    />\n                  }\n                  superPrevIcon={\n                    <span\n                      className=\"ant-picker-super-prev-icon\"\n                    />\n                  }\n                  tabIndex={-1}\n                  transitionName=\"slide-up\"\n                  value={\"2029-12-31T22:00:00.000Z\"}\n                />\n              </div>\n            }\n            popupPlacement=\"bottomLeft\"\n            prefixCls=\"ant-picker\"\n            transitionName=\"slide-up\"\n            visible={false}\n          >\n            <Trigger\n              action={[]}\n              afterPopupVisibleChange={[Function]}\n              autoDestroy={false}\n              blurDelay={0.15}\n              builtinPlacements={\n                {\n                  \"bottomLeft\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 1,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tl\",\n                      \"bl\",\n                    ],\n                  },\n                  \"bottomRight\": {\n                    \"offset\": [\n                      0,\n                      4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 1,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"tr\",\n                      \"br\",\n                    ],\n                  },\n                  \"topLeft\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"bl\",\n                      \"tl\",\n                    ],\n                  },\n                  \"topRight\": {\n                    \"offset\": [\n                      0,\n                      -4,\n                    ],\n                    \"overflow\": {\n                      \"adjustX\": 0,\n                      \"adjustY\": 1,\n                    },\n                    \"points\": [\n                      \"br\",\n                      \"tr\",\n                    ],\n                  },\n                }\n              }\n              defaultPopupVisible={false}\n              destroyPopupOnHide={false}\n              focusDelay={0}\n              getDocument={[Function]}\n              getPopupClassNameFromAlign={[Function]}\n              hideAction={[]}\n              mask={false}\n              maskClosable={true}\n              mouseEnterDelay={0}\n              mouseLeaveDelay={0.1}\n              onPopupAlign={[Function]}\n              onPopupVisibleChange={[Function]}\n              popup={\n                <div\n                  className=\"ant-picker-panel-container\"\n                  onMouseDown={[Function]}\n                >\n                  <PickerPanel\n                    allowClear={false}\n                    className=\"ant-picker-panel-focused\"\n                    clearIcon={<CloseCircleFilled />}\n                    components={\n                      {\n                        \"button\": [Function],\n                        \"rangeItem\": [Function],\n                      }\n                    }\n                    format=\"YYYY-MM-DD\"\n                    generateConfig={\n                      {\n                        \"addDate\": [Function],\n                        \"addMonth\": [Function],\n                        \"addYear\": [Function],\n                        \"getDate\": [Function],\n                        \"getHour\": [Function],\n                        \"getMinute\": [Function],\n                        \"getMonth\": [Function],\n                        \"getNow\": [Function],\n                        \"getSecond\": [Function],\n                        \"getWeekDay\": [Function],\n                        \"getYear\": [Function],\n                        \"isAfter\": [Function],\n                        \"isValidate\": [Function],\n                        \"locale\": {\n                          \"format\": [Function],\n                          \"getShortMonths\": [Function],\n                          \"getShortWeekDays\": [Function],\n                          \"getWeek\": [Function],\n                          \"getWeekFirstDay\": [Function],\n                          \"parse\": [Function],\n                        },\n                        \"setDate\": [Function],\n                        \"setHour\": [Function],\n                        \"setMinute\": [Function],\n                        \"setMonth\": [Function],\n                        \"setSecond\": [Function],\n                        \"setYear\": [Function],\n                      }\n                    }\n                    locale={\n                      {\n                        \"backToToday\": \"Back to today\",\n                        \"clear\": \"Clear\",\n                        \"dateFormat\": \"M/D/YYYY\",\n                        \"dateSelect\": \"select date\",\n                        \"dateTimeFormat\": \"M/D/YYYY HH:mm:ss\",\n                        \"dayFormat\": \"D\",\n                        \"decadeSelect\": \"Choose a decade\",\n                        \"locale\": \"en_US\",\n                        \"month\": \"Month\",\n                        \"monthBeforeYear\": true,\n                        \"monthPlaceholder\": \"Select month\",\n                        \"monthSelect\": \"Choose a month\",\n                        \"nextCentury\": \"Next century\",\n                        \"nextDecade\": \"Next decade\",\n                        \"nextMonth\": \"Next month (PageDown)\",\n                        \"nextYear\": \"Next year (Control + right)\",\n                        \"now\": \"Now\",\n                        \"ok\": \"Ok\",\n                        \"placeholder\": \"Select date\",\n                        \"previousCentury\": \"Last century\",\n                        \"previousDecade\": \"Last decade\",\n                        \"previousMonth\": \"Previous month (PageUp)\",\n                        \"previousYear\": \"Last year (Control + left)\",\n                        \"quarterPlaceholder\": \"Select quarter\",\n                        \"rangeMonthPlaceholder\": [\n                          \"Start month\",\n                          \"End month\",\n                        ],\n                        \"rangePlaceholder\": [\n                          \"Start date\",\n                          \"End date\",\n                        ],\n                        \"rangeWeekPlaceholder\": [\n                          \"Start week\",\n                          \"End week\",\n                        ],\n                        \"rangeYearPlaceholder\": [\n                          \"Start year\",\n                          \"End year\",\n                        ],\n                        \"timeSelect\": \"select time\",\n                        \"today\": \"Today\",\n                        \"weekPlaceholder\": \"Select week\",\n                        \"weekSelect\": \"Choose a week\",\n                        \"year\": \"Year\",\n                        \"yearFormat\": \"YYYY\",\n                        \"yearPlaceholder\": \"Select year\",\n                        \"yearSelect\": \"Choose a year\",\n                      }\n                    }\n                    nextIcon={\n                      <span\n                        className=\"ant-picker-next-icon\"\n                      />\n                    }\n                    onChange={[Function]}\n                    pickerRef={\n                      {\n                        \"current\": {\n                          \"blur\": [Function],\n                          \"focus\": [Function],\n                        },\n                      }\n                    }\n                    placeholder=\"Select date\"\n                    prefixCls=\"ant-picker\"\n                    prevIcon={\n                      <span\n                        className=\"ant-picker-prev-icon\"\n                      />\n                    }\n                    showToday={true}\n                    suffixIcon={<CalendarOutlined />}\n                    superNextIcon={\n                      <span\n                        className=\"ant-picker-super-next-icon\"\n                      />\n                    }\n                    superPrevIcon={\n                      <span\n                        className=\"ant-picker-super-prev-icon\"\n                      />\n                    }\n                    tabIndex={-1}\n                    transitionName=\"slide-up\"\n                    value={\"2029-12-31T22:00:00.000Z\"}\n                  />\n                </div>\n              }\n              popupAlign={{}}\n              popupClassName=\"\"\n              popupPlacement=\"bottomLeft\"\n              popupStyle={{}}\n              popupTransitionName=\"slide-up\"\n              popupVisible={false}\n              prefixCls=\"ant-picker-dropdown\"\n              showAction={[]}\n            >\n              <div\n                className=\"ant-picker datepicker ant-picker-small\"\n                key=\"trigger\"\n                onMouseUp={[Function]}\n              >\n                <div\n                  className=\"ant-picker-input\"\n                >\n                  <input\n                    autoComplete=\"off\"\n                    onBlur={[Function]}\n                    onChange={[Function]}\n                    onFocus={[Function]}\n                    onKeyDown={[Function]}\n                    onMouseDown={[Function]}\n                    placeholder=\"Select date\"\n                    readOnly={true}\n                    size={12}\n                    title=\"2030-01-01\"\n                    value=\"2030-01-01\"\n                  />\n                  <span\n                    className=\"ant-picker-suffix\"\n                  >\n                    <CalendarOutlined>\n                      <AntdIcon\n                        icon={\n                          {\n                            \"icon\": {\n                              \"attrs\": {\n                                \"focusable\": \"false\",\n                                \"viewBox\": \"64 64 896 896\",\n                              },\n                              \"children\": [\n                                {\n                                  \"attrs\": {\n                                    \"d\": \"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z\",\n                                  },\n                                  \"tag\": \"path\",\n                                },\n                              ],\n                              \"tag\": \"svg\",\n                            },\n                            \"name\": \"calendar\",\n                            \"theme\": \"outlined\",\n                          }\n                        }\n                      >\n                        <span\n                          aria-label=\"calendar\"\n                          className=\"anticon anticon-calendar\"\n                          role=\"img\"\n                        >\n                          <IconReact\n                            icon={\n                              {\n                                \"icon\": {\n                                  \"attrs\": {\n                                    \"focusable\": \"false\",\n                                    \"viewBox\": \"64 64 896 896\",\n                                  },\n                                  \"children\": [\n                                    {\n                                      \"attrs\": {\n                                        \"d\": \"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z\",\n                                      },\n                                      \"tag\": \"path\",\n                                    },\n                                  ],\n                                  \"tag\": \"svg\",\n                                },\n                                \"name\": \"calendar\",\n                                \"theme\": \"outlined\",\n                              }\n                            }\n                          >\n                            <svg\n                              aria-hidden=\"true\"\n                              data-icon=\"calendar\"\n                              fill=\"currentColor\"\n                              focusable=\"false\"\n                              height=\"1em\"\n                              key=\"svg-calendar\"\n                              viewBox=\"64 64 896 896\"\n                              width=\"1em\"\n                            >\n                              <path\n                                d=\"M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z\"\n                                key=\"svg-calendar-svg-0\"\n                              />\n                            </svg>\n                          </IconReact>\n                        </span>\n                      </AntdIcon>\n                    </CalendarOutlined>\n                  </span>\n                </div>\n              </div>\n            </Trigger>\n          </PickerTrigger>\n        </InnerPicker>\n      </Picker>\n    </LocaleReceiver>\n  </Picker>\n</div>\n`;\n\nexports[`ScheduleDialog Sets correct schedule settings Until feature Until not set 1`] = `\n<div\n  className=\"ends\"\n  data-testid=\"ends\"\n>\n  <ForwardRef\n    onChange={[Function]}\n    size=\"medium\"\n    value={false}\n  >\n    <div\n      className=\"ant-radio-group ant-radio-group-outline ant-radio-group-medium\"\n    >\n      <Radio\n        type=\"radio\"\n        value={false}\n      >\n        <label\n          className=\"ant-radio-wrapper ant-radio-wrapper-checked\"\n        >\n          <Checkbox\n            checked={true}\n            className=\"\"\n            defaultChecked={false}\n            onBlur={[Function]}\n            onChange={[Function]}\n            onFocus={[Function]}\n            onKeyDown={[Function]}\n            onKeyPress={[Function]}\n            onKeyUp={[Function]}\n            prefixCls=\"ant-radio\"\n            style={{}}\n            type=\"radio\"\n            value={false}\n          >\n            <span\n              className=\"ant-radio ant-radio-checked\"\n              style={{}}\n            >\n              <input\n                checked={true}\n                className=\"ant-radio-input\"\n                onBlur={[Function]}\n                onChange={[Function]}\n                onFocus={[Function]}\n                onKeyDown={[Function]}\n                onKeyPress={[Function]}\n                onKeyUp={[Function]}\n                type=\"radio\"\n                value={false}\n              />\n              <span\n                className=\"ant-radio-inner\"\n              />\n            </span>\n          </Checkbox>\n          <span>\n            Never\n          </span>\n        </label>\n      </Radio>\n      <Radio\n        type=\"radio\"\n        value={true}\n      >\n        <label\n          className=\"ant-radio-wrapper\"\n        >\n          <Checkbox\n            checked={false}\n            className=\"\"\n            defaultChecked={false}\n            onBlur={[Function]}\n            onChange={[Function]}\n            onFocus={[Function]}\n            onKeyDown={[Function]}\n            onKeyPress={[Function]}\n            onKeyUp={[Function]}\n            prefixCls=\"ant-radio\"\n            style={{}}\n            type=\"radio\"\n            value={true}\n          >\n            <span\n              className=\"ant-radio\"\n              style={{}}\n            >\n              <input\n                checked={false}\n                className=\"ant-radio-input\"\n                onBlur={[Function]}\n                onChange={[Function]}\n                onFocus={[Function]}\n                onKeyDown={[Function]}\n                onKeyPress={[Function]}\n                onKeyUp={[Function]}\n                type=\"radio\"\n                value={true}\n              />\n              <span\n                className=\"ant-radio-inner\"\n              />\n            </span>\n          </Checkbox>\n          <span>\n            On\n          </span>\n        </label>\n      </Radio>\n    </div>\n  </ForwardRef>\n</div>\n`;\n"
  },
  {
    "path": "client/app/components/queries/add-to-dashboard-dialog.less",
    "content": "@import (reference, less) \"~@/assets/less/main.less\";\n\n.ant-list {\n  &.add-to-dashboard-dialog-search-results {\n    margin-top: 15px;\n\n    .ant-list-items {\n      max-height: 300px;\n      overflow: auto;\n    }\n\n    .ant-list-item {\n      padding: 12px;\n      cursor: pointer;\n\n      &:hover,\n      &:active {\n        @table-row-hover-bg: fade(@redash-gray, 5%);\n        background-color: @table-row-hover-bg;\n      }\n    }\n  }\n\n  &.add-to-dashboard-dialog-selection {\n    .ant-list-item {\n      padding: 12px;\n\n      .add-to-dashboard-dialog-item-content {\n        flex: 1 1 auto;\n      }\n\n      .ant-list-item-action li {\n        margin: 0;\n        padding: 0;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/queries/editor-components/databricks/DatabricksSchemaBrowser.jsx",
    "content": "import React, { useState, useMemo, useEffect, useCallback } from \"react\";\nimport { filter, includes, get, find } from \"lodash\";\nimport PropTypes from \"prop-types\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport Button from \"antd/lib/button\";\nimport SyncOutlinedIcon from \"@ant-design/icons/SyncOutlined\";\nimport Input from \"antd/lib/input\";\nimport Select from \"antd/lib/select\";\nimport Tooltip from \"@/components/Tooltip\";\nimport { SchemaList, applyFilterOnSchema } from \"@/components/queries/SchemaBrowser\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\nimport useDatabricksSchema from \"./useDatabricksSchema\";\n\nimport \"./DatabricksSchemaBrowser.less\";\n\nexport default function DatabricksSchemaBrowser({\n  dataSource,\n  options,\n  onOptionsUpdate,\n  onSchemaUpdate,\n  onItemSelect,\n  ...props\n}) {\n  const {\n    databases,\n    loadingDatabases,\n    schema,\n    loadingSchema,\n    loadTableColumns,\n    currentDatabaseName,\n    setCurrentDatabase,\n    refreshAll,\n    refreshing,\n  } = useDatabricksSchema(dataSource, options, onOptionsUpdate);\n  const [filterString, setFilterString] = useState(\"\");\n  const [databaseFilterString, setDatabaseFilterString] = useState(\"\");\n  const filteredSchema = useMemo(() => applyFilterOnSchema(schema, filterString), [schema, filterString]);\n  const [isDatabaseSelectOpen, setIsDatabaseSelectOpen] = useState(false);\n  const [expandedFlags, setExpandedFlags] = useState({});\n  const [handleFilterChange] = useDebouncedCallback(setFilterString, 500);\n  const [handleDatabaseFilterChange, cancelHandleDatabaseFilterChange] = useDebouncedCallback(\n    setDatabaseFilterString,\n    500\n  );\n\n  const handleDatabaseSelection = useCallback(\n    databaseName => {\n      setCurrentDatabase(databaseName);\n      cancelHandleDatabaseFilterChange();\n      setDatabaseFilterString(\"\");\n    },\n    [cancelHandleDatabaseFilterChange, setCurrentDatabase]\n  );\n\n  const filteredDatabases = useMemo(\n    () => filter(databases, database => includes(database.toLowerCase(), databaseFilterString.toLowerCase())),\n    [databases, databaseFilterString]\n  );\n\n  const handleSchemaUpdate = useImmutableCallback(onSchemaUpdate);\n\n  useEffect(() => {\n    handleSchemaUpdate(schema);\n  }, [schema, handleSchemaUpdate]);\n\n  useEffect(() => {\n    setExpandedFlags({});\n  }, [currentDatabaseName]);\n\n  function toggleTable(tableName) {\n    const table = find(schema, { name: tableName });\n    if (!expandedFlags[tableName] && get(table, \"loading\", false)) {\n      loadTableColumns(tableName);\n    }\n    setExpandedFlags({\n      ...expandedFlags,\n      [tableName]: !expandedFlags[tableName],\n    });\n  }\n\n  return (\n    <div className=\"databricks-schema-browser schema-container\" {...props}>\n      <div className=\"schema-control\">\n        <Input\n          className={isDatabaseSelectOpen ? \"database-select-open\" : \"\"}\n          placeholder=\"Filter tables & columns...\"\n          aria-label=\"Search schema\"\n          disabled={loadingDatabases || loadingSchema}\n          onChange={event => handleFilterChange(event.target.value)}\n          addonBefore={\n            <Select\n              dropdownClassName=\"databricks-schema-browser-db-dropdown\"\n              loading={loadingDatabases}\n              disabled={loadingDatabases}\n              onChange={handleDatabaseSelection}\n              value={currentDatabaseName}\n              showSearch\n              onSearch={handleDatabaseFilterChange}\n              onDropdownVisibleChange={setIsDatabaseSelectOpen}\n              placeholder={\n                <>\n                  <i className=\"fa fa-database m-r-5\" aria-hidden=\"true\" /> Database\n                </>\n              }>\n              {filteredDatabases.map(database => (\n                <Select.Option key={database}>\n                  <i className=\"fa fa-database m-r-5\" aria-hidden=\"true\" />\n                  {database}\n                </Select.Option>\n              ))}\n            </Select>\n          }\n        />\n      </div>\n      <div className=\"schema-list-wrapper\">\n        <SchemaList\n          loading={loadingDatabases || loadingSchema}\n          schema={filteredSchema}\n          expandedFlags={expandedFlags}\n          onTableExpand={toggleTable}\n          onItemSelect={onItemSelect}\n        />\n        {!(loadingSchema || loadingDatabases) && (\n          <div className=\"load-button\">\n            <Tooltip title={!refreshing ? \"Refresh Databases and Current Schema\" : null}>\n              <Button type=\"link\" onClick={refreshAll} disabled={refreshing}>\n                <SyncOutlinedIcon spin={refreshing} />\n              </Button>\n            </Tooltip>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n\nDatabricksSchemaBrowser.propTypes = {\n  dataSource: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  options: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  onOptionsUpdate: PropTypes.func,\n  onSchemaUpdate: PropTypes.func,\n  onItemSelect: PropTypes.func,\n};\n\nDatabricksSchemaBrowser.defaultProps = {\n  dataSource: null,\n  options: null,\n  onOptionsUpdate: () => {},\n  onSchemaUpdate: () => {},\n  onItemSelect: () => {},\n};\n"
  },
  {
    "path": "client/app/components/queries/editor-components/databricks/DatabricksSchemaBrowser.less",
    "content": "@import (reference, less) \"~@/assets/less/ant\";\n\n.databricks-schema-browser {\n  .schema-control {\n    .database-select-open .ant-input-group-addon {\n      background-color: #fff;\n\n      .ant-select-selection-item {\n        visibility: hidden;\n      }\n    }\n\n    .ant-input-wrapper {\n      table-layout: fixed; // antd uses display: table, so this is needed for % units\n\n      .ant-input-group-addon {\n        width: 40%;\n        padding: 0;\n        border-bottom-left-radius: 0;\n\n        .ant-select {\n          width: 100%;\n\n          .ant-select-selection-item {\n            text-align: left;\n          }\n\n          &.ant-select-focused .ant-select-selector {\n            color: inherit;\n          }\n        }\n      }\n\n      .ant-input {\n        border-bottom-right-radius: 0;\n      }\n    }\n  }\n\n  .schema-list-wrapper {\n    position: relative;\n    height: 100%;\n    border: 1px solid #eaeaea;\n    border-top: 0;\n    border-radius: 0 0 4px 4px;\n    margin-bottom: 20px;\n    padding-bottom: 32px;\n\n    .load-button {\n      display: flex;\n      justify-content: center;\n      position: absolute;\n      width: 100%;\n      bottom: 0;\n\n      .ant-btn {\n        color: @text-color;\n        padding: 0 10px;\n      }\n    }\n  }\n}\n\n.databricks-schema-browser-db-dropdown {\n  width: 50vw !important;\n}\n"
  },
  {
    "path": "client/app/components/queries/editor-components/databricks/useDatabricksSchema.js",
    "content": "import { includes, has, get, map, first, isFunction, isEmpty, startsWith } from \"lodash\";\nimport { useEffect, useState, useMemo, useCallback, useRef } from \"react\";\nimport notification from \"@/services/notification\";\nimport DatabricksDataSource from \"@/services/databricks-data-source\";\n\nfunction getDatabases(dataSource, refresh = false) {\n  if (!dataSource) {\n    return Promise.resolve([]);\n  }\n\n  return DatabricksDataSource.getDatabases(dataSource, refresh).catch(() => {\n    notification.error(\"Failed to load Database list.\", \"Please try again later.\");\n    return Promise.reject();\n  });\n}\n\nfunction getSchema(dataSource, databaseName, refresh = false) {\n  if (!dataSource || !databaseName) {\n    return Promise.resolve([]);\n  }\n\n  return DatabricksDataSource.getDatabaseTables(dataSource, databaseName, refresh).catch(() => {\n    notification.error(`Failed to load tables for ${databaseName}.`, \"Please try again later.\");\n    return Promise.reject();\n  });\n}\n\nfunction addDisplayNameWithoutDatabaseName(schema, databaseName) {\n  if (!databaseName) {\n    return schema;\n  }\n  // add display name without {databaseName} + \".\"\n  return map(schema, table => {\n    const databaseNamePrefix = databaseName + \".\";\n    let displayName = table.name;\n    if (startsWith(table.name, databaseNamePrefix)) {\n      displayName = table.name.slice(databaseNamePrefix.length);\n    }\n    return { ...table, displayName };\n  });\n}\n\nexport default function useDatabricksSchema(dataSource, options = null, onOptionsUpdate = null) {\n  const [databases, setDatabases] = useState([]);\n  const [loadingDatabases, setLoadingDatabases] = useState(true);\n  const [currentDatabaseName, setCurrentDatabaseName] = useState();\n  const [schemas, setSchemas] = useState({});\n  const [loadingSchema, setLoadingSchema] = useState(false);\n  const [refreshing, setRefreshing] = useState(false);\n\n  const setCurrentSchema = useCallback(\n    schema =>\n      setSchemas(currentSchemas => ({\n        ...currentSchemas,\n        [currentDatabaseName]: schema,\n      })),\n    [currentDatabaseName]\n  );\n\n  const currentDatabaseNameRef = useRef();\n  currentDatabaseNameRef.current = currentDatabaseName;\n  const loadTableColumns = useCallback(\n    tableName => {\n      // remove [databaseName.] from the tableName\n      DatabricksDataSource.getTableColumns(\n        dataSource,\n        currentDatabaseName,\n        tableName.substring(currentDatabaseName.length + 1)\n      ).then(columns => {\n        if (currentDatabaseNameRef.current === currentDatabaseName) {\n          setSchemas(currentSchemas => {\n            const schema = get(currentSchemas, currentDatabaseName, []);\n            const updatedSchema = map(schema, table => {\n              if (table.name === tableName) {\n                return { ...table, columns, loading: false };\n              }\n              return table;\n            });\n            return {\n              ...currentSchemas,\n              [currentDatabaseName]: updatedSchema,\n            };\n          });\n        }\n      });\n    },\n    [dataSource, currentDatabaseName]\n  );\n\n  const schema = useMemo(() => {\n    const currentSchema = get(schemas, currentDatabaseName, []);\n    return addDisplayNameWithoutDatabaseName(currentSchema, currentDatabaseName);\n  }, [schemas, currentDatabaseName]);\n\n  const refreshAll = useCallback(() => {\n    if (!refreshing) {\n      setRefreshing(true);\n      const getDatabasesPromise = getDatabases(dataSource, true).then(setDatabases);\n      const getSchemasPromise = getSchema(dataSource, currentDatabaseName, true).then(({ schema }) =>\n        setCurrentSchema(schema)\n      );\n\n      Promise.all([getSchemasPromise.catch(() => {}), getDatabasesPromise.catch(() => {})]).then(() =>\n        setRefreshing(false)\n      );\n    }\n  }, [dataSource, currentDatabaseName, setCurrentSchema, refreshing]);\n\n  const schemasRef = useRef();\n  schemasRef.current = schemas;\n  useEffect(() => {\n    let isCancelled = false;\n    if (currentDatabaseName && !has(schemasRef.current, currentDatabaseName)) {\n      setLoadingSchema(true);\n      getSchema(dataSource, currentDatabaseName)\n        .catch(() => Promise.resolve({ schema: [], has_columns: true }))\n        .then(({ schema, has_columns }) => {\n          if (!isCancelled) {\n            if (!has_columns && !isEmpty(schema)) {\n              schema = map(schema, table => ({ ...table, loading: true }));\n              getSchema(dataSource, currentDatabaseName, true).then(({ schema }) => {\n                if (!isCancelled) {\n                  setCurrentSchema(schema);\n                }\n              });\n            }\n            setCurrentSchema(schema);\n          }\n        })\n        .finally(() => {\n          if (!isCancelled) {\n            setLoadingSchema(false);\n          }\n        });\n    }\n    return () => {\n      isCancelled = true;\n    };\n  }, [dataSource, currentDatabaseName, setCurrentSchema]);\n\n  const defaultDatabaseNameRef = useRef();\n  defaultDatabaseNameRef.current = get(options, \"selectedDatabase\", null);\n  useEffect(() => {\n    let isCancelled = false;\n    setLoadingDatabases(true);\n    setCurrentDatabaseName(undefined);\n    setSchemas({});\n    getDatabases(dataSource)\n      .catch(() => Promise.resolve([]))\n      .then(data => {\n        if (!isCancelled) {\n          setDatabases(data);\n\n          // We set the database using this order:\n          // 1. Currently selected value.\n          // 2. Last used stored in localStorage.\n          // 3. default database.\n          // 4. first database in the list.\n          let lastUsedDatabase =\n            defaultDatabaseNameRef.current || localStorage.getItem(`lastSelectedDatabricksDatabase_${dataSource.id}`);\n\n          if (!lastUsedDatabase) {\n            lastUsedDatabase = includes(data, \"default\") ? \"default\" : first(data) || null;\n          }\n\n          setCurrentDatabaseName(lastUsedDatabase);\n        }\n      })\n      .finally(() => {\n        if (!isCancelled) {\n          setLoadingDatabases(false);\n        }\n      });\n    return () => {\n      isCancelled = true;\n    };\n  }, [dataSource]);\n\n  const setCurrentDatabase = useCallback(\n    databaseName => {\n      if (databaseName) {\n        try {\n          localStorage.setItem(`lastSelectedDatabricksDatabase_${dataSource.id}`, databaseName);\n        } catch (e) {\n          // `localStorage.setItem` may throw exception if there are no enough space - in this case it could be ignored\n        }\n      }\n      setCurrentDatabaseName(databaseName);\n      if (isFunction(onOptionsUpdate) && databaseName !== defaultDatabaseNameRef.current) {\n        onOptionsUpdate({\n          ...options,\n          selectedDatabase: databaseName,\n        });\n      }\n    },\n    [dataSource.id, options, onOptionsUpdate]\n  );\n\n  return {\n    databases,\n    loadingDatabases,\n    schema,\n    loadingSchema,\n    currentDatabaseName,\n    setCurrentDatabase,\n    loadTableColumns,\n    refreshAll,\n    refreshing,\n  };\n}\n"
  },
  {
    "path": "client/app/components/queries/editor-components/editorComponents.js",
    "content": "import { isArray, isNil, each } from \"lodash\";\n\nconst componentsRegistry = new Map();\n\nexport const QueryEditorComponents = {\n  SCHEMA_BROWSER: \"SchemaBrowser\",\n  QUERY_EDITOR: \"QueryEditor\",\n};\n\nexport function registerEditorComponent(componentName, component, dataSourceTypes) {\n  if (isNil(dataSourceTypes)) {\n    dataSourceTypes = [null]; // use `null` entry for the default set of components\n  }\n\n  if (!isArray(dataSourceTypes)) {\n    dataSourceTypes = [dataSourceTypes];\n  }\n\n  each(dataSourceTypes, dataSourceType => {\n    componentsRegistry.set(dataSourceType, { ...componentsRegistry.get(dataSourceType), [componentName]: component });\n  });\n}\n\nexport function getEditorComponents(dataSourceType) {\n  return { ...componentsRegistry.get(null), ...componentsRegistry.get(dataSourceType) };\n}\n"
  },
  {
    "path": "client/app/components/queries/editor-components/index.js",
    "content": "import SchemaBrowser from \"@/components/queries/SchemaBrowser\";\nimport QueryEditor from \"@/components/queries/QueryEditor\";\nimport DatabricksSchemaBrowser from \"./databricks/DatabricksSchemaBrowser\";\n\nimport { registerEditorComponent, getEditorComponents, QueryEditorComponents } from \"./editorComponents\";\n\n// default\nregisterEditorComponent(QueryEditorComponents.SCHEMA_BROWSER, SchemaBrowser);\nregisterEditorComponent(QueryEditorComponents.QUERY_EDITOR, QueryEditor);\n\n// databricks\nregisterEditorComponent(QueryEditorComponents.SCHEMA_BROWSER, DatabricksSchemaBrowser, [\n  \"databricks\",\n  \"databricks_internal\",\n]);\n\nexport { getEditorComponents };\n"
  },
  {
    "path": "client/app/components/query-snippets/QuerySnippetDialog.jsx",
    "content": "import { isNil, get } from \"lodash\";\nimport React, { useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport Modal from \"antd/lib/modal\";\nimport DynamicForm from \"@/components/dynamic-form/DynamicForm\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport { useUniqueId } from \"@/lib/hooks/useUniqueId\";\n\nfunction QuerySnippetDialog({ querySnippet, dialog, readOnly }) {\n  const handleSubmit = useCallback(\n    (values, successCallback, errorCallback) => {\n      const querySnippetId = get(querySnippet, \"id\");\n\n      if (isNil(values.description)) {\n        values.description = \"\";\n      }\n\n      dialog\n        .close(querySnippetId ? { id: querySnippetId, ...values } : values)\n        .then(() => successCallback(\"Saved.\"))\n        .catch(() => errorCallback(\"Failed saving snippet.\"));\n    },\n    [dialog, querySnippet]\n  );\n\n  const isEditing = !!get(querySnippet, \"id\");\n\n  const formFields = [\n    { name: \"trigger\", title: \"Trigger\", type: \"text\", required: true, autoFocus: !isEditing },\n    { name: \"description\", title: \"Description\", type: \"text\" },\n    { name: \"snippet\", title: \"Snippet\", type: \"ace\", required: true },\n  ].map(field => ({ ...field, readOnly, initialValue: get(querySnippet, field.name, \"\") }));\n\n  const querySnippetsFormId = useUniqueId(\"querySnippetForm\");\n\n  return (\n    <Modal\n      {...dialog.props}\n      title={isEditing ? querySnippet.trigger : \"Create Query Snippet\"}\n      footer={[\n        <Button key=\"cancel\" {...dialog.props.cancelButtonProps} onClick={dialog.dismiss}>\n          {readOnly ? \"Close\" : \"Cancel\"}\n        </Button>,\n        !readOnly && (\n          <Button\n            key=\"submit\"\n            {...dialog.props.okButtonProps}\n            disabled={readOnly || dialog.props.okButtonProps.disabled}\n            htmlType=\"submit\"\n            type=\"primary\"\n            form={querySnippetsFormId}\n            data-test=\"SaveQuerySnippetButton\">\n            {isEditing ? \"Save\" : \"Create\"}\n          </Button>\n        ),\n      ]}\n      wrapProps={{\n        \"data-test\": \"QuerySnippetDialog\",\n      }}>\n      <DynamicForm\n        id={querySnippetsFormId}\n        fields={formFields}\n        onSubmit={handleSubmit}\n        hideSubmitButton\n        feedbackIcons\n      />\n    </Modal>\n  );\n}\n\nQuerySnippetDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n  querySnippet: PropTypes.object,\n  readOnly: PropTypes.bool,\n};\n\nQuerySnippetDialog.defaultProps = {\n  querySnippet: null,\n  readOnly: false,\n};\n\nexport default wrapDialog(QuerySnippetDialog);\n"
  },
  {
    "path": "client/app/components/tags-control/EditTagsDialog.jsx",
    "content": "import { map, trim, uniq, compact } from \"lodash\";\nimport React, { useState, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Select from \"antd/lib/select\";\nimport Modal from \"antd/lib/modal\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\n\nfunction EditTagsDialog({ dialog, tags, getAvailableTags }) {\n  const [availableTags, setAvailableTags] = useState([]);\n  const [isLoading, setIsLoading] = useState(true);\n  const [values, setValues] = useState(() => uniq(map(tags, trim))); // lazy evaluate\n  const [selectRef, setSelectRef] = useState(null);\n\n  // Select is initially disabled, so autoFocus prop cannot make it focused.\n  // Solution is to pass focus to the select when available tags are loaded and\n  // select becomes enabled.\n  useEffect(() => {\n    if (selectRef && !isLoading) {\n      selectRef.focus();\n    }\n  }, [selectRef, isLoading]);\n\n  useEffect(() => {\n    let isCancelled = false;\n    getAvailableTags().then(availableTags => {\n      if (!isCancelled) {\n        setAvailableTags(uniq(compact(map(availableTags, trim))));\n        setIsLoading(false);\n      }\n    });\n    return () => {\n      isCancelled = true;\n    };\n  }, [getAvailableTags]);\n\n  return (\n    <Modal\n      {...dialog.props}\n      onOk={() => dialog.close(values)}\n      title=\"Add/Edit Tags\"\n      className=\"shortModal\"\n      wrapProps={{ \"data-test\": \"EditTagsDialog\" }}>\n      <Select\n        ref={setSelectRef}\n        mode=\"tags\"\n        className=\"w-100\"\n        placeholder=\"Add some tags...\"\n        defaultValue={values}\n        onChange={v => setValues(compact(map(v, trim)))}\n        disabled={isLoading}\n        loading={isLoading}>\n        {map(availableTags, tag => (\n          <Select.Option key={tag}>{tag}</Select.Option>\n        ))}\n      </Select>\n    </Modal>\n  );\n}\n\nEditTagsDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n  tags: PropTypes.arrayOf(PropTypes.string),\n  getAvailableTags: PropTypes.func.isRequired,\n};\n\nEditTagsDialog.defaultProps = {\n  tags: [],\n};\n\nexport default wrapDialog(EditTagsDialog);\n"
  },
  {
    "path": "client/app/components/tags-control/TagsControl.jsx",
    "content": "import { map, trim } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Tooltip from \"@/components/Tooltip\";\nimport EditTagsDialog from \"./EditTagsDialog\";\nimport PlainButton from \"@/components/PlainButton\";\n\nexport class TagsControl extends React.Component {\n  static propTypes = {\n    tags: PropTypes.arrayOf(PropTypes.string),\n    canEdit: PropTypes.bool,\n    getAvailableTags: PropTypes.func,\n    onEdit: PropTypes.func,\n    className: PropTypes.string,\n    tagsExtra: PropTypes.node,\n    tagSeparator: PropTypes.node,\n    children: PropTypes.node,\n  };\n\n  static defaultProps = {\n    tags: [],\n    canEdit: false,\n    getAvailableTags: () => Promise.resolve([]),\n    onEdit: () => {},\n    className: \"\",\n    tagsExtra: null,\n    tagSeparator: null,\n    children: null,\n  };\n\n  editTags = (tags, getAvailableTags) => {\n    EditTagsDialog.showModal({ tags, getAvailableTags }).onClose(this.props.onEdit);\n  };\n\n  renderEditButton() {\n    const tags = map(this.props.tags, trim);\n    return (\n      <PlainButton\n        className=\"label label-tag hidden-xs\"\n        onClick={() => this.editTags(tags, this.props.getAvailableTags)}\n        data-test=\"EditTagsButton\">\n        {tags.length === 0 && (\n          <React.Fragment>\n            <i className=\"zmdi zmdi-plus m-r-5\" aria-hidden=\"true\" />\n            Add tag\n          </React.Fragment>\n        )}\n        {tags.length > 0 && (\n          <>\n            <i className=\"zmdi zmdi-edit\" aria-hidden=\"true\" />\n            <span className=\"sr-only\">Edit</span>\n          </>\n        )}\n      </PlainButton>\n    );\n  }\n\n  render() {\n    const { tags, tagSeparator } = this.props;\n    return (\n      <div className={\"tags-control \" + this.props.className} data-test=\"TagsControl\">\n        {this.props.children}\n        {map(tags, (tag, i) => (\n          <React.Fragment key={tag}>\n            {tagSeparator && i > 0 && <span className=\"tag-separator\">{tagSeparator}</span>}\n            <span className=\"label label-tag\" key={tag} title={tag} data-test=\"TagLabel\">\n              {tag}\n            </span>\n          </React.Fragment>\n        ))}\n        {this.props.canEdit && this.renderEditButton()}\n        {this.props.tagsExtra}\n      </div>\n    );\n  }\n}\n\nfunction modelTagsControl({ archivedTooltip }) {\n  // See comment for `propTypes`/`defaultProps`\n  // eslint-disable-next-line react/prop-types\n  function ModelTagsControl({ isDraft, isArchived, ...props }) {\n    return (\n      <TagsControl {...props}>\n        {!isArchived && isDraft && <span className=\"label label-tag-unpublished\">Unpublished</span>}\n        {isArchived && (\n          <Tooltip placement=\"right\" title={archivedTooltip}>\n            <span className=\"label label-tag-archived\">Archived</span>\n          </Tooltip>\n        )}\n      </TagsControl>\n    );\n  }\n\n  ModelTagsControl.propTypes = {\n    isDraft: PropTypes.bool,\n    isArchived: PropTypes.bool,\n  };\n\n  ModelTagsControl.defaultProps = {\n    isDraft: false,\n    isArchived: false,\n  };\n\n  return ModelTagsControl;\n}\n\nexport const QueryTagsControl = modelTagsControl({\n  archivedTooltip: \"This query is archived and can't be used in dashboards, or appear in search results.\",\n});\n\nexport const DashboardTagsControl = modelTagsControl({\n  archivedTooltip: \"This dashboard is archived and won't be listed in dashboards nor search results.\",\n});\n"
  },
  {
    "path": "client/app/components/visualizations/EditVisualizationDialog.jsx",
    "content": "import { isEqual, extend, map, sortBy, findIndex, filter, pick, omit } from \"lodash\";\nimport React, { useState, useMemo, useRef, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Modal from \"antd/lib/modal\";\nimport Select from \"antd/lib/select\";\nimport Input from \"antd/lib/input\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport Filters, { filterData } from \"@/components/Filters\";\nimport notification from \"@/services/notification\";\nimport Visualization from \"@/services/visualization\";\nimport recordEvent from \"@/services/recordEvent\";\nimport useQueryResultData from \"@/lib/useQueryResultData\";\nimport { useUniqueId } from \"@/lib/hooks/useUniqueId\";\nimport {\n  registeredVisualizations,\n  getDefaultVisualization,\n  newVisualization,\n  VisualizationType,\n} from \"@redash/viz/lib\";\nimport { Renderer, Editor } from \"@/components/visualizations/visualizationComponents\";\n\nimport \"./EditVisualizationDialog.less\";\n\nfunction updateQueryVisualizations(query, visualization) {\n  const index = findIndex(query.visualizations, (v) => v.id === visualization.id);\n  if (index > -1) {\n    query.visualizations[index] = visualization;\n  } else {\n    // new visualization\n    query.visualizations.push(visualization);\n  }\n  query.visualizations = [...query.visualizations]; // clone array\n}\n\nfunction saveVisualization(visualization) {\n  if (visualization.id) {\n    recordEvent(\"update\", \"visualization\", visualization.id, { type: visualization.type });\n  } else {\n    recordEvent(\"create\", \"visualization\", null, { type: visualization.type });\n  }\n\n  return Visualization.save(visualization)\n    .then((result) => {\n      notification.success(\"Visualization saved\");\n      return result;\n    })\n    .catch((error) => {\n      notification.error(\"Visualization could not be saved\");\n      return Promise.reject(error);\n    });\n}\n\nfunction confirmDialogClose(isDirty) {\n  return new Promise((resolve, reject) => {\n    if (isDirty) {\n      Modal.confirm({\n        title: \"Visualization Editor\",\n        content: \"Are you sure you want to close the editor without saving?\",\n        okText: \"Yes\",\n        cancelText: \"No\",\n        onOk: () => resolve(),\n        onCancel: () => reject(),\n      });\n    } else {\n      resolve();\n    }\n  });\n}\n\nfunction EditVisualizationDialog({ dialog, visualization, query, queryResult }) {\n  const errorHandlerRef = useRef();\n\n  const isNew = !visualization;\n\n  const data = useQueryResultData(queryResult);\n  const [filters, setFilters] = useState(data.filters);\n\n  const filteredData = useMemo(\n    () => ({\n      columns: data.columns,\n      rows: filterData(data.rows, filters),\n    }),\n    [data, filters]\n  );\n\n  const defaultState = useMemo(() => {\n    const config = visualization ? registeredVisualizations[visualization.type] : getDefaultVisualization();\n    const options = config.getOptions(isNew ? {} : visualization.options, data);\n    return {\n      type: config.type,\n      name: isNew ? config.name : visualization.name,\n      options,\n      originalOptions: options,\n    };\n  }, [data, isNew, visualization]);\n\n  const [type, setType] = useState(defaultState.type);\n  const [name, setName] = useState(defaultState.name);\n  const [nameChanged, setNameChanged] = useState(false);\n  const [options, setOptions] = useState(defaultState.options);\n\n  const [saveInProgress, setSaveInProgress] = useState(false);\n\n  useEffect(() => {\n    if (errorHandlerRef.current) {\n      errorHandlerRef.current.reset();\n    }\n  }, [data, options]);\n\n  function onTypeChanged(newType) {\n    setType(newType);\n\n    const config = registeredVisualizations[newType];\n    if (!nameChanged) {\n      setName(config.name);\n    }\n\n    setOptions(config.getOptions(isNew ? {} : visualization.options, data));\n  }\n\n  function onNameChanged(newName) {\n    setName(newName);\n    setNameChanged(newName !== name);\n  }\n\n  function onOptionsChanged(newOptions) {\n    const config = registeredVisualizations[type];\n    setOptions(config.getOptions(newOptions, data));\n  }\n\n  function save() {\n    setSaveInProgress(true);\n    let visualizationOptions = options;\n    if (type === \"TABLE\") {\n      visualizationOptions = omit(visualizationOptions, [\"paginationSize\"]);\n    }\n\n    const visualizationData = extend(newVisualization(type), visualization, {\n      name,\n      options: visualizationOptions,\n      query_id: query.id,\n    });\n    saveVisualization(visualizationData).then((savedVisualization) => {\n      updateQueryVisualizations(query, savedVisualization);\n      dialog.close(savedVisualization);\n    });\n  }\n\n  function dismiss() {\n    const optionsChanged = !isEqual(options, defaultState.originalOptions);\n    confirmDialogClose(nameChanged || optionsChanged)\n      .then(dialog.dismiss)\n      .catch(() => {});\n  }\n\n  // When editing existing visualization chart type selector is disabled, so add only existing visualization's\n  // descriptor there (to properly render the component). For new visualizations show all types except of deprecated\n  const availableVisualizations = isNew\n    ? filter(sortBy(registeredVisualizations, [\"name\"]), (vis) => !vis.isDeprecated)\n    : pick(registeredVisualizations, [type]);\n\n  const vizTypeId = useUniqueId(\"visualization-type\");\n  const vizNameId = useUniqueId(\"visualization-name\");\n\n  return (\n    <Modal\n      {...dialog.props}\n      wrapClassName=\"ant-modal-fullscreen\"\n      title=\"Visualization Editor\"\n      okText=\"Save\"\n      okButtonProps={{\n        loading: saveInProgress,\n        disabled: saveInProgress,\n      }}\n      onOk={save}\n      onCancel={dismiss}\n      wrapProps={{ \"data-test\": \"EditVisualizationDialog\" }}\n    >\n      <div className=\"edit-visualization-dialog\">\n        <div className=\"visualization-settings\">\n          <div className=\"m-b-15\">\n            <label htmlFor={vizTypeId}>Visualization Type</label>\n            <Select\n              data-test=\"VisualizationType\"\n              id={vizTypeId}\n              className=\"w-100\"\n              disabled={!isNew}\n              virtual={false}\n              value={type}\n              onChange={onTypeChanged}\n            >\n              {map(availableVisualizations, (vis) => (\n                <Select.Option key={vis.type} data-test={\"VisualizationType.\" + vis.type}>\n                  {vis.name}\n                </Select.Option>\n              ))}\n            </Select>\n          </div>\n          <div className=\"m-b-15\">\n            <label htmlFor={vizNameId}>Visualization Name</label>\n            <Input\n              data-test=\"VisualizationName\"\n              id={vizNameId}\n              className=\"w-100\"\n              value={name}\n              onChange={(event) => onNameChanged(event.target.value)}\n            />\n          </div>\n          <div data-test=\"VisualizationEditor\">\n            <Editor\n              type={type}\n              data={data}\n              options={options}\n              visualizationName={name}\n              onOptionsChange={onOptionsChanged}\n            />\n          </div>\n        </div>\n        <div className=\"visualization-preview\">\n          <label htmlFor=\"visualization-preview\" className=\"invisible hidden-xs\">\n            Preview\n          </label>\n          <Filters filters={filters} onChange={setFilters} />\n          <div className=\"scrollbox\" data-test=\"VisualizationPreview\">\n            <Renderer\n              type={type}\n              data={filteredData}\n              options={options}\n              visualizationName={name}\n              onOptionsChange={onOptionsChanged}\n            />\n          </div>\n        </div>\n      </div>\n    </Modal>\n  );\n}\n\nEditVisualizationDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n  query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  visualization: VisualizationType,\n  queryResult: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n};\n\nEditVisualizationDialog.defaultProps = {\n  visualization: null,\n};\n\nexport default wrapDialog(EditVisualizationDialog);\n"
  },
  {
    "path": "client/app/components/visualizations/EditVisualizationDialog.less",
    "content": "@media (min-width: 992px) {\n  .edit-visualization-dialog {\n    display: flex;\n    height: 100%;\n\n    .visualization-settings {\n      padding-right: 12px;\n      width: 40%;\n      overflow: auto;\n    }\n\n    .visualization-preview {\n      padding-left: 12px;\n      width: 60%;\n      overflow: auto;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/components/visualizations/VisualizationName.jsx",
    "content": "import React from \"react\";\nimport { VisualizationType, registeredVisualizations } from \"@redash/viz/lib\";\n\nimport \"./VisualizationName.less\";\n\nfunction VisualizationName({ visualization }) {\n  const config = registeredVisualizations[visualization.type];\n  return (\n    <span className=\"visualization-name\">\n      {config && visualization.name !== config.name ? visualization.name : null}\n    </span>\n  );\n}\n\nVisualizationName.propTypes = {\n  visualization: VisualizationType.isRequired,\n};\n\nexport default VisualizationName;\n"
  },
  {
    "path": "client/app/components/visualizations/VisualizationName.less",
    "content": ".visualization-name:empty + span {\n  color: rgba(0, 0, 0, 0.8);\n}\n\n.visualization-name {\n  &:after {\n    content: \"−\";\n    margin-left: 5px;\n  }\n\n  &:empty:after {\n    content: none;\n  }\n}\n"
  },
  {
    "path": "client/app/components/visualizations/VisualizationRenderer.jsx",
    "content": "import { isEqual, map, find, fromPairs } from \"lodash\";\nimport React, { useState, useMemo, useEffect, useRef } from \"react\";\nimport PropTypes from \"prop-types\";\nimport useQueryResultData from \"@/lib/useQueryResultData\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\nimport Filters, { FiltersType, filterData } from \"@/components/Filters\";\nimport { VisualizationType } from \"@redash/viz/lib\";\nimport { Renderer } from \"@/components/visualizations/visualizationComponents\";\n\nfunction combineFilters(localFilters, globalFilters) {\n  // tiny optimization - to avoid unnecessary updates\n  if (localFilters.length === 0 || globalFilters.length === 0) {\n    return localFilters;\n  }\n\n  return map(localFilters, localFilter => {\n    const globalFilter = find(globalFilters, f => f.name === localFilter.name);\n    if (globalFilter) {\n      return {\n        ...localFilter,\n        current: globalFilter.current,\n      };\n    }\n    return localFilter;\n  });\n}\n\nfunction areFiltersEqual(a, b) {\n  if (a.length !== b.length) {\n    return false;\n  }\n\n  a = fromPairs(map(a, item => [item.name, item]));\n  b = fromPairs(map(b, item => [item.name, item]));\n\n  return isEqual(a, b);\n}\n\nexport default function VisualizationRenderer(props) {\n  const data = useQueryResultData(props.queryResult);\n  const [filters, setFilters] = useState(() => combineFilters(data.filters, props.filters)); // lazy initialization\n  const filtersRef = useRef();\n  filtersRef.current = filters;\n\n  const handleFiltersChange = useImmutableCallback(newFilters => {\n    if (!areFiltersEqual(newFilters, filters)) {\n      setFilters(newFilters);\n      props.onFiltersChange(newFilters);\n    }\n  });\n\n  // Reset local filters when query results updated\n  useEffect(() => {\n    handleFiltersChange(combineFilters(data.filters, props.filters));\n  }, [data.filters, props.filters, handleFiltersChange]);\n\n  // Update local filters when global filters changed.\n  // For correct behavior need to watch only `props.filters` here,\n  // therefore using ref to access current local filters\n  useEffect(() => {\n    handleFiltersChange(combineFilters(filtersRef.current, props.filters));\n  }, [props.filters, handleFiltersChange]);\n\n  const filteredData = useMemo(\n    () => ({\n      columns: data.columns,\n      rows: filterData(data.rows, filters),\n    }),\n    [data, filters]\n  );\n\n  const { showFilters, visualization } = props;\n\n  let options = { ...visualization.options };\n\n  // define pagination size based on context for Table visualization\n  if (visualization.type === \"TABLE\") {\n    options.paginationSize = props.context === \"widget\" ? \"small\" : \"default\";\n  }\n\n  return (\n    <Renderer\n      key={`visualization${visualization.id}`}\n      type={visualization.type}\n      options={options}\n      data={filteredData}\n      visualizationName={visualization.name}\n      addonBefore={showFilters && <Filters filters={filters} onChange={handleFiltersChange} />}\n    />\n  );\n}\n\nVisualizationRenderer.propTypes = {\n  visualization: VisualizationType.isRequired,\n  queryResult: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  showFilters: PropTypes.bool,\n  filters: FiltersType,\n  onFiltersChange: PropTypes.func,\n  context: PropTypes.oneOf([\"query\", \"widget\"]).isRequired,\n};\n\nVisualizationRenderer.defaultProps = {\n  showFilters: true,\n  filters: [],\n  onFiltersChange: () => {},\n};\n"
  },
  {
    "path": "client/app/components/visualizations/visualizationComponents.jsx",
    "content": "import React from \"react\";\nimport { pick } from \"lodash\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport Link from \"@/components/Link\";\nimport { Renderer as VisRenderer, Editor as VisEditor, updateVisualizationsSettings } from \"@redash/viz/lib\";\nimport { clientConfig } from \"@/services/auth\";\n\nimport countriesDataUrl from \"@redash/viz/lib/visualizations/choropleth/maps/countries.geo.json\";\nimport usaDataUrl from \"@redash/viz/lib/visualizations/choropleth/maps/usa-albers.geo.json\";\nimport subdivJapanDataUrl from \"@redash/viz/lib/visualizations/choropleth/maps/japan.prefectures.geo.json\";\n\nfunction wrapComponentWithSettings(WrappedComponent) {\n  return function VisualizationComponent(props) {\n    updateVisualizationsSettings({\n      HelpTriggerComponent: HelpTrigger,\n      LinkComponent: Link,\n      choroplethAvailableMaps: {\n        countries: {\n          name: \"Countries\",\n          url: countriesDataUrl,\n          fieldNames: {\n            name: \"Short name\",\n            name_long: \"Full name\",\n            abbrev: \"Abbreviated name\",\n            iso_a2: \"ISO code (2 letters)\",\n            iso_a3: \"ISO code (3 letters)\",\n            iso_n3: \"ISO code (3 digits)\",\n          },\n        },\n        usa: {\n          name: \"USA\",\n          url: usaDataUrl,\n          fieldNames: {\n            name: \"Name\",\n            ns_code: \"National Standard ANSI Code (8-character)\",\n            geoid: \"Geographic ID\",\n            usps_abbrev: \"USPS Abbreviation\",\n            fips_code: \"FIPS Code (2-character)\",\n          },\n        },\n        subdiv_japan: {\n          name: \"Japan/Prefectures\",\n          url: subdivJapanDataUrl,\n          fieldNames: {\n            name: \"Name\",\n            name_alt: \"Name (alternative)\",\n            name_local: \"Name (local)\",\n            iso_3166_2: \"ISO-3166-2\",\n            postal: \"Postal Code\",\n            type: \"Type\",\n            type_en: \"Type (EN)\",\n            region: \"Region\",\n            region_code: \"Region Code\",\n          },\n        },\n      },\n      ...pick(clientConfig, [\n        \"dateFormat\",\n        \"dateTimeFormat\",\n        \"integerFormat\",\n        \"floatFormat\",\n        \"nullValue\",\n        \"booleanValues\",\n        \"tableCellMaxJSONSize\",\n        \"allowCustomJSVisualizations\",\n        \"hidePlotlyModeBar\",\n      ]),\n    });\n\n    return <WrappedComponent {...props} />;\n  };\n}\n\nexport const Renderer = wrapComponentWithSettings(VisRenderer);\nexport const Editor = wrapComponentWithSettings(VisEditor);\n"
  },
  {
    "path": "client/app/config/antd-spinner.jsx",
    "content": "import React from \"react\";\nimport Spin from \"antd/lib/spin\";\n\nSpin.setDefaultIndicator(\n  <span role=\"status\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n    <i className=\"fa fa-spinner fa-pulse\" aria-hidden=\"true\" />\n    <span className=\"sr-only\">Loading...</span>\n  </span>\n);\n"
  },
  {
    "path": "client/app/config/dashboard-grid-options.js",
    "content": "export default {\n  columns: 12, // grid columns count\n  rowHeight: 50, // grid row height (incl. bottom padding)\n  margins: 15, // widget margins\n  mobileBreakPoint: 800,\n  // defaults for widgets\n  defaultSizeX: 6,\n  defaultSizeY: 3,\n  minSizeX: 2,\n  maxSizeX: 12,\n  minSizeY: 2,\n  maxSizeY: 1000,\n};\n"
  },
  {
    "path": "client/app/config/index.js",
    "content": "import moment from \"moment\";\nimport { isFunction } from \"lodash\";\n\n// Ensure that this image will be available in assets folder\nimport \"@/assets/images/avatar.svg\";\n\n// Register visualizations\nimport \"@redash/viz/lib\";\n\n// Register routes before registering extensions as they may want to override some\nimport \"@/pages\";\n\nimport \"./antd-spinner\";\n\nmoment.updateLocale(\"en\", {\n  relativeTime: {\n    future: \"%s\",\n    past: \"%s\",\n    s: \"just now\",\n    m: \"a minute ago\",\n    mm: \"%d minutes ago\",\n    h: \"an hour ago\",\n    hh: \"%d hours ago\",\n    d: \"a day ago\",\n    dd: \"%d days ago\",\n    M: \"a month ago\",\n    MM: \"%d months ago\",\n    y: \"a year ago\",\n    yy: \"%d years ago\",\n  },\n});\n\nfunction requireImages() {\n  // client/app/assets/images/<path> => /images/<path>\n  const ctx = require.context(\"@/assets/images/\", true, /\\.(png|jpe?g|gif|svg)$/);\n  ctx.keys().forEach(ctx);\n}\n\nfunction registerExtensions() {\n  const context = require.context(\"extensions\", true, /^((?![\\\\/.]test[\\\\./]).)*\\.jsx?$/);\n  const modules = context\n    .keys()\n    .map(context)\n    .map(module => module.default);\n\n  return modules\n    .filter(isFunction)\n    .filter(f => f.init)\n    .map(f => f());\n}\n\nrequireImages();\nregisterExtensions();\n"
  },
  {
    "path": "client/app/extensions/.gitkeep",
    "content": ""
  },
  {
    "path": "client/app/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" translate=\"no\">\n  <head>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta charset=\"UTF-8\" />\n    <base href=\"<%= htmlWebpackPlugin.options.baseHref %>\" />\n    <title><%= htmlWebpackPlugin.options.title %></title>\n    <script src=\"<%= htmlWebpackPlugin.options.staticPath %>unsupportedRedirect.js\" async></script>\n\n    <link\n      rel=\"icon\"\n      type=\"image/png\"\n      sizes=\"32x32\"\n      href=\"<%= htmlWebpackPlugin.options.staticPath %>images/favicon-32x32.png\"\n    />\n    <link\n      rel=\"icon\"\n      type=\"image/png\"\n      sizes=\"96x96\"\n      href=\"<%= htmlWebpackPlugin.options.staticPath %>images/favicon-96x96.png\"\n    />\n    <link\n      rel=\"icon\"\n      type=\"image/png\"\n      sizes=\"16x16\"\n      href=\"<%= htmlWebpackPlugin.options.staticPath %>images/favicon-16x16.png\"\n    />\n  </head>\n\n  <body>\n    <section>\n      <div id=\"application-root\"></div>\n      <div class=\"loading-indicator\">\n        <div id=\"css-logo\">\n          <div id=\"circle\">\n            <div></div>\n          </div>\n          <div id=\"point\">\n            <div></div>\n          </div>\n          <div id=\"bars\">\n            <div class=\"bar\"></div>\n            <div class=\"bar\"></div>\n            <div class=\"bar\"></div>\n            <div class=\"bar\"></div>\n          </div>\n        </div>\n        <div id=\"shadow\"></div>\n      </div>\n    </section>\n  </body>\n</html>\n"
  },
  {
    "path": "client/app/index.js",
    "content": "import React from \"react\";\nimport ReactDOM from \"react-dom\";\n\nimport \"@/config\";\n\nimport ApplicationArea from \"@/components/ApplicationArea\";\nimport offlineListener from \"@/services/offline-listener\";\n\nReactDOM.render(<ApplicationArea />, document.getElementById(\"application-root\"), () => {\n  offlineListener.init();\n});\n"
  },
  {
    "path": "client/app/lib/accessibility.ts",
    "content": "import { HTMLAttributes } from \"react\";\n\ninterface SrNotifyProps {\n  text: string;\n  expiry: number;\n  container: HTMLElement;\n  politeness: HTMLAttributes<HTMLDivElement>[\"aria-live\"];\n}\n\nexport function srNotify({ text, expiry = 1000, container = document.body, politeness = \"polite\" }: SrNotifyProps) {\n  const element = document.createElement(\"div\");\n  const id = `speak-${Date.now()}`;\n\n  element.id = id;\n  element.className = \"sr-only\";\n  element.textContent = text;\n\n  element.setAttribute(\"role\", \"alert\");\n  element.setAttribute(\"aria-live\", politeness);\n\n  container.appendChild(element);\n\n  let timer: null | number = null;\n  let isDone = false;\n  const cleanupFn = () => {\n    if (isDone) {\n      return;\n    }\n    isDone = true;\n\n    try {\n      container.removeChild(element);\n    } catch (e) {\n      console.error(e);\n    }\n\n    if (timer) {\n      window.clearTimeout(timer);\n    }\n  };\n\n  timer = window.setTimeout(cleanupFn, expiry);\n\n  return cleanupFn;\n}\n"
  },
  {
    "path": "client/app/lib/calculateTextWidth.ts",
    "content": "const canvas = document.createElement(\"canvas\");\ncanvas.style.display = \"none\";\ndocument.body.appendChild(canvas);\n\nexport function calculateTextWidth(text: string, container = document.body) {\n  const ctx = canvas.getContext(\"2d\");\n  if (ctx) {\n    const containerStyle = window.getComputedStyle(container);\n    ctx.font = `${containerStyle.fontSize} ${containerStyle.fontFamily}`;\n    const textMetrics = ctx.measureText(text);\n    let actualWidth = textMetrics.width;\n    if (\"actualBoundingBoxLeft\" in textMetrics) {\n      // only available on evergreen browsers\n      actualWidth = Math.abs(textMetrics.actualBoundingBoxLeft) + Math.abs(textMetrics.actualBoundingBoxRight);\n    }\n    return actualWidth;\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "client/app/lib/hooks/useFullscreenHandler.js",
    "content": "import { has } from \"lodash\";\nimport { useEffect, useState } from \"react\";\nimport location from \"@/services/location\";\n\nexport default function useFullscreenHandler() {\n  const [fullscreen, setFullscreen] = useState(has(location.search, \"fullscreen\"));\n  useEffect(() => {\n    document.body.classList.toggle(\"headless\", fullscreen);\n    location.setSearch({ fullscreen: fullscreen ? true : null }, true);\n  }, [fullscreen]);\n\n  const toggleFullscreen = () => setFullscreen(!fullscreen);\n  return [fullscreen, toggleFullscreen];\n}\n"
  },
  {
    "path": "client/app/lib/hooks/useImmutableCallback.js",
    "content": "import { isFunction, noop } from \"lodash\";\nimport { useRef, useCallback } from \"react\";\n\n// This hook wraps a potentially changeable function object and always returns the same\n// function so it's safe to use it with other hooks: wrapper function stays the same,\n// but will always call a latest wrapped function.\n// A quick note regarding `react-hooks/exhaustive-deps`: since wrapper function doesn't\n// change, it's safe to use it as a dependency, it will never trigger other hooks.\nexport default function useImmutableCallback(callback) {\n  const callbackRef = useRef();\n  callbackRef.current = isFunction(callback) ? callback : noop;\n\n  return useCallback((...args) => callbackRef.current(...args), []);\n}\n"
  },
  {
    "path": "client/app/lib/hooks/useLazyRef.ts",
    "content": "import { useRef } from \"react\";\n\nexport function useLazyRef<T>(getInitialValue: () => T) {\n  const lazyRef = useRef<T>(null) as React.MutableRefObject<T>;\n\n  if (lazyRef.current === null) {\n    lazyRef.current = getInitialValue();\n  }\n\n  return lazyRef;\n}\n"
  },
  {
    "path": "client/app/lib/hooks/useSearchResults.js",
    "content": "import { useState, useEffect, useRef } from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\n\nexport default function useSearchResults(fetch, { initialResults = null, debounceTimeout = 200 } = {}) {\n  const [result, setResult] = useState(initialResults);\n  const [isLoading, setIsLoading] = useState(false);\n  const currentSearchTerm = useRef(null);\n  const isDestroyed = useRef(false);\n\n  const [doSearch] = useDebouncedCallback(searchTerm => {\n    setIsLoading(true);\n    currentSearchTerm.current = searchTerm;\n    fetch(searchTerm)\n      .catch(() => initialResults)\n      .then(data => {\n        if (searchTerm === currentSearchTerm.current && !isDestroyed.current) {\n          setResult(data);\n          setIsLoading(false);\n        }\n      });\n  }, debounceTimeout);\n\n  useEffect(\n    () =>\n      // ignore all requests after component destruction\n      () => {\n        isDestroyed.current = true;\n      },\n    []\n  );\n\n  return [doSearch, result, isLoading];\n}\n"
  },
  {
    "path": "client/app/lib/hooks/useUniqueId.ts",
    "content": "import { uniqueId } from \"lodash\";\nimport { useLazyRef } from \"./useLazyRef\";\n\nexport function useUniqueId(prefix: string) {\n  const { current: id } = useLazyRef(() => uniqueId(prefix));\n  return id;\n}\n"
  },
  {
    "path": "client/app/lib/localOptions.js",
    "content": "const PREFIX = \"localOptions:\";\n\nfunction get(key, defaultValue = undefined) {\n  const fullKey = PREFIX + key;\n  if (fullKey in window.localStorage) {\n    return JSON.parse(window.localStorage.getItem(fullKey));\n  }\n  return defaultValue;\n}\n\nfunction set(key, value) {\n  const fullKey = PREFIX + key;\n  window.localStorage.setItem(fullKey, JSON.stringify(value));\n}\n\nexport default {\n  get,\n  set,\n};\n"
  },
  {
    "path": "client/app/lib/pagination/index.js",
    "content": "/* eslint-disable import/prefer-default-export */\nexport { default as Paginator } from \"./paginator\";\n"
  },
  {
    "path": "client/app/lib/pagination/paginator.js",
    "content": "import { sortBy } from \"lodash\";\n\nexport default class Paginator {\n  constructor(rows, { page = 1, itemsPerPage = 20, totalCount = undefined } = {}) {\n    this.page = page;\n    this.itemsPerPage = itemsPerPage;\n    this.updateRows(rows, totalCount);\n    this.orderByField = undefined;\n    this.orderByReverse = false;\n  }\n\n  setPage(page) {\n    this.page = page;\n  }\n\n  getPageRows() {\n    const first = this.itemsPerPage * (this.page - 1);\n    const last = this.itemsPerPage * this.page;\n\n    return this.rows.slice(first, last);\n  }\n\n  updateRows(rows, totalCount = undefined) {\n    this.rows = rows;\n    if (this.rows) {\n      this.totalCount = totalCount || rows.length;\n    } else {\n      this.totalCount = 0;\n    }\n  }\n\n  orderBy(column) {\n    if (column === this.orderByField) {\n      this.orderByReverse = !this.orderByReverse;\n    } else {\n      this.orderByField = column;\n      this.orderByReverse = false;\n    }\n\n    if (this.orderByField) {\n      this.rows = sortBy(this.rows, this.orderByField);\n      if (this.orderByReverse) {\n        this.rows = this.rows.reverse();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/lib/queryFormat.test.js",
    "content": "import { Query } from \"@/services/query\";\nimport * as queryFormat from \"./queryFormat\";\n\ndescribe(\"QueryFormat.formatQuery\", () => {\n  test(\"returns same query text when syntax is not supported\", () => {\n    const unsupportedSyntax = \"unsupported-syntax\";\n    const queryText = \"select * from example\";\n    const isFormatQueryAvailable = queryFormat.isFormatQueryAvailable(unsupportedSyntax);\n    const formattedQuery = queryFormat.formatQuery(queryText, unsupportedSyntax);\n\n    expect(isFormatQueryAvailable).toBeFalsy();\n    expect(formattedQuery).toBe(queryText);\n  });\n\n  describe(\"sql\", () => {\n    const syntax = \"sql\";\n\n    test(\"returns the formatted query text\", () => {\n      const queryText = \"select column1, column2 from example where column1 = 2\";\n      const expectedFormattedQueryText = [\n        \"select\",\n        \"  column1,\",\n        \"  column2\",\n        \"from\",\n        \"  example\",\n        \"where\",\n        \"  column1 = 2\",\n      ].join(\"\\n\");\n      const isFormatQueryAvailable = queryFormat.isFormatQueryAvailable(syntax);\n      const formattedQueryText = queryFormat.formatQuery(queryText, syntax);\n      expect(isFormatQueryAvailable).toBeTruthy();\n      expect(formattedQueryText).toBe(expectedFormattedQueryText);\n    });\n\n    test(\"still recognizes parameters after formatting\", () => {\n      const queryText = \"select {{param1}}, {{ param2 }}, {{ date-range.start }} from example\";\n      const formattedQueryText = queryFormat.formatQuery(queryText, syntax);\n      const queryParameters = new Query({ query: queryText }).getParameters().parseQuery();\n      const formattedQueryParameters = new Query({ query: formattedQueryText }).getParameters().parseQuery();\n      expect(formattedQueryParameters.sort()).toEqual(queryParameters.sort());\n    });\n  });\n\n  describe(\"json\", () => {\n    const syntax = \"json\";\n\n    test(\"returns the formatted query text\", () => {\n      const queryText = '{\"collection\": \"example\",\"limit\": 10}';\n      const expectedFormattedQueryText = '{\\n    \"collection\": \"example\",\\n    \"limit\": 10\\n}';\n      const isFormatQueryAvailable = queryFormat.isFormatQueryAvailable(syntax);\n      const formattedQueryText = queryFormat.formatQuery(queryText, syntax);\n      expect(isFormatQueryAvailable).toBeTruthy();\n      expect(formattedQueryText).toBe(expectedFormattedQueryText);\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/lib/queryFormat.ts",
    "content": "import { trim } from \"lodash\";\nimport sqlFormatter from \"sql-formatter\";\n\ninterface QueryFormatterMap {\n  [syntax: string]: (queryText: string) => string;\n}\n\nconst QueryFormatters: QueryFormatterMap = {\n  sql: queryText => sqlFormatter.format(trim(queryText)),\n  json: queryText => JSON.stringify(JSON.parse(queryText), null, 4),\n};\n\nexport function isFormatQueryAvailable(syntax: string) {\n  return syntax in QueryFormatters;\n}\n\nexport function formatQuery(queryText: string, syntax: string) {\n  if (!isFormatQueryAvailable(syntax)) {\n    return queryText;\n  }\n  const formatter = QueryFormatters[syntax];\n  return formatter(queryText);\n}\n"
  },
  {
    "path": "client/app/lib/useQueryResultData.js",
    "content": "import { useMemo } from \"react\";\nimport { get, invoke } from \"lodash\";\n\nfunction getQueryResultData(queryResult, queryResultStatus = null) {\n  return {\n    status: queryResultStatus || invoke(queryResult, \"getStatus\") || null,\n    columns: invoke(queryResult, \"getColumns\") || [],\n    rows: invoke(queryResult, \"getData\") || [],\n    filters: invoke(queryResult, \"getFilters\") || [],\n    updatedAt: invoke(queryResult, \"getUpdatedAt\") || null,\n    retrievedAt: get(queryResult, \"query_result.retrieved_at\", null),\n    truncated: invoke(queryResult, \"getTruncated\") || null,\n    log: invoke(queryResult, \"getLog\") || [],\n    error: invoke(queryResult, \"getError\") || null,\n    runtime: invoke(queryResult, \"getRuntime\") || null,\n    metadata: get(queryResult, \"query_result.data.metadata\", {}),\n  };\n}\n\nexport default function useQueryResultData(queryResult) {\n  // make sure it re-executes when queryResult status changes\n  const queryResultStatus = invoke(queryResult, \"getStatus\");\n  return useMemo(() => getQueryResultData(queryResult, queryResultStatus), [queryResult, queryResultStatus]);\n}\n"
  },
  {
    "path": "client/app/lib/utils.js",
    "content": "import moment from \"moment\";\nimport { clientConfig } from \"@/services/auth\";\n\nexport const IntervalEnum = {\n  NEVER: \"Never\",\n  SECONDS: \"second\",\n  MINUTES: \"minute\",\n  HOURS: \"hour\",\n  DAYS: \"day\",\n  WEEKS: \"week\",\n  MILLISECONDS: \"millisecond\",\n};\n\nexport const AbbreviatedTimeUnits = {\n  SECONDS: \"s\",\n  MINUTES: \"m\",\n  HOURS: \"h\",\n  DAYS: \"d\",\n  WEEKS: \"w\",\n  MILLISECONDS: \"ms\",\n};\n\nfunction formatDateTimeValue(value, format) {\n  if (!value) {\n    return \"\";\n  }\n\n  const parsed = moment(value);\n  if (!parsed.isValid()) {\n    return \"-\";\n  }\n\n  return parsed.format(format);\n}\n\nexport function formatDateTime(value) {\n  return formatDateTimeValue(value, clientConfig.dateTimeFormat);\n}\n\nexport function formatDateTimePrecise(value, withMilliseconds = false) {\n  return formatDateTimeValue(value, clientConfig.dateFormat + (withMilliseconds ? \" HH:mm:ss.SSS\" : \" HH:mm:ss\"));\n}\n\nexport function formatDate(value) {\n  return formatDateTimeValue(value, clientConfig.dateFormat);\n}\n\nexport function localizeTime(time) {\n  const [hrs, mins] = time.split(\":\");\n  return moment\n    .utc()\n    .hour(hrs)\n    .minute(mins)\n    .local()\n    .format(\"HH:mm\");\n}\n\nexport function secondsToInterval(count) {\n  if (!count) {\n    return { interval: IntervalEnum.NEVER };\n  }\n\n  let interval = IntervalEnum.SECONDS;\n  if (count >= 60) {\n    count /= 60;\n    interval = IntervalEnum.MINUTES;\n  }\n  if (count >= 60) {\n    count /= 60;\n    interval = IntervalEnum.HOURS;\n  }\n  if (count >= 24 && interval === IntervalEnum.HOURS) {\n    count /= 24;\n    interval = IntervalEnum.DAYS;\n  }\n  if (count >= 7 && !(count % 7) && interval === IntervalEnum.DAYS) {\n    count /= 7;\n    interval = IntervalEnum.WEEKS;\n  }\n  return { count, interval };\n}\n\nexport function pluralize(text, count) {\n  const should = count !== 1;\n  return text + (should ? \"s\" : \"\");\n}\n\nexport function durationHumanize(durationInSeconds, options = {}) {\n  if (!durationInSeconds) {\n    return \"-\";\n  }\n  let ret = \"\";\n  const { interval, count } = secondsToInterval(durationInSeconds);\n  const rounded = Math.round(count);\n  if (rounded !== 1 || !options.omitSingleValueNumber) {\n    ret = `${rounded} `;\n  }\n  ret += pluralize(interval, rounded);\n  return ret;\n}\n\nexport function toHuman(text) {\n  return text.replace(/_/g, \" \").replace(/(?:^|\\s)\\S/g, a => a.toUpperCase());\n}\n\nexport function remove(items, item) {\n  if (items === undefined) {\n    return items;\n  }\n\n  let notEquals;\n\n  if (item instanceof Array) {\n    notEquals = other => item.indexOf(other) === -1;\n  } else {\n    notEquals = other => item !== other;\n  }\n\n  const filtered = [];\n\n  for (let i = 0; i < items.length; i += 1) {\n    if (notEquals(items[i])) {\n      filtered.push(items[i]);\n    }\n  }\n\n  return filtered;\n}\n\n/**\n * Formats number to string\n * @param value {number}\n * @param [fractionDigits] {number}\n * @return {string}\n */\nexport function formatNumber(value, fractionDigits = 3) {\n  return Math.round(value) !== value ? value.toFixed(fractionDigits) : value.toString();\n}\n\n/**\n * Formats any number using predefined units\n * @param value {string|number}\n * @param divisor {number}\n * @param [units] {Array<string>}\n * @param [fractionDigits] {number}\n * @return {{unit: string, value: string, divisor: number}}\n */\nexport function prettyNumberWithUnit(value, divisor, units = [], fractionDigits) {\n  if (isNaN(parseFloat(value)) || !isFinite(value)) {\n    return {\n      value: \"\",\n      unit: \"\",\n      divisor: 1,\n    };\n  }\n\n  let unit = 0;\n  let greatestDivisor = 1;\n\n  while (value >= divisor && unit < units.length - 1) {\n    value /= divisor;\n    greatestDivisor *= divisor;\n    unit += 1;\n  }\n\n  return {\n    value: formatNumber(value, fractionDigits),\n    unit: units[unit],\n    divisor: greatestDivisor,\n  };\n}\n\nexport function prettySizeWithUnit(bytes, fractionDigits) {\n  return prettyNumberWithUnit(bytes, 1024, [\"bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"], fractionDigits);\n}\n\nexport function prettySize(bytes) {\n  const { value, unit } = prettySizeWithUnit(bytes);\n  if (!value) {\n    return \"?\";\n  }\n  return value + \" \" + unit;\n}\n\nexport function join(arr) {\n  if (arr === undefined || arr === null) {\n    return \"\";\n  }\n\n  return arr.join(\" / \");\n}\n\nexport function formatColumnValue(value, columnType = null) {\n  if (moment.isMoment(value)) {\n    if (columnType === \"date\") {\n      return formatDate(value);\n    }\n    return formatDateTime(value);\n  }\n\n  if (typeof value === \"boolean\") {\n    return value.toString();\n  }\n\n  return value;\n}\n"
  },
  {
    "path": "client/app/multi_org.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta charset=\"UTF-8\" />\n    <base href=\"{{base_href}}\" />\n    <title><%= htmlWebpackPlugin.options.title %></title>\n    <script src=\"<%= htmlWebpackPlugin.options.staticPath %>unsupportedRedirect.js\" async></script>\n\n    <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/static/images/favicon-32x32.png\" />\n    <link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"/static/images/favicon-96x96.png\" />\n    <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/static/images/favicon-16x16.png\" />\n  </head>\n\n  <body>\n    <section>\n      <div id=\"application-root\"></div>\n      <div class=\"loading-indicator\">\n        <div id=\"css-logo\">\n          <div id=\"circle\">\n            <div></div>\n          </div>\n          <div id=\"point\">\n            <div></div>\n          </div>\n          <div id=\"bars\">\n            <div class=\"bar\"></div>\n            <div class=\"bar\"></div>\n            <div class=\"bar\"></div>\n            <div class=\"bar\"></div>\n          </div>\n        </div>\n        <div id=\"shadow\"></div>\n      </div>\n    </section>\n  </body>\n</html>\n"
  },
  {
    "path": "client/app/pages/admin/Jobs.jsx",
    "content": "import { partition, flatMap, values } from \"lodash\";\nimport React from \"react\";\nimport moment from \"moment\";\n\nimport Alert from \"antd/lib/alert\";\nimport Tabs from \"antd/lib/tabs\";\nimport * as Grid from \"antd/lib/grid\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport Layout from \"@/components/admin/Layout\";\nimport { CounterCard, WorkersTable, QueuesTable, QueryJobsTable, OtherJobsTable } from \"@/components/admin/RQStatus\";\n\nimport { axios } from \"@/services/axios\";\nimport location from \"@/services/location\";\nimport recordEvent from \"@/services/recordEvent\";\nimport routes from \"@/services/routes\";\n\nclass Jobs extends React.Component {\n  state = {\n    activeTab: location.hash,\n    isLoading: true,\n    error: null,\n\n    queueCounters: [],\n    overallCounters: { started: 0, queued: 0 },\n    startedJobs: [],\n    workers: [],\n  };\n\n  _refreshTimer = null;\n\n  componentDidMount() {\n    recordEvent(\"view\", \"page\", \"admin/rq_status\");\n    this.refresh();\n  }\n\n  componentWillUnmount() {\n    // Ignore data after component unmounted\n    clearTimeout(this._refreshTimer);\n    this.processQueues = () => {};\n    this.handleError = () => {};\n  }\n\n  refresh = () => {\n    axios\n      .get(\"/api/admin/queries/rq_status\")\n      .then(data => this.processQueues(data))\n      .catch(error => this.handleError(error));\n\n    this._refreshTimer = setTimeout(this.refresh, 60 * 1000);\n  };\n\n  processQueues = ({ queues, workers }) => {\n    const queueCounters = values(queues).map(({ started, ...rest }) => ({\n      started: started.length,\n      ...rest,\n    }));\n\n    const overallCounters = queueCounters.reduce(\n      (c, q) => ({\n        started: c.started + q.started,\n        queued: c.queued + q.queued,\n      }),\n      { started: 0, queued: 0 }\n    );\n\n    const startedJobs = flatMap(values(queues), queue =>\n      queue.started.map(job => ({\n        ...job,\n        enqueued_at: moment.utc(job.enqueued_at),\n        started_at: moment.utc(job.started_at),\n      }))\n    );\n\n    this.setState({ isLoading: false, queueCounters, startedJobs, overallCounters, workers });\n  };\n\n  handleError = error => {\n    this.setState({ isLoading: false, error });\n  };\n\n  render() {\n    const { isLoading, error, queueCounters, startedJobs, overallCounters, workers, activeTab } = this.state;\n    const [startedQueryJobs, otherStartedJobs] = partition(startedJobs, [\n      \"name\",\n      \"redash.tasks.queries.execution.execute_query\",\n    ]);\n\n    const changeTab = newTab => {\n      location.setHash(newTab);\n      this.setState({ activeTab: newTab });\n    };\n\n    return (\n      <Layout activeTab=\"jobs\">\n        <div className=\"p-15\">\n          {error && <Alert type=\"error\" message=\"Failed loading status. Please refresh.\" />}\n\n          {!error && (\n            <React.Fragment>\n              <Grid.Row gutter={15} className=\"m-b-15\">\n                <Grid.Col span={8}>\n                  <CounterCard title=\"Started Jobs\" value={overallCounters.started} loading={isLoading} />\n                </Grid.Col>\n                <Grid.Col span={8}>\n                  <CounterCard title=\"Queued Jobs\" value={overallCounters.queued} loading={isLoading} />\n                </Grid.Col>\n              </Grid.Row>\n\n              <Tabs activeKey={activeTab || \"queues\"} onTabClick={changeTab} animated={false}>\n                <Tabs.TabPane key=\"queues\" tab=\"Queues\">\n                  <QueuesTable loading={isLoading} items={queueCounters} />\n                </Tabs.TabPane>\n                <Tabs.TabPane key=\"workers\" tab=\"Workers\">\n                  <WorkersTable loading={isLoading} items={workers} />\n                </Tabs.TabPane>\n                <Tabs.TabPane key=\"queries\" tab=\"Queries\">\n                  <QueryJobsTable loading={isLoading} items={startedQueryJobs} />\n                </Tabs.TabPane>\n                <Tabs.TabPane key=\"other\" tab=\"Other Jobs\">\n                  <OtherJobsTable loading={isLoading} items={otherStartedJobs} />\n                </Tabs.TabPane>\n              </Tabs>\n            </React.Fragment>\n          )}\n        </div>\n      </Layout>\n    );\n  }\n}\n\nroutes.register(\n  \"Admin.Jobs\",\n  routeWithUserSession({\n    path: \"/admin/queries/jobs\",\n    title: \"RQ Status\",\n    render: pageProps => <Jobs {...pageProps} />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/admin/OutdatedQueries.jsx",
    "content": "import { map, uniqueId } from \"lodash\";\nimport React from \"react\";\n\nimport Switch from \"antd/lib/switch\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport Link from \"@/components/Link\";\nimport Paginator from \"@/components/Paginator\";\nimport { QueryTagsControl } from \"@/components/tags-control/TagsControl\";\nimport SchedulePhrase from \"@/components/queries/SchedulePhrase\";\nimport TimeAgo from \"@/components/TimeAgo\";\nimport Layout from \"@/components/admin/Layout\";\n\nimport { wrap as itemsList, ControllerType } from \"@/components/items-list/ItemsList\";\nimport { ItemsSource } from \"@/components/items-list/classes/ItemsSource\";\nimport { StateStorage } from \"@/components/items-list/classes/StateStorage\";\n\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport ItemsTable, { Columns } from \"@/components/items-list/components/ItemsTable\";\n\nimport { axios } from \"@/services/axios\";\nimport { Query } from \"@/services/query\";\nimport recordEvent from \"@/services/recordEvent\";\nimport routes from \"@/services/routes\";\n\nclass OutdatedQueries extends React.Component {\n  static propTypes = {\n    controller: ControllerType.isRequired,\n  };\n\n  listColumns = [\n    {\n      title: \"ID\",\n      field: \"id\",\n      width: \"1%\",\n      align: \"right\",\n      sorter: true,\n    },\n    Columns.custom.sortable(\n      (text, item) => (\n        <React.Fragment>\n          <Link className=\"table-main-title\" href={\"queries/\" + item.id}>\n            {item.name}\n          </Link>\n          <QueryTagsControl\n            className=\"d-block\"\n            tags={item.tags}\n            isDraft={item.is_draft}\n            isArchived={item.is_archived}\n          />\n        </React.Fragment>\n      ),\n      {\n        title: \"Name\",\n        field: \"name\",\n        width: null,\n      }\n    ),\n    Columns.avatar({ field: \"user\", className: \"p-l-0 p-r-0\" }, name => `Created by ${name}`),\n    Columns.dateTime.sortable({ title: \"Created At\", field: \"created_at\" }),\n    Columns.duration.sortable({ title: \"Runtime\", field: \"runtime\" }),\n    Columns.dateTime.sortable({ title: \"Last Executed At\", field: \"retrieved_at\", orderByField: \"executed_at\" }),\n    Columns.custom.sortable((text, item) => <SchedulePhrase schedule={item.schedule} isNew={item.isNew()} />, {\n      title: \"Update Schedule\",\n      field: \"schedule\",\n    }),\n  ];\n\n  state = {\n    autoUpdate: true,\n  };\n\n  _updateTimer = null;\n  autoUpdateSwitchId = uniqueId(\"auto-update-switch\");\n\n  componentDidMount() {\n    recordEvent(\"view\", \"page\", \"admin/queries/outdated\");\n    this.update(true);\n  }\n\n  componentWillUnmount() {\n    clearTimeout(this._updateTimer);\n  }\n\n  update = (isInitialCall = false) => {\n    if (!isInitialCall && this.state.autoUpdate) {\n      this.props.controller.update();\n    }\n    this._updateTimer = setTimeout(this.update, 60 * 1000);\n  };\n\n  render() {\n    const { controller } = this.props;\n    return (\n      <Layout activeTab={controller.params.currentPage}>\n        <div className=\"m-15\">\n          <div>\n            <label htmlFor={this.autoUpdateSwitchId} className=\"m-0\">\n              Auto update\n            </label>\n            <Switch\n              id={this.autoUpdateSwitchId}\n              className=\"m-l-10\"\n              checked={this.state.autoUpdate}\n              onChange={autoUpdate => this.setState({ autoUpdate })}\n            />\n          </div>\n          {controller.params.lastUpdatedAt && (\n            <div className=\"m-t-5\">\n              Last updated: <TimeAgo date={controller.params.lastUpdatedAt * 1000} />\n            </div>\n          )}\n        </div>\n        {!controller.isLoaded && <LoadingState />}\n        {controller.isLoaded && controller.isEmpty && (\n          <div className=\"text-center p-15\">There are no outdated queries.</div>\n        )}\n        {controller.isLoaded && !controller.isEmpty && (\n          <div className=\"bg-white tiled table-responsive\">\n            <ItemsTable\n              items={controller.pageItems}\n              columns={this.listColumns}\n              orderByField={controller.orderByField}\n              orderByReverse={controller.orderByReverse}\n              toggleSorting={controller.toggleSorting}\n            />\n            <Paginator\n              showPageSizeSelect\n              totalCount={controller.totalItemsCount}\n              pageSize={controller.itemsPerPage}\n              onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}\n              page={controller.page}\n              onChange={page => controller.updatePagination({ page })}\n            />\n          </div>\n        )}\n      </Layout>\n    );\n  }\n}\n\nconst OutdatedQueriesPage = itemsList(\n  OutdatedQueries,\n  () =>\n    new ItemsSource({\n      doRequest(request, context) {\n        return (\n          axios\n            .get(\"/api/admin/queries/outdated\")\n            // eslint-disable-next-line camelcase\n            .then(({ queries, updated_at }) => {\n              context.setCustomParams({ lastUpdatedAt: parseFloat(updated_at) });\n              return queries;\n            })\n        );\n      },\n      processResults(items) {\n        return map(items, item => new Query(item));\n      },\n      isPlainList: true,\n    }),\n  () => new StateStorage({ orderByField: \"created_at\", orderByReverse: true })\n);\n\nroutes.register(\n  \"Admin.OutdatedQueries\",\n  routeWithUserSession({\n    path: \"/admin/queries/outdated\",\n    title: \"Outdated Queries\",\n    render: pageProps => <OutdatedQueriesPage {...pageProps} currentPage=\"outdated_queries\" />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/admin/SystemStatus.jsx",
    "content": "import { omit } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport Layout from \"@/components/admin/Layout\";\nimport * as StatusBlock from \"@/components/admin/StatusBlock\";\n\nimport { axios } from \"@/services/axios\";\nimport recordEvent from \"@/services/recordEvent\";\nimport routes from \"@/services/routes\";\n\nimport \"./system-status.less\";\n\nclass SystemStatus extends React.Component {\n  static propTypes = {\n    onError: PropTypes.func,\n  };\n\n  static defaultProps = {\n    onError: () => {},\n  };\n\n  state = {\n    queues: [],\n    manager: null,\n    databaseMetrics: {},\n    status: {},\n  };\n\n  _refreshTimer = null;\n\n  componentDidMount() {\n    recordEvent(\"view\", \"page\", \"admin/status\");\n    this.refresh();\n  }\n\n  componentWillUnmount() {\n    clearTimeout(this._refreshTimer);\n  }\n\n  refresh = () => {\n    axios\n      .get(\"/status.json\")\n      .then(data => {\n        this.setState({\n          queues: data.manager.queues,\n          manager: {\n            startedAt: data.manager.started_at * 1000,\n            lastRefreshAt: data.manager.last_refresh_at * 1000,\n            outdatedQueriesCount: data.manager.outdated_queries_count,\n          },\n          databaseMetrics: data.database_metrics.metrics || [],\n          status: omit(data, [\"workers\", \"manager\", \"database_metrics\"]),\n        });\n      })\n      .catch(error => this.props.onError(error));\n    this._refreshTimer = setTimeout(this.refresh, 60 * 1000);\n  };\n\n  render() {\n    return (\n      <Layout activeTab=\"system_status\">\n        <div className=\"system-status-page\">\n          <div className=\"system-status-page-blocks\">\n            <div className=\"system-status-page-block\">\n              <StatusBlock.General info={this.state.status} />\n            </div>\n            <div className=\"system-status-page-block\">\n              <StatusBlock.Manager info={this.state.manager} />\n            </div>\n            <div className=\"system-status-page-block\">\n              <StatusBlock.Queues info={this.state.queues} />\n            </div>\n            <div className=\"system-status-page-block\">\n              <StatusBlock.DatabaseMetrics info={this.state.databaseMetrics} />\n            </div>\n          </div>\n        </div>\n      </Layout>\n    );\n  }\n}\n\nroutes.register(\n  \"Admin.SystemStatus\",\n  routeWithUserSession({\n    path: \"/admin/status\",\n    title: \"System Status\",\n    render: pageProps => <SystemStatus {...pageProps} />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/admin/system-status.less",
    "content": ".system-status-page {\n  @gutter: 15px;\n\n  overflow: hidden;\n  padding: @gutter;\n\n  .system-status-page-blocks {\n    display: flex;\n    align-items: stretch;\n    flex-wrap: wrap;\n    margin: -@gutter / 2;\n\n    .system-status-page-block {\n      flex: 0 0 auto;\n      padding: @gutter / 2;\n      width: 100%;\n\n      display: flex;\n      align-items: stretch;\n\n      > div {\n        width: 100%;\n      }\n\n      @media (min-width: 768px) {\n        & {\n          width: 50%;\n        }\n      }\n\n      @media (min-width: 1600px) {\n        & {\n          width: 25%;\n        }\n      }\n    }\n\n    .ant-list-item {\n      &:first-child {\n        padding-top: 0;\n      }\n\n      &:last-child {\n        padding-bottom: 0;\n      }\n\n      &-content {\n        margin: 0;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/alert/Alert.jsx",
    "content": "import { head, includes, trim, template, values } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\n\nimport { currentUser } from \"@/services/auth\";\nimport notification from \"@/services/notification\";\nimport AlertService from \"@/services/alert\";\nimport { Query as QueryService } from \"@/services/query\";\nimport routes from \"@/services/routes\";\n\nimport MenuButton from \"./components/MenuButton\";\nimport AlertView from \"./AlertView\";\nimport AlertEdit from \"./AlertEdit\";\nimport AlertNew from \"./AlertNew\";\nimport notifications from \"@/services/notifications\";\n\nconst MODES = {\n  NEW: 0,\n  VIEW: 1,\n  EDIT: 2,\n};\n\nconst defaultNameBuilder = template(\"<%= query.name %>: <%= options.column %> <%= options.op %> <%= options.value %>\");\n\nexport function getDefaultName(alert) {\n  if (!alert.query) {\n    return \"New Alert\";\n  }\n  return defaultNameBuilder(alert);\n}\n\nclass Alert extends React.Component {\n  static propTypes = {\n    mode: PropTypes.oneOf(values(MODES)),\n    alertId: PropTypes.string,\n    onError: PropTypes.func,\n  };\n\n  static defaultProps = {\n    mode: null,\n    alertId: null,\n    onError: () => {},\n  };\n\n  _isMounted = false;\n\n  state = {\n    alert: null,\n    queryResult: null,\n    pendingRearm: null,\n    canEdit: false,\n    mode: null,\n  };\n\n  componentDidMount() {\n    this._isMounted = true;\n    const { mode } = this.props;\n    this.setState({ mode });\n\n    if (mode === MODES.NEW) {\n      this.setState({\n        alert: {\n          options: {\n            selector: \"first\",\n            op: \">\",\n            value: 1,\n            muted: false,\n          },\n        },\n        pendingRearm: 0,\n        canEdit: true,\n      });\n    } else {\n      const { alertId } = this.props;\n      AlertService.get({ id: alertId })\n        .then((alert) => {\n          if (this._isMounted) {\n            const canEdit = currentUser.canEdit(alert);\n\n            // force view mode if can't edit\n            if (!canEdit) {\n              this.setState({ mode: MODES.VIEW });\n              notification.warn(\n                \"You cannot edit this alert\",\n                \"You do not have sufficient permissions to edit this alert, and have been redirected to the view-only page.\",\n                { duration: 0 }\n              );\n            }\n\n            this.setState({ alert, canEdit, pendingRearm: alert.rearm });\n            this.onQuerySelected(alert.query);\n          }\n        })\n        .catch((error) => {\n          if (this._isMounted) {\n            this.props.onError(error);\n          }\n        });\n    }\n  }\n\n  componentWillUnmount() {\n    this._isMounted = false;\n  }\n\n  save = () => {\n    const { alert, pendingRearm } = this.state;\n\n    alert.name = trim(alert.name) || getDefaultName(alert);\n    alert.rearm = pendingRearm || null;\n\n    return AlertService.save(alert)\n      .then((alert) => {\n        notification.success(\"Saved.\");\n        navigateTo(`alerts/${alert.id}`, true);\n        this.setState({ alert, mode: MODES.VIEW });\n      })\n      .catch(() => {\n        notification.error(\"Failed saving alert.\");\n      });\n  };\n\n  onQuerySelected = (query) => {\n    this.setState(({ alert }) => ({\n      alert: Object.assign(alert, { query }),\n      queryResult: null,\n    }));\n\n    if (query) {\n      // get cached result for column names and values\n      new QueryService(query).getQueryResultPromise().then((queryResult) => {\n        if (this._isMounted) {\n          this.setState({ queryResult });\n          let { column } = this.state.alert.options;\n          const columns = queryResult.getColumnNames();\n\n          // default to first column name if none chosen, or irrelevant in current query\n          if (!column || !includes(columns, column)) {\n            column = head(queryResult.getColumnNames());\n          }\n          this.setAlertOptions({ column });\n        }\n      });\n    }\n  };\n\n  onNameChange = (name) => {\n    const { alert } = this.state;\n    this.setState({\n      alert: Object.assign(alert, { name }),\n    });\n  };\n\n  onRearmChange = (pendingRearm) => {\n    this.setState({ pendingRearm });\n  };\n\n  setAlertOptions = (obj) => {\n    const { alert } = this.state;\n    const options = { ...alert.options, ...obj };\n    this.setState({\n      alert: Object.assign(alert, { options }),\n    });\n  };\n\n  delete = () => {\n    const { alert } = this.state;\n    return AlertService.delete(alert)\n      .then(() => {\n        notification.success(\"Alert deleted successfully.\");\n        navigateTo(\"alerts\");\n      })\n      .catch(() => {\n        notification.error(\"Failed deleting alert.\");\n      });\n  };\n\n  evaluate = () => {\n    const { alert } = this.state;\n    return AlertService.evaluate(alert)\n      .then(() => {\n        notification.success(\"Alert evaluated. Refresh page for updated status.\");\n      })\n      .catch(() => {\n        notifications.error(\"Failed to evaluate alert.\");\n      });\n  };\n\n  mute = () => {\n    const { alert } = this.state;\n    return AlertService.mute(alert)\n      .then(() => {\n        this.setAlertOptions({ muted: true });\n        notification.warn(\"Notifications have been muted.\");\n      })\n      .catch(() => {\n        notification.error(\"Failed muting notifications.\");\n      });\n  };\n\n  unmute = () => {\n    const { alert } = this.state;\n    return AlertService.unmute(alert)\n      .then(() => {\n        this.setAlertOptions({ muted: false });\n        notification.success(\"Notifications have been restored.\");\n      })\n      .catch(() => {\n        notification.error(\"Failed restoring notifications.\");\n      });\n  };\n\n  edit = () => {\n    const { id } = this.state.alert;\n    navigateTo(`alerts/${id}/edit`, true);\n    this.setState({ mode: MODES.EDIT });\n  };\n\n  cancel = () => {\n    const { id } = this.state.alert;\n    navigateTo(`alerts/${id}`, true);\n    this.setState({ mode: MODES.VIEW });\n  };\n\n  render() {\n    const { alert } = this.state;\n    if (!alert) {\n      return <LoadingState className=\"m-t-30\" />;\n    }\n\n    const muted = !!alert.options.muted;\n    const { queryResult, mode, canEdit, pendingRearm } = this.state;\n\n    const menuButton = (\n      <MenuButton\n        doDelete={this.delete}\n        muted={muted}\n        mute={this.mute}\n        unmute={this.unmute}\n        canEdit={canEdit}\n        evaluate={this.evaluate}\n      />\n    );\n\n    const commonProps = {\n      alert,\n      queryResult,\n      pendingRearm,\n      save: this.save,\n      menuButton,\n      onQuerySelected: this.onQuerySelected,\n      onRearmChange: this.onRearmChange,\n      onNameChange: this.onNameChange,\n      onCriteriaChange: this.setAlertOptions,\n      onNotificationTemplateChange: this.setAlertOptions,\n    };\n\n    return (\n      <div className=\"alert-page\">\n        <div className=\"container\">\n          {mode === MODES.NEW && <AlertNew {...commonProps} />}\n          {mode === MODES.VIEW && (\n            <AlertView canEdit={canEdit} onEdit={this.edit} muted={muted} unmute={this.unmute} {...commonProps} />\n          )}\n          {mode === MODES.EDIT && <AlertEdit cancel={this.cancel} {...commonProps} />}\n        </div>\n      </div>\n    );\n  }\n}\n\nroutes.register(\n  \"Alerts.New\",\n  routeWithUserSession({\n    path: \"/alerts/new\",\n    title: \"New Alert\",\n    render: (pageProps) => <Alert {...pageProps} mode={MODES.NEW} />,\n  })\n);\nroutes.register(\n  \"Alerts.View\",\n  routeWithUserSession({\n    path: \"/alerts/:alertId\",\n    title: \"Alert\",\n    render: (pageProps) => <Alert {...pageProps} mode={MODES.VIEW} />,\n  })\n);\nroutes.register(\n  \"Alerts.Edit\",\n  routeWithUserSession({\n    path: \"/alerts/:alertId/edit\",\n    title: \"Alert\",\n    render: (pageProps) => <Alert {...pageProps} mode={MODES.EDIT} />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/alert/AlertEdit.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { Alert as AlertType } from \"@/components/proptypes\";\n\nimport Form from \"antd/lib/form\";\nimport Button from \"antd/lib/button\";\n\nimport Title from \"./components/Title\";\nimport Criteria from \"./components/Criteria\";\nimport NotificationTemplate from \"./components/NotificationTemplate\";\nimport Rearm from \"./components/Rearm\";\nimport Query from \"./components/Query\";\n\nimport HorizontalFormItem from \"./components/HorizontalFormItem\";\n\nexport default class AlertEdit extends React.Component {\n  _isMounted = false;\n\n  state = {\n    saving: false,\n  };\n\n  componentDidMount() {\n    this._isMounted = true;\n  }\n\n  componentWillUnmount() {\n    this._isMounted = false;\n  }\n\n  save = () => {\n    this.setState({ saving: true });\n    this.props.save().catch(() => {\n      if (this._isMounted) {\n        this.setState({ saving: false });\n      }\n    });\n  };\n\n  cancel = () => {\n    this.props.cancel();\n  };\n\n  render() {\n    const { alert, queryResult, pendingRearm, onNotificationTemplateChange, menuButton } = this.props;\n    const { onQuerySelected, onNameChange, onRearmChange, onCriteriaChange } = this.props;\n    const { query, name, options } = alert;\n    const { saving } = this.state;\n\n    return (\n      <>\n        <Title name={name} alert={alert} onChange={onNameChange} editMode>\n          <DynamicComponent name=\"AlertEdit.HeaderExtra\" alert={alert} />\n          <Button className=\"m-r-5\" onClick={() => this.cancel()}>\n            <i className=\"fa fa-times m-r-5\" aria-hidden=\"true\" />\n            Cancel\n          </Button>\n          <Button type=\"primary\" onClick={() => this.save()}>\n            {saving ? (\n              <span role=\"status\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                <i className=\"fa fa-spinner fa-pulse m-r-5\" aria-hidden=\"true\" />\n                <span className=\"sr-only\">Saving...</span>\n              </span>\n            ) : (\n              <>\n                <i className=\"fa fa-check m-r-5\" aria-hidden=\"true\" />\n              </>\n            )}\n            Save Changes\n          </Button>\n          {menuButton}\n        </Title>\n        <div className=\"bg-white tiled p-20\">\n          <div className=\"d-flex\">\n            <Form className=\"flex-fill\">\n              <HorizontalFormItem label=\"Query\">\n                <Query query={query} queryResult={queryResult} onChange={onQuerySelected} editMode />\n              </HorizontalFormItem>\n              {queryResult && options && (\n                <>\n                  <HorizontalFormItem label=\"Trigger when\" className=\"alert-criteria\">\n                    <Criteria\n                      columnNames={queryResult.getColumnNames()}\n                      resultValues={queryResult.getData()}\n                      alertOptions={options}\n                      onChange={onCriteriaChange}\n                      editMode\n                    />\n                  </HorizontalFormItem>\n                  <HorizontalFormItem label=\"When triggered, send notification\">\n                    <Rearm value={pendingRearm || 0} onChange={onRearmChange} editMode />\n                  </HorizontalFormItem>\n                  <HorizontalFormItem label=\"Template\">\n                    <NotificationTemplate\n                      alert={alert}\n                      query={query}\n                      columnNames={queryResult.getColumnNames()}\n                      resultValues={queryResult.getData()}\n                      subject={options.custom_subject}\n                      setSubject={subject => onNotificationTemplateChange({ custom_subject: subject })}\n                      body={options.custom_body}\n                      setBody={body => onNotificationTemplateChange({ custom_body: body })}\n                    />\n                  </HorizontalFormItem>\n                </>\n              )}\n            </Form>\n            <div>\n              <HelpTrigger className=\"f-13\" type=\"ALERT_SETUP\">\n                Setup Instructions <i className=\"fa fa-question-circle\" aria-hidden=\"true\" />\n                <span className=\"sr-only\">(help)</span>\n              </HelpTrigger>\n            </div>\n          </div>\n        </div>\n      </>\n    );\n  }\n}\n\nAlertEdit.propTypes = {\n  alert: AlertType.isRequired,\n  queryResult: PropTypes.object, // eslint-disable-line react/forbid-prop-types,\n  pendingRearm: PropTypes.number,\n  menuButton: PropTypes.node.isRequired,\n  save: PropTypes.func.isRequired,\n  cancel: PropTypes.func.isRequired,\n  onQuerySelected: PropTypes.func.isRequired,\n  onNameChange: PropTypes.func.isRequired,\n  onCriteriaChange: PropTypes.func.isRequired,\n  onRearmChange: PropTypes.func.isRequired,\n  onNotificationTemplateChange: PropTypes.func.isRequired,\n};\n\nAlertEdit.defaultProps = {\n  queryResult: null,\n  pendingRearm: null,\n};\n"
  },
  {
    "path": "client/app/pages/alert/AlertNew.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport { Alert as AlertType } from \"@/components/proptypes\";\n\nimport Form from \"antd/lib/form\";\nimport Button from \"antd/lib/button\";\n\nimport Title from \"./components/Title\";\nimport Criteria from \"./components/Criteria\";\nimport NotificationTemplate from \"./components/NotificationTemplate\";\nimport Rearm from \"./components/Rearm\";\nimport Query from \"./components/Query\";\nimport HorizontalFormItem from \"./components/HorizontalFormItem\";\n\nexport default class AlertNew extends React.Component {\n  state = {\n    saving: false,\n  };\n\n  save = () => {\n    this.setState({ saving: true });\n    this.props.save().catch(() => {\n      this.setState({ saving: false });\n    });\n  };\n\n  render() {\n    const { alert, queryResult, pendingRearm, onNotificationTemplateChange } = this.props;\n    const { onQuerySelected, onNameChange, onRearmChange, onCriteriaChange } = this.props;\n    const { query, name, options } = alert;\n    const { saving } = this.state;\n\n    return (\n      <>\n        <Title alert={alert} name={name} onChange={onNameChange} editMode />\n        <div className=\"bg-white tiled p-20\">\n          <div className=\"d-flex\">\n            <Form className=\"flex-fill\">\n              <div className=\"m-b-30\">\n                Start by selecting the query that you would like to monitor using the search bar.\n                <br />\n                Keep in mind that Alerts do not work with queries that use parameters.\n              </div>\n              <HorizontalFormItem label=\"Query\">\n                <Query query={query} queryResult={queryResult} onChange={onQuerySelected} editMode />\n              </HorizontalFormItem>\n              {queryResult && options && (\n                <>\n                  <HorizontalFormItem label=\"Trigger when\" className=\"alert-criteria\">\n                    <Criteria\n                      columnNames={queryResult.getColumnNames()}\n                      resultValues={queryResult.getData()}\n                      alertOptions={options}\n                      onChange={onCriteriaChange}\n                      editMode\n                    />\n                  </HorizontalFormItem>\n                  <HorizontalFormItem label=\"When triggered, send notification\">\n                    <Rearm value={pendingRearm || 0} onChange={onRearmChange} editMode />\n                  </HorizontalFormItem>\n                  <HorizontalFormItem label=\"Template\">\n                    <NotificationTemplate\n                      alert={alert}\n                      query={query}\n                      columnNames={queryResult.getColumnNames()}\n                      resultValues={queryResult.getData()}\n                      subject={options.custom_subject}\n                      setSubject={subject => onNotificationTemplateChange({ custom_subject: subject })}\n                      body={options.custom_body}\n                      setBody={body => onNotificationTemplateChange({ custom_body: body })}\n                    />\n                  </HorizontalFormItem>\n                </>\n              )}\n              <HorizontalFormItem>\n                <Button type=\"primary\" onClick={this.save} disabled={!query} className=\"btn-create-alert\">\n                  {saving && (\n                    <span role=\"status\" aria-live=\"polite\" aria-relevant=\"additions removals\">\n                      <i className=\"fa fa-spinner fa-pulse m-r-5\" aria-hidden=\"true\" />\n                      <span className=\"sr-only\">Saving...</span>\n                    </span>\n                  )}\n                  Create Alert\n                </Button>\n              </HorizontalFormItem>\n            </Form>\n            <HelpTrigger className=\"f-13\" type=\"ALERT_SETUP\">\n              Setup Instructions <i className=\"fa fa-question-circle\" aria-hidden=\"true\" />\n              <span className=\"sr-only\">(help)</span>\n            </HelpTrigger>\n          </div>\n        </div>\n      </>\n    );\n  }\n}\n\nAlertNew.propTypes = {\n  alert: AlertType.isRequired,\n  queryResult: PropTypes.object, // eslint-disable-line react/forbid-prop-types,\n  pendingRearm: PropTypes.number,\n  onQuerySelected: PropTypes.func.isRequired,\n  save: PropTypes.func.isRequired,\n  onNameChange: PropTypes.func.isRequired,\n  onRearmChange: PropTypes.func.isRequired,\n  onCriteriaChange: PropTypes.func.isRequired,\n  onNotificationTemplateChange: PropTypes.func.isRequired,\n};\n\nAlertNew.defaultProps = {\n  queryResult: null,\n  pendingRearm: null,\n};\n"
  },
  {
    "path": "client/app/pages/alert/AlertView.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\n\nimport Link from \"@/components/Link\";\nimport TimeAgo from \"@/components/TimeAgo\";\nimport { Alert as AlertType } from \"@/components/proptypes\";\n\nimport Form from \"antd/lib/form\";\nimport Button from \"antd/lib/button\";\nimport Tooltip from \"@/components/Tooltip\";\nimport AntAlert from \"antd/lib/alert\";\nimport * as Grid from \"antd/lib/grid\";\n\nimport Title from \"./components/Title\";\nimport Criteria from \"./components/Criteria\";\nimport Rearm from \"./components/Rearm\";\nimport Query from \"./components/Query\";\nimport AlertDestinations from \"./components/AlertDestinations\";\nimport HorizontalFormItem from \"./components/HorizontalFormItem\";\nimport { STATE_CLASS } from \"../alerts/AlertsList\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\n\nfunction AlertState({ state, lastTriggered }) {\n  return (\n    <div className=\"alert-state\">\n      <span className={`alert-state-indicator label ${STATE_CLASS[state]}`}>Status: {state}</span>\n      {state === \"unknown\" && <div className=\"ant-form-item-explain\">Alert condition has not been evaluated.</div>}\n      {lastTriggered && (\n        <div className=\"ant-form-item-explain\">\n          Last triggered{\" \"}\n          <span className=\"alert-last-triggered\">\n            <TimeAgo date={lastTriggered} />\n          </span>\n        </div>\n      )}\n    </div>\n  );\n}\n\nAlertState.propTypes = {\n  state: PropTypes.string.isRequired,\n  lastTriggered: PropTypes.string,\n};\n\nAlertState.defaultProps = {\n  lastTriggered: null,\n};\n\n// eslint-disable-next-line react/prefer-stateless-function\nexport default class AlertView extends React.Component {\n  state = {\n    unmuting: false,\n  };\n\n  unmute = () => {\n    this.setState({ unmuting: true });\n    this.props.unmute().finally(() => {\n      this.setState({ unmuting: false });\n    });\n  };\n\n  render() {\n    const { alert, queryResult, canEdit, onEdit, menuButton } = this.props;\n    const { query, name, options, rearm } = alert;\n\n    return (\n      <>\n        <Title name={name} alert={alert}>\n          <DynamicComponent name=\"AlertView.HeaderExtra\" alert={alert} />\n          {canEdit ? (\n            <>\n              <Button type=\"default\" onClick={canEdit ? onEdit : null} className={cx({ disabled: !canEdit })}>\n                <i className=\"fa fa-edit m-r-5\" aria-hidden=\"true\" />\n                Edit\n              </Button>\n              {menuButton}\n            </>\n          ) : (\n            <Tooltip title=\"You do not have sufficient permissions to edit this alert\">\n              <Button type=\"default\" onClick={canEdit ? onEdit : null} className={cx({ disabled: !canEdit })}>\n                <i className=\"fa fa-edit m-r-5\" aria-hidden=\"true\" />\n                Edit\n              </Button>\n              {menuButton}\n            </Tooltip>\n          )}\n        </Title>\n        <div className=\"bg-white tiled p-20\">\n          <Grid.Row type=\"flex\" gutter={16}>\n            <Grid.Col xs={24} md={16} className=\"d-flex\">\n              <Form className=\"flex-fill\">\n                <HorizontalFormItem>\n                  <AlertState state={alert.state} lastTriggered={alert.last_triggered_at} />\n                </HorizontalFormItem>\n                <HorizontalFormItem label=\"Query\">\n                  <Query query={query} queryResult={queryResult} />\n                </HorizontalFormItem>\n                {queryResult && options && (\n                  <>\n                    <HorizontalFormItem label=\"Trigger when\" className=\"alert-criteria\">\n                      <Criteria\n                        columnNames={queryResult.getColumnNames()}\n                        resultValues={queryResult.getData()}\n                        alertOptions={options}\n                      />\n                    </HorizontalFormItem>\n                    <HorizontalFormItem label=\"Notifications\" className=\"form-item-line-height-normal\">\n                      <Rearm value={rearm || 0} />\n                      <br />\n                      Set to {options.custom_subject || options.custom_body ? \"custom\" : \"default\"} notification\n                      template.\n                    </HorizontalFormItem>\n                  </>\n                )}\n              </Form>\n            </Grid.Col>\n            <Grid.Col xs={24} md={8}>\n              {options.muted && (\n                <AntAlert\n                  className=\"m-b-20\"\n                  message={\n                    <>\n                      <i className=\"fa fa-bell-slash-o\" aria-hidden=\"true\" /> Notifications are muted\n                    </>\n                  }\n                  description={\n                    <>\n                      Notifications for this alert will not be sent.\n                      <br />\n                      {canEdit && (\n                        <>\n                          To restore notifications click\n                          <Button\n                            size=\"small\"\n                            type=\"primary\"\n                            onClick={this.unmute}\n                            loading={this.state.unmuting}\n                            className=\"m-t-5 m-l-5\">\n                            Unmute\n                          </Button>\n                        </>\n                      )}\n                    </>\n                  }\n                  type=\"warning\"\n                />\n              )}\n              <h4>\n                Destinations{\" \"}\n                <Tooltip title=\"Open Alert Destinations page in a new tab.\">\n                  <Link href=\"destinations\" target=\"_blank\">\n                    <i className=\"fa fa-external-link f-13\" aria-hidden=\"true\" />\n                    <span className=\"sr-only\">(opens in a new tab)</span>\n                  </Link>\n                </Tooltip>\n              </h4>\n              <AlertDestinations alertId={alert.id} />\n            </Grid.Col>\n          </Grid.Row>\n        </div>\n      </>\n    );\n  }\n}\n\nAlertView.propTypes = {\n  alert: AlertType.isRequired,\n  queryResult: PropTypes.object, // eslint-disable-line react/forbid-prop-types,\n  canEdit: PropTypes.bool.isRequired,\n  onEdit: PropTypes.func.isRequired,\n  menuButton: PropTypes.node.isRequired,\n  unmute: PropTypes.func,\n};\n\nAlertView.defaultProps = {\n  queryResult: null,\n  unmute: null,\n};\n"
  },
  {
    "path": "client/app/pages/alert/components/AlertDestinations.jsx",
    "content": "import { without, find, includes, map, toLower } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport Link from \"@/components/Link\";\nimport Button from \"antd/lib/button\";\nimport SelectItemsDialog from \"@/components/SelectItemsDialog\";\nimport { Destination as DestinationType, UserProfile as UserType } from \"@/components/proptypes\";\n\nimport DestinationService, { IMG_ROOT } from \"@/services/destination\";\nimport AlertSubscription from \"@/services/alert-subscription\";\nimport { clientConfig, currentUser } from \"@/services/auth\";\nimport notification from \"@/services/notification\";\nimport ListItemAddon from \"@/components/groups/ListItemAddon\";\nimport EmailSettingsWarning from \"@/components/EmailSettingsWarning\";\nimport PlainButton from \"@/components/PlainButton\";\nimport Tooltip from \"@/components/Tooltip\";\n\nimport CloseOutlinedIcon from \"@ant-design/icons/CloseOutlined\";\nimport Switch from \"antd/lib/switch\";\n\nimport \"./AlertDestinations.less\";\n\nconst USER_EMAIL_DEST_ID = -1;\n\nfunction normalizeSub(sub) {\n  if (!sub.destination) {\n    sub.destination = {\n      id: USER_EMAIL_DEST_ID,\n      name: sub.user.email,\n      icon: \"DEPRECATED\",\n      type: \"email\",\n    };\n  }\n  return sub;\n}\n\nfunction ListItem({ destination: { name, type }, user, unsubscribe }) {\n  const canUnsubscribe = currentUser.isAdmin || currentUser.id === user.id;\n\n  return (\n    <li className=\"destination-wrapper\">\n      <img src={`${IMG_ROOT}/${type}.png`} className=\"destination-icon\" alt={name} />\n      <span className=\"flex-fill\">{name}</span>\n      {type === \"email\" && (\n        <EmailSettingsWarning className=\"destination-warning\" featureName=\"alert emails\" mode=\"icon\" />\n      )}\n      {canUnsubscribe && (\n        <Tooltip title=\"Remove\" mouseEnterDelay={0.5}>\n          <PlainButton className=\"remove-button\" onClick={unsubscribe}>\n            {/* TODO: lacks visual feedback */}\n            <CloseOutlinedIcon />\n          </PlainButton>\n        </Tooltip>\n      )}\n    </li>\n  );\n}\n\nListItem.propTypes = {\n  destination: DestinationType.isRequired,\n  user: UserType.isRequired,\n  unsubscribe: PropTypes.func.isRequired,\n};\n\nexport default class AlertDestinations extends React.Component {\n  static propTypes = {\n    alertId: PropTypes.any.isRequired,\n  };\n\n  state = {\n    dests: [],\n    subs: null,\n  };\n\n  componentDidMount() {\n    const { alertId } = this.props;\n    Promise.all([\n      DestinationService.query(), // get all destinations\n      AlertSubscription.query({ alertId }), // get subcriptions per alert\n    ]).then(([dests, subs]) => {\n      subs = subs.map(normalizeSub);\n      this.setState({ dests, subs });\n    });\n  }\n\n  showAddAlertSubDialog = () => {\n    const { dests, subs } = this.state;\n\n    SelectItemsDialog.showModal({\n      width: 570,\n      showCount: true,\n      extraFooterContent: (\n        <>\n          <i className=\"fa fa-info-circle\" aria-hidden=\"true\" /> Create new destinations in{\" \"}\n          <Tooltip title=\"Opens page in a new tab.\">\n            <Link href=\"destinations/new\" target=\"_blank\">\n              Alert Destinations\n            </Link>\n          </Tooltip>\n        </>\n      ),\n      dialogTitle: \"Add Existing Alert Destinations\",\n      inputPlaceholder: \"Search destinations...\",\n      searchItems: searchTerm => {\n        searchTerm = toLower(searchTerm);\n        return Promise.resolve(dests.filter(d => includes(toLower(d.name), searchTerm)));\n      },\n      renderItem: (item, { isSelected }) => {\n        const alreadyInGroup = !!find(subs, s => s.destination.id === item.id);\n\n        return {\n          content: (\n            <div className=\"destination-wrapper\">\n              <img src={`${IMG_ROOT}/${item.type}.png`} className=\"destination-icon\" alt={item.name} />\n              <span className=\"flex-fill\">{item.name}</span>\n              <ListItemAddon isSelected={isSelected} alreadyInGroup={alreadyInGroup} deselectedIcon=\"fa-plus\" />\n            </div>\n          ),\n          isDisabled: alreadyInGroup,\n          className: isSelected || alreadyInGroup ? \"selected\" : \"\",\n        };\n      },\n    }).onClose(items => {\n      const promises = map(items, item => this.subscribe(item));\n      return Promise.all(promises)\n        .then(() => {\n          notification.success(\"Subscribed.\");\n        })\n        .catch(() => {\n          notification.error(\"Failed saving subscription.\");\n          return Promise.reject(null); // keep dialog visible but suppress its default error message\n        });\n    });\n  };\n\n  onUserEmailToggle = sub => {\n    if (sub) {\n      this.unsubscribe(sub);\n    } else {\n      this.subscribe();\n    }\n  };\n\n  subscribe = dest => {\n    const { alertId } = this.props;\n\n    const sub = { alert_id: alertId };\n    if (dest) {\n      sub.destination_id = dest.id;\n    }\n\n    return AlertSubscription.create(sub).then(sub => {\n      const { subs } = this.state;\n      this.setState({\n        subs: [...subs, normalizeSub(sub)],\n      });\n    });\n  };\n\n  unsubscribe = sub => {\n    AlertSubscription.delete(sub)\n      .then(() => {\n        // not showing subscribe notification cause it's redundant here\n        const { subs } = this.state;\n        this.setState({\n          subs: without(subs, sub),\n        });\n      })\n      .catch(() => {\n        notification.error(\"Failed unsubscribing.\");\n      });\n  };\n\n  render() {\n    if (!this.props.alertId) {\n      return null;\n    }\n\n    const { subs } = this.state;\n    const currentUserEmailSub = find(subs, {\n      destination: { id: USER_EMAIL_DEST_ID },\n      user: { id: currentUser.id },\n    });\n    const filteredSubs = without(subs, currentUserEmailSub);\n    const { mailSettingsMissing } = clientConfig;\n\n    return (\n      <div className=\"alert-destinations\" data-test=\"AlertDestinations\">\n        <Tooltip title='Click to add an existing \"Alert Destination\"' mouseEnterDelay={0.5}>\n          <Button\n            data-test=\"ShowAddAlertSubDialog\"\n            type=\"primary\"\n            size=\"small\"\n            className=\"add-button\"\n            onClick={this.showAddAlertSubDialog}>\n            <i className=\"fa fa-plus f-12 m-r-5\" aria-hidden=\"true\" /> Add\n          </Button>\n        </Tooltip>\n        <ul>\n          <li className=\"destination-wrapper\">\n            <i className=\"destination-icon fa fa-envelope\" aria-hidden=\"true\" />\n            <span className=\"flex-fill\">{currentUser.email}</span>\n            <EmailSettingsWarning className=\"destination-warning\" featureName=\"alert emails\" mode=\"icon\" />\n            {!mailSettingsMissing && (\n              <Switch\n                size=\"small\"\n                className=\"toggle-button\"\n                checked={!!currentUserEmailSub}\n                loading={!subs}\n                onChange={() => this.onUserEmailToggle(currentUserEmailSub)}\n                data-test=\"UserEmailToggle\"\n              />\n            )}\n          </li>\n          {filteredSubs.map(s => (\n            <ListItem key={s.id} unsubscribe={() => this.unsubscribe(s)} {...s} />\n          ))}\n        </ul>\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "client/app/pages/alert/components/AlertDestinations.less",
    "content": ".alert-destinations {\n    position: relative;\n    \n    ul {\n      list-style: none;\n      padding: 0;\n      margin-top: 15px;\n\n      li {\n          color: rgba(0, 0, 0, 0.85);\n          height: 46px;\n          border-bottom: 1px solid #e8e8e8;\n\n          .remove-button {\n              cursor: pointer;\n              height: 40px;\n              width: 40px;\n              display: flex;\n              align-items: center;\n              justify-content: center;\n          }\n\n          .toggle-button {\n              margin: 0 7px;\n          }\n\n          .destination-warning {\n              color: #f5222d;\n\n              &:last-child {\n                  margin-right: 14px;\n              }\n          }\n      }\n  }\n\n  .add-button {\n      position: absolute;\n      right: 0;\n      top:-33px;\n  }\n}\n\n.destination-wrapper {\n  padding-left: 8px;\n  display: flex;\n  align-items: center;\n  min-height: 38px;\n  width: 100%;\n\n  .select-items-dialog & {\n      padding: 0;\n  }\n\n  .destination-icon  {\n      height: 25px;\n      width: 25px;\n      margin: 2px 5px 0 0;\n      filter: grayscale(1);\n\n      &.fa {\n          display: flex;\n          align-items: center;\n          justify-content: center;\n          font-size: 12px;\n      }\n\n      .select-items-dialog & {\n          width: 35px;\n          height: 35px;\n      }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/alert/components/Criteria.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { head, includes, toString, isEmpty } from \"lodash\";\n\nimport Input from \"antd/lib/input\";\nimport WarningFilledIcon from \"@ant-design/icons/WarningFilled\";\nimport Select from \"antd/lib/select\";\nimport Divider from \"antd/lib/divider\";\n\nimport { AlertOptions as AlertOptionsType } from \"@/components/proptypes\";\n\nimport \"./Criteria.less\";\n\nconst CONDITIONS = {\n  \">\": \"\\u003e\",\n  \">=\": \"\\u2265\",\n  \"<\": \"\\u003c\",\n  \"<=\": \"\\u2264\",\n  \"==\": \"\\u003d\",\n  \"!=\": \"\\u2260\",\n};\n\nconst VALID_STRING_CONDITIONS = [\"==\", \"!=\"];\n\nfunction DisabledInput({ children, minWidth }) {\n  return (\n    <div className=\"criteria-disabled-input\" style={{ minWidth }}>\n      {children}\n    </div>\n  );\n}\n\nDisabledInput.propTypes = {\n  children: PropTypes.node.isRequired,\n  minWidth: PropTypes.number.isRequired,\n};\n\nexport default function Criteria({ columnNames, resultValues, alertOptions, onChange, editMode }) {\n  const columnValue = !isEmpty(resultValues) ? head(resultValues)[alertOptions.column] : null;\n  const invalidMessage = (() => {\n    // bail if condition is valid for strings\n    if (includes(VALID_STRING_CONDITIONS, alertOptions.op)) {\n      return null;\n    }\n\n    if (isNaN(alertOptions.value)) {\n      return \"Value column type doesn't match threshold type.\";\n    }\n\n    if (isNaN(columnValue)) {\n      return \"Value column isn't supported by condition type.\";\n    }\n\n    return null;\n  })();\n\n  let columnHint;\n\n  if (alertOptions.selector === \"first\") {\n    columnHint = (\n      <small className=\"alert-criteria-hint\">\n        Top row value is <code className=\"p-0\">{toString(columnValue) || \"unknown\"}</code>\n      </small>\n    );\n  } else if (alertOptions.selector === \"max\") {\n    columnHint = (\n      <small className=\"alert-criteria-hint\">\n        Max column value is{\" \"}\n        <code className=\"p-0\">\n          {toString(\n            Math.max(...resultValues.map((o) => Number(o[alertOptions.column])).filter((value) => !isNaN(value)))\n          ) || \"unknown\"}\n        </code>\n      </small>\n    );\n  } else if (alertOptions.selector === \"min\") {\n    columnHint = (\n      <small className=\"alert-criteria-hint\">\n        Min column value is{\" \"}\n        <code className=\"p-0\">\n          {toString(\n            Math.min(...resultValues.map((o) => Number(o[alertOptions.column])).filter((value) => !isNaN(value)))\n          ) || \"unknown\"}\n        </code>\n      </small>\n    );\n  }\n\n  return (\n    <div data-test=\"Criteria\">\n      <div className=\"input-title\">\n        <span className=\"input-label\">Selector</span>\n        {editMode ? (\n          <Select\n            value={alertOptions.selector}\n            onChange={(selector) => onChange({ selector })}\n            optionLabelProp=\"label\"\n            dropdownMatchSelectWidth={false}\n            style={{ width: 80 }}\n          >\n            <Select.Option value=\"first\" label=\"first\">\n              first\n            </Select.Option>\n            <Select.Option value=\"min\" label=\"min\">\n              min\n            </Select.Option>\n            <Select.Option value=\"max\" label=\"max\">\n              max\n            </Select.Option>\n          </Select>\n        ) : (\n          <DisabledInput minWidth={60}>{alertOptions.selector}</DisabledInput>\n        )}\n      </div>\n      <div className=\"input-title\">\n        <span className=\"input-label\">Value column</span>\n        {editMode ? (\n          <Select\n            value={alertOptions.column}\n            onChange={(column) => onChange({ column })}\n            dropdownMatchSelectWidth={false}\n            style={{ minWidth: 100 }}\n          >\n            {columnNames.map((name) => (\n              <Select.Option key={name}>{name}</Select.Option>\n            ))}\n          </Select>\n        ) : (\n          <DisabledInput minWidth={70}>{alertOptions.column}</DisabledInput>\n        )}\n      </div>\n      <div className=\"input-title\">\n        <span className=\"input-label\">Condition</span>\n        {editMode ? (\n          <Select\n            value={alertOptions.op}\n            onChange={(op) => onChange({ op })}\n            optionLabelProp=\"label\"\n            dropdownMatchSelectWidth={false}\n            style={{ width: 55 }}\n          >\n            <Select.Option value=\">\" label={CONDITIONS[\">\"]}>\n              {CONDITIONS[\">\"]} greater than\n            </Select.Option>\n            <Select.Option value=\">=\" label={CONDITIONS[\">=\"]}>\n              {CONDITIONS[\">=\"]} greater than or equals\n            </Select.Option>\n            <Select.Option disabled key=\"dv1\">\n              <Divider className=\"select-option-divider m-t-10 m-b-5\" />\n            </Select.Option>\n            <Select.Option value=\"<\" label={CONDITIONS[\"<\"]}>\n              {CONDITIONS[\"<\"]} less than\n            </Select.Option>\n            <Select.Option value=\"<=\" label={CONDITIONS[\"<=\"]}>\n              {CONDITIONS[\"<=\"]} less than or equals\n            </Select.Option>\n            <Select.Option disabled key=\"dv2\">\n              <Divider className=\"select-option-divider m-t-10 m-b-5\" />\n            </Select.Option>\n            <Select.Option value=\"==\" label={CONDITIONS[\"==\"]}>\n              {CONDITIONS[\"==\"]} equals\n            </Select.Option>\n            <Select.Option value=\"!=\" label={CONDITIONS[\"!=\"]}>\n              {CONDITIONS[\"!=\"]} not equal to\n            </Select.Option>\n          </Select>\n        ) : (\n          <DisabledInput minWidth={50}>{CONDITIONS[alertOptions.op]}</DisabledInput>\n        )}\n      </div>\n      <div className=\"input-title\">\n        <label className=\"input-label\" htmlFor=\"threshold-criterion\">\n          Threshold\n        </label>\n        {editMode ? (\n          <Input\n            id=\"threshold-criterion\"\n            style={{ width: 90 }}\n            value={alertOptions.value}\n            onChange={(e) => onChange({ value: e.target.value })}\n          />\n        ) : (\n          <DisabledInput minWidth={50}>{alertOptions.value}</DisabledInput>\n        )}\n      </div>\n      <div className=\"ant-form-item-explain\">\n        {columnHint}\n        <br />\n        {invalidMessage && (\n          <small>\n            <WarningFilledIcon className=\"warning-icon-danger\" /> {invalidMessage}\n          </small>\n        )}\n      </div>\n    </div>\n  );\n}\n\nCriteria.propTypes = {\n  columnNames: PropTypes.arrayOf(PropTypes.string).isRequired,\n  resultValues: PropTypes.arrayOf(PropTypes.object).isRequired,\n  alertOptions: AlertOptionsType.isRequired,\n  onChange: PropTypes.func,\n  editMode: PropTypes.bool,\n};\n\nCriteria.defaultProps = {\n  onChange: () => {},\n  editMode: false,\n};\n"
  },
  {
    "path": "client/app/pages/alert/components/Criteria.less",
    "content": ".alert-criteria {\n  margin-top: 40px !important;\n\n  .input-title {\n    display: inline-block;\n    width: auto;\n    margin-right: 8px;\n    margin-bottom: 17px; // assure no collision when not enough room for horizontal layout\n    position: relative;\n    vertical-align: middle;\n\n    & > .input-label {\n      position: absolute;\n      top: -16px;\n      left: 0;\n      line-height: normal;\n      font-size: 10px;\n\n      & + * {\n        vertical-align: top;\n      }\n    }\n  }\n\n  .ant-form-item-explain {\n    margin-top: -17px; // compensation for .input-title bottom margin\n  }\n\n  .alert-criteria-hint code {\n    overflow: hidden;\n    max-width: 100px;\n    display: inline-block;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    vertical-align: middle;\n  }\n}\n\n.criteria-disabled-input {\n  text-align: center;\n  line-height: 35px;\n  height: 35px;\n  max-width: 200px;\n  background: #ececec;\n  border: 1px solid #d9d9d9;\n  border-radius: 2px;\n  margin-bottom: 5px;\n  text-overflow: ellipsis;\n  overflow: hidden;\n  white-space: nowrap;\n  padding: 0 8px;\n}\n"
  },
  {
    "path": "client/app/pages/alert/components/HorizontalFormItem.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport Form from \"antd/lib/form\";\n\nexport default function HorizontalFormItem({ children, label, className, ...props }) {\n  const labelCol = { span: 4 };\n  const wrapperCol = { span: 16 };\n  if (!label) {\n    wrapperCol.offset = 4;\n  }\n\n  className = cx(\"alert-form-item\", className);\n\n  return (\n    <Form.Item labelCol={labelCol} wrapperCol={wrapperCol} label={label} className={className} {...props}>\n      {children}\n    </Form.Item>\n  );\n}\n\nHorizontalFormItem.propTypes = {\n  children: PropTypes.node,\n  label: PropTypes.string,\n  className: PropTypes.string,\n};\n\nHorizontalFormItem.defaultProps = {\n  children: null,\n  label: null,\n  className: null,\n};\n"
  },
  {
    "path": "client/app/pages/alert/components/MenuButton.jsx",
    "content": "import React, { useState, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\n\nimport Modal from \"antd/lib/modal\";\nimport Dropdown from \"antd/lib/dropdown\";\nimport Menu from \"antd/lib/menu\";\nimport Button from \"antd/lib/button\";\n\nimport LoadingOutlinedIcon from \"@ant-design/icons/LoadingOutlined\";\nimport EllipsisOutlinedIcon from \"@ant-design/icons/EllipsisOutlined\";\nimport PlainButton from \"@/components/PlainButton\";\n\nexport default function MenuButton({ doDelete, canEdit, mute, unmute, evaluate, muted }) {\n  const [loading, setLoading] = useState(false);\n\n  const execute = useCallback(action => {\n    setLoading(true);\n    action().finally(() => {\n      setLoading(false);\n    });\n  }, []);\n\n  const confirmDelete = useCallback(() => {\n    Modal.confirm({\n      title: \"Delete Alert\",\n      content: \"Are you sure you want to delete this alert?\",\n      okText: \"Delete\",\n      okType: \"danger\",\n      onOk: () => {\n        setLoading(true);\n        doDelete().catch(() => {\n          setLoading(false);\n        });\n      },\n      maskClosable: true,\n      autoFocusButton: null,\n    });\n  }, [doDelete]);\n\n  return (\n    <Dropdown\n      className={cx(\"m-l-5\", { disabled: !canEdit })}\n      trigger={[canEdit ? \"click\" : undefined]}\n      placement=\"bottomRight\"\n      overlay={\n        <Menu>\n          <Menu.Item>\n            {muted ? (\n              <PlainButton onClick={() => execute(unmute)}>Unmute Notifications</PlainButton>\n            ) : (\n              <PlainButton onClick={() => execute(mute)}>Mute Notifications</PlainButton>\n            )}\n          </Menu.Item>\n          <Menu.Item>\n            <PlainButton onClick={confirmDelete}>Delete</PlainButton>\n          </Menu.Item>\n          <Menu.Item>\n            <PlainButton onClick={() => execute(evaluate)}>Evaluate</PlainButton>\n          </Menu.Item>\n        </Menu>\n      }>\n      <Button aria-label=\"More actions\">\n        {loading ? <LoadingOutlinedIcon /> : <EllipsisOutlinedIcon rotate={90} aria-hidden=\"true\" />}\n      </Button>\n    </Dropdown>\n  );\n}\n\nMenuButton.propTypes = {\n  doDelete: PropTypes.func.isRequired,\n  canEdit: PropTypes.bool.isRequired,\n  mute: PropTypes.func.isRequired,\n  unmute: PropTypes.func.isRequired,\n  evaluate: PropTypes.func.isRequired,\n  muted: PropTypes.bool,\n};\n\nMenuButton.defaultProps = {\n  muted: false,\n};\n"
  },
  {
    "path": "client/app/pages/alert/components/NotificationTemplate.jsx",
    "content": "import React, { useState } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { head, isEmpty, isNull, isUndefined } from \"lodash\";\nimport Mustache from \"mustache\";\n\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport { Alert as AlertType, Query as QueryType } from \"@/components/proptypes\";\n\nimport Input from \"antd/lib/input\";\nimport Select from \"antd/lib/select\";\nimport Modal from \"antd/lib/modal\";\nimport Switch from \"antd/lib/switch\";\n\nimport \"./NotificationTemplate.less\";\n\nfunction normalizeCustomTemplateData(alert, query, columnNames, resultValues) {\n  const topValue = !isEmpty(resultValues) ? head(resultValues)[alert.options.column] : null;\n\n  return {\n    ALERT_STATUS: \"TRIGGERED\",\n    ALERT_CONDITION: alert.options.op,\n    ALERT_THRESHOLD: alert.options.value,\n    ALERT_NAME: alert.name,\n    ALERT_URL: `${window.location.origin}/alerts/${alert.id}`,\n    QUERY_NAME: query.name,\n    QUERY_URL: `${window.location.origin}/queries/${query.id}`,\n    QUERY_RESULT_VALUE: isNull(topValue) || isUndefined(topValue) ? \"UNKNOWN\" : topValue,\n    QUERY_RESULT_ROWS: resultValues,\n    QUERY_RESULT_COLS: columnNames,\n  };\n}\n\nfunction NotificationTemplate({ alert, query, columnNames, resultValues, subject, setSubject, body, setBody }) {\n  const hasContent = !!(subject || body);\n  const [enabled, setEnabled] = useState(hasContent ? 1 : 0);\n  const [showPreview, setShowPreview] = useState(false);\n\n  const renderData = normalizeCustomTemplateData(alert, query, columnNames, resultValues);\n\n  const render = tmpl => Mustache.render(tmpl || \"\", renderData);\n  const onEnabledChange = value => {\n    if (value || !hasContent) {\n      setEnabled(value);\n      setShowPreview(false);\n    } else {\n      Modal.confirm({\n        title: \"Are you sure?\",\n        content: \"Switching to default template will discard your custom template.\",\n        onOk: () => {\n          setSubject(null);\n          setBody(null);\n          setEnabled(value);\n          setShowPreview(false);\n        },\n        maskClosable: true,\n        autoFocusButton: null,\n      });\n    }\n  };\n\n  return (\n    <div className=\"alert-template\">\n      <Select\n        value={enabled}\n        onChange={onEnabledChange}\n        optionLabelProp=\"label\"\n        dropdownMatchSelectWidth={false}\n        style={{ width: \"fit-content\" }}>\n        <Select.Option value={0} label=\"Use default template\">\n          Default template\n        </Select.Option>\n        <Select.Option value={1} label=\"Use custom template\">\n          Custom template\n        </Select.Option>\n      </Select>\n      {!!enabled && (\n        <div className=\"alert-custom-template\" data-test=\"AlertCustomTemplate\">\n          <div className=\"d-flex align-items-center\">\n            <h5 className=\"flex-fill\">Subject / Body</h5>\n            Preview{\" \"}\n            <Switch size=\"small\" className=\"alert-template-preview\" value={showPreview} onChange={setShowPreview} />\n          </div>\n          {/* TODO: consider adding real labels (not clear for sighted users as well) */}\n          <Input\n            value={showPreview ? render(subject) : subject}\n            aria-label=\"Subject\"\n            onChange={e => setSubject(e.target.value)}\n            disabled={showPreview}\n            data-test=\"CustomSubject\"\n          />\n          <Input.TextArea\n            value={showPreview ? render(body) : body}\n            aria-label=\"Body\"\n            autoSize={{ minRows: 9 }}\n            onChange={e => setBody(e.target.value)}\n            disabled={showPreview}\n            data-test=\"CustomBody\"\n          />\n          <HelpTrigger type=\"ALERT_NOTIF_TEMPLATE_GUIDE\" className=\"f-13\">\n            <i className=\"fa fa-question-circle\" aria-hidden=\"true\" /> Formatting guide{\" \"}\n            <span className=\"sr-only\">(help)</span>\n          </HelpTrigger>\n        </div>\n      )}\n    </div>\n  );\n}\n\nNotificationTemplate.propTypes = {\n  alert: AlertType.isRequired,\n  query: QueryType.isRequired,\n  columnNames: PropTypes.arrayOf(PropTypes.string).isRequired,\n  resultValues: PropTypes.arrayOf(PropTypes.any).isRequired,\n  subject: PropTypes.string,\n  setSubject: PropTypes.func.isRequired,\n  body: PropTypes.string,\n  setBody: PropTypes.func.isRequired,\n};\n\nNotificationTemplate.defaultProps = {\n  subject: \"\",\n  body: \"\",\n};\n\nexport default NotificationTemplate;\n"
  },
  {
    "path": "client/app/pages/alert/components/NotificationTemplate.less",
    "content": ".alert-template {\n  display: flex;\n  flex-direction: column;\n\n  input {\n      margin-bottom: 10px;\n  }\n\n  textarea {\n      margin-bottom: 0 !important;\n  }\n\n  input, textarea {\n      font-family: \"Roboto Mono\", monospace;\n      font-size: 12px;\n      letter-spacing: -0.4px  ;\n\n      &[disabled] {\n          color: inherit;\n          cursor: auto;\n      }\n  }\n\n  .alert-custom-template {\n      margin-top: 10px;\n      padding: 4px 10px 2px;\n      background: #fbfbfb;\n      border: 1px dashed #d9d9d9;\n      border-radius: 3px;\n      max-width: 500px;\n  }\n\n  .alert-template-preview {\n      margin: 0 0 0 5px !important;\n  }\n}"
  },
  {
    "path": "client/app/pages/alert/components/Query.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport Link from \"@/components/Link\";\nimport QuerySelector from \"@/components/QuerySelector\";\nimport SchedulePhrase from \"@/components/queries/SchedulePhrase\";\nimport { Query as QueryType } from \"@/components/proptypes\";\n\nimport Tooltip from \"@/components/Tooltip\";\n\nimport WarningFilledIcon from \"@ant-design/icons/WarningFilled\";\nimport QuestionCircleTwoToneIcon from \"@ant-design/icons/QuestionCircleTwoTone\";\nimport LoadingOutlinedIcon from \"@ant-design/icons/LoadingOutlined\";\n\nimport \"./Query.less\";\n\nexport default function QueryFormItem({ query, queryResult, onChange, editMode }) {\n  const queryHint =\n    query && query.schedule ? (\n      <small>\n        Scheduled to refresh{\" \"}\n        <i className=\"alert-query-schedule\">\n          <SchedulePhrase schedule={query.schedule} isNew={false} />\n        </i>\n      </small>\n    ) : (\n      <small>\n        <WarningFilledIcon className=\"warning-icon-danger\" /> This query has no <i>refresh schedule</i>.{\" \"}\n        <Tooltip title=\"A query schedule is not necessary but is highly recommended for alerts. An Alert without a query schedule will only send notifications if a user in your organization manually executes this query.\">\n          <a role=\"presentation\">\n            Why it&apos;s recommended <QuestionCircleTwoToneIcon />\n          </a>\n        </Tooltip>\n      </small>\n    );\n\n  return (\n    <>\n      {editMode ? (\n        <QuerySelector onChange={onChange} selectedQuery={query} className=\"alert-query-selector\" type=\"select\" />\n      ) : (\n        <Tooltip title=\"Open query in a new tab.\">\n          <Link href={`queries/${query.id}`} target=\"_blank\" rel=\"noopener noreferrer\" className=\"alert-query-link\">\n            {query.name} <i className=\"fa fa-external-link\" aria-hidden=\"true\" />\n            <span className=\"sr-only\">(opens in a new tab)</span>\n          </Link>\n        </Tooltip>\n      )}\n      <div className=\"ant-form-item-explain\">{query && queryHint}</div>\n      {query && !queryResult && (\n        <div className=\"m-t-30\">\n          <LoadingOutlinedIcon className=\"m-r-5\" /> Loading query data\n        </div>\n      )}\n    </>\n  );\n}\n\nQueryFormItem.propTypes = {\n  query: QueryType,\n  queryResult: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  onChange: PropTypes.func,\n  editMode: PropTypes.bool,\n};\n\nQueryFormItem.defaultProps = {\n  query: null,\n  queryResult: null,\n  onChange: () => {},\n  editMode: false,\n};\n"
  },
  {
    "path": "client/app/pages/alert/components/Query.less",
    "content": ".alert-query-link {\n  font-size: 14px;\n\n  .fa-external-link {\n    vertical-align: text-bottom;\n  }\n}\n\n.alert-query-schedule {\n  font-style: italic;\n  text-transform: lowercase;\n}\n\n@media only percy {\n  // hide query selector arrow in Percy to avoid a flaky snapshot\n  .alert-query-selector {\n    .ant-select-arrow-icon {\n      display: none !important;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/alert/components/Rearm.jsx",
    "content": "import React, { useState, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { toLower, isNumber } from \"lodash\";\n\nimport InputNumber from \"antd/lib/input-number\";\nimport Select from \"antd/lib/select\";\n\nimport \"./Rearm.less\";\n\nconst DURATIONS = [\n  [\"Second\", 1],\n  [\"Minute\", 60],\n  [\"Hour\", 3600],\n  [\"Day\", 86400],\n  [\"Week\", 604800],\n];\n\nfunction RearmByDuration({ value, onChange, editMode }) {\n  const [durationIdx, setDurationIdx] = useState();\n  const [count, setCount] = useState();\n\n  useEffect(() => {\n    for (let i = DURATIONS.length - 1; i >= 0; i -= 1) {\n      const [, durValue] = DURATIONS[i];\n      if (value % durValue === 0) {\n        setDurationIdx(i);\n        setCount(value / durValue);\n        break;\n      }\n    }\n  }, [value]);\n\n  if (!isNumber(count) || !isNumber(durationIdx)) {\n    return null;\n  }\n\n  const onChangeCount = newCount => {\n    setCount(newCount);\n    onChange(newCount * DURATIONS[durationIdx][1]);\n  };\n\n  const onChangeIdx = newIdx => {\n    setDurationIdx(newIdx);\n    onChange(count * DURATIONS[newIdx][1]);\n  };\n\n  const plural = count !== 1 ? \"s\" : \"\";\n\n  if (editMode) {\n    return (\n      <>\n        <InputNumber value={count} onChange={onChangeCount} min={1} precision={0} />\n        <Select value={durationIdx} onChange={onChangeIdx}>\n          {DURATIONS.map(([name], idx) => (\n            <Select.Option value={idx} key={name}>\n              {name}\n              {plural}\n            </Select.Option>\n          ))}\n        </Select>\n      </>\n    );\n  }\n\n  const [name] = DURATIONS[durationIdx];\n  return count + \" \" + toLower(name) + plural;\n}\n\nRearmByDuration.propTypes = {\n  onChange: PropTypes.func,\n  value: PropTypes.number.isRequired,\n  editMode: PropTypes.bool.isRequired,\n};\n\nRearmByDuration.defaultProps = {\n  onChange: () => {},\n};\n\nfunction RearmEditor({ value, onChange }) {\n  const [selected, setSelected] = useState(value < 2 ? value : 2);\n\n  const _onChange = newSelected => {\n    setSelected(newSelected);\n    onChange(newSelected < 2 ? newSelected : 3600);\n  };\n\n  return (\n    <div className=\"alert-rearm\">\n      <Select\n        optionLabelProp=\"label\"\n        defaultValue={selected || 0}\n        dropdownMatchSelectWidth={false}\n        onChange={_onChange}>\n        <Select.Option value={0} label=\"Just once\">\n          Just once <em>until back to normal</em>\n        </Select.Option>\n        <Select.Option value={1} label=\"Each time alert is evaluated\">\n          Each time alert is evaluated <em>until back to normal</em>\n        </Select.Option>\n        <Select.Option value={2} label=\"At most every\">\n          At most every ... <em>when alert is evaluated</em>\n        </Select.Option>\n      </Select>\n      {selected === 2 && value && <RearmByDuration value={value} onChange={onChange} editMode />}\n    </div>\n  );\n}\n\nRearmEditor.propTypes = {\n  onChange: PropTypes.func.isRequired,\n  value: PropTypes.number.isRequired,\n};\n\nfunction RearmViewer({ value }) {\n  let phrase = \"\";\n  switch (value) {\n    case 0:\n      phrase = \"just once, until back to normal\";\n      break;\n    case 1:\n      phrase = \"each time alert is evaluated, until back to normal\";\n      break;\n    default:\n      phrase = (\n        <>\n          at most every <RearmByDuration value={value} editMode={false} />, when alert is evaluated\n        </>\n      );\n  }\n\n  return <span>Notifications are sent {phrase}.</span>;\n}\n\nRearmViewer.propTypes = {\n  value: PropTypes.number.isRequired,\n};\n\nexport default function Rearm({ editMode, ...props }) {\n  return editMode ? <RearmEditor {...props} /> : <RearmViewer {...props} />;\n}\n\nRearm.propTypes = {\n  onChange: PropTypes.func,\n  value: PropTypes.number.isRequired,\n  editMode: PropTypes.bool,\n};\n\nRearm.defaultProps = {\n  onChange: null,\n  editMode: false,\n};\n"
  },
  {
    "path": "client/app/pages/alert/components/Rearm.less",
    "content": ".alert-rearm > * {\n  vertical-align: top;\n  margin-right: 8px !important;\n\n  &.ant-select {\n      width: auto !important;\n  }\n}"
  },
  {
    "path": "client/app/pages/alert/components/Title.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Input from \"antd/lib/input\";\nimport { getDefaultName } from \"../Alert\";\n\nimport { Alert as AlertType } from \"@/components/proptypes\";\n\nimport \"./Title.less\";\n\nexport default function Title({ alert, editMode, name, onChange, children }) {\n  const defaultName = getDefaultName(alert);\n  return (\n    <div className=\"alert-header\">\n      <div className=\"alert-title\">\n        <h3>\n          {editMode && alert.query ? (\n            // BUG: Input is not the same width as the container\n            // TODO: consider adding a label (not obvious for sighted users)\n            <Input\n              className=\"f-inherit\"\n              placeholder={defaultName}\n              value={name}\n              aria-label=\"Alert title\"\n              onChange={e => onChange(e.target.value)}\n            />\n          ) : (\n            name || defaultName\n          )}\n        </h3>\n      </div>\n      <div className=\"alert-actions\">{children}</div>\n    </div>\n  );\n}\n\nTitle.propTypes = {\n  alert: AlertType.isRequired,\n  name: PropTypes.string,\n  children: PropTypes.node,\n  onChange: PropTypes.func,\n  editMode: PropTypes.bool,\n};\n\nTitle.defaultProps = {\n  name: null,\n  children: null,\n  onChange: null,\n  editMode: false,\n};\n"
  },
  {
    "path": "client/app/pages/alert/components/Title.less",
    "content": ".alert-header {\n  display: flex;\n  align-items: center;\n  flex-wrap: wrap;\n  margin-top: 10px;\n  margin-bottom: 5px;\n\n  & > div {\n    padding: 5px 0;\n  }\n\n  .alert-title {\n    flex: 1 1;\n\n    h3 {\n      margin: 0 15px 0 0;\n\n      @media (max-width: 767px) {\n        font-size: 18px;\n      }\n    }\n  }\n\n  .alert-actions {\n    display: flex;\n    flex-wrap: nowrap;\n\n    @media (max-width: 515px) {\n      flex-basis: 100%;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/alerts/AlertsList.jsx",
    "content": "import { toUpper } from \"lodash\";\nimport React from \"react\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport Link from \"@/components/Link\";\nimport PageHeader from \"@/components/PageHeader\";\nimport Paginator from \"@/components/Paginator\";\nimport EmptyState, { EmptyStateHelpMessage } from \"@/components/empty-state/EmptyState\";\nimport { wrap as itemsList, ControllerType } from \"@/components/items-list/ItemsList\";\nimport { ResourceItemsSource } from \"@/components/items-list/classes/ItemsSource\";\nimport { StateStorage } from \"@/components/items-list/classes/StateStorage\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\n\nimport ItemsTable, { Columns } from \"@/components/items-list/components/ItemsTable\";\n\nimport Alert from \"@/services/alert\";\nimport { currentUser } from \"@/services/auth\";\nimport routes from \"@/services/routes\";\n\nexport const STATE_CLASS = {\n  unknown: \"label-warning\",\n  ok: \"label-success\",\n  triggered: \"label-danger\",\n};\n\nclass AlertsList extends React.Component {\n  static propTypes = {\n    controller: ControllerType.isRequired,\n  };\n\n  listColumns = [\n    Columns.custom.sortable(\n      (text, alert) => (\n        <span title={alert.options.muted ? \"Muted\" : \"Active\"}>\n          <i className={`fa fa-bell-${alert.options.muted ? \"slash\" : \"o\"} p-r-0`} aria-hidden=\"true\" />\n          <span className=\"sr-only\">{alert.options.muted ? \"Muted\" : \"Active\"}</span>\n        </span>\n      ),\n      {\n        title: (\n          <>\n            <i className=\"fa fa-bell p-r-0\" aria-hidden=\"true\" />\n            <span className=\"sr-only\">Sort by notification status.</span>\n          </>\n        ),\n        field: \"muted\",\n        width: \"1%\",\n      }\n    ),\n    Columns.custom.sortable(\n      (text, alert) => (\n        <div>\n          <Link className=\"table-main-title\" href={\"alerts/\" + alert.id}>\n            {alert.name}\n          </Link>\n        </div>\n      ),\n      {\n        title: \"Name\",\n        field: \"name\",\n      }\n    ),\n    Columns.custom((text, item) => item.user.name, { title: \"Created By\", width: \"1%\" }),\n    Columns.custom.sortable(\n      (text, alert) => (\n        <div>\n          <span className={`label ${STATE_CLASS[alert.state]}`}>{toUpper(alert.state)}</span>\n        </div>\n      ),\n      {\n        title: \"State\",\n        field: \"state\",\n        width: \"1%\",\n        className: \"text-nowrap\",\n      }\n    ),\n    Columns.timeAgo.sortable({ title: \"Last Updated At\", field: \"updated_at\", width: \"1%\" }),\n    Columns.dateTime.sortable({ title: \"Created At\", field: \"created_at\", width: \"1%\" }),\n  ];\n\n  render() {\n    const { controller } = this.props;\n\n    return (\n      <div className=\"page-alerts-list\">\n        <div className=\"container\">\n          <PageHeader\n            title={controller.params.pageTitle}\n            actions={\n              currentUser.hasPermission(\"list_alerts\") ? (\n                <Link.Button block type=\"primary\" href=\"alerts/new\">\n                  <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n                  New Alert\n                </Link.Button>\n              ) : null\n            }\n          />\n          <div>\n            {controller.isLoaded && controller.isEmpty ? (\n              <DynamicComponent name=\"AlertsList.EmptyState\">\n                <EmptyState\n                  icon=\"fa fa-bell-o\"\n                  illustration=\"alert\"\n                  description=\"Get notified on certain events\"\n                  helpMessage={<EmptyStateHelpMessage helpTriggerType=\"ALERTS\" />}\n                  showAlertStep\n                />\n              </DynamicComponent>\n            ) : (\n              <div className=\"table-responsive bg-white tiled\">\n                <ItemsTable\n                  loading={!controller.isLoaded}\n                  items={controller.pageItems}\n                  columns={this.listColumns}\n                  orderByField={controller.orderByField}\n                  orderByReverse={controller.orderByReverse}\n                  toggleSorting={controller.toggleSorting}\n                />\n                <Paginator\n                  showPageSizeSelect\n                  totalCount={controller.totalItemsCount}\n                  pageSize={controller.itemsPerPage}\n                  onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}\n                  page={controller.page}\n                  onChange={page => controller.updatePagination({ page })}\n                />\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    );\n  }\n}\n\nconst AlertsListPage = itemsList(\n  AlertsList,\n  () =>\n    new ResourceItemsSource({\n      isPlainList: true,\n      getRequest() {\n        return {};\n      },\n      getResource() {\n        return Alert.query.bind(Alert);\n      },\n    }),\n  () => new StateStorage({ orderByField: \"created_at\", orderByReverse: true, itemsPerPage: 20 })\n);\n\nroutes.register(\n  \"Alerts.List\",\n  routeWithUserSession({\n    path: \"/alerts\",\n    title: \"Alerts\",\n    render: pageProps => <AlertsListPage {...pageProps} currentPage=\"alerts\" />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/dashboards/DashboardList.jsx",
    "content": "import React from \"react\";\nimport cx from \"classnames\";\n\nimport Button from \"antd/lib/button\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport Link from \"@/components/Link\";\nimport PageHeader from \"@/components/PageHeader\";\nimport Paginator from \"@/components/Paginator\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { DashboardTagsControl } from \"@/components/tags-control/TagsControl\";\nimport { wrap as itemsList, ControllerType } from \"@/components/items-list/ItemsList\";\nimport { ResourceItemsSource } from \"@/components/items-list/classes/ItemsSource\";\nimport { UrlStateStorage } from \"@/components/items-list/classes/StateStorage\";\nimport * as Sidebar from \"@/components/items-list/components/Sidebar\";\nimport ItemsTable, { Columns } from \"@/components/items-list/components/ItemsTable\";\nimport useItemsListExtraActions from \"@/components/items-list/hooks/useItemsListExtraActions\";\nimport CreateDashboardDialog from \"@/components/dashboards/CreateDashboardDialog\";\nimport Layout from \"@/components/layouts/ContentWithSidebar\";\n\nimport { Dashboard } from \"@/services/dashboard\";\nimport { currentUser } from \"@/services/auth\";\nimport routes from \"@/services/routes\";\n\nimport DashboardListEmptyState from \"./components/DashboardListEmptyState\";\n\nimport \"./dashboard-list.css\";\n\nconst sidebarMenu = [\n  {\n    key: \"all\",\n    href: \"dashboards\",\n    title: \"All Dashboards\",\n    icon: () => <Sidebar.MenuIcon icon=\"zmdi zmdi-view-quilt\" />,\n  },\n  {\n    key: \"my\",\n    href: \"dashboards/my\",\n    title: \"My Dashboards\",\n    icon: () => <Sidebar.ProfileImage user={currentUser} />,\n  },\n  {\n    key: \"favorites\",\n    href: \"dashboards/favorites\",\n    title: \"Favorites\",\n    icon: () => <Sidebar.MenuIcon icon=\"fa fa-star\" />,\n  },\n];\n\nconst listColumns = [\n  Columns.favorites({ className: \"p-r-0\" }),\n  Columns.custom.sortable(\n    (text, item) => (\n      <React.Fragment>\n        <Link className=\"table-main-title\" href={item.url} data-test={`DashboardId${item.id}`}>\n          {item.name}\n        </Link>\n        <DashboardTagsControl\n          className=\"d-block\"\n          tags={item.tags}\n          isDraft={item.is_draft}\n          isArchived={item.is_archived}\n        />\n      </React.Fragment>\n    ),\n    {\n      title: \"Name\",\n      field: \"name\",\n      width: null,\n    }\n  ),\n  Columns.custom((text, item) => item.user.name, { title: \"Created By\", width: \"1%\" }),\n  Columns.dateTime.sortable({\n    title: \"Created At\",\n    field: \"created_at\",\n    width: \"1%\",\n  }),\n];\n\nfunction DashboardListExtraActions(props) {\n  return <DynamicComponent name=\"DashboardList.Actions\" {...props} />;\n}\n\nfunction DashboardList({ controller }) {\n  let usedListColumns = listColumns;\n  if (controller.params.currentPage === \"favorites\") {\n    usedListColumns = [\n      ...usedListColumns,\n      Columns.dateTime.sortable({ title: \"Starred At\", field: \"starred_at\", width: \"1%\" }),\n    ];\n  }\n  const {\n    areExtraActionsAvailable,\n    listColumns: tableColumns,\n    Component: ExtraActionsComponent,\n    selectedItems,\n  } = useItemsListExtraActions(controller, usedListColumns, DashboardListExtraActions);\n\n  return (\n    <div className=\"page-dashboard-list\">\n      <div className=\"container\">\n        <PageHeader\n          title={controller.params.pageTitle}\n          actions={\n            currentUser.hasPermission(\"create_dashboard\") ? (\n              <Button block type=\"primary\" onClick={() => CreateDashboardDialog.showModal()}>\n                <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n                New Dashboard\n              </Button>\n            ) : null\n          }\n        />\n        <Layout>\n          <Layout.Sidebar className=\"m-b-0\">\n            <Sidebar.SearchInput\n              placeholder=\"Search Dashboards...\"\n              label=\"Search dashboards\"\n              value={controller.searchTerm}\n              onChange={controller.updateSearch}\n            />\n            <Sidebar.Menu items={sidebarMenu} selected={controller.params.currentPage} />\n            <Sidebar.Tags url=\"api/dashboards/tags\" onChange={controller.updateSelectedTags} showUnselectAll />\n          </Layout.Sidebar>\n          <Layout.Content>\n            <div data-test=\"DashboardLayoutContent\">\n              {controller.isLoaded && controller.isEmpty ? (\n                <DashboardListEmptyState\n                  page={controller.params.currentPage}\n                  searchTerm={controller.searchTerm}\n                  selectedTags={controller.selectedTags}\n                />\n              ) : (\n                <React.Fragment>\n                  <div className={cx({ \"m-b-10\": areExtraActionsAvailable })}>\n                    <ExtraActionsComponent selectedItems={selectedItems} />\n                  </div>\n                  <div className=\"bg-white tiled table-responsive\">\n                    <ItemsTable\n                      items={controller.pageItems}\n                      loading={!controller.isLoaded}\n                      columns={tableColumns}\n                      orderByField={controller.orderByField}\n                      orderByReverse={controller.orderByReverse}\n                      toggleSorting={controller.toggleSorting}\n                    />\n                    <Paginator\n                      showPageSizeSelect\n                      totalCount={controller.totalItemsCount}\n                      pageSize={controller.itemsPerPage}\n                      onPageSizeChange={(itemsPerPage) => controller.updatePagination({ itemsPerPage })}\n                      page={controller.page}\n                      onChange={(page) => controller.updatePagination({ page })}\n                    />\n                  </div>\n                </React.Fragment>\n              )}\n            </div>\n          </Layout.Content>\n        </Layout>\n      </div>\n    </div>\n  );\n}\n\nDashboardList.propTypes = {\n  controller: ControllerType.isRequired,\n};\n\nconst DashboardListPage = itemsList(\n  DashboardList,\n  () =>\n    new ResourceItemsSource({\n      getResource({ params: { currentPage } }) {\n        return {\n          all: Dashboard.query.bind(Dashboard),\n          my: Dashboard.myDashboards.bind(Dashboard),\n          favorites: Dashboard.favorites.bind(Dashboard),\n        }[currentPage];\n      },\n      getItemProcessor() {\n        return (item) => new Dashboard(item);\n      },\n    }),\n  ({ ...props }) => new UrlStateStorage({ orderByField: props.orderByField ?? \"created_at\", orderByReverse: true })\n);\n\nroutes.register(\n  \"Dashboards.List\",\n  routeWithUserSession({\n    path: \"/dashboards\",\n    title: \"Dashboards\",\n    render: (pageProps) => <DashboardListPage {...pageProps} currentPage=\"all\" />,\n  })\n);\nroutes.register(\n  \"Dashboards.Favorites\",\n  routeWithUserSession({\n    path: \"/dashboards/favorites\",\n    title: \"Favorite Dashboards\",\n    render: (pageProps) => <DashboardListPage {...pageProps} currentPage=\"favorites\" orderByField=\"starred_at\" />,\n  })\n);\nroutes.register(\n  \"Dashboards.My\",\n  routeWithUserSession({\n    path: \"/dashboards/my\",\n    title: \"My Dashboards\",\n    render: (pageProps) => <DashboardListPage {...pageProps} currentPage=\"my\" />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/dashboards/DashboardPage.jsx",
    "content": "import { isEmpty, map } from \"lodash\";\nimport React, { useState, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\n\nimport Button from \"antd/lib/button\";\nimport Checkbox from \"antd/lib/checkbox\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport DashboardGrid from \"@/components/dashboards/DashboardGrid\";\nimport Parameters from \"@/components/Parameters\";\nimport Filters from \"@/components/Filters\";\n\nimport { Dashboard } from \"@/services/dashboard\";\nimport recordEvent from \"@/services/recordEvent\";\nimport resizeObserver from \"@/services/resizeObserver\";\nimport routes from \"@/services/routes\";\nimport location from \"@/services/location\";\nimport url from \"@/services/url\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nimport useDashboard from \"./hooks/useDashboard\";\nimport DashboardHeader from \"./components/DashboardHeader\";\n\nimport \"./DashboardPage.less\";\n\nfunction DashboardSettings({ dashboardConfiguration }) {\n  const { dashboard, updateDashboard } = dashboardConfiguration;\n  return (\n    <div className=\"m-b-10 p-15 bg-white tiled\">\n      <Checkbox\n        checked={!!dashboard.dashboard_filters_enabled}\n        onChange={({ target }) => updateDashboard({ dashboard_filters_enabled: target.checked })}\n        data-test=\"DashboardFiltersCheckbox\"\n      >\n        Use Dashboard Level Filters\n      </Checkbox>\n    </div>\n  );\n}\n\nDashboardSettings.propTypes = {\n  dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n};\n\nfunction AddWidgetContainer({ dashboardConfiguration, className, ...props }) {\n  const { showAddTextboxDialog, showAddWidgetDialog } = dashboardConfiguration;\n  return (\n    <div className={cx(\"add-widget-container\", className)} {...props}>\n      <h2>\n        <i className=\"zmdi zmdi-widgets\" aria-hidden=\"true\" />\n        <span className=\"hidden-xs hidden-sm\">\n          Widgets are individual query visualizations or text boxes you can place on your dashboard in various\n          arrangements.\n        </span>\n      </h2>\n      <div>\n        <Button className=\"m-r-15\" onClick={showAddTextboxDialog} data-test=\"AddTextboxButton\">\n          Add Textbox\n        </Button>\n        <Button type=\"primary\" onClick={showAddWidgetDialog} data-test=\"AddWidgetButton\">\n          Add Widget\n        </Button>\n      </div>\n    </div>\n  );\n}\n\nAddWidgetContainer.propTypes = {\n  dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  className: PropTypes.string,\n};\n\nfunction DashboardComponent(props) {\n  const dashboardConfiguration = useDashboard(props.dashboard);\n  const {\n    dashboard,\n    filters,\n    setFilters,\n    loadDashboard,\n    loadWidget,\n    removeWidget,\n    saveDashboardLayout,\n    globalParameters,\n    updateDashboard,\n    refreshDashboard,\n    refreshWidget,\n    editingLayout,\n    setGridDisabled,\n  } = dashboardConfiguration;\n\n  const [pageContainer, setPageContainer] = useState(null);\n  const [bottomPanelStyles, setBottomPanelStyles] = useState({});\n  const onParametersEdit = (parameters) => {\n    const paramOrder = map(parameters, \"name\");\n    updateDashboard({ options: { ...dashboard.options, globalParamOrder: paramOrder } });\n  };\n\n  useEffect(() => {\n    if (pageContainer) {\n      const unobserve = resizeObserver(pageContainer, () => {\n        if (editingLayout) {\n          const style = window.getComputedStyle(pageContainer, null);\n          const bounds = pageContainer.getBoundingClientRect();\n          const paddingLeft = parseFloat(style.paddingLeft) || 0;\n          const paddingRight = parseFloat(style.paddingRight) || 0;\n          setBottomPanelStyles({\n            left: Math.round(bounds.left) + paddingRight,\n            width: pageContainer.clientWidth - paddingLeft - paddingRight,\n          });\n        }\n\n        // reflow grid when container changes its size\n        window.dispatchEvent(new Event(\"resize\"));\n      });\n      return unobserve;\n    }\n  }, [pageContainer, editingLayout]);\n\n  return (\n    <div className=\"container\" ref={setPageContainer} data-test={`DashboardId${dashboard.id}Container`}>\n      <DashboardHeader\n        dashboardConfiguration={dashboardConfiguration}\n        headerExtra={\n          <DynamicComponent\n            name=\"Dashboard.HeaderExtra\"\n            dashboard={dashboard}\n            dashboardConfiguration={dashboardConfiguration}\n          />\n        }\n      />\n      {!isEmpty(globalParameters) && (\n        <div className=\"dashboard-parameters m-b-10 p-15 bg-white tiled\" data-test=\"DashboardParameters\">\n          <Parameters\n            parameters={globalParameters}\n            onValuesChange={refreshDashboard}\n            sortable={editingLayout}\n            onParametersEdit={onParametersEdit}\n          />\n        </div>\n      )}\n      {!isEmpty(filters) && (\n        <div className=\"m-b-10 p-15 bg-white tiled\" data-test=\"DashboardFilters\">\n          <Filters filters={filters} onChange={setFilters} />\n        </div>\n      )}\n      {editingLayout && <DashboardSettings dashboardConfiguration={dashboardConfiguration} />}\n      <div id=\"dashboard-container\">\n        <DashboardGrid\n          dashboard={dashboard}\n          widgets={dashboard.widgets}\n          filters={filters}\n          isEditing={editingLayout}\n          onLayoutChange={editingLayout ? saveDashboardLayout : () => {}}\n          onBreakpointChange={setGridDisabled}\n          onLoadWidget={loadWidget}\n          onRefreshWidget={refreshWidget}\n          onRemoveWidget={removeWidget}\n          onParameterMappingsChange={loadDashboard}\n        />\n      </div>\n      {editingLayout && (\n        <AddWidgetContainer dashboardConfiguration={dashboardConfiguration} style={bottomPanelStyles} />\n      )}\n    </div>\n  );\n}\n\nDashboardComponent.propTypes = {\n  dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n};\n\nfunction DashboardPage({ dashboardSlug, dashboardId, onError }) {\n  const [dashboard, setDashboard] = useState(null);\n  const handleError = useImmutableCallback(onError);\n\n  useEffect(() => {\n    Dashboard.get({ id: dashboardId, slug: dashboardSlug })\n      .then((dashboardData) => {\n        recordEvent(\"view\", \"dashboard\", dashboardData.id);\n        setDashboard(dashboardData);\n\n        // if loaded by slug, update location url to use the id\n        if (!dashboardId) {\n          location.setPath(url.parse(dashboardData.url).pathname, true);\n        }\n      })\n      .catch(handleError);\n  }, [dashboardId, dashboardSlug, handleError]);\n\n  return <div className=\"dashboard-page\">{dashboard && <DashboardComponent dashboard={dashboard} />}</div>;\n}\n\nDashboardPage.propTypes = {\n  dashboardSlug: PropTypes.string,\n  dashboardId: PropTypes.string,\n  onError: PropTypes.func,\n};\n\nDashboardPage.defaultProps = {\n  dashboardSlug: null,\n  dashboardId: null,\n  onError: PropTypes.func,\n};\n\n// route kept for backward compatibility\nroutes.register(\n  \"Dashboards.LegacyViewOrEdit\",\n  routeWithUserSession({\n    path: \"/dashboard/:dashboardSlug\",\n    render: (pageProps) => <DashboardPage {...pageProps} />,\n  })\n);\n\nroutes.register(\n  \"Dashboards.ViewOrEdit\",\n  routeWithUserSession({\n    path: \"/dashboards/:dashboardId([^-]+)(-.*)?\",\n    render: (pageProps) => <DashboardPage {...pageProps} />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/dashboards/DashboardPage.less",
    "content": "@import (reference, less) \"~@/assets/less/inc/variables\";\n\n/****\n  grid bg - based on 6 cols, 35px rows and 15px spacing\n****/\n\n// let the bg go all the way to the bottom\n.dashboard-page,\n.dashboard-page .container {\n  display: flex;\n  flex-grow: 1;\n  flex-direction: column;\n  width: 100%;\n}\n\n#dashboard-container {\n  position: relative;\n  flex-grow: 1;\n  display: flex;\n}\n\n.add-widget-container {\n  background: #fff;\n  border-radius: @redash-radius;\n  padding: 15px;\n  position: fixed;\n  bottom: 20px;\n  z-index: 99;\n  box-shadow: fade(@redash-gray, 50%) 0px 7px 29px -3px;\n  display: flex;\n  justify-content: space-between;\n\n  h2 {\n    margin: 0;\n    font-size: 14px;\n    line-height: 2.1;\n    font-weight: 400;\n\n    .zmdi {\n      margin: 0;\n      margin-right: 5px;\n      font-size: 24px;\n      position: absolute;\n      bottom: 18px;\n    }\n\n    span {\n      vertical-align: middle;\n      padding-left: 30px;\n    }\n  }\n\n  .btn {\n    align-self: center;\n  }\n}\n"
  },
  {
    "path": "client/app/pages/dashboards/PublicDashboardPage.jsx",
    "content": "import { isEmpty } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport routeWithApiKeySession from \"@/components/ApplicationArea/routeWithApiKeySession\";\nimport Link from \"@/components/Link\";\nimport BigMessage from \"@/components/BigMessage\";\nimport PageHeader from \"@/components/PageHeader\";\nimport Parameters from \"@/components/Parameters\";\nimport DashboardGrid from \"@/components/dashboards/DashboardGrid\";\nimport Filters from \"@/components/Filters\";\n\nimport { Dashboard } from \"@/services/dashboard\";\nimport routes from \"@/services/routes\";\n\nimport logoUrl from \"@/assets/images/redash_icon_small.png\";\n\nimport useDashboard from \"./hooks/useDashboard\";\n\nimport \"./PublicDashboardPage.less\";\n\nfunction PublicDashboard({ dashboard }) {\n  const { globalParameters, filters, setFilters, refreshDashboard, loadWidget, refreshWidget } = useDashboard(\n    dashboard\n  );\n\n  return (\n    <div className=\"container p-t-10 p-b-20\">\n      <PageHeader title={dashboard.name} />\n      {!isEmpty(globalParameters) && (\n        <div className=\"m-b-10 p-15 bg-white tiled\">\n          <Parameters parameters={globalParameters} onValuesChange={refreshDashboard} />\n        </div>\n      )}\n      {!isEmpty(filters) && (\n        <div className=\"m-b-10 p-15 bg-white tiled\">\n          <Filters filters={filters} onChange={setFilters} />\n        </div>\n      )}\n      <div id=\"dashboard-container\">\n        <DashboardGrid\n          dashboard={dashboard}\n          widgets={dashboard.widgets}\n          filters={filters}\n          isEditing={false}\n          isPublic\n          onLoadWidget={loadWidget}\n          onRefreshWidget={refreshWidget}\n        />\n      </div>\n    </div>\n  );\n}\n\nPublicDashboard.propTypes = {\n  dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n};\n\nclass PublicDashboardPage extends React.Component {\n  static propTypes = {\n    token: PropTypes.string.isRequired,\n    onError: PropTypes.func,\n  };\n\n  static defaultProps = {\n    onError: () => {},\n  };\n\n  state = {\n    loading: true,\n    dashboard: null,\n  };\n\n  componentDidMount() {\n    Dashboard.getByToken({ token: this.props.token })\n      .then(dashboard => this.setState({ dashboard, loading: false }))\n      .catch(error => this.props.onError(error));\n  }\n\n  render() {\n    const { loading, dashboard } = this.state;\n    return (\n      <div className=\"public-dashboard-page\">\n        {loading ? (\n          <div className=\"container loading-message\">\n            <BigMessage className=\"\" icon=\"fa-spinner fa-2x fa-pulse\" message=\"Loading...\" />\n          </div>\n        ) : (\n          <PublicDashboard dashboard={dashboard} />\n        )}\n        <div id=\"footer\">\n          <div className=\"text-center\">\n            <Link href=\"https://redash.io\">\n              <img alt=\"Redash Logo\" src={logoUrl} width=\"38\" />\n            </Link>\n          </div>\n          Powered by <Link href=\"https://redash.io/?ref=public-dashboard\">Redash</Link>\n        </div>\n      </div>\n    );\n  }\n}\n\nroutes.register(\n  \"Dashboards.ViewShared\",\n  routeWithApiKeySession({\n    path: \"/public/dashboards/:token\",\n    render: pageProps => <PublicDashboardPage {...pageProps} />,\n    getApiKey: currentRoute => currentRoute.routeParams.token,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/dashboards/PublicDashboardPage.less",
    "content": ".public-dashboard-page {\n  width: 100%;\n\n  .page-header-wrapper {\n    margin-top: 0;\n    margin-left: 15px;\n    margin-right: 15px;\n  }\n\n  > .container {\n    min-height: calc(100% - 95px);\n  }\n\n  .loading-message {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n  }\n\n  #footer {\n    height: 95px;\n    text-align: center;\n  }\n}\n"
  },
  {
    "path": "client/app/pages/dashboards/components/DashboardHeader.jsx",
    "content": "import React from \"react\";\nimport cx from \"classnames\";\nimport PropTypes from \"prop-types\";\nimport { map, includes } from \"lodash\";\nimport Button from \"antd/lib/button\";\nimport Dropdown from \"antd/lib/dropdown\";\nimport Menu from \"antd/lib/menu\";\nimport EllipsisOutlinedIcon from \"@ant-design/icons/EllipsisOutlined\";\nimport Modal from \"antd/lib/modal\";\nimport Tooltip from \"@/components/Tooltip\";\nimport FavoritesControl from \"@/components/FavoritesControl\";\nimport EditInPlace from \"@/components/EditInPlace\";\nimport PlainButton from \"@/components/PlainButton\";\nimport { DashboardTagsControl } from \"@/components/tags-control/TagsControl\";\nimport getTags from \"@/services/getTags\";\nimport { clientConfig } from \"@/services/auth\";\nimport { policy } from \"@/services/policy\";\nimport recordEvent from \"@/services/recordEvent\";\nimport { durationHumanize } from \"@/lib/utils\";\nimport { DashboardStatusEnum } from \"../hooks/useDashboard\";\n\nimport \"./DashboardHeader.less\";\n\nfunction getDashboardTags() {\n  return getTags(\"api/dashboards/tags\").then((tags) => map(tags, (t) => t.name));\n}\n\nfunction buttonType(value) {\n  return value ? \"primary\" : \"default\";\n}\n\nfunction DashboardPageTitle({ dashboardConfiguration }) {\n  const { dashboard, canEditDashboard, updateDashboard, editingLayout } = dashboardConfiguration;\n  return (\n    <div className=\"title-with-tags\">\n      <div className=\"page-title\">\n        <FavoritesControl item={dashboard} />\n        <h3>\n          <EditInPlace\n            isEditable={editingLayout}\n            onDone={(name) => updateDashboard({ name })}\n            value={dashboard.name}\n            ignoreBlanks\n          />\n        </h3>\n        <Tooltip title={dashboard.user.name} placement=\"bottom\">\n          <img src={dashboard.user.profile_image_url} className=\"profile-image\" alt={dashboard.user.name} />\n        </Tooltip>\n      </div>\n      <DashboardTagsControl\n        tags={dashboard.tags}\n        isDraft={dashboard.is_draft}\n        isArchived={dashboard.is_archived}\n        canEdit={canEditDashboard}\n        getAvailableTags={getDashboardTags}\n        onEdit={(tags) => updateDashboard({ tags })}\n      />\n    </div>\n  );\n}\n\nDashboardPageTitle.propTypes = {\n  dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n};\n\nfunction RefreshButton({ dashboardConfiguration }) {\n  const { refreshRate, setRefreshRate, disableRefreshRate, refreshing, refreshDashboard } = dashboardConfiguration;\n  const allowedIntervals = policy.getDashboardRefreshIntervals();\n  const refreshRateOptions = clientConfig.dashboardRefreshIntervals;\n  const onRefreshRateSelected = ({ key }) => {\n    const parsedRefreshRate = parseFloat(key);\n    if (parsedRefreshRate) {\n      setRefreshRate(parsedRefreshRate);\n      refreshDashboard();\n    } else {\n      disableRefreshRate();\n    }\n  };\n  return (\n    <Button.Group>\n      <Tooltip title={refreshRate ? `Auto Refreshing every ${durationHumanize(refreshRate)}` : null}>\n        <Button type={buttonType(refreshRate)} onClick={() => refreshDashboard()}>\n          <i className={cx(\"zmdi zmdi-refresh m-r-5\", { \"zmdi-hc-spin\": refreshing })} aria-hidden=\"true\" />\n          {refreshRate ? durationHumanize(refreshRate) : \"Refresh\"}\n        </Button>\n      </Tooltip>\n      <Dropdown\n        trigger={[\"click\"]}\n        placement=\"bottomRight\"\n        overlay={\n          <Menu onClick={onRefreshRateSelected} selectedKeys={[`${refreshRate}`]}>\n            {refreshRateOptions.map((option) => (\n              <Menu.Item key={`${option}`} disabled={!includes(allowedIntervals, option)}>\n                {durationHumanize(option)}\n              </Menu.Item>\n            ))}\n            {refreshRate && <Menu.Item key={null}>Disable auto refresh</Menu.Item>}\n          </Menu>\n        }\n      >\n        <Button className=\"icon-button hidden-xs\" type={buttonType(refreshRate)}>\n          <i className=\"fa fa-angle-down\" aria-hidden=\"true\" />\n          <span className=\"sr-only\">Split button!</span>\n        </Button>\n      </Dropdown>\n    </Button.Group>\n  );\n}\n\nRefreshButton.propTypes = {\n  dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n};\n\nfunction DashboardMoreOptionsButton({ dashboardConfiguration }) {\n  const {\n    dashboard,\n    setEditingLayout,\n    togglePublished,\n    archiveDashboard,\n    managePermissions,\n    gridDisabled,\n    isDashboardOwnerOrAdmin,\n    isDuplicating,\n    duplicateDashboard,\n  } = dashboardConfiguration;\n\n  const archive = () => {\n    Modal.confirm({\n      title: \"Archive Dashboard\",\n      content: `Are you sure you want to archive the \"${dashboard.name}\" dashboard?`,\n      okText: \"Archive\",\n      okType: \"danger\",\n      onOk: archiveDashboard,\n      maskClosable: true,\n      autoFocusButton: null,\n    });\n  };\n\n  return (\n    <Dropdown\n      trigger={[\"click\"]}\n      placement=\"bottomRight\"\n      overlay={\n        <Menu data-test=\"DashboardMoreButtonMenu\">\n          <Menu.Item className={cx({ hidden: gridDisabled })}>\n            <PlainButton onClick={() => setEditingLayout(true)}>Edit</PlainButton>\n          </Menu.Item>\n          {!isDuplicating && dashboard.canEdit() && (\n            <Menu.Item>\n              <PlainButton onClick={duplicateDashboard}>\n                Fork <i className=\"fa fa-external-link m-l-5\" aria-hidden=\"true\" />\n                <span className=\"sr-only\">(opens in a new tab)</span>\n              </PlainButton>\n            </Menu.Item>\n          )}\n          {clientConfig.showPermissionsControl && isDashboardOwnerOrAdmin && (\n            <Menu.Item>\n              <PlainButton onClick={managePermissions}>Manage Permissions</PlainButton>\n            </Menu.Item>\n          )}\n          {!clientConfig.disablePublish && !dashboard.is_draft && (\n            <Menu.Item>\n              <PlainButton onClick={togglePublished}>Unpublish</PlainButton>\n            </Menu.Item>\n          )}\n          <Menu.Item>\n            <PlainButton onClick={archive}>Archive</PlainButton>\n          </Menu.Item>\n        </Menu>\n      }\n    >\n      <Button className=\"icon-button m-l-5\" data-test=\"DashboardMoreButton\" aria-label=\"More actions\">\n        <EllipsisOutlinedIcon rotate={90} aria-hidden=\"true\" />\n      </Button>\n    </Dropdown>\n  );\n}\n\nDashboardMoreOptionsButton.propTypes = {\n  dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n};\n\nfunction DashboardControl({ dashboardConfiguration, headerExtra }) {\n  const {\n    dashboard,\n    togglePublished,\n    canEditDashboard,\n    fullscreen,\n    toggleFullscreen,\n    showShareDashboardDialog,\n    updateDashboard,\n  } = dashboardConfiguration;\n  const showPublishButton = dashboard.is_draft;\n  const showRefreshButton = true;\n  const showFullscreenButton = !dashboard.is_draft;\n  const canShareDashboard = canEditDashboard && !dashboard.is_draft;\n  const showShareButton = !clientConfig.disablePublicUrls && (dashboard.publicAccessEnabled || canShareDashboard);\n  const showMoreOptionsButton = canEditDashboard;\n\n  const unarchiveDashboard = () => {\n    recordEvent(\"unarchive\", \"dashboard\", dashboard.id);\n    updateDashboard({ is_archived: false }, false);\n  };\n  return (\n    <div className=\"dashboard-control\">\n      {dashboard.can_edit && dashboard.is_archived && <Button onClick={unarchiveDashboard}>Unarchive</Button>}\n      {!dashboard.is_archived && (\n        <span className=\"hidden-print\">\n          {showPublishButton && (\n            <Button className=\"m-r-5 hidden-xs\" onClick={togglePublished}>\n              <span className=\"fa fa-paper-plane m-r-5\" /> Publish\n            </Button>\n          )}\n          {showRefreshButton && <RefreshButton dashboardConfiguration={dashboardConfiguration} />}\n          {showFullscreenButton && (\n            <Tooltip className=\"hidden-xs\" title=\"Enable/Disable Fullscreen display\">\n              <Button\n                type={buttonType(fullscreen)}\n                className=\"icon-button m-l-5\"\n                onClick={toggleFullscreen}\n                aria-label=\"Toggle fullscreen display\"\n              >\n                <i className=\"zmdi zmdi-fullscreen\" aria-hidden=\"true\" />\n              </Button>\n            </Tooltip>\n          )}\n          {headerExtra}\n          {showShareButton && (\n            <Tooltip title=\"Dashboard Sharing Options\">\n              <Button\n                className=\"icon-button m-l-5\"\n                type={buttonType(dashboard.publicAccessEnabled)}\n                onClick={showShareDashboardDialog}\n                data-test=\"OpenShareForm\"\n                aria-label=\"Share\"\n              >\n                <i className=\"zmdi zmdi-share\" aria-hidden=\"true\" />\n              </Button>\n            </Tooltip>\n          )}\n          {showMoreOptionsButton && <DashboardMoreOptionsButton dashboardConfiguration={dashboardConfiguration} />}\n        </span>\n      )}\n    </div>\n  );\n}\n\nDashboardControl.propTypes = {\n  dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  headerExtra: PropTypes.node,\n};\n\nfunction DashboardEditControl({ dashboardConfiguration, headerExtra }) {\n  const {\n    setEditingLayout,\n    doneBtnClickedWhileSaving,\n    dashboardStatus,\n    retrySaveDashboardLayout,\n    saveDashboardParameters,\n  } = dashboardConfiguration;\n  const handleDoneEditing = () => {\n    saveDashboardParameters().then(() => setEditingLayout(false));\n  };\n  let status;\n  if (dashboardStatus === DashboardStatusEnum.SAVED) {\n    status = <span className=\"save-status\">Saved</span>;\n  } else if (dashboardStatus === DashboardStatusEnum.SAVING) {\n    status = (\n      <span className=\"save-status\" data-saving>\n        Saving\n      </span>\n    );\n  } else {\n    status = (\n      <span className=\"save-status\" data-error>\n        Saving Failed\n      </span>\n    );\n  }\n  return (\n    <div className=\"dashboard-control\">\n      {status}\n      {dashboardStatus === DashboardStatusEnum.SAVING_FAILED ? (\n        <Button type=\"primary\" onClick={retrySaveDashboardLayout}>\n          Retry\n        </Button>\n      ) : (\n        <Button loading={doneBtnClickedWhileSaving} type=\"primary\" onClick={handleDoneEditing}>\n          {!doneBtnClickedWhileSaving && <i className=\"fa fa-check m-r-5\" aria-hidden=\"true\" />} Done Editing\n        </Button>\n      )}\n      {headerExtra}\n    </div>\n  );\n}\n\nDashboardEditControl.propTypes = {\n  dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  headerExtra: PropTypes.node,\n};\n\nexport default function DashboardHeader({ dashboardConfiguration, headerExtra }) {\n  const { editingLayout } = dashboardConfiguration;\n  const DashboardControlComponent = editingLayout ? DashboardEditControl : DashboardControl;\n\n  return (\n    <div className=\"dashboard-header\">\n      <DashboardPageTitle dashboardConfiguration={dashboardConfiguration} />\n      <DashboardControlComponent dashboardConfiguration={dashboardConfiguration} headerExtra={headerExtra} />\n    </div>\n  );\n}\n\nDashboardHeader.propTypes = {\n  dashboardConfiguration: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  headerExtra: PropTypes.node,\n};\n"
  },
  {
    "path": "client/app/pages/dashboards/components/DashboardHeader.less",
    "content": "@import (reference, less) \"~@/components/ApplicationArea/ApplicationLayout/index.less\";\n\n.dashboard-header {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: stretch;\n  position: -webkit-sticky; // required for Safari\n  position: sticky;\n  background: #f6f7f9;\n  z-index: 99;\n  width: 100%;\n  top: 0;\n  padding-top: 10px;\n  margin-bottom: 10px;\n\n  & > div {\n    padding: 5px 0;\n  }\n\n  .title-with-tags {\n    flex: 1 1;\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    margin: -5px 0;\n\n    & > div {\n      padding: 5px 0;\n    }\n\n    h3 {\n      margin: 0;\n\n      @media (max-width: 767px) {\n        font-size: 18px;\n      }\n    }\n  }\n\n  @media @mobileBreakpoint {\n    & {\n      position: static;\n    }\n  }\n\n  .profile-image {\n    width: 16px;\n    height: 16px;\n    border-radius: 100%;\n    margin: 3px 5px 0 5px;\n  }\n\n  .tags-control > .label-tag {\n    opacity: 0;\n    transition: opacity 0.2s ease-in-out;\n  }\n\n  &:hover,\n  &:focus,\n  &:active,\n  &:focus-within {\n    .tags-control > .label-tag {\n      opacity: 1;\n    }\n  }\n\n  .dashboard-control {\n    .icon-button {\n      width: 32px;\n      padding: 0 10px;\n    }\n\n    .save-status {\n      vertical-align: middle;\n      margin-right: 7px;\n      font-size: 12px;\n      text-align: left;\n      display: inline-block;\n\n      &[data-saving] {\n        opacity: 0.6;\n        width: 45px;\n\n        &:after {\n          content: \"\";\n          animation: saving 2s linear infinite;\n        }\n      }\n\n      &[data-error] {\n        color: #f44336;\n      }\n    }\n\n    @media (max-width: 515px) {\n      flex-basis: 100%;\n    }\n  }\n\n  @keyframes saving {\n    0%,\n    100% {\n      content: \".\";\n    }\n    33% {\n      content: \"..\";\n    }\n    66% {\n      content: \"...\";\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/dashboards/components/DashboardListEmptyState.tsx",
    "content": "import * as React from \"react\";\nimport * as PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport BigMessage from \"@/components/BigMessage\";\nimport NoTaggedObjectsFound from \"@/components/NoTaggedObjectsFound\";\nimport EmptyState, { EmptyStateHelpMessage } from \"@/components/empty-state/EmptyState\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport CreateDashboardDialog from \"@/components/dashboards/CreateDashboardDialog\";\nimport { currentUser } from \"@/services/auth\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\n\nexport interface DashboardListEmptyStateProps {\n  page: string;\n  searchTerm: string;\n  selectedTags: string[];\n}\n\nexport default function DashboardListEmptyState({ page, searchTerm, selectedTags }: DashboardListEmptyStateProps) {\n  if (searchTerm !== \"\") {\n    return <BigMessage message=\"Sorry, we couldn't find anything.\" icon=\"fa-search\" />;\n  }\n  if (selectedTags.length > 0) {\n    return <NoTaggedObjectsFound objectType=\"dashboards\" tags={selectedTags} />;\n  }\n  switch (page) {\n    case \"favorites\":\n      return <BigMessage message=\"Mark dashboards as Favorite to list them here.\" icon=\"fa-star\" />;\n    case \"my\":\n      const my_msg = currentUser.hasPermission(\"create_dashboard\") ? (\n        <span>\n          <Button type=\"primary\" size=\"small\" onClick={() => CreateDashboardDialog.showModal()}>\n            Create your first dashboard!\n          </Button>{\" \"}\n          <HelpTrigger className=\"f-14\" type=\"DASHBOARDS\" showTooltip={false}>\n            Need help?\n          </HelpTrigger>\n        </span>\n      ) : (\n        <span>Sorry, we couldn't find anything.</span>\n      );\n      return <BigMessage icon=\"fa-search\">{my_msg}</BigMessage>;\n    default:\n      return (\n        <DynamicComponent name=\"DashboardList.EmptyState\">\n          <EmptyState\n            icon=\"zmdi zmdi-view-quilt\"\n            description=\"See the big picture\"\n            illustration=\"dashboard\"\n            helpMessage={<EmptyStateHelpMessage helpTriggerType=\"DASHBOARDS\" />}\n            showDashboardStep\n          />\n        </DynamicComponent>\n      );\n  }\n}\n\nDashboardListEmptyState.propTypes = {\n  page: PropTypes.string.isRequired,\n  searchTerm: PropTypes.string.isRequired,\n  selectedTags: PropTypes.array.isRequired,\n};\n"
  },
  {
    "path": "client/app/pages/dashboards/components/ShareDashboardDialog.jsx",
    "content": "import { replace } from \"lodash\";\nimport React from \"react\";\nimport { axios } from \"@/services/axios\";\nimport PropTypes from \"prop-types\";\nimport Switch from \"antd/lib/switch\";\nimport Modal from \"antd/lib/modal\";\nimport Form from \"antd/lib/form\";\nimport Alert from \"antd/lib/alert\";\nimport notification from \"@/services/notification\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport InputWithCopy from \"@/components/InputWithCopy\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\n\nconst API_SHARE_URL = \"api/dashboards/{id}/share\";\n\nclass ShareDashboardDialog extends React.Component {\n  static propTypes = {\n    dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n    hasOnlySafeQueries: PropTypes.bool.isRequired,\n    dialog: DialogPropType.isRequired,\n  };\n\n  formItemProps = {\n    labelCol: { span: 8 },\n    wrapperCol: { span: 16 },\n    style: { marginBottom: 7 },\n  };\n\n  constructor(props) {\n    super(props);\n    const { dashboard } = this.props;\n\n    this.state = {\n      saving: false,\n    };\n\n    this.apiUrl = replace(API_SHARE_URL, \"{id}\", dashboard.id);\n    this.enabled = this.props.hasOnlySafeQueries || dashboard.publicAccessEnabled;\n  }\n\n  static get headerContent() {\n    return (\n      <React.Fragment>\n        Share Dashboard\n        <div className=\"modal-header-desc\">\n          Allow public access to this dashboard with a secret address. <HelpTrigger type=\"SHARE_DASHBOARD\" />\n        </div>\n      </React.Fragment>\n    );\n  }\n\n  enableAccess = () => {\n    const { dashboard } = this.props;\n    this.setState({ saving: true });\n\n    axios\n      .post(this.apiUrl)\n      .then(data => {\n        dashboard.publicAccessEnabled = true;\n        dashboard.public_url = data.public_url;\n      })\n      .catch(() => {\n        notification.error(\"Failed to turn on sharing for this dashboard\");\n      })\n      .finally(() => {\n        this.setState({ saving: false });\n      });\n  };\n\n  disableAccess = () => {\n    const { dashboard } = this.props;\n    this.setState({ saving: true });\n\n    axios\n      .delete(this.apiUrl)\n      .then(() => {\n        dashboard.publicAccessEnabled = false;\n        delete dashboard.public_url;\n      })\n      .catch(() => {\n        notification.error(\"Failed to turn off sharing for this dashboard\");\n      })\n      .finally(() => {\n        this.setState({ saving: false });\n      });\n  };\n\n  onChange = checked => {\n    if (checked) {\n      this.enableAccess();\n    } else {\n      this.disableAccess();\n    }\n  };\n\n  render() {\n    const { dialog, dashboard, hasOnlySafeQueries } = this.props;\n    const headerContent = this.constructor.headerContent;\n    return (\n      <Modal {...dialog.props} title={headerContent} footer={null}>\n        <Form layout=\"horizontal\">\n          {!hasOnlySafeQueries && (\n            <Form.Item>\n              <Alert\n                message=\"For your security, sharing is currently not supported for dashboards containing queries with text parameters. Consider changing the text parameters in your query to a different type.\"\n                type=\"error\"\n              />\n            </Form.Item>\n          )}\n\n          <Form.Item label=\"Allow public access\" {...this.formItemProps}>\n            <Switch\n              checked={dashboard.publicAccessEnabled}\n              onChange={this.onChange}\n              loading={this.state.saving}\n              disabled={!this.enabled}\n              data-test=\"PublicAccessEnabled\"\n            />\n          </Form.Item>\n          {dashboard.public_url && (\n            <Form.Item label=\"Secret address\" {...this.formItemProps}>\n              <InputWithCopy value={dashboard.public_url} data-test=\"SecretAddress\" />\n            </Form.Item>\n          )}\n        </Form>\n      </Modal>\n    );\n  }\n}\n\nexport default wrapDialog(ShareDashboardDialog);\n"
  },
  {
    "path": "client/app/pages/dashboards/dashboard-list.css",
    "content": "/* Prevent text selection on shift+click. */\ndiv.tags-list {\n  -webkit-user-select: none; /* webkit (safari, chrome) browsers */\n  -moz-user-select: none; /* mozilla browsers */\n  -khtml-user-select: none; /* webkit (konqueror) browsers */\n  -ms-user-select: none; /* IE10+ */\n}\n\n/* same rule as for sidebar */\n@media (max-width: 990px) {\n  .page-dashboard-list .page-header-actions {\n    width: auto;\n  }\n}\n"
  },
  {
    "path": "client/app/pages/dashboards/hooks/useDashboard.js",
    "content": "import { useState, useEffect, useMemo, useCallback, useRef } from \"react\";\nimport { isEmpty, includes, compact, map, has, pick, keys, extend, every, get } from \"lodash\";\nimport notification from \"@/services/notification\";\nimport location from \"@/services/location\";\nimport url from \"@/services/url\";\nimport { Dashboard, collectDashboardFilters } from \"@/services/dashboard\";\nimport { currentUser } from \"@/services/auth\";\nimport recordEvent from \"@/services/recordEvent\";\nimport { QueryResultError } from \"@/services/query\";\nimport AddWidgetDialog from \"@/components/dashboards/AddWidgetDialog\";\nimport TextboxDialog from \"@/components/dashboards/TextboxDialog\";\nimport PermissionsEditorDialog from \"@/components/PermissionsEditorDialog\";\nimport { editableMappingsToParameterMappings, synchronizeWidgetTitles } from \"@/components/ParameterMappingInput\";\nimport ShareDashboardDialog from \"../components/ShareDashboardDialog\";\nimport useFullscreenHandler from \"../../../lib/hooks/useFullscreenHandler\";\nimport useRefreshRateHandler from \"./useRefreshRateHandler\";\nimport useEditModeHandler from \"./useEditModeHandler\";\nimport useDuplicateDashboard from \"./useDuplicateDashboard\";\nimport { policy } from \"@/services/policy\";\n\nexport { DashboardStatusEnum } from \"./useEditModeHandler\";\n\nfunction getAffectedWidgets(widgets, updatedParameters = []) {\n  return !isEmpty(updatedParameters)\n    ? widgets.filter((widget) =>\n        Object.values(widget.getParameterMappings())\n          .filter(({ type }) => type === \"dashboard-level\")\n          .some(({ mapTo }) =>\n            includes(\n              updatedParameters.map((p) => p.name),\n              mapTo\n            )\n          )\n      )\n    : widgets;\n}\n\nfunction useDashboard(dashboardData) {\n  const [dashboard, setDashboard] = useState(dashboardData);\n  const [filters, setFilters] = useState([]);\n  const [refreshing, setRefreshing] = useState(false);\n  const [gridDisabled, setGridDisabled] = useState(false);\n  const globalParameters = useMemo(() => dashboard.getParametersDefs(), [dashboard]);\n  const canEditDashboard = !dashboard.is_archived && policy.canEdit(dashboard);\n  const isDashboardOwnerOrAdmin = useMemo(\n    () =>\n      !dashboard.is_archived &&\n      has(dashboard, \"user.id\") &&\n      (currentUser.id === dashboard.user.id || currentUser.isAdmin),\n    [dashboard]\n  );\n  const hasOnlySafeQueries = useMemo(\n    () => every(dashboard.widgets, (w) => (w.getQuery() ? w.getQuery().is_safe : true)),\n    [dashboard]\n  );\n\n  const [isDuplicating, duplicateDashboard] = useDuplicateDashboard(dashboard);\n\n  const managePermissions = useCallback(() => {\n    const aclUrl = `api/dashboards/${dashboard.id}/acl`;\n    PermissionsEditorDialog.showModal({\n      aclUrl,\n      context: \"dashboard\",\n      author: dashboard.user,\n    });\n  }, [dashboard]);\n\n  const updateDashboard = useCallback(\n    (data, includeVersion = true) => {\n      setDashboard((currentDashboard) => extend({}, currentDashboard, data));\n      data = { ...data, id: dashboard.id };\n      if (includeVersion) {\n        data = { ...data, version: dashboard.version };\n      }\n      return Dashboard.save(data)\n        .then((updatedDashboard) => {\n          setDashboard((currentDashboard) => extend({}, currentDashboard, pick(updatedDashboard, keys(data))));\n          if (has(data, \"name\")) {\n            location.setPath(url.parse(updatedDashboard.url).pathname, true);\n          }\n        })\n        .catch((error) => {\n          const status = get(error, \"response.status\");\n          if (status === 403) {\n            notification.error(\"Dashboard update failed\", \"Permission Denied.\");\n          } else if (status === 409) {\n            notification.error(\n              \"It seems like the dashboard has been modified by another user. \",\n              \"Please copy/backup your changes and reload this page.\",\n              { duration: null }\n            );\n          }\n        });\n    },\n    [dashboard]\n  );\n\n  const togglePublished = useCallback(() => {\n    recordEvent(\"toggle_published\", \"dashboard\", dashboard.id);\n    updateDashboard({ is_draft: !dashboard.is_draft }, false);\n  }, [dashboard, updateDashboard]);\n\n  const loadWidget = useCallback((widget, forceRefresh = false) => {\n    widget.getParametersDefs(); // Force widget to read parameters values from URL\n    setDashboard((currentDashboard) => extend({}, currentDashboard));\n    return widget\n      .load(forceRefresh)\n      .catch((error) => {\n        // QueryResultErrors are expected\n        if (error instanceof QueryResultError) {\n          return;\n        }\n        return Promise.reject(error);\n      })\n      .finally(() => setDashboard((currentDashboard) => extend({}, currentDashboard)));\n  }, []);\n\n  const refreshWidget = useCallback((widget) => loadWidget(widget, true), [loadWidget]);\n\n  const removeWidget = useCallback((widgetId) => {\n    setDashboard((currentDashboard) =>\n      extend({}, currentDashboard, {\n        widgets: currentDashboard.widgets.filter((widget) => widget.id !== undefined && widget.id !== widgetId),\n      })\n    );\n  }, []);\n\n  const dashboardRef = useRef();\n  dashboardRef.current = dashboard;\n\n  const loadDashboard = useCallback(\n    (forceRefresh = false, updatedParameters = []) => {\n      const affectedWidgets = getAffectedWidgets(dashboardRef.current.widgets, updatedParameters);\n      const loadWidgetPromises = compact(\n        affectedWidgets.map((widget) => loadWidget(widget, forceRefresh).catch((error) => error))\n      );\n\n      return Promise.all(loadWidgetPromises).then(() => {\n        const queryResults = compact(map(dashboardRef.current.widgets, (widget) => widget.getQueryResult()));\n        const updatedFilters = collectDashboardFilters(dashboardRef.current, queryResults, location.search);\n        setFilters(updatedFilters);\n      });\n    },\n    [loadWidget]\n  );\n\n  const refreshDashboard = useCallback(\n    (updatedParameters) => {\n      if (!refreshing) {\n        setRefreshing(true);\n        loadDashboard(true, updatedParameters).finally(() => setRefreshing(false));\n      }\n    },\n    [refreshing, loadDashboard]\n  );\n\n  const saveDashboardParameters = useCallback(() => {\n    const currentDashboard = dashboardRef.current;\n\n    return updateDashboard({\n      options: {\n        ...currentDashboard.options,\n        parameters: map(globalParameters, (p) => p.toSaveableObject()),\n      },\n    }).catch((error) => {\n      console.error(\"Failed to persist parameter values:\", error);\n      notification.error(\"Parameter values could not be saved. Your changes may not be persisted.\");\n      throw error;\n    });\n  }, [globalParameters, updateDashboard]);\n\n  const archiveDashboard = useCallback(() => {\n    recordEvent(\"archive\", \"dashboard\", dashboard.id);\n    Dashboard.delete(dashboard).then((updatedDashboard) =>\n      setDashboard((currentDashboard) => extend({}, currentDashboard, pick(updatedDashboard, [\"is_archived\"])))\n    );\n  }, [dashboard]); // eslint-disable-line react-hooks/exhaustive-deps\n\n  const showShareDashboardDialog = useCallback(() => {\n    const handleDialogClose = () => setDashboard((currentDashboard) => extend({}, currentDashboard));\n\n    ShareDashboardDialog.showModal({\n      dashboard,\n      hasOnlySafeQueries,\n    })\n      .onClose(handleDialogClose)\n      .onDismiss(handleDialogClose);\n  }, [dashboard, hasOnlySafeQueries]);\n\n  const showAddTextboxDialog = useCallback(() => {\n    TextboxDialog.showModal({\n      isNew: true,\n    }).onClose((text) =>\n      dashboard.addWidget(text).then(() => setDashboard((currentDashboard) => extend({}, currentDashboard)))\n    );\n  }, [dashboard]);\n\n  const showAddWidgetDialog = useCallback(() => {\n    AddWidgetDialog.showModal({\n      dashboard,\n    }).onClose(({ visualization, parameterMappings }) =>\n      dashboard\n        .addWidget(visualization, {\n          parameterMappings: editableMappingsToParameterMappings(parameterMappings),\n        })\n        .then((widget) => {\n          const widgetsToSave = [\n            widget,\n            ...synchronizeWidgetTitles(widget.options.parameterMappings, dashboard.widgets),\n          ];\n          return Promise.all(widgetsToSave.map((w) => w.save())).then(() =>\n            setDashboard((currentDashboard) => extend({}, currentDashboard))\n          );\n        })\n    );\n  }, [dashboard]);\n\n  const [refreshRate, setRefreshRate, disableRefreshRate] = useRefreshRateHandler(refreshDashboard);\n  const [fullscreen, toggleFullscreen] = useFullscreenHandler();\n  const editModeHandler = useEditModeHandler(!gridDisabled && canEditDashboard, dashboard.widgets);\n\n  useEffect(() => {\n    setDashboard(dashboardData);\n    loadDashboard();\n  }, [dashboardData]); // eslint-disable-line react-hooks/exhaustive-deps\n\n  useEffect(() => {\n    document.title = dashboard.name;\n  }, [dashboard.name]);\n\n  // reload dashboard when filter option changes\n  useEffect(() => {\n    loadDashboard();\n  }, [dashboard.dashboard_filters_enabled]); // eslint-disable-line react-hooks/exhaustive-deps\n\n  return {\n    dashboard,\n    globalParameters,\n    refreshing,\n    filters,\n    setFilters,\n    loadDashboard,\n    refreshDashboard,\n    updateDashboard,\n    togglePublished,\n    archiveDashboard,\n    loadWidget,\n    refreshWidget,\n    removeWidget,\n    canEditDashboard,\n    isDashboardOwnerOrAdmin,\n    refreshRate,\n    setRefreshRate,\n    disableRefreshRate,\n    ...editModeHandler,\n    saveDashboardParameters,\n    gridDisabled,\n    setGridDisabled,\n    fullscreen,\n    toggleFullscreen,\n    showShareDashboardDialog,\n    showAddTextboxDialog,\n    showAddWidgetDialog,\n    managePermissions,\n    isDuplicating,\n    duplicateDashboard,\n  };\n}\n\nexport default useDashboard;\n"
  },
  {
    "path": "client/app/pages/dashboards/hooks/useDataSources.js",
    "content": "import { filter } from \"lodash\";\nimport { useState, useEffect } from \"react\";\nimport DataSource from \"@/services/data-source\";\n\n/**\n * Provides a list of all data sources, as well as a boolean to say whether they've been loaded\n */\nexport default function useDataSources() {\n  const [allDataSources, setAllDataSources] = useState([]);\n  const [dataSourcesLoaded, setDataSourcesLoaded] = useState(false);\n  const dataSources = filter(allDataSources, ds => !ds.view_only);\n\n  useEffect(() => {\n    let cancelDataSourceLoading = false;\n    DataSource.query().then(data => {\n      if (!cancelDataSourceLoading) {\n        setDataSourcesLoaded(true);\n        setAllDataSources(data);\n      }\n    });\n\n    return () => {\n      cancelDataSourceLoading = true;\n    };\n  }, []);\n\n  return { dataSourcesLoaded, dataSources };\n}\n"
  },
  {
    "path": "client/app/pages/dashboards/hooks/useDuplicateDashboard.js",
    "content": "import { noop, extend, pick } from \"lodash\";\nimport { useCallback, useState } from \"react\";\nimport url from \"url\";\nimport qs from \"query-string\";\nimport { Dashboard } from \"@/services/dashboard\";\n\nfunction keepCurrentUrlParams(targetUrl) {\n  const currentUrlParams = qs.parse(window.location.search);\n  targetUrl = url.parse(targetUrl);\n  const targetUrlParams = qs.parse(targetUrl.search);\n  return url.format(\n    extend(pick(targetUrl, [\"protocol\", \"auth\", \"host\", \"pathname\"]), {\n      search: qs.stringify(extend(currentUrlParams, targetUrlParams)),\n    })\n  );\n}\n\nexport default function useDuplicateDashboard(dashboard) {\n  const [isDuplicating, setIsDuplicating] = useState(false);\n\n  const duplicateDashboard = useCallback(() => {\n    // To prevent opening the same tab, name must be unique for each browser\n    const tabName = `duplicatedDashboardTab/${Math.random().toString()}`;\n\n    // We should open tab here because this moment is a part of user interaction;\n    // later browser will block such attempts\n    const tab = window.open(\"\", tabName);\n\n    setIsDuplicating(true);\n    Dashboard.fork({ id: dashboard.id })\n      .then(newDashboard => {\n        tab.location = keepCurrentUrlParams(newDashboard.getUrl());\n      })\n      .finally(() => {\n        setIsDuplicating(false);\n      });\n  }, [dashboard.id]);\n\n  return [isDuplicating, isDuplicating ? noop : duplicateDashboard];\n}\n"
  },
  {
    "path": "client/app/pages/dashboards/hooks/useEditModeHandler.js",
    "content": "import { debounce, find, has, isMatch, map, pickBy } from \"lodash\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport location from \"@/services/location\";\nimport notification from \"@/services/notification\";\n\nexport const DashboardStatusEnum = {\n  SAVED: \"saved\",\n  SAVING: \"saving\",\n  SAVING_FAILED: \"saving_failed\",\n};\n\nfunction getChangedPositions(widgets, nextPositions = {}) {\n  return pickBy(nextPositions, (nextPos, widgetId) => {\n    const widget = find(widgets, { id: Number(widgetId) });\n    const prevPos = widget.options.position;\n    return !isMatch(prevPos, nextPos);\n  });\n}\n\nexport default function useEditModeHandler(canEditDashboard, widgets) {\n  const [editingLayout, setEditingLayout] = useState(canEditDashboard && has(location.search, \"edit\"));\n  const [dashboardStatus, setDashboardStatus] = useState(DashboardStatusEnum.SAVED);\n  const [recentPositions, setRecentPositions] = useState([]);\n  const [doneBtnClickedWhileSaving, setDoneBtnClickedWhileSaving] = useState(false);\n\n  useEffect(() => {\n    location.setSearch({ edit: editingLayout ? true : null }, true);\n  }, [editingLayout]);\n\n  useEffect(() => {\n    if (doneBtnClickedWhileSaving && dashboardStatus === DashboardStatusEnum.SAVED) {\n      setDoneBtnClickedWhileSaving(false);\n      setEditingLayout(false);\n    }\n  }, [doneBtnClickedWhileSaving, dashboardStatus]);\n\n  const saveDashboardLayout = useCallback(\n    positions => {\n      if (!canEditDashboard) {\n        setDashboardStatus(DashboardStatusEnum.SAVED);\n        return;\n      }\n\n      const changedPositions = getChangedPositions(widgets, positions);\n\n      setDashboardStatus(DashboardStatusEnum.SAVING);\n      setRecentPositions(positions);\n      const saveChangedWidgets = map(changedPositions, (position, id) => {\n        // find widget\n        const widget = find(widgets, { id: Number(id) });\n\n        // skip already deleted widget\n        if (!widget) {\n          return Promise.resolve();\n        }\n\n        return widget.save(\"options\", { position });\n      });\n\n      return Promise.all(saveChangedWidgets)\n        .then(() => setDashboardStatus(DashboardStatusEnum.SAVED))\n        .catch(() => {\n          setDashboardStatus(DashboardStatusEnum.SAVING_FAILED);\n          notification.error(\"Error saving changes.\");\n        });\n    },\n    [canEditDashboard, widgets]\n  );\n\n  const saveDashboardLayoutDebounced = useCallback(\n    (...args) => {\n      setDashboardStatus(DashboardStatusEnum.SAVING);\n      return debounce(() => saveDashboardLayout(...args), 2000)();\n    },\n    [saveDashboardLayout]\n  );\n\n  const retrySaveDashboardLayout = useCallback(() => saveDashboardLayout(recentPositions), [\n    recentPositions,\n    saveDashboardLayout,\n  ]);\n\n  const setEditing = useCallback(\n    editing => {\n      if (!editing && dashboardStatus !== DashboardStatusEnum.SAVED) {\n        setDoneBtnClickedWhileSaving(true);\n        return;\n      }\n      setEditingLayout(canEditDashboard && editing);\n    },\n    [dashboardStatus, canEditDashboard]\n  );\n\n  return {\n    editingLayout: canEditDashboard && editingLayout,\n    setEditingLayout: setEditing,\n    saveDashboardLayout: editingLayout ? saveDashboardLayoutDebounced : saveDashboardLayout,\n    retrySaveDashboardLayout,\n    doneBtnClickedWhileSaving,\n    dashboardStatus,\n  };\n}\n"
  },
  {
    "path": "client/app/pages/dashboards/hooks/useRefreshRateHandler.js",
    "content": "import { isNaN, max, min } from \"lodash\";\nimport { useEffect, useState, useMemo } from \"react\";\nimport location from \"@/services/location\";\nimport { policy } from \"@/services/policy\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nfunction getLimitedRefreshRate(refreshRate) {\n  const allowedIntervals = policy.getDashboardRefreshIntervals();\n  return max([30, min(allowedIntervals), refreshRate]);\n}\n\nfunction getRefreshRateFromUrl() {\n  const refreshRate = parseFloat(location.search.refresh);\n  return isNaN(refreshRate) ? null : getLimitedRefreshRate(refreshRate);\n}\n\nexport default function useRefreshRateHandler(refreshDashboard) {\n  const [refreshRate, setRefreshRate] = useState(getRefreshRateFromUrl());\n\n  // `refreshDashboard` may change quite frequently (on every update of `dashboard` instance), but we\n  // have to keep the same timer running, because timer will restart when re-creating, and instead of\n  // running refresh every N seconds - it will run refresh every N seconds after last dashboard update\n  // (which is not right obviously)\n  const doRefreshDashboard = useImmutableCallback(refreshDashboard);\n\n  // URL and timer should be updated only when `refreshRate` changes\n  useEffect(() => {\n    location.setSearch({ refresh: refreshRate || null }, true);\n    if (refreshRate) {\n      const refreshTimer = setInterval(doRefreshDashboard, refreshRate * 1000);\n      return () => clearInterval(refreshTimer);\n    }\n  }, [refreshRate, doRefreshDashboard]);\n\n  return useMemo(() => [refreshRate, rate => setRefreshRate(getLimitedRefreshRate(rate)), () => setRefreshRate(null)], [\n    refreshRate,\n  ]);\n}\n"
  },
  {
    "path": "client/app/pages/data-sources/DataSourcesList.jsx",
    "content": "import { isEmpty, reject } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport Button from \"antd/lib/button\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\nimport CardsList from \"@/components/cards-list/CardsList\";\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport CreateSourceDialog from \"@/components/CreateSourceDialog\";\nimport DynamicComponent, { registerComponent } from \"@/components/DynamicComponent\";\nimport helper from \"@/components/dynamic-form/dynamicFormHelper\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\nimport PlainButton from \"@/components/PlainButton\";\n\nimport DataSource, { IMG_ROOT } from \"@/services/data-source\";\nimport { policy } from \"@/services/policy\";\nimport recordEvent from \"@/services/recordEvent\";\nimport routes from \"@/services/routes\";\n\nexport function DataSourcesListComponent({ dataSources, onClickCreate }) {\n  const items = dataSources.map(dataSource => ({\n    title: dataSource.name,\n    imgSrc: `${IMG_ROOT}/${dataSource.type}.png`,\n    href: `data_sources/${dataSource.id}`,\n  }));\n\n  return isEmpty(dataSources) ? (\n    <div className=\"text-center\">\n      There are no data sources yet.\n      {policy.isCreateDataSourceEnabled() && (\n        <div className=\"m-t-5\">\n          <PlainButton type=\"link\" onClick={onClickCreate} data-test=\"CreateDataSourceLink\">\n            Click here\n          </PlainButton>{\" \"}\n          to add one.\n        </div>\n      )}\n    </div>\n  ) : (\n    <CardsList items={items} />\n  );\n}\n\nregisterComponent(\"DataSourcesListComponent\", DataSourcesListComponent);\n\nclass DataSourcesList extends React.Component {\n  static propTypes = {\n    isNewDataSourcePage: PropTypes.bool,\n    onError: PropTypes.func,\n  };\n\n  static defaultProps = {\n    isNewDataSourcePage: false,\n    onError: () => {},\n  };\n\n  state = {\n    dataSourceTypes: [],\n    dataSources: [],\n    loading: true,\n  };\n\n  newDataSourceDialog = null;\n\n  componentDidMount() {\n    Promise.all([DataSource.query(), DataSource.types()])\n      .then(values =>\n        this.setState(\n          {\n            dataSources: values[0],\n            dataSourceTypes: values[1],\n            loading: false,\n          },\n          () => {\n            // all resources are loaded in state\n            if (this.props.isNewDataSourcePage) {\n              if (policy.canCreateDataSource()) {\n                this.showCreateSourceDialog();\n              } else {\n                navigateTo(\"data_sources\", true);\n              }\n            }\n          }\n        )\n      )\n      .catch(error => this.props.onError(error));\n  }\n\n  componentWillUnmount() {\n    if (this.newDataSourceDialog) {\n      this.newDataSourceDialog.dismiss();\n    }\n  }\n\n  createDataSource = (selectedType, values) => {\n    const target = { options: {}, type: selectedType.type };\n    helper.updateTargetWithValues(target, values);\n\n    return DataSource.create(target).then(dataSource => {\n      this.setState({ loading: true });\n      DataSource.query().then(dataSources => this.setState({ dataSources, loading: false }));\n      return dataSource;\n    });\n  };\n\n  showCreateSourceDialog = () => {\n    recordEvent(\"view\", \"page\", \"data_sources/new\");\n    this.newDataSourceDialog = CreateSourceDialog.showModal({\n      types: reject(this.state.dataSourceTypes, \"deprecated\"),\n      sourceType: \"Data Source\",\n      imageFolder: IMG_ROOT,\n      helpTriggerPrefix: \"DS_\",\n      onCreate: this.createDataSource,\n    });\n\n    this.newDataSourceDialog\n      .onClose((result = {}) => {\n        this.newDataSourceDialog = null;\n        if (result.success) {\n          navigateTo(`data_sources/${result.data.id}`);\n        }\n      })\n      .onDismiss(() => {\n        this.newDataSourceDialog = null;\n        navigateTo(\"data_sources\", true);\n      });\n  };\n\n  render() {\n    const newDataSourceProps = {\n      type: \"primary\",\n      onClick: policy.isCreateDataSourceEnabled() ? this.showCreateSourceDialog : null,\n      disabled: !policy.isCreateDataSourceEnabled(),\n      \"data-test\": \"CreateDataSourceButton\",\n    };\n\n    return (\n      <div>\n        <div className=\"m-b-15\">\n          <Button {...newDataSourceProps}>\n            <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n            New Data Source\n          </Button>\n          <DynamicComponent name=\"DataSourcesListExtra\" />\n        </div>\n        {this.state.loading ? (\n          <LoadingState className=\"\" />\n        ) : (\n          <DynamicComponent\n            name=\"DataSourcesListComponent\"\n            dataSources={this.state.dataSources}\n            onClickCreate={this.showCreateSourceDialog}\n          />\n        )}\n      </div>\n    );\n  }\n}\n\nconst DataSourcesListPage = wrapSettingsTab(\n  \"DataSources.List\",\n  {\n    permission: \"admin\",\n    title: \"Data Sources\",\n    path: \"data_sources\",\n    order: 1,\n  },\n  DataSourcesList\n);\n\nroutes.register(\n  \"DataSources.List\",\n  routeWithUserSession({\n    path: \"/data_sources\",\n    title: \"Data Sources\",\n    render: pageProps => <DataSourcesListPage {...pageProps} />,\n  })\n);\nroutes.register(\n  \"DataSources.New\",\n  routeWithUserSession({\n    path: \"/data_sources/new\",\n    title: \"Data Sources\",\n    render: pageProps => <DataSourcesListPage {...pageProps} isNewDataSourcePage />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/data-sources/EditDataSource.jsx",
    "content": "import { get, find, toUpper } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport Modal from \"antd/lib/modal\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport DynamicForm from \"@/components/dynamic-form/DynamicForm\";\nimport helper from \"@/components/dynamic-form/dynamicFormHelper\";\nimport HelpTrigger, { TYPES as HELP_TRIGGER_TYPES } from \"@/components/HelpTrigger\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\n\nimport DataSource, { IMG_ROOT } from \"@/services/data-source\";\nimport notification from \"@/services/notification\";\nimport routes from \"@/services/routes\";\n\nclass EditDataSource extends React.Component {\n  static propTypes = {\n    dataSourceId: PropTypes.string.isRequired,\n    onError: PropTypes.func,\n  };\n\n  static defaultProps = {\n    onError: () => {},\n  };\n\n  state = {\n    dataSource: null,\n    type: null,\n    loading: true,\n  };\n\n  componentDidMount() {\n    DataSource.get({ id: this.props.dataSourceId })\n      .then(dataSource => {\n        const { type } = dataSource;\n        this.setState({ dataSource });\n        DataSource.types().then(types => this.setState({ type: find(types, { type }), loading: false }));\n      })\n      .catch(error => this.props.onError(error));\n  }\n\n  saveDataSource = (values, successCallback, errorCallback) => {\n    const { dataSource } = this.state;\n    helper.updateTargetWithValues(dataSource, values);\n    DataSource.save(dataSource)\n      .then(() => successCallback(\"Saved.\"))\n      .catch(error => {\n        const message = get(error, \"response.data.message\", \"Failed saving.\");\n        errorCallback(message);\n      });\n  };\n\n  deleteDataSource = callback => {\n    const { dataSource } = this.state;\n\n    const doDelete = () => {\n      DataSource.delete(dataSource)\n        .then(() => {\n          notification.success(\"Data source deleted successfully.\");\n          navigateTo(\"data_sources\");\n        })\n        .catch(() => {\n          callback();\n        });\n    };\n\n    Modal.confirm({\n      title: \"Delete Data Source\",\n      content: \"Are you sure you want to delete this data source?\",\n      okText: \"Delete\",\n      okType: \"danger\",\n      onOk: doDelete,\n      onCancel: callback,\n      maskClosable: true,\n      autoFocusButton: null,\n    });\n  };\n\n  testConnection = callback => {\n    const { dataSource } = this.state;\n    DataSource.test({ id: dataSource.id })\n      .then(httpResponse => {\n        if (httpResponse.ok) {\n          notification.success(\"Success\");\n        } else {\n          notification.error(\"Connection Test Failed:\", httpResponse.message, { duration: 10 });\n        }\n        callback();\n      })\n      .catch(() => {\n        notification.error(\n          \"Connection Test Failed:\",\n          \"Unknown error occurred while performing connection test. Please try again later.\",\n          { duration: 10 }\n        );\n        callback();\n      });\n  };\n\n  renderForm() {\n    const { dataSource, type } = this.state;\n    const fields = helper.getFields(type, dataSource);\n    const helpTriggerType = `DS_${toUpper(type.type)}`;\n    const formProps = {\n      fields,\n      type,\n      actions: [\n        { name: \"Delete\", type: \"danger\", callback: this.deleteDataSource },\n        { name: \"Test Connection\", pullRight: true, callback: this.testConnection, disableWhenDirty: true },\n      ],\n      onSubmit: this.saveDataSource,\n      feedbackIcons: true,\n      defaultShowExtraFields: helper.hasFilledExtraField(type, dataSource),\n    };\n\n    return (\n      <div className=\"row\" data-test=\"DataSource\">\n        <div className=\"text-right m-r-10\">\n          {HELP_TRIGGER_TYPES[helpTriggerType] && (\n            <HelpTrigger className=\"f-13\" type={helpTriggerType}>\n              Setup Instructions <i className=\"fa fa-question-circle\" aria-hidden=\"true\" />\n              <span className=\"sr-only\">(help)</span>\n            </HelpTrigger>\n          )}\n        </div>\n        <div className=\"text-center m-b-10\">\n          <img className=\"p-5\" src={`${IMG_ROOT}/${type.type}.png`} alt={type.name} width=\"64\" />\n          <h3 className=\"m-0\">{type.name}</h3>\n        </div>\n        <div className=\"col-md-4 col-md-offset-4 m-b-10\">\n          <DynamicForm {...formProps} />\n        </div>\n      </div>\n    );\n  }\n\n  render() {\n    return this.state.loading ? <LoadingState className=\"\" /> : this.renderForm();\n  }\n}\n\nconst EditDataSourcePage = wrapSettingsTab(\"DataSources.Edit\", null, EditDataSource);\n\nroutes.register(\n  \"DataSources.Edit\",\n  routeWithUserSession({\n    path: \"/data_sources/:dataSourceId\",\n    title: \"Data Sources\",\n    render: pageProps => <EditDataSourcePage {...pageProps} />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/destinations/DestinationsList.jsx",
    "content": "import { isEmpty, reject } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport Button from \"antd/lib/button\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\nimport CardsList from \"@/components/cards-list/CardsList\";\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport CreateSourceDialog from \"@/components/CreateSourceDialog\";\nimport helper from \"@/components/dynamic-form/dynamicFormHelper\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\nimport PlainButton from \"@/components/PlainButton\";\n\nimport Destination, { IMG_ROOT } from \"@/services/destination\";\nimport { policy } from \"@/services/policy\";\nimport routes from \"@/services/routes\";\n\nclass DestinationsList extends React.Component {\n  static propTypes = {\n    isNewDestinationPage: PropTypes.bool,\n    onError: PropTypes.func,\n  };\n\n  static defaultProps = {\n    isNewDestinationPage: false,\n    onError: () => {},\n  };\n\n  state = {\n    destinationTypes: [],\n    destinations: [],\n    loading: true,\n  };\n\n  componentDidMount() {\n    Promise.all([Destination.query(), Destination.types()])\n      .then(values =>\n        this.setState(\n          {\n            destinations: values[0],\n            destinationTypes: values[1],\n            loading: false,\n          },\n          () => {\n            // all resources are loaded in state\n            if (this.props.isNewDestinationPage) {\n              if (policy.canCreateDestination()) {\n                this.showCreateSourceDialog();\n              } else {\n                navigateTo(\"destinations\", true);\n              }\n            }\n          }\n        )\n      )\n      .catch(error => this.props.onError(error));\n  }\n\n  createDestination = (selectedType, values) => {\n    const target = { options: {}, type: selectedType.type };\n    helper.updateTargetWithValues(target, values);\n\n    return Destination.create(target).then(destination => {\n      this.setState({ loading: true });\n      Destination.query().then(destinations => this.setState({ destinations, loading: false }));\n      return destination;\n    });\n  };\n\n  showCreateSourceDialog = () => {\n    CreateSourceDialog.showModal({\n      types: reject(this.state.destinationTypes, \"deprecated\"),\n      sourceType: \"Alert Destination\",\n      imageFolder: IMG_ROOT,\n      onCreate: this.createDestination,\n    })\n      .onClose((result = {}) => {\n        if (result.success) {\n          navigateTo(`destinations/${result.data.id}`);\n        }\n      })\n      .onDismiss(() => {\n        navigateTo(\"destinations\", true);\n      });\n  };\n\n  renderDestinations() {\n    const { destinations } = this.state;\n    const items = destinations.map(destination => ({\n      title: destination.name,\n      imgSrc: `${IMG_ROOT}/${destination.type}.png`,\n      href: `destinations/${destination.id}`,\n    }));\n\n    return isEmpty(destinations) ? (\n      <div className=\"text-center\">\n        There are no alert destinations yet.\n        {policy.isCreateDestinationEnabled() && (\n          <div className=\"m-t-5\">\n            <PlainButton type=\"link\" onClick={this.showCreateSourceDialog}>\n              Click here\n            </PlainButton>{\" \"}\n            to add one.\n          </div>\n        )}\n      </div>\n    ) : (\n      <CardsList items={items} />\n    );\n  }\n\n  render() {\n    const newDestinationProps = {\n      type: \"primary\",\n      onClick: policy.isCreateDestinationEnabled() ? this.showCreateSourceDialog : null,\n      disabled: !policy.isCreateDestinationEnabled(),\n    };\n\n    return (\n      <div>\n        <div className=\"m-b-15\">\n          <Button {...newDestinationProps}>\n            <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n            New Alert Destination\n          </Button>\n        </div>\n        {this.state.loading ? <LoadingState className=\"\" /> : this.renderDestinations()}\n      </div>\n    );\n  }\n}\n\nconst DestinationsListPage = wrapSettingsTab(\n  \"AlertDestinations.List\",\n  {\n    permission: \"admin\",\n    title: \"Alert Destinations\",\n    path: \"destinations\",\n    order: 4,\n  },\n  DestinationsList\n);\n\nroutes.register(\n  \"AlertDestinations.List\",\n  routeWithUserSession({\n    path: \"/destinations\",\n    title: \"Alert Destinations\",\n    render: pageProps => <DestinationsListPage {...pageProps} />,\n  })\n);\nroutes.register(\n  \"AlertDestinations.New\",\n  routeWithUserSession({\n    path: \"/destinations/new\",\n    title: \"Alert Destinations\",\n    render: pageProps => <DestinationsListPage {...pageProps} isNewDestinationPage />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/destinations/EditDestination.jsx",
    "content": "import { get, find } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport Modal from \"antd/lib/modal\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport DynamicForm from \"@/components/dynamic-form/DynamicForm\";\nimport helper from \"@/components/dynamic-form/dynamicFormHelper\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\n\nimport Destination, { IMG_ROOT } from \"@/services/destination\";\nimport notification from \"@/services/notification\";\nimport routes from \"@/services/routes\";\n\nclass EditDestination extends React.Component {\n  static propTypes = {\n    destinationId: PropTypes.string.isRequired,\n    onError: PropTypes.func,\n  };\n\n  static defaultProps = {\n    onError: () => {},\n  };\n\n  state = {\n    destination: null,\n    type: null,\n    loading: true,\n  };\n\n  componentDidMount() {\n    Destination.get({ id: this.props.destinationId })\n      .then(destination => {\n        const { type } = destination;\n        this.setState({ destination });\n        Destination.types().then(types => this.setState({ type: find(types, { type }), loading: false }));\n      })\n      .catch(error => this.props.onError(error));\n  }\n\n  saveDestination = (values, successCallback, errorCallback) => {\n    const { destination } = this.state;\n    helper.updateTargetWithValues(destination, values);\n    Destination.save(destination)\n      .then(() => successCallback(\"Saved.\"))\n      .catch(error => {\n        const message = get(error, \"response.data.message\", \"Failed saving.\");\n        errorCallback(message);\n      });\n  };\n\n  deleteDestination = callback => {\n    const { destination } = this.state;\n\n    const doDelete = () => {\n      Destination.delete(destination)\n        .then(() => {\n          notification.success(\"Alert destination deleted successfully.\");\n          navigateTo(\"destinations\");\n        })\n        .catch(() => {\n          callback();\n        });\n    };\n\n    Modal.confirm({\n      title: \"Delete Alert Destination\",\n      content: \"Are you sure you want to delete this alert destination?\",\n      okText: \"Delete\",\n      okType: \"danger\",\n      onOk: doDelete,\n      onCancel: callback,\n      maskClosable: true,\n      autoFocusButton: null,\n    });\n  };\n\n  renderForm() {\n    const { destination, type } = this.state;\n    const fields = helper.getFields(type, destination);\n    const formProps = {\n      fields,\n      type,\n      actions: [{ name: \"Delete\", type: \"danger\", callback: this.deleteDestination }],\n      onSubmit: this.saveDestination,\n      defaultShowExtraFields: helper.hasFilledExtraField(type, destination),\n      feedbackIcons: true,\n    };\n\n    return (\n      <div className=\"row\" data-test=\"Destination\">\n        <div className=\"text-center m-b-10\">\n          <img className=\"p-5\" src={`${IMG_ROOT}/${type.type}.png`} alt={type.name} width=\"64\" />\n          <h3 className=\"m-0\">{type.name}</h3>\n        </div>\n        <div className=\"col-md-4 col-md-offset-4 m-b-10\">\n          <DynamicForm {...formProps} />\n        </div>\n      </div>\n    );\n  }\n\n  render() {\n    return this.state.loading ? <LoadingState className=\"\" /> : this.renderForm();\n  }\n}\n\nconst EditDestinationPage = wrapSettingsTab(\"AlertDestinations.Edit\", null, EditDestination);\n\nroutes.register(\n  \"AlertDestinations.Edit\",\n  routeWithUserSession({\n    path: \"/destinations/:destinationId\",\n    title: \"Alert Destinations\",\n    render: pageProps => <EditDestinationPage {...pageProps} />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/groups/GroupDataSources.jsx",
    "content": "import { filter, map, includes, toLower } from \"lodash\";\nimport React from \"react\";\nimport Button from \"antd/lib/button\";\nimport Dropdown from \"antd/lib/dropdown\";\nimport Menu from \"antd/lib/menu\";\nimport DownOutlinedIcon from \"@ant-design/icons/DownOutlined\";\n\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\nimport Paginator from \"@/components/Paginator\";\n\nimport { wrap as itemsList, ControllerType } from \"@/components/items-list/ItemsList\";\nimport { ResourceItemsSource } from \"@/components/items-list/classes/ItemsSource\";\nimport { StateStorage } from \"@/components/items-list/classes/StateStorage\";\n\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport ItemsTable, { Columns } from \"@/components/items-list/components/ItemsTable\";\nimport SelectItemsDialog from \"@/components/SelectItemsDialog\";\nimport { DataSourcePreviewCard } from \"@/components/PreviewCard\";\n\nimport GroupName from \"@/components/groups/GroupName\";\nimport ListItemAddon from \"@/components/groups/ListItemAddon\";\nimport Sidebar from \"@/components/groups/DetailsPageSidebar\";\nimport Layout from \"@/components/layouts/ContentWithSidebar\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\n\nimport notification from \"@/services/notification\";\nimport { currentUser } from \"@/services/auth\";\nimport Group from \"@/services/group\";\nimport DataSource from \"@/services/data-source\";\nimport routes from \"@/services/routes\";\n\nclass GroupDataSources extends React.Component {\n  static propTypes = {\n    controller: ControllerType.isRequired,\n  };\n\n  groupId = parseInt(this.props.controller.params.groupId, 10);\n\n  group = null;\n\n  sidebarMenu = [\n    {\n      key: \"users\",\n      href: `groups/${this.groupId}`,\n      title: \"Members\",\n    },\n    {\n      key: \"datasources\",\n      href: `groups/${this.groupId}/data_sources`,\n      title: \"Data Sources\",\n      isAvailable: () => currentUser.isAdmin,\n    },\n  ];\n\n  listColumns = [\n    Columns.custom((text, datasource) => <DataSourcePreviewCard dataSource={datasource} withLink />, {\n      title: \"Name\",\n      field: \"name\",\n      width: null,\n    }),\n    Columns.custom(\n      (text, datasource) => {\n        const menu = (\n          <Menu\n            selectedKeys={[datasource.view_only ? \"viewonly\" : \"full\"]}\n            onClick={item => this.setDataSourcePermissions(datasource, item.key)}>\n            <Menu.Item key=\"full\">Full Access</Menu.Item>\n            <Menu.Item key=\"viewonly\">View Only</Menu.Item>\n          </Menu>\n        );\n\n        return (\n          <Dropdown trigger={[\"click\"]} overlay={menu}>\n            <Button className=\"w-100\" aria-label=\"Permissions\">\n              {datasource.view_only ? \"View Only\" : \"Full Access\"}\n              <DownOutlinedIcon aria-hidden=\"true\" />\n            </Button>\n          </Dropdown>\n        );\n      },\n      {\n        width: \"1%\",\n        className: \"p-r-0\",\n        isAvailable: () => currentUser.isAdmin,\n      }\n    ),\n    Columns.custom(\n      (text, datasource) => (\n        <Button className=\"w-100\" type=\"danger\" onClick={() => this.removeGroupDataSource(datasource)}>\n          Remove\n        </Button>\n      ),\n      {\n        width: \"1%\",\n        isAvailable: () => currentUser.isAdmin,\n      }\n    ),\n  ];\n\n  componentDidMount() {\n    Group.get({ id: this.groupId })\n      .then(group => {\n        this.group = group;\n        this.forceUpdate();\n      })\n      .catch(error => {\n        this.props.controller.handleError(error);\n      });\n  }\n\n  removeGroupDataSource = datasource => {\n    Group.removeDataSource({ id: this.groupId, dataSourceId: datasource.id })\n      .then(() => {\n        this.props.controller.updatePagination({ page: 1 });\n        this.props.controller.update();\n      })\n      .catch(() => {\n        notification.error(\"Failed to remove data source from group.\");\n      });\n  };\n\n  setDataSourcePermissions = (datasource, permission) => {\n    const viewOnly = permission !== \"full\";\n\n    Group.updateDataSource({ id: this.groupId, dataSourceId: datasource.id }, { view_only: viewOnly })\n      .then(() => {\n        datasource.view_only = viewOnly;\n        this.forceUpdate();\n      })\n      .catch(() => {\n        notification.error(\"Failed change data source permissions.\");\n      });\n  };\n\n  addDataSources = () => {\n    const allDataSources = DataSource.query();\n    const alreadyAddedDataSources = map(this.props.controller.allItems, ds => ds.id);\n    SelectItemsDialog.showModal({\n      dialogTitle: \"Add Data Sources\",\n      inputPlaceholder: \"Search data sources...\",\n      selectedItemsTitle: \"New Data Sources\",\n      searchItems: searchTerm => {\n        searchTerm = toLower(searchTerm);\n        return allDataSources.then(items => filter(items, ds => includes(toLower(ds.name), searchTerm)));\n      },\n      renderItem: (item, { isSelected }) => {\n        const alreadyInGroup = includes(alreadyAddedDataSources, item.id);\n        return {\n          content: (\n            <DataSourcePreviewCard dataSource={item}>\n              <ListItemAddon isSelected={isSelected} alreadyInGroup={alreadyInGroup} />\n            </DataSourcePreviewCard>\n          ),\n          isDisabled: alreadyInGroup,\n          className: isSelected || alreadyInGroup ? \"selected\" : \"\",\n        };\n      },\n      renderStagedItem: (item, { isSelected }) => ({\n        content: (\n          <DataSourcePreviewCard dataSource={item}>\n            <ListItemAddon isSelected={isSelected} isStaged />\n          </DataSourcePreviewCard>\n        ),\n      }),\n    }).onClose(items => {\n      const promises = map(items, ds => Group.addDataSource({ id: this.groupId }, { data_source_id: ds.id }));\n      return Promise.all(promises).then(() => this.props.controller.update());\n    });\n  };\n\n  render() {\n    const { controller } = this.props;\n    return (\n      <div data-test=\"Group\">\n        <GroupName className=\"d-block m-t-0 m-b-15\" group={this.group} onChange={() => this.forceUpdate()} />\n        <Layout>\n          <Layout.Sidebar>\n            <Sidebar\n              controller={controller}\n              group={this.group}\n              items={this.sidebarMenu}\n              canAddDataSources={currentUser.isAdmin}\n              onAddDataSourcesClick={this.addDataSources}\n              onGroupDeleted={() => navigateTo(\"groups\")}\n            />\n          </Layout.Sidebar>\n          <Layout.Content>\n            {!controller.isLoaded && <LoadingState className=\"\" />}\n            {controller.isLoaded && controller.isEmpty && (\n              <div className=\"text-center\">\n                <p>There are no data sources in this group yet.</p>\n                {currentUser.isAdmin && (\n                  <Button type=\"primary\" onClick={this.addDataSources}>\n                    <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n                    Add Data Sources\n                  </Button>\n                )}\n              </div>\n            )}\n            {controller.isLoaded && !controller.isEmpty && (\n              <div className=\"table-responsive\">\n                <ItemsTable\n                  items={controller.pageItems}\n                  columns={this.listColumns}\n                  showHeader={false}\n                  context={this.actions}\n                  orderByField={controller.orderByField}\n                  orderByReverse={controller.orderByReverse}\n                  toggleSorting={controller.toggleSorting}\n                />\n                <Paginator\n                  showPageSizeSelect\n                  totalCount={controller.totalItemsCount}\n                  pageSize={controller.itemsPerPage}\n                  onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}\n                  page={controller.page}\n                  onChange={page => controller.updatePagination({ page })}\n                />\n              </div>\n            )}\n          </Layout.Content>\n        </Layout>\n      </div>\n    );\n  }\n}\n\nconst GroupDataSourcesPage = wrapSettingsTab(\n  \"Groups.DataSources\",\n  null,\n  itemsList(\n    GroupDataSources,\n    () =>\n      new ResourceItemsSource({\n        isPlainList: true,\n        getRequest(unused, { params: { groupId } }) {\n          return { id: groupId };\n        },\n        getResource() {\n          return Group.dataSources.bind(Group);\n        },\n      }),\n    () => new StateStorage({ orderByField: \"name\" })\n  )\n);\n\nroutes.register(\n  \"Groups.DataSources\",\n  routeWithUserSession({\n    path: \"/groups/:groupId/data_sources\",\n    title: \"Group Data Sources\",\n    render: pageProps => <GroupDataSourcesPage {...pageProps} currentPage=\"datasources\" />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/groups/GroupMembers.jsx",
    "content": "import { includes, map } from \"lodash\";\nimport React from \"react\";\nimport Button from \"antd/lib/button\";\n\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\nimport Paginator from \"@/components/Paginator\";\n\nimport { wrap as itemsList, ControllerType } from \"@/components/items-list/ItemsList\";\nimport { ResourceItemsSource } from \"@/components/items-list/classes/ItemsSource\";\nimport { StateStorage } from \"@/components/items-list/classes/StateStorage\";\n\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport ItemsTable, { Columns } from \"@/components/items-list/components/ItemsTable\";\nimport SelectItemsDialog from \"@/components/SelectItemsDialog\";\nimport { UserPreviewCard } from \"@/components/PreviewCard\";\n\nimport GroupName from \"@/components/groups/GroupName\";\nimport ListItemAddon from \"@/components/groups/ListItemAddon\";\nimport Sidebar from \"@/components/groups/DetailsPageSidebar\";\nimport Layout from \"@/components/layouts/ContentWithSidebar\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\n\nimport notification from \"@/services/notification\";\nimport { currentUser } from \"@/services/auth\";\nimport Group from \"@/services/group\";\nimport User from \"@/services/user\";\nimport routes from \"@/services/routes\";\n\nclass GroupMembers extends React.Component {\n  static propTypes = {\n    controller: ControllerType.isRequired,\n  };\n\n  groupId = parseInt(this.props.controller.params.groupId, 10);\n\n  group = null;\n\n  sidebarMenu = [\n    {\n      key: \"users\",\n      href: `groups/${this.groupId}`,\n      title: \"Members\",\n    },\n    {\n      key: \"datasources\",\n      href: `groups/${this.groupId}/data_sources`,\n      title: \"Data Sources\",\n      isAvailable: () => currentUser.isAdmin,\n    },\n  ];\n\n  listColumns = [\n    Columns.custom((text, user) => <UserPreviewCard user={user} withLink />, {\n      title: \"Name\",\n      field: \"name\",\n      width: null,\n    }),\n    Columns.custom(\n      (text, user) => {\n        if (!this.group) {\n          return null;\n        }\n\n        // cannot remove self from built-in groups\n        if (this.group.type === \"builtin\" && currentUser.id === user.id) {\n          return null;\n        }\n        return (\n          <Button className=\"w-100\" type=\"danger\" onClick={event => this.removeGroupMember(event, user)}>\n            Remove\n          </Button>\n        );\n      },\n      {\n        width: \"1%\",\n        isAvailable: () => currentUser.isAdmin,\n      }\n    ),\n  ];\n\n  componentDidMount() {\n    Group.get({ id: this.groupId })\n      .then(group => {\n        this.group = group;\n        this.forceUpdate();\n      })\n      .catch(error => {\n        this.props.controller.handleError(error);\n      });\n  }\n\n  removeGroupMember = (event, user) =>\n    Group.removeMember({ id: this.groupId, userId: user.id })\n      .then(() => {\n        this.props.controller.updatePagination({ page: 1 });\n        this.props.controller.update();\n      })\n      .catch(() => {\n        notification.error(\"Failed to remove member from group.\");\n      });\n\n  addMembers = () => {\n    const alreadyAddedUsers = map(this.props.controller.allItems, u => u.id);\n    SelectItemsDialog.showModal({\n      dialogTitle: \"Add Members\",\n      inputPlaceholder: \"Search users...\",\n      selectedItemsTitle: \"New Members\",\n      searchItems: searchTerm => User.query({ q: searchTerm }).then(({ results }) => results),\n      renderItem: (item, { isSelected }) => {\n        const alreadyInGroup = includes(alreadyAddedUsers, item.id);\n        return {\n          content: (\n            <UserPreviewCard user={item}>\n              <ListItemAddon isSelected={isSelected} alreadyInGroup={alreadyInGroup} />\n            </UserPreviewCard>\n          ),\n          isDisabled: alreadyInGroup,\n          className: isSelected || alreadyInGroup ? \"selected\" : \"\",\n        };\n      },\n      renderStagedItem: (item, { isSelected }) => ({\n        content: (\n          <UserPreviewCard user={item}>\n            <ListItemAddon isSelected={isSelected} isStaged />\n          </UserPreviewCard>\n        ),\n      }),\n    }).onClose(items => {\n      const promises = map(items, u => Group.addMember({ id: this.groupId }, { user_id: u.id }));\n      return Promise.all(promises).then(() => this.props.controller.update());\n    });\n  };\n\n  render() {\n    const { controller } = this.props;\n    return (\n      <div data-test=\"Group\">\n        <GroupName className=\"d-block m-t-0 m-b-15\" group={this.group} onChange={() => this.forceUpdate()} />\n        <Layout>\n          <Layout.Sidebar>\n            <Sidebar\n              controller={controller}\n              group={this.group}\n              items={this.sidebarMenu}\n              canAddMembers={currentUser.isAdmin}\n              onAddMembersClick={this.addMembers}\n              onGroupDeleted={() => navigateTo(\"groups\")}\n            />\n          </Layout.Sidebar>\n          <Layout.Content>\n            {!controller.isLoaded && <LoadingState className=\"\" />}\n            {controller.isLoaded && controller.isEmpty && (\n              <div className=\"text-center\">\n                <p>There are no members in this group yet.</p>\n                {currentUser.isAdmin && (\n                  <Button type=\"primary\" onClick={this.addMembers}>\n                    <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n                    Add Members\n                  </Button>\n                )}\n              </div>\n            )}\n            {controller.isLoaded && !controller.isEmpty && (\n              <div className=\"table-responsive\">\n                <ItemsTable\n                  items={controller.pageItems}\n                  columns={this.listColumns}\n                  showHeader={false}\n                  context={this.actions}\n                  orderByField={controller.orderByField}\n                  orderByReverse={controller.orderByReverse}\n                  toggleSorting={controller.toggleSorting}\n                />\n                <Paginator\n                  showPageSizeSelect\n                  totalCount={controller.totalItemsCount}\n                  pageSize={controller.itemsPerPage}\n                  onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}\n                  page={controller.page}\n                  onChange={page => controller.updatePagination({ page })}\n                />\n              </div>\n            )}\n          </Layout.Content>\n        </Layout>\n      </div>\n    );\n  }\n}\n\nconst GroupMembersPage = wrapSettingsTab(\n  \"Groups.Members\",\n  null,\n  itemsList(\n    GroupMembers,\n    () =>\n      new ResourceItemsSource({\n        isPlainList: true,\n        getRequest(unused, { params: { groupId } }) {\n          return { id: groupId };\n        },\n        getResource() {\n          return Group.members.bind(Group);\n        },\n      }),\n    () => new StateStorage({ orderByField: \"name\" })\n  )\n);\n\nroutes.register(\n  \"Groups.Members\",\n  routeWithUserSession({\n    path: \"/groups/:groupId\",\n    title: \"Group Members\",\n    render: pageProps => <GroupMembersPage {...pageProps} currentPage=\"users\" />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/groups/GroupsList.jsx",
    "content": "import React from \"react\";\n\nimport Button from \"antd/lib/button\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport Link from \"@/components/Link\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\nimport Paginator from \"@/components/Paginator\";\n\nimport { wrap as itemsList, ControllerType } from \"@/components/items-list/ItemsList\";\nimport { ResourceItemsSource } from \"@/components/items-list/classes/ItemsSource\";\nimport { StateStorage } from \"@/components/items-list/classes/StateStorage\";\n\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport EmptyState from \"@/components/items-list/components/EmptyState\";\nimport ItemsTable, { Columns } from \"@/components/items-list/components/ItemsTable\";\n\nimport CreateGroupDialog from \"@/components/groups/CreateGroupDialog\";\nimport DeleteGroupButton from \"@/components/groups/DeleteGroupButton\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\n\nimport Group from \"@/services/group\";\nimport { currentUser } from \"@/services/auth\";\nimport routes from \"@/services/routes\";\n\nclass GroupsList extends React.Component {\n  static propTypes = {\n    controller: ControllerType.isRequired,\n  };\n\n  listColumns = [\n    Columns.custom(\n      (text, group) => (\n        <div>\n          <Link href={\"groups/\" + group.id}>{group.name}</Link>\n          {group.type === \"builtin\" && <span className=\"label label-default m-l-10\">built-in</span>}\n        </div>\n      ),\n      {\n        field: \"name\",\n        width: null,\n      }\n    ),\n    Columns.custom(\n      (text, group) => (\n        <Button.Group>\n          <Link.Button href={`groups/${group.id}`}>Members</Link.Button>\n          {currentUser.isAdmin && <Link.Button href={`groups/${group.id}/data_sources`}>Data Sources</Link.Button>}\n        </Button.Group>\n      ),\n      {\n        width: \"1%\",\n        className: \"text-nowrap\",\n      }\n    ),\n    Columns.custom(\n      (text, group) => {\n        const canRemove = group.type !== \"builtin\";\n        return (\n          <DeleteGroupButton\n            className=\"w-100\"\n            disabled={!canRemove}\n            group={group}\n            title={canRemove ? null : \"Cannot delete built-in group\"}\n            onClick={() => this.onGroupDeleted()}>\n            Delete\n          </DeleteGroupButton>\n        );\n      },\n      {\n        width: \"1%\",\n        className: \"text-nowrap p-l-0\",\n        isAvailable: () => currentUser.isAdmin,\n      }\n    ),\n  ];\n\n  createGroup = () => {\n    CreateGroupDialog.showModal().onClose(group =>\n      Group.create(group).then(newGroup => navigateTo(`groups/${newGroup.id}`))\n    );\n  };\n\n  onGroupDeleted = () => {\n    this.props.controller.updatePagination({ page: 1 });\n    this.props.controller.update();\n  };\n\n  render() {\n    const { controller } = this.props;\n\n    return (\n      <div data-test=\"GroupList\">\n        {currentUser.isAdmin && (\n          <div className=\"m-b-15\">\n            <Button type=\"primary\" onClick={this.createGroup}>\n              <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n              New Group\n            </Button>\n          </div>\n        )}\n\n        {!controller.isLoaded && <LoadingState className=\"\" />}\n        {controller.isLoaded && controller.isEmpty && <EmptyState className=\"\" />}\n        {controller.isLoaded && !controller.isEmpty && (\n          <div className=\"table-responsive\">\n            <ItemsTable\n              items={controller.pageItems}\n              columns={this.listColumns}\n              showHeader={false}\n              context={this.actions}\n              orderByField={controller.orderByField}\n              orderByReverse={controller.orderByReverse}\n              toggleSorting={controller.toggleSorting}\n            />\n            <Paginator\n              showPageSizeSelect\n              totalCount={controller.totalItemsCount}\n              pageSize={controller.itemsPerPage}\n              onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}\n              page={controller.page}\n              onChange={page => controller.updatePagination({ page })}\n            />\n          </div>\n        )}\n      </div>\n    );\n  }\n}\n\nconst GroupsListPage = wrapSettingsTab(\n  \"Groups.List\",\n  {\n    permission: \"list_users\",\n    title: \"Groups\",\n    path: \"groups\",\n    order: 3,\n  },\n  itemsList(\n    GroupsList,\n    () =>\n      new ResourceItemsSource({\n        isPlainList: true,\n        getRequest() {\n          return {};\n        },\n        getResource() {\n          return Group.query.bind(Group);\n        },\n      }),\n    () => new StateStorage({ orderByField: \"name\", itemsPerPage: 10 })\n  )\n);\n\nroutes.register(\n  \"Groups.List\",\n  routeWithUserSession({\n    path: \"/groups\",\n    title: \"Groups\",\n    render: pageProps => <GroupsListPage {...pageProps} currentPage=\"groups\" />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/home/Home.jsx",
    "content": "import { includes } from \"lodash\";\nimport React, { useEffect } from \"react\";\n\nimport Alert from \"antd/lib/alert\";\nimport Link from \"@/components/Link\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport EmptyState, { EmptyStateHelpMessage } from \"@/components/empty-state/EmptyState\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport BeaconConsent from \"@/components/BeaconConsent\";\nimport PlainButton from \"@/components/PlainButton\";\n\nimport { axios } from \"@/services/axios\";\nimport recordEvent from \"@/services/recordEvent\";\nimport { messages } from \"@/services/auth\";\nimport notification from \"@/services/notification\";\nimport routes from \"@/services/routes\";\n\nimport { DashboardAndQueryFavoritesList } from \"./components/FavoritesList\";\n\nimport \"./Home.less\";\n\nfunction DeprecatedEmbedFeatureAlert() {\n  return (\n    <Alert\n      className=\"m-b-15\"\n      type=\"warning\"\n      message={\n        <>\n          You have enabled <code>ALLOW_PARAMETERS_IN_EMBEDS</code>. This setting is now deprecated and should be turned\n          off. Parameters in embeds are supported by default.{\" \"}\n          <Link\n            href=\"https://discuss.redash.io/t/support-for-parameters-in-embedded-visualizations/3337\"\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n          >\n            Read more\n          </Link>\n          .\n        </>\n      }\n    />\n  );\n}\n\nfunction EmailNotVerifiedAlert() {\n  const verifyEmail = () => {\n    axios.post(\"verification_email/\").then((data) => {\n      notification.success(data.message);\n    });\n  };\n\n  return (\n    <Alert\n      className=\"m-b-15\"\n      type=\"warning\"\n      message={\n        <>\n          We have sent an email with a confirmation link to your email address. Please follow the link to verify your\n          email address.{\" \"}\n          <PlainButton type=\"link\" onClick={verifyEmail}>\n            Resend email\n          </PlainButton>\n          .\n        </>\n      }\n    />\n  );\n}\n\nexport default function Home() {\n  useEffect(() => {\n    recordEvent(\"view\", \"page\", \"personal_homepage\");\n  }, []);\n\n  return (\n    <div className=\"home-page\">\n      <div className=\"container\">\n        {includes(messages, \"using-deprecated-embed-feature\") && <DeprecatedEmbedFeatureAlert />}\n        {includes(messages, \"email-not-verified\") && <EmailNotVerifiedAlert />}\n        <DynamicComponent name=\"Home.EmptyState\">\n          <EmptyState\n            header=\"Welcome to Redash 👋\"\n            description=\"Connect to any data source, easily visualize and share your data\"\n            illustration=\"dashboard\"\n            helpMessage={<EmptyStateHelpMessage helpTriggerType=\"GETTING_STARTED\" />}\n            showDashboardStep\n            showInviteStep\n            onboardingMode\n          />\n        </DynamicComponent>\n        <DynamicComponent name=\"HomeExtra\" />\n        <DashboardAndQueryFavoritesList />\n        <BeaconConsent />\n      </div>\n    </div>\n  );\n}\n\nroutes.register(\n  \"Home\",\n  routeWithUserSession({\n    path: \"/\",\n    title: \"Redash\",\n    render: (pageProps) => <Home {...pageProps} />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/home/Home.less",
    "content": ".home-page {\n  padding-top: 15px;\n}\n\n.home-favorites-list {\n  margin-top: -20px;\n}\n"
  },
  {
    "path": "client/app/pages/home/components/FavoritesList.jsx",
    "content": "import { isEmpty } from \"lodash\";\nimport React, { useEffect, useState } from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport Link from \"@/components/Link\";\nimport LoadingOutlinedIcon from \"@ant-design/icons/LoadingOutlined\";\n\nimport { Dashboard } from \"@/services/dashboard\";\nimport { Query } from \"@/services/query\";\n\nexport function FavoriteList({ title, resource, itemUrl, emptyState }) {\n  const [items, setItems] = useState([]);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    setLoading(true);\n    resource\n      .favorites({ order: \"-starred_at\" })\n      .then(({ results }) => setItems(results))\n      .finally(() => setLoading(false));\n  }, [resource]);\n\n  return (\n    <>\n      <div className=\"d-flex align-items-center m-b-20\">\n        <p className=\"flex-fill f-500 c-black m-0\">{title}</p>\n        {loading && <LoadingOutlinedIcon />}\n      </div>\n      {!isEmpty(items) && (\n        <div role=\"list\" className=\"list-group\">\n          {items.map((item) => (\n            <Link key={itemUrl(item)} role=\"listitem\" className=\"list-group-item\" href={itemUrl(item)}>\n              <span className=\"btn-favorite m-r-5\">\n                <i className=\"fa fa-star\" aria-hidden=\"true\" />\n              </span>\n              {item.name}\n              {item.is_draft && <span className=\"label label-default m-l-5\">Unpublished</span>}\n            </Link>\n          ))}\n        </div>\n      )}\n      {isEmpty(items) && !loading && emptyState}\n    </>\n  );\n}\n\nFavoriteList.propTypes = {\n  title: PropTypes.string.isRequired,\n  resource: PropTypes.func.isRequired, // eslint-disable-line react/forbid-prop-types\n  itemUrl: PropTypes.func.isRequired,\n  emptyState: PropTypes.node,\n};\nFavoriteList.defaultProps = { emptyState: null };\n\nexport function DashboardAndQueryFavoritesList() {\n  return (\n    <div className=\"tile\">\n      <div className=\"t-body tb-padding\">\n        <div className=\"row home-favorites-list\">\n          <div className=\"col-sm-6 m-t-20\">\n            <FavoriteList\n              title=\"Favorite Dashboards\"\n              resource={Dashboard}\n              itemUrl={(dashboard) => dashboard.url}\n              emptyState={\n                <p>\n                  <span className=\"btn-favorite m-r-5\">\n                    <i className=\"fa fa-star\" aria-hidden=\"true\" />\n                  </span>\n                  Favorite <Link href=\"dashboards\">Dashboards</Link> will appear here\n                </p>\n              }\n            />\n          </div>\n          <div className=\"col-sm-6 m-t-20\">\n            <FavoriteList\n              title=\"Favorite Queries\"\n              resource={Query}\n              itemUrl={(query) => `queries/${query.id}`}\n              emptyState={\n                <p>\n                  <span className=\"btn-favorite m-r-5\">\n                    <i className=\"fa fa-star\" aria-hidden=\"true\" />\n                  </span>\n                  Favorite <Link href=\"queries\">Queries</Link> will appear here\n                </p>\n              }\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "client/app/pages/index.js",
    "content": "import \"./home/Home\";\n\nimport \"./admin/Jobs\";\nimport \"./admin/OutdatedQueries\";\nimport \"./admin/SystemStatus\";\n\nimport \"./alerts/AlertsList\";\nimport \"./alert/Alert\";\n\nimport \"./dashboards/DashboardList\";\nimport \"./dashboards/DashboardPage\";\nimport \"./dashboards/PublicDashboardPage\";\n\nimport \"./data-sources/DataSourcesList\";\nimport \"./data-sources/EditDataSource\";\n\nimport \"./destinations/DestinationsList\";\nimport \"./destinations/EditDestination\";\n\nimport \"./groups/GroupsList\";\nimport \"./groups/GroupDataSources\";\nimport \"./groups/GroupMembers\";\n\nimport \"./queries-list/QueriesList\";\nimport \"./queries/QuerySource\";\nimport \"./queries/QueryView\";\nimport \"./queries/VisualizationEmbed\";\n\nimport \"./query-snippets/QuerySnippetsList\";\n\nimport \"./settings/OrganizationSettings\";\n\nimport \"./users/UsersList\";\nimport \"./users/UserProfile\";\n"
  },
  {
    "path": "client/app/pages/queries/QuerySource.jsx",
    "content": "import { extend, find, includes, isEmpty, map } from \"lodash\";\nimport React, { useCallback, useEffect, useRef, useState } from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport useMedia from \"use-media\";\nimport Button from \"antd/lib/button\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport Resizable from \"@/components/Resizable\";\nimport Parameters from \"@/components/Parameters\";\nimport EditInPlace from \"@/components/EditInPlace\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport recordEvent from \"@/services/recordEvent\";\nimport { ExecutionStatus } from \"@/services/query-result\";\nimport routes from \"@/services/routes\";\nimport notification from \"@/services/notification\";\nimport * as queryFormat from \"@/lib/queryFormat\";\n\nimport QueryPageHeader from \"./components/QueryPageHeader\";\nimport QueryMetadata from \"./components/QueryMetadata\";\nimport QueryVisualizationTabs from \"./components/QueryVisualizationTabs\";\nimport QueryExecutionStatus from \"./components/QueryExecutionStatus\";\nimport QuerySourceAlerts from \"./components/QuerySourceAlerts\";\nimport wrapQueryPage from \"./components/wrapQueryPage\";\nimport QueryExecutionMetadata from \"./components/QueryExecutionMetadata\";\n\nimport { getEditorComponents } from \"@/components/queries/editor-components\";\nimport useQuery from \"./hooks/useQuery\";\nimport useVisualizationTabHandler from \"./hooks/useVisualizationTabHandler\";\nimport useAutocompleteFlags from \"./hooks/useAutocompleteFlags\";\nimport useAutoLimitFlags from \"./hooks/useAutoLimitFlags\";\nimport useQueryExecute from \"./hooks/useQueryExecute\";\nimport useQueryResultData from \"@/lib/useQueryResultData\";\nimport useQueryDataSources from \"./hooks/useQueryDataSources\";\nimport useQueryFlags from \"./hooks/useQueryFlags\";\nimport useQueryParameters from \"./hooks/useQueryParameters\";\nimport useAddNewParameterDialog from \"./hooks/useAddNewParameterDialog\";\nimport useEditScheduleDialog from \"./hooks/useEditScheduleDialog\";\nimport useAddVisualizationDialog from \"./hooks/useAddVisualizationDialog\";\nimport useEditVisualizationDialog from \"./hooks/useEditVisualizationDialog\";\nimport useDeleteVisualization from \"./hooks/useDeleteVisualization\";\nimport useUpdateQuery from \"./hooks/useUpdateQuery\";\nimport useUpdateQueryDescription from \"./hooks/useUpdateQueryDescription\";\nimport useUnsavedChangesAlert from \"./hooks/useUnsavedChangesAlert\";\n\nimport \"./components/QuerySourceDropdown\"; // register QuerySourceDropdown\nimport \"./QuerySource.less\";\n\nfunction chooseDataSourceId(dataSourceIds, availableDataSources) {\n  availableDataSources = map(availableDataSources, ds => ds.id);\n  return find(dataSourceIds, id => includes(availableDataSources, id)) || null;\n}\n\nfunction QuerySource(props) {\n  const { query, setQuery, isDirty, saveQuery } = useQuery(props.query);\n  const { dataSourcesLoaded, dataSources, dataSource } = useQueryDataSources(query);\n  const [schema, setSchema] = useState([]);\n  const queryFlags = useQueryFlags(query, dataSource);\n  const [parameters, areParametersDirty, updateParametersDirtyFlag] = useQueryParameters(query);\n  const [selectedVisualization, setSelectedVisualization] = useVisualizationTabHandler(query.visualizations);\n  const { QueryEditor, SchemaBrowser } = getEditorComponents(dataSource && dataSource.type);\n  const isMobile = !useMedia({ minWidth: 768 });\n\n  useUnsavedChangesAlert(isDirty);\n\n  const {\n    queryResult,\n    isExecuting: isQueryExecuting,\n    executionStatus,\n    executeQuery,\n    error: executionError,\n    cancelCallback: cancelExecution,\n    isCancelling: isExecutionCancelling,\n    updatedAt,\n    loadedInitialResults,\n  } = useQueryExecute(query);\n\n  const queryResultData = useQueryResultData(queryResult);\n\n  const editorRef = useRef(null);\n  const [autocompleteAvailable, autocompleteEnabled, toggleAutocomplete] = useAutocompleteFlags(schema);\n  const [autoLimitAvailable, autoLimitChecked, setAutoLimit] = useAutoLimitFlags(dataSource, query, setQuery);\n\n  const [handleQueryEditorChange] = useDebouncedCallback(queryText => {\n    setQuery(extend(query.clone(), { query: queryText }));\n  }, 100);\n\n  useEffect(() => {\n    // TODO: ignore new pages?\n    recordEvent(\"view_source\", \"query\", query.id);\n  }, [query.id]);\n\n  useEffect(() => {\n    document.title = query.name;\n  }, [query.name]);\n\n  const updateQuery = useUpdateQuery(query, setQuery);\n  const updateQueryDescription = useUpdateQueryDescription(query, setQuery);\n  const querySyntax = dataSource ? dataSource.syntax || \"sql\" : null;\n  const isFormatQueryAvailable = queryFormat.isFormatQueryAvailable(querySyntax);\n  const formatQuery = () => {\n    try {\n      const formattedQueryText = queryFormat.formatQuery(query.query, querySyntax);\n      setQuery(extend(query.clone(), { query: formattedQueryText }));\n    } catch (err) {\n      notification.error(String(err));\n    }\n  };\n\n  const handleDataSourceChange = useCallback(\n    dataSourceId => {\n      if (dataSourceId) {\n        try {\n          localStorage.setItem(\"lastSelectedDataSourceId\", dataSourceId);\n        } catch (e) {\n          // `localStorage.setItem` may throw exception if there are no enough space - in this case it could be ignored\n        }\n      }\n      if (query.data_source_id !== dataSourceId) {\n        recordEvent(\"update_data_source\", \"query\", query.id, { dataSourceId });\n        const updates = {\n          data_source_id: dataSourceId,\n          latest_query_data_id: null,\n          latest_query_data: null,\n        };\n        setQuery(extend(query.clone(), updates));\n        updateQuery(updates, { successMessage: null }); // show message only on error\n      }\n    },\n    [query, setQuery, updateQuery]\n  );\n\n  useEffect(() => {\n    // choose data source id for new queries\n    if (dataSourcesLoaded && queryFlags.isNew) {\n      const firstDataSourceId = dataSources.length > 0 ? dataSources[0].id : null;\n      const selectedDataSourceId = parseInt(localStorage.getItem(\"lastSelectedDataSourceId\")) || null;\n\n      handleDataSourceChange(\n        chooseDataSourceId([query.data_source_id, selectedDataSourceId, firstDataSourceId], dataSources)\n      );\n    }\n  }, [query.data_source_id, queryFlags.isNew, dataSourcesLoaded, dataSources, handleDataSourceChange]);\n\n  const editSchedule = useEditScheduleDialog(query, setQuery);\n  const openAddNewParameterDialog = useAddNewParameterDialog(query, (newQuery, param) => {\n    if (editorRef.current) {\n      editorRef.current.paste(param.toQueryTextFragment());\n      editorRef.current.focus();\n    }\n    setQuery(newQuery);\n  });\n\n  const handleSchemaItemSelect = useCallback(schemaItem => {\n    if (editorRef.current) {\n      editorRef.current.paste(schemaItem);\n    }\n  }, []);\n\n  const [selectedText, setSelectedText] = useState(null);\n\n  const doExecuteQuery = useCallback(\n    (skipParametersDirtyFlag = false) => {\n      if (!queryFlags.canExecute || (!skipParametersDirtyFlag && (areParametersDirty || isQueryExecuting))) {\n        return;\n      }\n      if (isDirty || !isEmpty(selectedText)) {\n        executeQuery(null, () => {\n          return query.getQueryResultByText(0, selectedText);\n        });\n      } else {\n        executeQuery();\n      }\n    },\n    [query, queryFlags.canExecute, areParametersDirty, isQueryExecuting, isDirty, selectedText, executeQuery]\n  );\n\n  const [isQuerySaving, setIsQuerySaving] = useState(false);\n\n  const doSaveQuery = useCallback(() => {\n    if (!isQuerySaving) {\n      setIsQuerySaving(true);\n      saveQuery().finally(() => setIsQuerySaving(false));\n    }\n  }, [isQuerySaving, saveQuery]);\n\n  const addVisualization = useAddVisualizationDialog(query, queryResult, doSaveQuery, (newQuery, visualization) => {\n    setQuery(newQuery);\n    setSelectedVisualization(visualization.id);\n  });\n  const editVisualization = useEditVisualizationDialog(query, queryResult, newQuery => setQuery(newQuery));\n  const deleteVisualization = useDeleteVisualization(query, setQuery);\n\n  return (\n    <div className={cx(\"query-page-wrapper\", { \"query-fixed-layout\": !isMobile })}>\n      <QuerySourceAlerts query={query} dataSourcesAvailable={!dataSourcesLoaded || dataSources.length > 0} />\n      <div className=\"container w-100 p-b-10\">\n        <QueryPageHeader\n          query={query}\n          dataSource={dataSource}\n          sourceMode\n          selectedVisualization={selectedVisualization}\n          headerExtra={<DynamicComponent name=\"QuerySource.HeaderExtra\" query={query} />}\n          onChange={setQuery}\n        />\n      </div>\n      <main className=\"query-fullscreen\">\n        <Resizable direction=\"horizontal\" sizeAttribute=\"flex-basis\" toggleShortcut=\"Alt+Shift+D, Alt+D\">\n          <nav>\n            {dataSourcesLoaded && (\n              <div className=\"editor__left__data-source\">\n                <DynamicComponent\n                  name={\"QuerySourceDropdown\"}\n                  dataSources={dataSources}\n                  value={dataSource ? dataSource.id : undefined}\n                  disabled={!queryFlags.canEdit || !dataSourcesLoaded || dataSources.length === 0}\n                  loading={!dataSourcesLoaded}\n                  onChange={handleDataSourceChange}\n                />\n              </div>\n            )}\n            <div className=\"editor__left__schema\">\n              <SchemaBrowser\n                dataSource={dataSource}\n                options={query.options.schemaOptions}\n                onOptionsUpdate={schemaOptions =>\n                  setQuery(extend(query.clone(), { options: { ...query.options, schemaOptions } }))\n                }\n                onSchemaUpdate={setSchema}\n                onItemSelect={handleSchemaItemSelect}\n              />\n            </div>\n\n            {!query.isNew() && (\n              <div className=\"query-page-query-description\">\n                <EditInPlace\n                  isEditable={queryFlags.canEdit}\n                  markdown\n                  ignoreBlanks={false}\n                  placeholder=\"Add description\"\n                  value={query.description}\n                  onDone={updateQueryDescription}\n                  multiline\n                />\n              </div>\n            )}\n\n            {!query.isNew() && <QueryMetadata layout=\"table\" query={query} onEditSchedule={editSchedule} />}\n          </nav>\n        </Resizable>\n\n        <div className=\"content\">\n          <div className=\"flex-fill p-relative\">\n            <div\n              className=\"p-absolute d-flex flex-column p-l-15 p-r-15\"\n              style={{ left: 0, top: 0, right: 0, bottom: 0, overflow: \"auto\" }}>\n              <Resizable direction=\"vertical\" sizeAttribute=\"flex-basis\">\n                <div className=\"row editor\">\n                  <section className=\"query-editor-wrapper\" data-test=\"QueryEditor\">\n                    <QueryEditor\n                      ref={editorRef}\n                      data-executing={isQueryExecuting ? \"true\" : null}\n                      syntax={dataSource ? dataSource.syntax : null}\n                      value={query.query}\n                      schema={schema}\n                      autocompleteEnabled={autocompleteAvailable && autocompleteEnabled}\n                      onChange={handleQueryEditorChange}\n                      onSelectionChange={setSelectedText}\n                    />\n\n                    <QueryEditor.Controls\n                      addParameterButtonProps={{\n                        title: \"Add New Parameter\",\n                        shortcut: \"mod+p\",\n                        onClick: openAddNewParameterDialog,\n                      }}\n                      formatButtonProps={{\n                        title: isFormatQueryAvailable\n                          ? \"Format Query\"\n                          : \"Query formatting is not supported for your Data Source syntax\",\n                        disabled: !dataSource || !isFormatQueryAvailable,\n                        shortcut: isFormatQueryAvailable ? \"mod+shift+f\" : null,\n                        onClick: formatQuery,\n                      }}\n                      saveButtonProps={\n                        queryFlags.canEdit && {\n                          text: (\n                            <React.Fragment>\n                              <span className=\"hidden-xs\">Save</span>\n                              {isDirty && !isQuerySaving ? \"*\" : null}\n                            </React.Fragment>\n                          ),\n                          shortcut: \"mod+s\",\n                          onClick: doSaveQuery,\n                          loading: isQuerySaving,\n                        }\n                      }\n                      executeButtonProps={{\n                        disabled: !queryFlags.canExecute || isQueryExecuting || areParametersDirty,\n                        shortcut: \"mod+enter, alt+enter, ctrl+enter, shift+enter\",\n                        onClick: doExecuteQuery,\n                        text: (\n                          <span className=\"hidden-xs\">{selectedText === null ? \"Execute\" : \"Execute Selected\"}</span>\n                        ),\n                      }}\n                      autocompleteToggleProps={{\n                        available: autocompleteAvailable,\n                        enabled: autocompleteEnabled,\n                        onToggle: toggleAutocomplete,\n                      }}\n                      autoLimitCheckboxProps={{\n                        available: autoLimitAvailable,\n                        checked: autoLimitChecked,\n                        onChange: setAutoLimit,\n                      }}\n                      dataSourceSelectorProps={\n                        dataSource\n                          ? {\n                              disabled: !queryFlags.canEdit,\n                              value: dataSource.id,\n                              onChange: handleDataSourceChange,\n                              options: map(dataSources, ds => ({ value: ds.id, label: ds.name })),\n                            }\n                          : false\n                      }\n                    />\n                  </section>\n                </div>\n              </Resizable>\n\n              {!queryFlags.isNew && <QueryMetadata layout=\"horizontal\" query={query} onEditSchedule={editSchedule} />}\n\n              <section className=\"query-results-wrapper\">\n                {query.hasParameters() && (\n                  <div className=\"query-parameters-wrapper\">\n                    <Parameters\n                      editable={queryFlags.canEdit}\n                      sortable={queryFlags.canEdit}\n                      disableUrlUpdate={queryFlags.isNew}\n                      parameters={parameters}\n                      onPendingValuesChange={() => updateParametersDirtyFlag()}\n                      onValuesChange={() => {\n                        updateParametersDirtyFlag(false);\n                        doExecuteQuery(true);\n                      }}\n                      onParametersEdit={() => {\n                        // save if query clean\n                        // https://discuss.redash.io/t/query-unsaved-changes-indication/3302/5\n                        if (!isDirty) {\n                          saveQuery();\n                        }\n                      }}\n                    />\n                  </div>\n                )}\n                {(executionError || isQueryExecuting) && (\n                  <div className=\"query-alerts\">\n                    <QueryExecutionStatus\n                      status={executionStatus}\n                      updatedAt={updatedAt}\n                      error={executionError}\n                      isCancelling={isExecutionCancelling}\n                      onCancel={cancelExecution}\n                    />\n                  </div>\n                )}\n\n                <React.Fragment>\n                  {queryResultData.log.length > 0 && (\n                    <div className=\"query-results-log\">\n                      <p>Log Information:</p>\n                      {map(queryResultData.log, (line, index) => (\n                        <p key={`log-line-${index}`} className=\"query-log-line\">\n                          {line}\n                        </p>\n                      ))}\n                    </div>\n                  )}\n                  {loadedInitialResults && !(queryFlags.isNew && !queryResult) && (\n                    <QueryVisualizationTabs\n                      queryResult={queryResult}\n                      visualizations={query.visualizations}\n                      showNewVisualizationButton={queryFlags.canEdit && queryResultData.status === ExecutionStatus.DONE}\n                      canDeleteVisualizations={queryFlags.canEdit}\n                      selectedTab={selectedVisualization}\n                      onChangeTab={setSelectedVisualization}\n                      onAddVisualization={addVisualization}\n                      onDeleteVisualization={deleteVisualization}\n                      refreshButton={\n                        <Button\n                          type=\"primary\"\n                          disabled={!queryFlags.canExecute || areParametersDirty}\n                          loading={isQueryExecuting}\n                          onClick={doExecuteQuery}>\n                          {!isQueryExecuting && <i className=\"zmdi zmdi-refresh m-r-5\" aria-hidden=\"true\" />}\n                          Refresh Now\n                        </Button>\n                      }\n                    />\n                  )}\n                </React.Fragment>\n              </section>\n            </div>\n          </div>\n          {queryResult && !queryResult.getError() && (\n            <div className=\"bottom-controller-container\">\n              <QueryExecutionMetadata\n                query={query}\n                queryResult={queryResult}\n                selectedVisualization={selectedVisualization}\n                isQueryExecuting={isQueryExecuting}\n                showEditVisualizationButton={!queryFlags.isNew && queryFlags.canEdit}\n                onEditVisualization={editVisualization}\n              />\n            </div>\n          )}\n        </div>\n      </main>\n    </div>\n  );\n}\n\nQuerySource.propTypes = {\n  query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n};\n\nconst QuerySourcePage = wrapQueryPage(QuerySource);\n\nroutes.register(\n  \"Queries.New\",\n  routeWithUserSession({\n    path: \"/queries/new\",\n    render: pageProps => <QuerySourcePage {...pageProps} />,\n    bodyClass: \"fixed-layout\",\n  })\n);\nroutes.register(\n  \"Queries.Edit\",\n  routeWithUserSession({\n    path: \"/queries/:queryId/source\",\n    render: pageProps => <QuerySourcePage {...pageProps} />,\n    bodyClass: \"fixed-layout\",\n  })\n);\n"
  },
  {
    "path": "client/app/pages/queries/QuerySource.less",
    "content": ".query-fullscreen {\n  .query-editor-wrapper {\n    padding: 15px;\n    margin-bottom: 10px;\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    flex-wrap: nowrap;\n\n    .query-editor-container {\n      flex: 1 1 auto;\n\n      &[data-executing] {\n        .ace_marker-layer {\n          .ace_selection {\n            background-color: rgb(255, 210, 181);\n          }\n        }\n      }\n    }\n\n    .query-editor-controls {\n      flex: 0 0 auto;\n      margin-top: 10px;\n    }\n  }\n\n  .query-page-query-description {\n    border-top: 1px solid #efefef;\n    padding: 0 15px 0 0;\n\n    .edit-in-place {\n      display: block;\n      max-height: 150px;\n      overflow: auto;\n      padding: 15px 5px 15px 15px;\n\n      &.active {\n        overflow: visible;\n        max-height: unset !important;\n        .ant-input {\n          resize: vertical;\n          height: 30vh;\n        }\n      }\n    }\n  }\n\n  .query-results-wrapper {\n    display: flex;\n    flex-direction: column;\n    margin: 15px 0 15px 0;\n\n    .query-parameters-wrapper {\n      flex: 0 0 auto;\n    }\n\n    .query-alerts {\n      margin: 15px 0;\n      flex: 0 0 auto;\n    }\n\n    .query-results-log {\n      padding: 10px;\n      flex: 0 0 auto;\n    }\n\n    .ant-tabs {\n      flex: 1 1 auto;\n      display: flex;\n      flex-direction: column;\n\n      .ant-tabs-bar {\n        flex: 0 0 auto;\n      }\n\n      .ant-tabs-content-holder {\n        flex: 1 1 auto;\n        position: relative;\n\n        @media (min-width: 880px) {\n          .ant-tabs-tabpane {\n            position: absolute;\n            left: 0;\n            top: 0;\n            right: 0;\n            bottom: 0;\n            overflow: auto;\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/queries/QueryView.jsx",
    "content": "import React, { useState, useEffect, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport useMedia from \"use-media\";\nimport Button from \"antd/lib/button\";\n\nimport FullscreenOutlinedIcon from \"@ant-design/icons/FullscreenOutlined\";\nimport FullscreenExitOutlinedIcon from \"@ant-design/icons/FullscreenExitOutlined\";\n\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport EditInPlace from \"@/components/EditInPlace\";\nimport Parameters from \"@/components/Parameters\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport PlainButton from \"@/components/PlainButton\";\n\nimport DataSource from \"@/services/data-source\";\nimport { ExecutionStatus } from \"@/services/query-result\";\nimport routes from \"@/services/routes\";\nimport { policy } from \"@/services/policy\";\n\nimport useQueryResultData from \"@/lib/useQueryResultData\";\n\nimport QueryPageHeader from \"./components/QueryPageHeader\";\nimport QueryVisualizationTabs from \"./components/QueryVisualizationTabs\";\nimport QueryExecutionStatus from \"./components/QueryExecutionStatus\";\nimport QueryMetadata from \"./components/QueryMetadata\";\nimport wrapQueryPage from \"./components/wrapQueryPage\";\nimport QueryViewButton from \"./components/QueryViewButton\";\nimport QueryExecutionMetadata from \"./components/QueryExecutionMetadata\";\n\nimport useVisualizationTabHandler from \"./hooks/useVisualizationTabHandler\";\nimport useQueryExecute from \"./hooks/useQueryExecute\";\nimport useUpdateQueryDescription from \"./hooks/useUpdateQueryDescription\";\nimport useQueryFlags from \"./hooks/useQueryFlags\";\nimport useQueryParameters from \"./hooks/useQueryParameters\";\nimport useEditScheduleDialog from \"./hooks/useEditScheduleDialog\";\nimport useEditVisualizationDialog from \"./hooks/useEditVisualizationDialog\";\nimport useDeleteVisualization from \"./hooks/useDeleteVisualization\";\nimport useFullscreenHandler from \"../../lib/hooks/useFullscreenHandler\";\n\nimport \"./QueryView.less\";\n\nfunction QueryView(props) {\n  const [query, setQuery] = useState(props.query);\n  const [dataSource, setDataSource] = useState();\n  const queryFlags = useQueryFlags(query, dataSource);\n  const [parameters, areParametersDirty, updateParametersDirtyFlag] = useQueryParameters(query);\n  const [selectedVisualization, setSelectedVisualization] = useVisualizationTabHandler(query.visualizations);\n  const isDesktop = useMedia({ minWidth: 768 });\n  const isFixedLayout = useMedia({ minHeight: 500 }) && isDesktop;\n  const [fullscreen, toggleFullscreen] = useFullscreenHandler(isDesktop);\n  const [addingDescription, setAddingDescription] = useState(false);\n\n  const {\n    queryResult,\n    loadedInitialResults,\n    isExecuting,\n    executionStatus,\n    executeQuery,\n    error: executionError,\n    cancelCallback: cancelExecution,\n    isCancelling: isExecutionCancelling,\n    updatedAt,\n  } = useQueryExecute(query);\n\n  const queryResultData = useQueryResultData(queryResult);\n\n  const updateQueryDescription = useUpdateQueryDescription(query, setQuery);\n  const editSchedule = useEditScheduleDialog(query, setQuery);\n  const addVisualization = useEditVisualizationDialog(query, queryResult, (newQuery, visualization) => {\n    setQuery(newQuery);\n    setSelectedVisualization(visualization.id);\n  });\n  const editVisualization = useEditVisualizationDialog(query, queryResult, newQuery => setQuery(newQuery));\n  const deleteVisualization = useDeleteVisualization(query, setQuery);\n\n  const doExecuteQuery = useCallback(\n    (skipParametersDirtyFlag = false) => {\n      if (!queryFlags.canExecute || (!skipParametersDirtyFlag && (areParametersDirty || isExecuting))) {\n        return;\n      }\n      executeQuery();\n    },\n    [areParametersDirty, executeQuery, isExecuting, queryFlags.canExecute]\n  );\n\n  useEffect(() => {\n    document.title = query.name;\n  }, [query.name]);\n\n  useEffect(() => {\n    DataSource.get({ id: query.data_source_id }).then(setDataSource);\n  }, [query.data_source_id]);\n\n  return (\n    <div\n      className={cx(\"query-page-wrapper\", {\n        \"query-view-fullscreen\": fullscreen,\n        \"query-fixed-layout\": isFixedLayout,\n      })}>\n      <div className=\"container w-100\">\n        <QueryPageHeader\n          query={query}\n          dataSource={dataSource}\n          onChange={setQuery}\n          selectedVisualization={selectedVisualization}\n          headerExtra={\n            <DynamicComponent name=\"QueryView.HeaderExtra\" query={query}>\n              {policy.canRun(query) && (\n                <QueryViewButton\n                  className=\"m-r-5\"\n                  type=\"primary\"\n                  shortcut=\"mod+enter, alt+enter, ctrl+enter\"\n                  disabled={!queryFlags.canExecute || isExecuting || areParametersDirty}\n                  onClick={doExecuteQuery}>\n                  Refresh\n                </QueryViewButton>\n              )}\n            </DynamicComponent>\n          }\n          tagsExtra={\n            !query.description &&\n            queryFlags.canEdit &&\n            !addingDescription &&\n            !fullscreen && (\n              <PlainButton className=\"label label-tag hidden-xs\" role=\"none\" onClick={() => setAddingDescription(true)}>\n                <i className=\"zmdi zmdi-plus m-r-5\" aria-hidden=\"true\" />\n                Add description\n              </PlainButton>\n            )\n          }\n        />\n        {(query.description || addingDescription) && (\n          <div className={cx(\"m-t-5\", { hidden: fullscreen })}>\n            <EditInPlace\n              className=\"w-100\"\n              value={query.description}\n              isEditable={queryFlags.canEdit}\n              onDone={updateQueryDescription}\n              onStopEditing={() => setAddingDescription(false)}\n              placeholder=\"Add description\"\n              ignoreBlanks={false}\n              editorProps={{ autoSize: { minRows: 2, maxRows: 4 } }}\n              defaultEditing={addingDescription}\n              multiline\n            />\n          </div>\n        )}\n      </div>\n      <div className=\"query-view-content\">\n        {query.hasParameters() && (\n          <div className={cx(\"bg-white tiled p-15 m-t-15 m-l-15 m-r-15\", { hidden: fullscreen })}>\n            <Parameters\n              parameters={parameters}\n              onValuesChange={() => {\n                updateParametersDirtyFlag(false);\n                doExecuteQuery(true);\n              }}\n              onPendingValuesChange={() => updateParametersDirtyFlag()}\n            />\n          </div>\n        )}\n        <div className=\"query-results m-t-15\">\n          {loadedInitialResults && (\n            <QueryVisualizationTabs\n              queryResult={queryResult}\n              visualizations={query.visualizations}\n              showNewVisualizationButton={queryFlags.canEdit && queryResultData.status === ExecutionStatus.DONE}\n              canDeleteVisualizations={queryFlags.canEdit}\n              selectedTab={selectedVisualization}\n              onChangeTab={setSelectedVisualization}\n              onAddVisualization={addVisualization}\n              onDeleteVisualization={deleteVisualization}\n              refreshButton={\n                policy.canRun(query) && (\n                  <Button\n                    type=\"primary\"\n                    disabled={!queryFlags.canExecute || areParametersDirty}\n                    loading={isExecuting}\n                    onClick={doExecuteQuery}>\n                    {!isExecuting && <i className=\"zmdi zmdi-refresh m-r-5\" aria-hidden=\"true\" />}\n                    Refresh Now\n                  </Button>\n                )\n              }\n              canRefresh={policy.canRun(query)}\n            />\n          )}\n          <div className=\"query-results-footer\">\n            {queryResult && !queryResult.getError() && (\n              <QueryExecutionMetadata\n                query={query}\n                queryResult={queryResult}\n                selectedVisualization={selectedVisualization}\n                isQueryExecuting={isExecuting}\n                showEditVisualizationButton={queryFlags.canEdit}\n                onEditVisualization={editVisualization}\n                extraActions={\n                  <QueryViewButton\n                    className=\"icon-button m-r-5 hidden-xs\"\n                    title=\"Toggle Fullscreen\"\n                    type=\"default\"\n                    shortcut=\"alt+f\"\n                    onClick={toggleFullscreen}>\n                    {fullscreen ? <FullscreenExitOutlinedIcon /> : <FullscreenOutlinedIcon />}\n                  </QueryViewButton>\n                }\n              />\n            )}\n            {(executionError || isExecuting) && (\n              <div className=\"query-execution-status\">\n                <QueryExecutionStatus\n                  status={executionStatus}\n                  error={executionError}\n                  isCancelling={isExecutionCancelling}\n                  onCancel={cancelExecution}\n                  updatedAt={updatedAt}\n                />\n              </div>\n            )}\n          </div>\n        </div>\n        <div className={cx(\"p-t-15 p-r-15 p-l-15\", { hidden: fullscreen })}>\n          <QueryMetadata layout=\"horizontal\" query={query} dataSource={dataSource} onEditSchedule={editSchedule} />\n        </div>\n      </div>\n    </div>\n  );\n}\n\nQueryView.propTypes = { query: PropTypes.object.isRequired }; // eslint-disable-line react/forbid-prop-types\n\nconst QueryViewPage = wrapQueryPage(QueryView);\n\nroutes.register(\n  \"Queries.View\",\n  routeWithUserSession({\n    path: \"/queries/:queryId\",\n    render: pageProps => <QueryViewPage {...pageProps} />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/queries/QueryView.less",
    "content": "page-query-view {\n  display: flex;\n  flex-grow: 1;\n}\n\n.query-page-wrapper {\n  width: 100%;\n\n  .query-view-content {\n    .query-results {\n      margin: 0px 15px 0px;\n\n      .query-results-footer {\n        position: relative;\n\n        .query-execution-status {\n          position: fixed;\n          min-width: 250px;\n          bottom: 0;\n          right: 0;\n          padding: 15px;\n          z-index: 1000;\n\n          @media (min-width: 768px) {\n            display: flex;\n            min-height: 100%;\n            position: absolute;\n            padding: 3px;\n\n            .ant-alert {\n              flex: 1 1;\n            }\n          }\n        }\n      }\n\n      .query-execution-metadata {\n        border: 1px solid #d9d9d9;\n        border-top-width: 0px;\n        box-sizing: border-box;\n        border-radius: 0px 0px 4px 4px;\n        background: white;\n      }\n\n      .query-parameters-wrapper {\n        margin: 15px 0 5px 0;\n      }\n\n      .query-alerts {\n        margin: 15px 0;\n      }\n\n      .query-results-log {\n        padding: 10px;\n      }\n    }\n  }\n\n  &.query-fixed-layout .query-view-content {\n    display: flex;\n    flex-direction: column;\n    flex-grow: 1;\n\n    .query-results {\n      display: flex;\n      flex-direction: column;\n      flex-grow: 1;\n      overflow: auto;\n    }\n\n    .query-parameters-wrapper {\n      flex: 0 0 auto;\n    }\n\n    .query-alerts {\n      flex: 0 0 auto;\n    }\n\n    .query-results-log {\n      flex: 0 0 auto;\n    }\n\n    .ant-tabs {\n      flex: 1 1 auto;\n      display: flex;\n      flex-direction: column;\n\n      .ant-tabs-bar {\n        flex: 0 0 auto;\n      }\n\n      .ant-tabs-content-holder {\n        flex: 1 1 auto;\n        position: relative;\n\n        .ant-tabs-tabpane {\n          position: absolute;\n          left: 0;\n          top: 0;\n          right: 0;\n          bottom: 0;\n          overflow: auto;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/queries/VisualizationEmbed.jsx",
    "content": "import { find, has } from \"lodash\";\nimport React, { useState, useEffect, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport moment from \"moment\";\nimport { markdown } from \"markdown\";\n\nimport Button from \"antd/lib/button\";\nimport Dropdown from \"antd/lib/dropdown\";\nimport Menu from \"antd/lib/menu\";\nimport Tooltip from \"@/components/Tooltip\";\nimport Link from \"@/components/Link\";\nimport routeWithApiKeySession from \"@/components/ApplicationArea/routeWithApiKeySession\";\nimport Parameters from \"@/components/Parameters\";\nimport { Moment } from \"@/components/proptypes\";\nimport TimeAgo from \"@/components/TimeAgo\";\nimport Timer from \"@/components/Timer\";\nimport QueryResultsLink from \"@/components/EditVisualizationButton/QueryResultsLink\";\nimport VisualizationName from \"@/components/visualizations/VisualizationName\";\nimport VisualizationRenderer from \"@/components/visualizations/VisualizationRenderer\";\n\nimport FileOutlinedIcon from \"@ant-design/icons/FileOutlined\";\nimport FileExcelOutlinedIcon from \"@ant-design/icons/FileExcelOutlined\";\n\nimport { VisualizationType } from \"@redash/viz/lib\";\nimport HtmlContent from \"@redash/viz/lib/components/HtmlContent\";\n\nimport { formatDateTime } from \"@/lib/utils\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\nimport { Query } from \"@/services/query\";\nimport location from \"@/services/location\";\nimport routes from \"@/services/routes\";\n\nimport logoUrl from \"@/assets/images/redash_icon_small.png\";\n\nfunction VisualizationEmbedHeader({ queryName, queryDescription, visualization }) {\n  return (\n    <div className=\"embed-heading p-b-10 p-r-15 p-l-15\">\n      <h3>\n        <img src={logoUrl} alt=\"Redash Logo\" style={{ height: \"24px\", verticalAlign: \"text-bottom\" }} />\n        <VisualizationName visualization={visualization} /> {queryName}\n        {queryDescription && (\n          <small>\n            <HtmlContent className=\"markdown text-muted\">{markdown.toHTML(queryDescription || \"\")}</HtmlContent>\n          </small>\n        )}\n      </h3>\n    </div>\n  );\n}\n\nVisualizationEmbedHeader.propTypes = {\n  queryName: PropTypes.string.isRequired,\n  queryDescription: PropTypes.string,\n  visualization: VisualizationType.isRequired,\n};\n\nVisualizationEmbedHeader.defaultProps = { queryDescription: \"\" };\n\nfunction VisualizationEmbedFooter({\n  query,\n  queryResults,\n  updatedAt,\n  refreshStartedAt,\n  queryUrl,\n  hideTimestamp,\n  apiKey,\n}) {\n  const downloadMenu = (\n    <Menu>\n      <Menu.Item>\n        <QueryResultsLink\n          fileType=\"csv\"\n          query={query}\n          queryResult={queryResults}\n          apiKey={apiKey}\n          disabled={!queryResults || !queryResults.getData || !queryResults.getData()}\n          embed>\n          <FileOutlinedIcon /> Download as CSV File\n        </QueryResultsLink>\n      </Menu.Item>\n      <Menu.Item>\n        <QueryResultsLink\n          fileType=\"tsv\"\n          query={query}\n          queryResult={queryResults}\n          apiKey={apiKey}\n          disabled={!queryResults || !queryResults.getData || !queryResults.getData()}\n          embed>\n          <FileOutlinedIcon /> Download as TSV File\n        </QueryResultsLink>\n      </Menu.Item>\n      <Menu.Item>\n        <QueryResultsLink\n          fileType=\"xlsx\"\n          query={query}\n          queryResult={queryResults}\n          apiKey={apiKey}\n          disabled={!queryResults || !queryResults.getData || !queryResults.getData()}\n          embed>\n          <FileExcelOutlinedIcon /> Download as Excel File\n        </QueryResultsLink>\n      </Menu.Item>\n    </Menu>\n  );\n\n  return (\n    <div className=\"tile__bottom-control\">\n      {!hideTimestamp && (\n        <span>\n          <span className=\"small hidden-print\">\n            <i className=\"zmdi zmdi-time-restore\" aria-hidden=\"true\" />{\" \"}\n            {refreshStartedAt ? <Timer from={refreshStartedAt} /> : <TimeAgo date={updatedAt} />}\n          </span>\n          <span className=\"small visible-print\">\n            <i className=\"zmdi zmdi-time-restore\" aria-hidden=\"true\" /> {formatDateTime(updatedAt)}\n          </span>\n        </span>\n      )}\n      {queryUrl && (\n        <span className=\"hidden-print\">\n          <Tooltip title=\"Open in Redash\">\n            <Link.Button className=\"icon-button\" href={queryUrl} target=\"_blank\">\n              <i className=\"fa fa-external-link\" aria-hidden=\"true\" />\n              <span className=\"sr-only\">Open in Redash</span>\n            </Link.Button>\n          </Tooltip>\n          {!query.hasParameters() && (\n            <Dropdown overlay={downloadMenu} disabled={!queryResults} trigger={[\"click\"]} placement=\"topLeft\">\n              <Button loading={!queryResults && !!refreshStartedAt} className=\"m-l-5\">\n                Download Dataset\n                <i className=\"fa fa-caret-up m-l-5\" aria-hidden=\"true\" />\n              </Button>\n            </Dropdown>\n          )}\n        </span>\n      )}\n    </div>\n  );\n}\n\nVisualizationEmbedFooter.propTypes = {\n  query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  queryResults: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  updatedAt: PropTypes.string,\n  refreshStartedAt: Moment,\n  queryUrl: PropTypes.string,\n  hideTimestamp: PropTypes.bool,\n  apiKey: PropTypes.string,\n};\n\nVisualizationEmbedFooter.defaultProps = {\n  queryResults: null,\n  updatedAt: null,\n  refreshStartedAt: null,\n  queryUrl: null,\n  hideTimestamp: false,\n  apiKey: null,\n};\n\nfunction VisualizationEmbed({ queryId, visualizationId, apiKey, onError }) {\n  const [query, setQuery] = useState(null);\n  const [error, setError] = useState(null);\n  const [refreshStartedAt, setRefreshStartedAt] = useState(null);\n  const [queryResults, setQueryResults] = useState(null);\n\n  const handleError = useImmutableCallback(onError);\n\n  useEffect(() => {\n    let isCancelled = false;\n    Query.get({ id: queryId })\n      .then(result => {\n        if (!isCancelled) {\n          setQuery(result);\n        }\n      })\n      .catch(handleError);\n\n    return () => {\n      isCancelled = true;\n    };\n  }, [queryId, handleError]);\n\n  const refreshQueryResults = useCallback(() => {\n    if (query) {\n      setError(null);\n      setRefreshStartedAt(moment());\n      query\n        .getQueryResultPromise()\n        .then(result => {\n          setQueryResults(result);\n        })\n        .catch(err => {\n          setError(err.getError());\n        })\n        .finally(() => setRefreshStartedAt(null));\n    }\n  }, [query]);\n\n  useEffect(() => {\n    document.querySelector(\"body\").classList.add(\"headless\");\n    refreshQueryResults();\n  }, [refreshQueryResults]);\n\n  if (!query) {\n    return null;\n  }\n\n  const hideHeader = has(location.search, \"hide_header\");\n  const hideParametersUI = has(location.search, \"hide_parameters\");\n  const hideQueryLink = has(location.search, \"hide_link\");\n  const hideTimestamp = has(location.search, \"hide_timestamp\");\n\n  const showQueryDescription = has(location.search, \"showDescription\");\n  visualizationId = parseInt(visualizationId, 10);\n  const visualization = find(query.visualizations, vis => vis.id === visualizationId);\n\n  if (!visualization) {\n    // call error handler async, otherwise it will destroy the component on render phase\n    setTimeout(() => {\n      onError(new Error(\"Visualization does not exist\"));\n    }, 10);\n    return null;\n  }\n\n  return (\n    <div className=\"tile m-t-10 m-l-10 m-r-10 p-t-10 embed__vis\" data-test=\"VisualizationEmbed\">\n      {!hideHeader && (\n        <VisualizationEmbedHeader\n          queryName={query.name}\n          queryDescription={showQueryDescription ? query.description : null}\n          visualization={visualization}\n        />\n      )}\n      <div className=\"col-md-12 query__vis\">\n        {!hideParametersUI && query.hasParameters() && (\n          <div className=\"p-t-15 p-b-10\">\n            <Parameters parameters={query.getParametersDefs()} onValuesChange={refreshQueryResults} />\n          </div>\n        )}\n        {error && <div className=\"alert alert-danger\" data-test=\"ErrorMessage\">{`Error: ${error}`}</div>}\n        {!error && queryResults && (\n          <VisualizationRenderer visualization={visualization} queryResult={queryResults} context=\"widget\" />\n        )}\n        {!queryResults && refreshStartedAt && (\n          <div className=\"d-flex justify-content-center\">\n            <div className=\"spinner\">\n              <i className=\"zmdi zmdi-refresh zmdi-hc-spin zmdi-hc-5x\" aria-hidden=\"true\" />\n              <span className=\"sr-only\">Refreshing...</span>\n            </div>\n          </div>\n        )}\n      </div>\n      <VisualizationEmbedFooter\n        query={query}\n        queryResults={queryResults}\n        updatedAt={queryResults ? queryResults.getUpdatedAt() : undefined}\n        refreshStartedAt={refreshStartedAt}\n        queryUrl={!hideQueryLink ? query.getUrl() : null}\n        hideTimestamp={hideTimestamp}\n        apiKey={apiKey}\n      />\n    </div>\n  );\n}\n\nVisualizationEmbed.propTypes = {\n  queryId: PropTypes.string.isRequired,\n  visualizationId: PropTypes.string,\n  apiKey: PropTypes.string.isRequired,\n  onError: PropTypes.func,\n};\n\nVisualizationEmbed.defaultProps = {\n  onError: () => {},\n};\n\nroutes.register(\n  \"Visualizations.ViewShared\",\n  routeWithApiKeySession({\n    path: \"/embed/query/:queryId/visualization/:visualizationId\",\n    render: pageProps => <VisualizationEmbed {...pageProps} />,\n    getApiKey: () => location.search.api_key,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/queries/components/QueryExecutionMetadata.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport WarningTwoTone from \"@ant-design/icons/WarningTwoTone\";\nimport TimeAgo from \"@/components/TimeAgo\";\nimport Tooltip from \"@/components/Tooltip\";\nimport useAddToDashboardDialog from \"../hooks/useAddToDashboardDialog\";\nimport useEmbedDialog from \"../hooks/useEmbedDialog\";\nimport QueryControlDropdown from \"@/components/EditVisualizationButton/QueryControlDropdown\";\nimport EditVisualizationButton from \"@/components/EditVisualizationButton\";\nimport useQueryResultData from \"@/lib/useQueryResultData\";\nimport { durationHumanize, pluralize, prettySize } from \"@/lib/utils\";\nimport { isUndefined } from \"lodash\";\n\nimport \"./QueryExecutionMetadata.less\";\n\nexport default function QueryExecutionMetadata({\n  query,\n  queryResult,\n  isQueryExecuting,\n  selectedVisualization,\n  showEditVisualizationButton,\n  onEditVisualization,\n  extraActions,\n}) {\n  const queryResultData = useQueryResultData(queryResult);\n  const openAddToDashboardDialog = useAddToDashboardDialog(query);\n  const openEmbedDialog = useEmbedDialog(query);\n  return (\n    <div className=\"query-execution-metadata\">\n      <span className=\"m-r-5\">\n        <QueryControlDropdown\n          query={query}\n          queryResult={queryResult}\n          queryExecuting={isQueryExecuting}\n          showEmbedDialog={openEmbedDialog}\n          embed={false}\n          apiKey={query.api_key}\n          selectedTab={selectedVisualization}\n          openAddToDashboardForm={openAddToDashboardDialog}\n        />\n      </span>\n      {extraActions}\n      {showEditVisualizationButton && (\n        <EditVisualizationButton openVisualizationEditor={onEditVisualization} selectedTab={selectedVisualization} />\n      )}\n      <span className=\"m-l-5 m-r-10\">\n        <span>\n          {queryResultData.truncated === true && (\n            <span className=\"m-r-5\">\n              <Tooltip\n                title={\n                  \"Result truncated to \" +\n                  queryResultData.rows.length +\n                  \" rows. Databricks may truncate query results that are unstably large.\"\n                }\n              >\n                <WarningTwoTone twoToneColor=\"#FF9800\" />\n              </Tooltip>\n            </span>\n          )}\n          <strong>{queryResultData.rows.length}</strong> {pluralize(\"row\", queryResultData.rows.length)}\n        </span>\n        <span className=\"m-l-5\">\n          {!isQueryExecuting && (\n            <React.Fragment>\n              <strong>{durationHumanize(queryResultData.runtime)}</strong>\n              <span className=\"hidden-xs\"> runtime</span>\n            </React.Fragment>\n          )}\n          {isQueryExecuting && <span>Running&hellip;</span>}\n        </span>\n        {!isUndefined(queryResultData.metadata.data_scanned) && !isQueryExecuting && (\n          <span className=\"m-l-5\">\n            Data Scanned <strong>{prettySize(queryResultData.metadata.data_scanned)}</strong>\n          </span>\n        )}\n      </span>\n      <div>\n        <span className=\"m-r-10\">\n          <span className=\"hidden-xs\">Refreshed </span>\n          <strong>\n            <TimeAgo date={queryResultData.retrievedAt} placeholder=\"-\" />\n          </strong>\n        </span>\n      </div>\n    </div>\n  );\n}\n\nQueryExecutionMetadata.propTypes = {\n  query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  queryResult: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types\n  isQueryExecuting: PropTypes.bool,\n  selectedVisualization: PropTypes.number,\n  showEditVisualizationButton: PropTypes.bool,\n  onEditVisualization: PropTypes.func,\n  extraActions: PropTypes.node,\n};\n\nQueryExecutionMetadata.defaultProps = {\n  isQueryExecuting: false,\n  selectedVisualization: null,\n  showEditVisualizationButton: false,\n  onEditVisualization: () => {},\n  extraActions: null,\n};\n"
  },
  {
    "path": "client/app/pages/queries/components/QueryExecutionMetadata.less",
    "content": ".query-execution-metadata {\n  padding: 10px 15px;\n  background: #fff;\n  display: flex;\n  align-items: center;\n\n  button,\n  div,\n  span {\n    position: relative;\n  }\n\n  div:last-child {\n    flex-grow: 1;\n    text-align: right;\n  }\n\n  &:before {\n    content: \"\";\n    height: 50px;\n    position: fixed;\n    bottom: 0;\n    width: 100%;\n    pointer-events: none;\n    left: 0;\n  }\n}\n"
  },
  {
    "path": "client/app/pages/queries/components/QueryExecutionStatus.jsx",
    "content": "import { includes } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Alert from \"antd/lib/alert\";\nimport Button from \"antd/lib/button\";\nimport Timer from \"@/components/Timer\";\n\nexport default function QueryExecutionStatus({ status, updatedAt, error, isCancelling, onCancel }) {\n  const alertType = status === \"failed\" ? \"error\" : \"info\";\n  const showTimer = status !== \"failed\" && updatedAt;\n  const isCancelButtonAvailable = includes([\"waiting\", \"processing\"], status);\n  let message = isCancelling ? <React.Fragment>Cancelling&hellip;</React.Fragment> : null;\n\n  switch (status) {\n    case \"waiting\":\n      if (!isCancelling) {\n        message = <React.Fragment>Query in queue&hellip;</React.Fragment>;\n      }\n      break;\n    case \"processing\":\n      if (!isCancelling) {\n        message = <React.Fragment>Executing query&hellip;</React.Fragment>;\n      }\n      break;\n    case \"loading-result\":\n      message = <React.Fragment>Loading results&hellip;</React.Fragment>;\n      break;\n    case \"failed\":\n      message = (\n        <React.Fragment>\n          Error running query: <strong>{error}</strong>\n        </React.Fragment>\n      );\n      break;\n    // no default\n  }\n\n  return (\n    <Alert\n      data-test=\"QueryExecutionStatus\"\n      type={alertType}\n      message={\n        <div className=\"d-flex align-items-center\">\n          <div className=\"flex-fill p-t-5 p-b-5\">\n            {message} {showTimer && <Timer from={updatedAt} />}\n          </div>\n          <div>\n            {isCancelButtonAvailable && (\n              <Button className=\"m-l-10\" type=\"primary\" size=\"small\" disabled={isCancelling} onClick={onCancel}>\n                Cancel\n              </Button>\n            )}\n          </div>\n        </div>\n      }\n    />\n  );\n}\n\nQueryExecutionStatus.propTypes = {\n  status: PropTypes.string,\n  updatedAt: PropTypes.any,\n  error: PropTypes.string,\n  isCancelling: PropTypes.bool,\n  onCancel: PropTypes.func,\n};\n\nQueryExecutionStatus.defaultProps = {\n  status: \"waiting\",\n  updatedAt: null,\n  error: null,\n  isCancelling: true,\n  onCancel: () => {},\n};\n"
  },
  {
    "path": "client/app/pages/queries/components/QueryMetadata.jsx",
    "content": "import { isFunction, has } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport { Moment } from \"@/components/proptypes\";\nimport TimeAgo from \"@/components/TimeAgo\";\nimport SchedulePhrase from \"@/components/queries/SchedulePhrase\";\nimport { IMG_ROOT } from \"@/services/data-source\";\n\nimport \"./QueryMetadata.less\";\n\nexport default function QueryMetadata({ query, dataSource, layout, onEditSchedule }) {\n  return (\n    <div className={`query-metadata query-metadata-${layout}`}>\n      <div className=\"query-metadata-item\">\n        <img className=\"profile__image_thumb\" src={query.user.profile_image_url} alt=\"Avatar\" />\n        <div className=\"query-metadata-property\">\n          <strong className={cx(\"query-metadata-label\", { \"text-muted\": query.user.is_disabled })}>\n            {query.user.name}\n          </strong>\n          <span className=\"query-metadata-value\">\n            created{\" \"}\n            <strong>\n              <TimeAgo date={query.created_at} />\n            </strong>\n          </span>\n        </div>\n      </div>\n      <div className=\"query-metadata-item\">\n        <img className=\"profile__image_thumb\" src={query.last_modified_by.profile_image_url} alt=\"Avatar\" />\n        <div className=\"query-metadata-property\">\n          <strong className={cx(\"query-metadata-label\", { \"text-muted\": query.last_modified_by.is_disabled })}>\n            {query.last_modified_by.name}\n          </strong>\n          <span className=\"query-metadata-value\">\n            updated{\" \"}\n            <strong>\n              <TimeAgo date={query.updated_at} />\n            </strong>\n          </span>\n        </div>\n      </div>\n      <div className=\"query-metadata-space\" />\n      {has(dataSource, \"name\") && has(dataSource, \"type\") && (\n        <div className=\"query-metadata-item\">\n          Data Source:\n          <img src={`${IMG_ROOT}/${dataSource.type}.png`} width=\"20\" alt={dataSource.type} />\n          <div className=\"query-metadata-property\">\n            <div className=\"query-metadata-label\">{dataSource.name}</div>\n          </div>\n        </div>\n      )}\n      <div className=\"query-metadata-item\">\n        <div className=\"query-metadata-property\">\n          <span className=\"query-metadata-label\">\n            <span className=\"zmdi zmdi-refresh m-r-5\" />\n            Refresh Schedule\n          </span>\n          <span className=\"query-metadata-value\">\n            <SchedulePhrase\n              isLink={isFunction(onEditSchedule)}\n              isNew={query.isNew()}\n              schedule={query.schedule}\n              onClick={onEditSchedule}\n            />\n          </span>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nQueryMetadata.propTypes = {\n  layout: PropTypes.oneOf([\"table\", \"horizontal\"]),\n  query: PropTypes.shape({\n    created_at: PropTypes.oneOfType([PropTypes.string, Moment]).isRequired,\n    updated_at: PropTypes.oneOfType([PropTypes.string, Moment]).isRequired,\n    user: PropTypes.shape({\n      name: PropTypes.string.isRequired,\n      profile_image_url: PropTypes.string.isRequired,\n      is_disabled: PropTypes.bool,\n    }).isRequired,\n    last_modified_by: PropTypes.shape({\n      name: PropTypes.string.isRequired,\n      profile_image_url: PropTypes.string.isRequired,\n      is_disabled: PropTypes.bool,\n    }).isRequired,\n    schedule: PropTypes.object,\n  }).isRequired,\n  dataSource: PropTypes.shape({\n    type: PropTypes.string,\n    name: PropTypes.string,\n  }),\n  onEditSchedule: PropTypes.func,\n};\n\nQueryMetadata.defaultProps = {\n  layout: \"table\",\n  dataSource: null,\n  onEditSchedule: null,\n};\n"
  },
  {
    "path": "client/app/pages/queries/components/QueryMetadata.less",
    "content": ".query-metadata {\n  .query-metadata-item {\n    display: flex;\n    flex-wrap: nowrap;\n    align-items: center;\n    margin: 0;\n\n    img {\n      margin: 0 5px 0 0;\n    }\n\n    .query-metadata-property {\n      flex: 1 1 auto;\n\n      .query-metadata-label {\n        margin: 0 5px 0 0;\n        &:only-child {\n          margin-right: 0;\n        }\n      }\n\n      .query-metadata-value {\n        margin: 0;\n      }\n    }\n  }\n\n  .query-metadata-space {\n    display: none;\n  }\n\n  &.query-metadata-table {\n    padding: 15px;\n    border-top: 1px solid #efefef;\n\n    .query-metadata-item {\n      margin-bottom: 8px;\n\n      &:last-child {\n        margin-top: 20px;\n        margin-bottom: 0;\n      }\n\n      .query-metadata-property {\n        display: flex;\n        flex-wrap: wrap;\n        justify-content: space-between;\n      }\n    }\n  }\n\n  &.query-metadata-horizontal {\n    padding: 5px 0;\n    margin: 0 -5px;\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    justify-content: space-between;\n\n    @media (max-width: 500px) {\n      & {\n        flex-direction: column;\n        justify-content: stretch;\n      }\n    }\n\n    @media (min-width: 1000px) {\n      justify-content: flex-start;\n\n      .query-metadata-space {\n        display: block;\n        flex: 1 1 auto;\n        text-align: right;\n      }\n    }\n\n    .query-metadata-item {\n      padding: 5px;\n\n      &:last-child {\n        .query-metadata-property {\n          .query-metadata-label {\n            white-space: nowrap;\n            &:after {\n              content: \":\";\n            }\n          }\n        }\n      }\n\n      .query-metadata-property {\n        .query-metadata-label {\n          .zmdi {\n            display: none;\n          }\n        }\n\n        .query-metadata-value {\n          strong {\n            font-weight: normal;\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/queries/components/QueryPageHeader.jsx",
    "content": "import { extend, map, filter, reduce } from \"lodash\";\nimport React, { useMemo } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport Dropdown from \"antd/lib/dropdown\";\nimport Menu from \"antd/lib/menu\";\nimport EllipsisOutlinedIcon from \"@ant-design/icons/EllipsisOutlined\";\nimport useMedia from \"use-media\";\nimport Link from \"@/components/Link\";\nimport EditInPlace from \"@/components/EditInPlace\";\nimport FavoritesControl from \"@/components/FavoritesControl\";\nimport { QueryTagsControl } from \"@/components/tags-control/TagsControl\";\nimport getTags from \"@/services/getTags\";\nimport { clientConfig } from \"@/services/auth\";\nimport useQueryFlags from \"../hooks/useQueryFlags\";\nimport useArchiveQuery from \"../hooks/useArchiveQuery\";\nimport usePublishQuery from \"../hooks/usePublishQuery\";\nimport useUnpublishQuery from \"../hooks/useUnpublishQuery\";\nimport useUpdateQueryTags from \"../hooks/useUpdateQueryTags\";\nimport useRenameQuery from \"../hooks/useRenameQuery\";\nimport useDuplicateQuery from \"../hooks/useDuplicateQuery\";\nimport useApiKeyDialog from \"../hooks/useApiKeyDialog\";\nimport usePermissionsEditorDialog from \"../hooks/usePermissionsEditorDialog\";\n\nimport \"./QueryPageHeader.less\";\n\nfunction getQueryTags() {\n  return getTags(\"api/queries/tags\").then(tags => map(tags, t => t.name));\n}\n\nfunction createMenu(menu) {\n  const handlers = {};\n\n  const groups = map(menu, group =>\n    filter(\n      map(group, (props, key) => {\n        props = extend({ isAvailable: true, isEnabled: true, onClick: () => {} }, props);\n        if (props.isAvailable) {\n          handlers[key] = props.onClick;\n          return (\n            <Menu.Item key={key} disabled={!props.isEnabled}>\n              {props.title}\n            </Menu.Item>\n          );\n        }\n        return null;\n      })\n    )\n  );\n\n  return (\n    <Menu onClick={({ key }) => handlers[key]()}>\n      {reduce(\n        filter(groups, group => group.length > 0),\n        (result, items, key) => {\n          const divider = result.length > 0 ? <Menu.Divider key={`divider${key}`} /> : null;\n          return [...result, divider, ...items];\n        },\n        []\n      )}\n    </Menu>\n  );\n}\n\nexport default function QueryPageHeader({\n  query,\n  dataSource,\n  sourceMode,\n  selectedVisualization,\n  headerExtra,\n  tagsExtra,\n  onChange,\n}) {\n  const isDesktop = useMedia({ minWidth: 768 });\n  const queryFlags = useQueryFlags(query, dataSource);\n  const updateName = useRenameQuery(query, onChange);\n  const updateTags = useUpdateQueryTags(query, onChange);\n  const archiveQuery = useArchiveQuery(query, onChange);\n  const publishQuery = usePublishQuery(query, onChange);\n  const unpublishQuery = useUnpublishQuery(query, onChange);\n  const [isDuplicating, duplicateQuery] = useDuplicateQuery(query);\n  const openApiKeyDialog = useApiKeyDialog(query, onChange);\n  const openPermissionsEditorDialog = usePermissionsEditorDialog(query);\n\n  const moreActionsMenu = useMemo(\n    () =>\n      createMenu([\n        {\n          fork: {\n            isEnabled: !queryFlags.isNew && queryFlags.canFork && !isDuplicating,\n            title: (\n              <React.Fragment>\n                Fork <i className=\"fa fa-external-link m-l-5\" aria-hidden=\"true\" />\n                <span className=\"sr-only\">(opens in a new tab)</span>\n              </React.Fragment>\n            ),\n            onClick: duplicateQuery,\n          },\n        },\n        {\n          archive: {\n            isAvailable: !queryFlags.isNew && queryFlags.canEdit && !queryFlags.isArchived,\n            title: \"Archive\",\n            onClick: archiveQuery,\n          },\n          managePermissions: {\n            isAvailable:\n              !queryFlags.isNew && queryFlags.canEdit && !queryFlags.isArchived && clientConfig.showPermissionsControl,\n            title: \"Manage Permissions\",\n            onClick: openPermissionsEditorDialog,\n          },\n          publish: {\n            isAvailable:\n              !isDesktop && queryFlags.isDraft && !queryFlags.isArchived && !queryFlags.isNew && queryFlags.canEdit,\n            title: \"Publish\",\n            onClick: publishQuery,\n          },\n          unpublish: {\n            isAvailable: !clientConfig.disablePublish && !queryFlags.isNew && queryFlags.canEdit && !queryFlags.isDraft,\n            title: \"Unpublish\",\n            onClick: unpublishQuery,\n          },\n        },\n        {\n          showAPIKey: {\n            isAvailable: !clientConfig.disablePublicUrls && !queryFlags.isNew,\n            title: \"Show API Key\",\n            onClick: openApiKeyDialog,\n          },\n        },\n      ]),\n    [\n      queryFlags.isNew,\n      queryFlags.canFork,\n      queryFlags.canEdit,\n      queryFlags.isArchived,\n      queryFlags.isDraft,\n      isDuplicating,\n      duplicateQuery,\n      archiveQuery,\n      openPermissionsEditorDialog,\n      isDesktop,\n      publishQuery,\n      unpublishQuery,\n      openApiKeyDialog,\n    ]\n  );\n\n  return (\n    <div className=\"query-page-header\">\n      <div className=\"title-with-tags\">\n        <div className=\"page-title\">\n          <div className=\"d-flex align-items-center\">\n            {!queryFlags.isNew && <FavoritesControl item={query} />}\n            <h3>\n              <EditInPlace isEditable={queryFlags.canEdit} onDone={updateName} ignoreBlanks value={query.name} />\n            </h3>\n          </div>\n        </div>\n        <div className=\"query-tags\">\n          <QueryTagsControl\n            tags={query.tags}\n            isDraft={queryFlags.isDraft}\n            isArchived={queryFlags.isArchived}\n            canEdit={queryFlags.canEdit}\n            getAvailableTags={getQueryTags}\n            onEdit={updateTags}\n            tagsExtra={tagsExtra}\n          />\n        </div>\n      </div>\n      <div className=\"header-actions\">\n        {headerExtra}\n        {isDesktop && queryFlags.isDraft && !queryFlags.isArchived && !queryFlags.isNew && queryFlags.canEdit && (\n          <Button className=\"m-r-5\" onClick={publishQuery}>\n            <i className=\"fa fa-paper-plane m-r-5\" aria-hidden=\"true\" /> Publish\n          </Button>\n        )}\n\n        {!queryFlags.isNew && queryFlags.canViewSource && (\n          <span>\n            {!sourceMode && (\n              <Link.Button className=\"m-r-5\" href={query.getUrl(true, selectedVisualization)}>\n                <i className=\"fa fa-pencil-square-o\" aria-hidden=\"true\" />\n                <span className=\"m-l-5\">Edit Source</span>\n              </Link.Button>\n            )}\n            {sourceMode && (\n              <Link.Button\n                className=\"m-r-5\"\n                href={query.getUrl(false, selectedVisualization)}\n                data-test=\"QueryPageShowResultOnly\">\n                <i className=\"fa fa-table\" aria-hidden=\"true\" />\n                <span className=\"m-l-5\">Show Results Only</span>\n              </Link.Button>\n            )}\n          </span>\n        )}\n\n        {!queryFlags.isNew && (\n          <Dropdown overlay={moreActionsMenu} trigger={[\"click\"]}>\n            <Button data-test=\"QueryPageHeaderMoreButton\" aria-label=\"More actions\">\n              <EllipsisOutlinedIcon rotate={90} aria-hidden=\"true\" />\n            </Button>\n          </Dropdown>\n        )}\n      </div>\n    </div>\n  );\n}\n\nQueryPageHeader.propTypes = {\n  query: PropTypes.shape({\n    id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),\n    name: PropTypes.string,\n    tags: PropTypes.arrayOf(PropTypes.string),\n  }).isRequired,\n  dataSource: PropTypes.object,\n  sourceMode: PropTypes.bool,\n  selectedVisualization: PropTypes.number,\n  headerExtra: PropTypes.node,\n  tagsExtra: PropTypes.node,\n  onChange: PropTypes.func,\n};\n\nQueryPageHeader.defaultProps = {\n  dataSource: null,\n  sourceMode: false,\n  selectedVisualization: null,\n  headerExtra: null,\n  tagsExtra: null,\n  onChange: () => {},\n};\n"
  },
  {
    "path": "client/app/pages/queries/components/QueryPageHeader.less",
    "content": ".query-page-header {\n  display: flex;\n  flex-wrap: wrap;\n  align-items: stretch;\n  margin-top: 10px;\n\n  & > div {\n    padding: 5px 0;\n  }\n\n  .title-with-tags {\n    display: flex;\n    flex-wrap: wrap;\n    align-items: center;\n    flex: 1 1;\n    margin: -5px 0;\n\n    & > div {\n      padding: 5px 0;\n    }\n\n    .page-title {\n      h3 {\n        margin: 0 10px 0 0 !important;\n\n        @media (max-width: 767px) {\n          font-size: 18px;\n        }\n      }\n    }\n  }\n\n  .query-tags {\n    display: inline-block;\n    vertical-align: middle;\n  }\n\n  .tags-control > .label-tag {\n    opacity: 0;\n    transition: opacity 0.2s ease-in-out;\n  }\n\n  &:hover,\n  &:focus,\n  &:active,\n  &:focus-within {\n    .tags-control > .label-tag {\n      opacity: 1;\n    }\n  }\n\n  .header-actions {\n    display: flex;\n    flex-wrap: nowrap;\n\n    @media (max-width: 515px) {\n      flex-basis: 100%;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/queries/components/QuerySourceAlerts.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Card from \"antd/lib/card\";\nimport WarningFilledIcon from \"@ant-design/icons/WarningFilled\";\nimport Typography from \"antd/lib/typography\";\nimport Link from \"@/components/Link\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { currentUser } from \"@/services/auth\";\n\nimport useQueryFlags from \"../hooks/useQueryFlags\";\nimport \"./QuerySourceAlerts.less\";\n\nexport default function QuerySourceAlerts({ query, dataSourcesAvailable }) {\n  const queryFlags = useQueryFlags(query); // we don't use flags that depend on data source\n\n  let message = null;\n  if (queryFlags.isNew && !queryFlags.canCreate) {\n    message = (\n      <React.Fragment>\n        <Typography.Title level={4}>\n          You don't have permission to create new queries on any of the data sources available to you.\n        </Typography.Title>\n        <p>\n          <Typography.Text type=\"secondary\">\n            You can either <Link href=\"queries\">browse existing queries</Link>, or ask for additional permissions from\n            your Redash admin.\n          </Typography.Text>\n        </p>\n      </React.Fragment>\n    );\n  } else if (!dataSourcesAvailable) {\n    if (currentUser.isAdmin) {\n      message = (\n        <React.Fragment>\n          <Typography.Title level={4}>\n            Looks like no data sources were created yet or none of them available to the group(s) you're member of.\n          </Typography.Title>\n          <p>\n            <Typography.Text type=\"secondary\">Please create one first, and then start querying.</Typography.Text>\n          </p>\n\n          <div className=\"query-source-alerts-actions\">\n            <Link.Button type=\"primary\" href=\"data_sources/new\">\n              Create Data Source\n            </Link.Button>\n            <Link.Button type=\"default\" href=\"groups\">\n              Manage Group Permissions\n            </Link.Button>\n          </div>\n        </React.Fragment>\n      );\n    } else {\n      message = (\n        <React.Fragment>\n          <Typography.Title level={4}>\n            Looks like no data sources were created yet or none of them available to the group(s) you're member of.\n          </Typography.Title>\n          <p>\n            <Typography.Text type=\"secondary\">Please ask your Redash admin to create one first.</Typography.Text>\n          </p>\n        </React.Fragment>\n      );\n    }\n  }\n\n  if (!message) {\n    return null;\n  }\n\n  return (\n    <div className=\"query-source-alerts\">\n      <Card>\n        <DynamicComponent name=\"QuerySource.Alerts\" query={query} dataSourcesAvailable={dataSourcesAvailable}>\n          <div className=\"query-source-alerts-icon\">\n            <WarningFilledIcon />\n          </div>\n          {message}\n        </DynamicComponent>\n      </Card>\n    </div>\n  );\n}\n\nQuerySourceAlerts.propTypes = {\n  query: PropTypes.object.isRequired,\n  dataSourcesAvailable: PropTypes.bool,\n};\n\nQuerySourceAlerts.defaultProps = {\n  dataSourcesAvailable: false,\n};\n"
  },
  {
    "path": "client/app/pages/queries/components/QuerySourceAlerts.less",
    "content": "@import (reference, less) \"~@/assets/less/inc/variables.less\";\n\n.query-source-alerts {\n  position: absolute;\n  left: 0;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  z-index: 2000;\n  background: rgba(0, 0, 0, 0.45); // same as Ant drawer\n  display: flex;\n  justify-content: center;\n  align-items: flex-start;\n  padding-top: 10vh;\n\n  @large-spacing: 20px;\n  @small-spacing: 10px;\n  @icon-size: 60px;\n\n  .ant-card {\n    width: 50%;\n    min-width: 300px;\n    height: auto;\n  }\n\n  .ant-card-body {\n    .query-source-alerts-icon {\n      font-size: @icon-size;\n      line-height: @icon-size;\n      margin: @large-spacing 0;\n      text-align: center;\n      color: @brand-warning;\n    }\n\n    h4 {\n      text-align: center;\n      margin: @large-spacing 0;\n      font-weight: normal;\n    }\n\n    p {\n      text-align: center;\n      margin: @small-spacing 0;\n      font-size: 1.1em;\n    }\n\n    .query-source-alerts-actions {\n      text-align: center;\n      margin: @large-spacing 0;\n\n      .ant-btn {\n        margin: 0 15px 0 0;\n        &:last-child {\n          margin-right: 0;\n        }\n      }\n    }\n\n    :first-child {\n      margin-top: 0;\n    }\n    :last-child {\n      margin-bottom: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/queries/components/QuerySourceDropdown.jsx",
    "content": "import Select from \"antd/lib/select\";\nimport { map } from \"lodash\";\nimport DynamicComponent, { registerComponent } from \"@/components/DynamicComponent\";\nimport PropTypes from \"prop-types\";\nimport React from \"react\";\n\nimport \"./QuerySourceDropdownItem\"; // register QuerySourceDropdownItem\n\nexport function QuerySourceDropdown(props) {\n  return (\n    <Select\n      className=\"w-100\"\n      data-test=\"SelectDataSource\"\n      placeholder=\"Choose data source...\"\n      value={props.value}\n      disabled={props.disabled}\n      loading={props.loading}\n      optionFilterProp=\"data-name\"\n      showSearch\n      onChange={props.onChange}>\n      {map(props.dataSources, ds => (\n        <Select.Option key={`ds-${ds.id}`} value={ds.id} data-name={ds.name} data-test={`SelectDataSource${ds.id}`}>\n          <DynamicComponent name={\"QuerySourceDropdownItem\"} dataSource={ds} />\n        </Select.Option>\n      ))}\n    </Select>\n  );\n}\n\nQuerySourceDropdown.propTypes = {\n  dataSources: PropTypes.any,\n  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n  disabled: PropTypes.bool,\n  loading: PropTypes.bool,\n  onChange: PropTypes.func,\n};\n\nregisterComponent(\"QuerySourceDropdown\", QuerySourceDropdown);\n"
  },
  {
    "path": "client/app/pages/queries/components/QuerySourceDropdownItem.jsx",
    "content": "import PropTypes from \"prop-types\";\nimport React from \"react\";\nimport { registerComponent } from \"@/components/DynamicComponent\";\nimport { QuerySourceTypeIcon } from \"@/pages/queries/components/QuerySourceTypeIcon\";\n\nexport function QuerySourceDropdownItem({ dataSource, children }) {\n  return (\n    <React.Fragment>\n      <QuerySourceTypeIcon type={dataSource.type} aria-label={dataSource.name} title={dataSource.name} />\n      {children ? children : <span>{dataSource.name}</span>}\n    </React.Fragment>\n  );\n}\n\nQuerySourceDropdownItem.propTypes = {\n  dataSource: PropTypes.shape({\n    name: PropTypes.string,\n    id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n    type: PropTypes.string,\n  }).isRequired,\n  children: PropTypes.element,\n};\n\nregisterComponent(\"QuerySourceDropdownItem\", QuerySourceDropdownItem);\n"
  },
  {
    "path": "client/app/pages/queries/components/QuerySourceTypeIcon.jsx",
    "content": "import PropTypes from \"prop-types\";\nimport React from \"react\";\n\nexport function QuerySourceTypeIcon(props) {\n  return <img src={`/static/images/db-logos/${props.type}.png`} width=\"20\" alt={props.alt} />;\n}\n\nQuerySourceTypeIcon.propTypes = {\n  type: PropTypes.string,\n  alt: PropTypes.string,\n};\n"
  },
  {
    "path": "client/app/pages/queries/components/QueryViewButton.jsx",
    "content": "import React, { useState, useMemo, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport KeyboardShortcuts from \"@/services/KeyboardShortcuts\";\nimport { ButtonTooltip } from \"@/components/queries/QueryEditor/QueryEditorControls\";\n\nexport default function QueryViewButton({ title, shortcut, disabled, children, onClick, ...props }) {\n  const [tooltipVisible, setTooltipVisible] = useState(false);\n\n  const eventHandlers = useMemo(\n    () => ({\n      onMouseEnter: () => setTooltipVisible(true),\n      onMouseLeave: () => setTooltipVisible(false),\n    }),\n    []\n  );\n\n  useEffect(() => {\n    if (disabled) {\n      setTooltipVisible(false);\n    }\n  }, [disabled]);\n\n  useEffect(() => {\n    if (shortcut) {\n      const shortcuts = {\n        [shortcut]: onClick,\n      };\n\n      KeyboardShortcuts.bind(shortcuts);\n      return () => {\n        KeyboardShortcuts.unbind(shortcuts);\n      };\n    }\n  }, [shortcut, onClick]);\n\n  return (\n    <ButtonTooltip title={title} shortcut={shortcut} visible={tooltipVisible}>\n      <span {...eventHandlers}>\n        <Button\n          data-test=\"ExecuteButton\"\n          disabled={disabled}\n          onClick={onClick}\n          style={disabled ? { pointerEvents: \"none\" } : {}}\n          {...props}>\n          {children}\n        </Button>\n      </span>\n    </ButtonTooltip>\n  );\n}\n\nQueryViewButton.propTypes = {\n  className: PropTypes.string,\n  shortcut: PropTypes.string,\n  disabled: PropTypes.bool,\n  children: PropTypes.node,\n  onClick: PropTypes.func,\n};\n\nQueryViewButton.defaultProps = {\n  className: null,\n  shortcut: null,\n  disabled: false,\n  children: null,\n  onClick: () => {},\n};\n"
  },
  {
    "path": "client/app/pages/queries/components/QueryVisualizationTabs.jsx",
    "content": "import React, { useState, useMemo, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport cx from \"classnames\";\nimport { find, orderBy } from \"lodash\";\nimport useMedia from \"use-media\";\nimport Tabs from \"antd/lib/tabs\";\nimport Button from \"antd/lib/button\";\nimport Modal from \"antd/lib/modal\";\nimport VisualizationRenderer from \"@/components/visualizations/VisualizationRenderer\";\nimport PlainButton from \"@/components/PlainButton\";\n\nimport \"./QueryVisualizationTabs.less\";\n\nconst { TabPane } = Tabs;\n\nfunction EmptyState({ title, message, refreshButton }) {\n  return (\n    <div className=\"query-results-empty-state\">\n      <div className=\"empty-state-content\">\n        <div>\n          <img src=\"/static/images/illustrations/no-query-results.svg\" alt=\"No Query Results Illustration\" />\n        </div>\n        <h3>{title}</h3>\n        <div className=\"m-b-20\">{message}</div>\n        {refreshButton}\n      </div>\n    </div>\n  );\n}\n\nEmptyState.propTypes = {\n  title: PropTypes.string.isRequired,\n  message: PropTypes.string.isRequired,\n  refreshButton: PropTypes.node,\n};\n\nEmptyState.defaultProps = {\n  refreshButton: null,\n};\n\nfunction TabWithDeleteButton({ visualizationName, canDelete, onDelete, ...props }) {\n  const handleDelete = useCallback(\n    (e) => {\n      e.stopPropagation();\n      Modal.confirm({\n        title: \"Delete Visualization\",\n        content: \"Are you sure you want to delete this visualization?\",\n        okText: \"Delete\",\n        okType: \"danger\",\n        onOk: onDelete,\n        maskClosable: true,\n        autoFocusButton: null,\n      });\n    },\n    [onDelete]\n  );\n\n  return (\n    <span {...props}>\n      {visualizationName}\n      {canDelete && (\n        <PlainButton className=\"delete-visualization-button\" onClick={handleDelete} aria-label=\"Close\" title=\"Close\">\n          <i className=\"zmdi zmdi-close\" aria-hidden=\"true\" />\n        </PlainButton>\n      )}\n    </span>\n  );\n}\n\nTabWithDeleteButton.propTypes = {\n  visualizationName: PropTypes.string.isRequired,\n  canDelete: PropTypes.bool,\n  onDelete: PropTypes.func,\n};\nTabWithDeleteButton.defaultProps = { canDelete: false, onDelete: () => {} };\n\nconst defaultVisualizations = [\n  {\n    type: \"TABLE\",\n    name: \"Table\",\n    id: null,\n    options: {},\n  },\n];\n\nexport default function QueryVisualizationTabs({\n  queryResult,\n  selectedTab,\n  showNewVisualizationButton,\n  canDeleteVisualizations,\n  onChangeTab,\n  onAddVisualization,\n  onDeleteVisualization,\n  refreshButton,\n  canRefresh,\n  ...props\n}) {\n  const visualizations = useMemo(\n    () => (props.visualizations.length > 0 ? props.visualizations : defaultVisualizations),\n    [props.visualizations]\n  );\n\n  const tabsProps = {};\n  if (find(visualizations, { id: selectedTab })) {\n    tabsProps.activeKey = `${selectedTab}`;\n  }\n\n  if (showNewVisualizationButton) {\n    tabsProps.tabBarExtraContent = (\n      <Button\n        className=\"add-visualization-button\"\n        data-test=\"NewVisualization\"\n        type=\"link\"\n        onClick={() => onAddVisualization()}\n      >\n        <i className=\"fa fa-plus\" aria-hidden=\"true\" />\n        <span className=\"m-l-5 hidden-xs\">Add Visualization</span>\n      </Button>\n    );\n  }\n\n  const orderedVisualizations = useMemo(() => orderBy(visualizations, [\"id\"]), [visualizations]);\n  const isFirstVisualization = useCallback((visId) => visId === orderedVisualizations[0].id, [orderedVisualizations]);\n  const isMobile = useMedia({ maxWidth: 768 });\n\n  const [filters, setFilters] = useState([]);\n\n  return (\n    <Tabs\n      {...tabsProps}\n      type=\"card\"\n      className={cx(\"query-visualization-tabs card-style\")}\n      data-test=\"QueryPageVisualizationTabs\"\n      animated={false}\n      tabBarGutter={0}\n      onChange={(activeKey) => onChangeTab(+activeKey)}\n      destroyInactiveTabPane\n    >\n      {orderedVisualizations.map((visualization) => (\n        <TabPane\n          key={`${visualization.id}`}\n          tab={\n            <TabWithDeleteButton\n              data-test={`QueryPageVisualizationTab${visualization.id}`}\n              canDelete={!isMobile && canDeleteVisualizations && !isFirstVisualization(visualization.id)}\n              visualizationName={visualization.name}\n              onDelete={() => onDeleteVisualization(visualization.id)}\n            />\n          }\n        >\n          {queryResult ? (\n            <VisualizationRenderer\n              visualization={visualization}\n              queryResult={queryResult}\n              context=\"query\"\n              filters={filters}\n              onFiltersChange={setFilters}\n            />\n          ) : (\n            <EmptyState\n              title=\"Query has no result\"\n              message={\n                canRefresh\n                  ? \"Execute/Refresh the query to show results.\"\n                  : \"You do not have a permission to execute/refresh this query.\"\n              }\n              refreshButton={refreshButton}\n            />\n          )}\n        </TabPane>\n      ))}\n    </Tabs>\n  );\n}\n\nQueryVisualizationTabs.propTypes = {\n  queryResult: PropTypes.object, // eslint-disable-line react/forbid-prop-types\n  visualizations: PropTypes.arrayOf(PropTypes.object),\n  selectedTab: PropTypes.number,\n  showNewVisualizationButton: PropTypes.bool,\n  canDeleteVisualizations: PropTypes.bool,\n  onChangeTab: PropTypes.func,\n  onAddVisualization: PropTypes.func,\n  onDeleteVisualization: PropTypes.func,\n  refreshButton: PropTypes.node,\n  canRefresh: PropTypes.bool,\n};\n\nQueryVisualizationTabs.defaultProps = {\n  queryResult: null,\n  visualizations: [],\n  selectedTab: null,\n  showNewVisualizationButton: false,\n  canDeleteVisualizations: false,\n  onChangeTab: () => {},\n  onAddVisualization: () => {},\n  onDeleteVisualization: () => {},\n  refreshButton: null,\n  canRefresh: true,\n};\n"
  },
  {
    "path": "client/app/pages/queries/components/QueryVisualizationTabs.less",
    "content": ".query-visualization-tabs {\n  .query-results-empty-state {\n    height: 100%;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    flex-direction: column;\n    padding: 15px;\n\n    .empty-state-content {\n      max-width: 280px;\n      text-align: center;\n    }\n\n    img {\n      max-width: 100%;\n    }\n  }\n\n  .ant-tabs-nav-wrap,\n  .ant-tabs-extra-content {\n    flex: initial !important;\n  }\n\n  .ant-tabs-nav-wrap {\n    z-index: 1;\n  }\n\n  .ant-tabs-tab {\n    background: #f6f8f9 !important;\n    border-color: #d9d9d9 !important;\n    border-bottom: 0px !important;\n    border-radius: 0 !important;\n    // border-width animation makes it flicker on Firefox\n    transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border-width 0s !important;\n\n    &:first-child {\n      border-radius: 2px 0 0 0 !important;\n    }\n\n    &:last-child {\n      border-radius: 0 2px 0 0 !important;\n    }\n\n    &:not(:first-child) {\n      margin-left: -1px !important;\n    }\n\n    &.ant-tabs-tab-active {\n      background: white !important;\n      font-weight: normal;\n      border-top: 2px solid #2196f3 !important;\n\n      .ant-tabs-tab-btn {\n        font-weight: normal;\n      }\n    }\n\n    // add internal bottom border to non-active tabs\n    &:not(.ant-tabs-tab-active) {\n      box-shadow: 0px -1px 0px #d9d9d9 inset;\n    }\n  }\n\n  .ant-tabs-content-holder {\n    margin-top: -17px;\n    border: 1px solid #d9d9d9;\n    box-sizing: border-box;\n    border-radius: 0px 4px 0px 0px;\n\n    .ant-tabs-tabpane {\n      padding: 16px;\n      background: white;\n    }\n  }\n\n  .add-visualization-button {\n    span {\n      color: #767676;\n    }\n  }\n\n  .delete-visualization-button {\n    height: 1.5rem;\n    width: 1.5rem;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    margin-left: 5px;\n    color: #a09797;\n    font-size: 11px;\n    border-radius: 100%;\n\n    &:hover,\n    &:focus {\n      color: white;\n      background-color: #ff8080;\n    }\n\n    &:active {\n      filter: brightness(80%);\n    }\n  }\n}\n\n// hide delete button when it in the dropdown\n.ant-tabs-dropdown-menu-item .delete-visualization-button {\n  display: none;\n}\n\n.query-fixed-layout .query-visualization-tabs .visualization-renderer {\n  padding: 15px;\n}\n"
  },
  {
    "path": "client/app/pages/queries/components/wrapQueryPage.jsx",
    "content": "import React, { useState, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport { Query } from \"@/services/query\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nexport default function wrapQueryPage(WrappedComponent) {\n  function QueryPageWrapper({ queryId, onError, ...props }) {\n    const [query, setQuery] = useState(null);\n\n    const handleError = useImmutableCallback(onError);\n\n    useEffect(() => {\n      let isCancelled = false;\n      const promise = queryId ? Query.get({ id: queryId }) : Promise.resolve(Query.newQuery());\n      promise\n        .then(result => {\n          if (!isCancelled) {\n            setQuery(result);\n          }\n        })\n        .catch(handleError);\n\n      return () => {\n        isCancelled = true;\n      };\n    }, [queryId, handleError]);\n\n    if (!query) {\n      return <LoadingState className=\"flex-fill\" />;\n    }\n\n    return <WrappedComponent query={query} onError={onError} {...props} />;\n  }\n\n  QueryPageWrapper.propTypes = {\n    queryId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),\n  };\n\n  QueryPageWrapper.defaultProps = {\n    queryId: null,\n  };\n\n  return QueryPageWrapper;\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useAddNewParameterDialog.js",
    "content": "import { map } from \"lodash\";\nimport { useCallback } from \"react\";\nimport EditParameterSettingsDialog from \"@/components/EditParameterSettingsDialog\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nexport default function useAddNewParameterDialog(query, onParameterAdded) {\n  const handleParameterAdded = useImmutableCallback(onParameterAdded);\n\n  return useCallback(() => {\n    EditParameterSettingsDialog.showModal({\n      parameter: {\n        title: null,\n        name: \"\",\n        type: \"text\",\n        value: null,\n      },\n      existingParams: map(query.getParameters().get(), p => p.name),\n    }).onClose(param => {\n      const newQuery = query.clone();\n      param = newQuery.getParameters().add(param);\n      handleParameterAdded(newQuery, param);\n    });\n  }, [query, handleParameterAdded]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useAddToDashboardDialog.js",
    "content": "import { find } from \"lodash\";\nimport { useCallback } from \"react\";\nimport AddToDashboardDialog from \"@/components/queries/AddToDashboardDialog\";\n\nexport default function useAddToDashboardDialog(query) {\n  return useCallback(\n    visualizationId => {\n      const visualization = find(query.visualizations, { id: visualizationId });\n      AddToDashboardDialog.showModal({ visualization });\n    },\n    [query.visualizations]\n  );\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useAddVisualizationDialog.js",
    "content": "import { useState, useCallback, useEffect } from \"react\";\nimport useQueryFlags from \"./useQueryFlags\";\nimport useEditVisualizationDialog from \"./useEditVisualizationDialog\";\n\nexport default function useAddVisualizationDialog(query, queryResult, saveQuery, onChange) {\n  const queryFlags = useQueryFlags(query);\n  const editVisualization = useEditVisualizationDialog(query, queryResult, onChange);\n  const [shouldOpenDialog, setShouldOpenDialog] = useState(false);\n\n  useEffect(() => {\n    if (!queryFlags.isNew && shouldOpenDialog) {\n      setShouldOpenDialog(false);\n      editVisualization();\n    }\n  }, [queryFlags.isNew, shouldOpenDialog, editVisualization]);\n\n  return useCallback(() => {\n    if (queryFlags.isNew) {\n      setShouldOpenDialog(true);\n      saveQuery();\n    } else {\n      editVisualization();\n    }\n  }, [queryFlags.isNew, saveQuery, editVisualization]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useApiKeyDialog.js",
    "content": "import { useCallback } from \"react\";\nimport ApiKeyDialog from \"@/components/queries/ApiKeyDialog\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nexport default function useApiKeyDialog(query, onChange) {\n  const handleChange = useImmutableCallback(onChange);\n\n  return useCallback(() => {\n    ApiKeyDialog.showModal({ query }).onClose(handleChange);\n  }, [query, handleChange]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useArchiveQuery.jsx",
    "content": "import { extend } from \"lodash\";\nimport React, { useCallback } from \"react\";\nimport Modal from \"antd/lib/modal\";\nimport { Query } from \"@/services/query\";\nimport notification from \"@/services/notification\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nfunction confirmArchive() {\n  return new Promise((resolve, reject) => {\n    Modal.confirm({\n      title: \"Archive Query\",\n      content: (\n        <React.Fragment>\n          <div className=\"m-b-5\">Are you sure you want to archive this query?</div>\n          <div>All alerts and dashboard widgets created with its visualizations will be deleted.</div>\n        </React.Fragment>\n      ),\n      okText: \"Archive\",\n      okType: \"danger\",\n      onOk: () => {\n        resolve();\n      },\n      onCancel: () => {\n        reject();\n      },\n      maskClosable: true,\n      autoFocusButton: null,\n    });\n  });\n}\n\nfunction doArchiveQuery(query) {\n  return Query.delete({ id: query.id })\n    .then(() => {\n      return extend(query.clone(), { is_archived: true, schedule: null });\n    })\n    .catch(error => {\n      notification.error(\"Query could not be archived.\");\n      return Promise.reject(error);\n    });\n}\n\nexport default function useArchiveQuery(query, onChange) {\n  const handleChange = useImmutableCallback(onChange);\n\n  return useCallback(() => {\n    confirmArchive()\n      .then(() => doArchiveQuery(query))\n      .then(handleChange);\n  }, [query, handleChange]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useAutoLimitFlags.js",
    "content": "import { useCallback, useState } from \"react\";\nimport localOptions from \"@/lib/localOptions\";\nimport { get, extend } from \"lodash\";\n\nfunction isAutoLimitAvailable(dataSource) {\n  return get(dataSource, \"supports_auto_limit\", false);\n}\n\nexport default function useAutoLimitFlags(dataSource, query, setQuery) {\n  const isAvailable = isAutoLimitAvailable(dataSource);\n  const [isChecked, setIsChecked] = useState(query.options.apply_auto_limit);\n  query.options.apply_auto_limit = isChecked;\n\n  const setAutoLimit = useCallback(\n    state => {\n      setIsChecked(state);\n      localOptions.set(\"applyAutoLimit\", state);\n      setQuery(extend(query.clone(), { options: { ...query.options, apply_auto_limit: state } }));\n    },\n    [query, setQuery]\n  );\n\n  return [isAvailable, isChecked, setAutoLimit];\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useAutocompleteFlags.js",
    "content": "import { useCallback, useMemo, useState } from \"react\";\nimport localOptions from \"@/lib/localOptions\";\n\nexport default function useAutocompleteFlags(schema) {\n  const isAvailable = true;\n  const [isEnabled, setIsEnabled] = useState(localOptions.get(\"liveAutocomplete\", true));\n\n  const toggleAutocomplete = useCallback((state) => {\n    setIsEnabled(state);\n    localOptions.set(\"liveAutocomplete\", state);\n  }, []);\n\n  return useMemo(() => [isAvailable, isEnabled, toggleAutocomplete], [isAvailable, isEnabled, toggleAutocomplete]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useDataSourceSchema.js",
    "content": "import { useCallback, useEffect, useRef, useState, useMemo } from \"react\";\nimport DataSource from \"@/services/data-source\";\nimport notification from \"@/services/notification\";\n\nfunction getSchema(dataSource, refresh = undefined) {\n  if (!dataSource) {\n    return Promise.resolve([]);\n  }\n\n  return DataSource.fetchSchema(dataSource, refresh).catch(() => {\n    notification.error(\"Schema refresh failed.\", \"Please try again later.\");\n    return Promise.resolve([]);\n  });\n}\n\nexport default function useDataSourceSchema(dataSource) {\n  const [schema, setSchema] = useState([]);\n  const [loadingSchema, setLoadingSchema] = useState(true);\n  const refreshSchemaTokenRef = useRef(null);\n\n  const reloadSchema = useCallback(\n    (refresh = undefined) => {\n      setLoadingSchema(true);\n      const refreshToken = Math.random()\n        .toString(36)\n        .substr(2);\n      refreshSchemaTokenRef.current = refreshToken;\n      getSchema(dataSource, refresh)\n        .then(data => {\n          if (refreshSchemaTokenRef.current === refreshToken) {\n            setSchema(data);\n          }\n        })\n        .finally(() => {\n          if (refreshSchemaTokenRef.current === refreshToken) {\n            setLoadingSchema(false);\n          }\n        });\n    },\n    [dataSource]\n  );\n\n  useEffect(() => {\n    reloadSchema();\n  }, [reloadSchema]);\n\n  useEffect(() => {\n    return () => {\n      // cancel pending operations\n      refreshSchemaTokenRef.current = null;\n    };\n  }, []);\n\n  return useMemo(() => [schema, loadingSchema, reloadSchema], [schema, loadingSchema, reloadSchema]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useDeleteVisualization.js",
    "content": "import { extend, filter } from \"lodash\";\nimport { useCallback } from \"react\";\nimport Visualization from \"@/services/visualization\";\nimport notification from \"@/services/notification\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nexport default function useDeleteVisualization(query, onChange) {\n  const handleChange = useImmutableCallback(onChange);\n\n  return useCallback(\n    visualizationId =>\n      Visualization.delete({ id: visualizationId })\n        .then(() => {\n          const filteredVisualizations = filter(query.visualizations, v => v.id !== visualizationId);\n          handleChange(extend(query.clone(), { visualizations: filteredVisualizations }));\n        })\n        .catch(() => {\n          notification.error(\"Error deleting visualization.\", \"Maybe it's used in a dashboard?\");\n        }),\n    [query, handleChange]\n  );\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useDuplicateQuery.js",
    "content": "import { noop, extend, pick } from \"lodash\";\nimport { useCallback, useState } from \"react\";\nimport url from \"url\";\nimport qs from \"query-string\";\nimport { Query } from \"@/services/query\";\n\nfunction keepCurrentUrlParams(targetUrl) {\n  const currentUrlParams = qs.parse(window.location.search);\n  targetUrl = url.parse(targetUrl);\n  const targetUrlParams = qs.parse(targetUrl.search);\n  return url.format(\n    extend(pick(targetUrl, [\"protocol\", \"auth\", \"host\", \"pathname\", \"hash\"]), {\n      search: qs.stringify(extend(currentUrlParams, targetUrlParams)),\n    })\n  );\n}\n\nexport default function useDuplicateQuery(query) {\n  const [isDuplicating, setIsDuplicating] = useState(false);\n\n  const duplicateQuery = useCallback(() => {\n    // To prevent opening the same tab, name must be unique for each browser\n    const tabName = `duplicatedQueryTab/${Math.random().toString()}`;\n\n    // We should open tab here because this moment is a part of user interaction;\n    // later browser will block such attempts\n    const tab = window.open(\"\", tabName);\n\n    setIsDuplicating(true);\n    Query.fork({ id: query.id })\n      .then(newQuery => {\n        tab.location = keepCurrentUrlParams(newQuery.getUrl(true));\n      })\n      .finally(() => {\n        setIsDuplicating(false);\n      });\n  }, [query.id]);\n\n  return [isDuplicating, isDuplicating ? noop : duplicateQuery];\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useEditScheduleDialog.js",
    "content": "import { isArray, intersection } from \"lodash\";\nimport { useCallback } from \"react\";\nimport ScheduleDialog from \"@/components/queries/ScheduleDialog\";\nimport { clientConfig } from \"@/services/auth\";\nimport { policy } from \"@/services/policy\";\nimport useUpdateQuery from \"./useUpdateQuery\";\nimport useQueryFlags from \"./useQueryFlags\";\nimport recordEvent from \"@/services/recordEvent\";\n\nexport default function useEditScheduleDialog(query, onChange) {\n  // We won't use flags that depend on data source\n  const queryFlags = useQueryFlags(query);\n\n  const updateQuery = useUpdateQuery(query, onChange);\n\n  return useCallback(() => {\n    if (!queryFlags.canEdit || !queryFlags.canSchedule) {\n      return;\n    }\n\n    const intervals = clientConfig.queryRefreshIntervals;\n    const allowedIntervals = policy.getQueryRefreshIntervals();\n    const refreshOptions = isArray(allowedIntervals) ? intersection(intervals, allowedIntervals) : intervals;\n\n    ScheduleDialog.showModal({\n      schedule: query.schedule,\n      refreshOptions,\n    }).onClose(schedule => {\n      recordEvent(\"edit_schedule\", \"query\", query.id);\n      updateQuery({ schedule });\n    });\n  }, [query.id, query.schedule, queryFlags.canEdit, queryFlags.canSchedule, updateQuery]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useEditVisualizationDialog.js",
    "content": "import { extend, filter, find } from \"lodash\";\nimport { useCallback } from \"react\";\nimport EditVisualizationDialog from \"@/components/visualizations/EditVisualizationDialog\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nexport default function useEditVisualizationDialog(query, queryResult, onChange) {\n  const handleChange = useImmutableCallback(onChange);\n\n  return useCallback(\n    (visualizationId = null) => {\n      const visualization = find(query.visualizations, { id: visualizationId }) || null;\n      EditVisualizationDialog.showModal({\n        query,\n        visualization,\n        queryResult,\n      }).onClose(updatedVisualization => {\n        const filteredVisualizations = filter(query.visualizations, v => v.id !== updatedVisualization.id);\n        handleChange(\n          extend(query.clone(), { visualizations: [...filteredVisualizations, updatedVisualization] }),\n          updatedVisualization\n        );\n      });\n    },\n    [query, queryResult, handleChange]\n  );\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useEmbedDialog.js",
    "content": "import { find } from \"lodash\";\nimport { useCallback } from \"react\";\nimport EmbedQueryDialog from \"@/components/queries/EmbedQueryDialog\";\n\nexport default function useEmbedDialog(query) {\n  return useCallback(\n    (unusedQuery, visualizationId) => {\n      const visualization = find(query.visualizations, { id: visualizationId });\n      EmbedQueryDialog.showModal({ query, visualization });\n    },\n    [query]\n  );\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/usePermissionsEditorDialog.js",
    "content": "import { useCallback } from \"react\";\nimport PermissionsEditorDialog from \"@/components/PermissionsEditorDialog\";\n\nexport default function usePermissionsEditorDialog(query) {\n  return useCallback(() => {\n    PermissionsEditorDialog.showModal({\n      aclUrl: `api/queries/${query.id}/acl`,\n      context: \"query\",\n      author: query.user,\n    });\n  }, [query.id, query.user]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/usePublishQuery.js",
    "content": "import { useCallback } from \"react\";\nimport useUpdateQuery from \"./useUpdateQuery\";\nimport recordEvent from \"@/services/recordEvent\";\n\nexport default function usePublishQuery(query, onChange) {\n  const updateQuery = useUpdateQuery(query, onChange);\n\n  return useCallback(() => {\n    recordEvent(\"toggle_published\", \"query\", query.id);\n    updateQuery({ is_draft: false });\n  }, [query.id, updateQuery]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useQuery.js",
    "content": "import { isEmpty } from \"lodash\";\nimport { useState, useMemo } from \"react\";\nimport useUpdateQuery from \"./useUpdateQuery\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\n\nexport default function useQuery(originalQuery) {\n  const [query, setQuery] = useState(originalQuery);\n  const [originalQuerySource, setOriginalQuerySource] = useState(originalQuery.query);\n  const [originalAutoLimit, setOriginalAutoLimit] = useState(query.options.apply_auto_limit);\n\n  const updateQuery = useUpdateQuery(query, updatedQuery => {\n    // It's important to update URL first, and only then update state\n    if (updatedQuery.id !== query.id) {\n      // Don't reload page when saving new query\n      navigateTo(updatedQuery.getUrl(true), true);\n    }\n    setQuery(updatedQuery);\n    setOriginalQuerySource(updatedQuery.query);\n    setOriginalAutoLimit(updatedQuery.options.apply_auto_limit);\n  });\n\n  return useMemo(\n    () => ({\n      query,\n      setQuery,\n      isDirty:\n        query.query !== originalQuerySource ||\n        (!isEmpty(query.query) && query.options.apply_auto_limit !== originalAutoLimit),\n      saveQuery: () => updateQuery(),\n    }),\n    [query, originalQuerySource, updateQuery, originalAutoLimit]\n  );\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useQueryDataSources.js",
    "content": "import { filter, find, toString } from \"lodash\";\nimport { useState, useMemo, useEffect } from \"react\";\nimport DataSource from \"@/services/data-source\";\n\nexport default function useQueryDataSources(query) {\n  const [allDataSources, setAllDataSources] = useState([]);\n  const [dataSourcesLoaded, setDataSourcesLoaded] = useState(false);\n  const dataSources = useMemo(() => filter(allDataSources, ds => !ds.view_only || ds.id === query.data_source_id), [\n    allDataSources,\n    query.data_source_id,\n  ]);\n  const dataSource = useMemo(\n    () => find(dataSources, ds => toString(ds.id) === toString(query.data_source_id)) || null,\n    [query.data_source_id, dataSources]\n  );\n\n  useEffect(() => {\n    let cancelDataSourceLoading = false;\n    DataSource.query().then(data => {\n      if (!cancelDataSourceLoading) {\n        setDataSourcesLoaded(true);\n        setAllDataSources(data);\n      }\n    });\n\n    return () => {\n      cancelDataSourceLoading = true;\n    };\n  }, []);\n\n  return useMemo(() => ({ dataSourcesLoaded, dataSources, dataSource }), [dataSourcesLoaded, dataSources, dataSource]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useQueryExecute.js",
    "content": "import { useReducer, useEffect, useRef } from \"react\";\nimport location from \"@/services/location\";\nimport recordEvent from \"@/services/recordEvent\";\nimport { ExecutionStatus } from \"@/services/query-result\";\nimport notifications from \"@/services/notifications\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nfunction getMaxAge() {\n  const { maxAge } = location.search;\n  return maxAge !== undefined ? maxAge : -1;\n}\n\nconst reducer = (prevState, updatedProperty) => ({\n  ...prevState,\n  ...updatedProperty,\n});\n\n// This is currently specific to a Query page, we can refactor\n// it slightly to make it suitable for dashboard widgets instead of the other solution it\n// has in there.\nexport default function useQueryExecute(query) {\n  const [executionState, setExecutionState] = useReducer(reducer, {\n    queryResult: null,\n    isExecuting: false,\n    loadedInitialResults: false,\n    executionStatus: null,\n    isCancelling: false,\n    cancelCallback: null,\n    error: null,\n  });\n\n  const queryResultInExecution = useRef(null);\n  // Clear executing queryResult when component is unmounted to avoid errors\n  useEffect(() => {\n    return () => {\n      queryResultInExecution.current = null;\n    };\n  }, []);\n\n  const executeQuery = useImmutableCallback((maxAge = 0, queryExecutor) => {\n    let newQueryResult;\n    if (queryExecutor) {\n      newQueryResult = queryExecutor();\n    } else {\n      newQueryResult = query.getQueryResult(maxAge);\n    }\n\n    recordEvent(\"execute\", \"query\", query.id);\n    notifications.getPermissions();\n\n    queryResultInExecution.current = newQueryResult;\n\n    setExecutionState({\n      updatedAt: newQueryResult.getUpdatedAt(),\n      executionStatus: newQueryResult.getStatus(),\n      isExecuting: true,\n      cancelCallback: () => {\n        recordEvent(\"cancel_execute\", \"query\", query.id);\n        setExecutionState({ isCancelling: true });\n        newQueryResult.cancelExecution();\n      },\n    });\n\n    const onStatusChange = status => {\n      if (queryResultInExecution.current === newQueryResult) {\n        setExecutionState({ updatedAt: newQueryResult.getUpdatedAt(), executionStatus: status });\n      }\n    };\n\n    newQueryResult\n      .toPromise(onStatusChange)\n      .then(queryResult => {\n        if (queryResultInExecution.current === newQueryResult) {\n          // TODO: this should probably belong in the QueryEditor page.\n          if (queryResult && queryResult.query_result.query === query.query) {\n            query.latest_query_data_id = queryResult.getId();\n            query.queryResult = queryResult;\n          }\n\n          if (executionState.loadedInitialResults) {\n            notifications.showNotification(\"Redash\", `${query.name} updated.`);\n          }\n\n          setExecutionState({\n            queryResult,\n            loadedInitialResults: true,\n            error: null,\n            isExecuting: false,\n            isCancelling: false,\n            executionStatus: null,\n          });\n        }\n      })\n      .catch(queryResult => {\n        if (queryResultInExecution.current === newQueryResult) {\n          if (executionState.loadedInitialResults) {\n            notifications.showNotification(\"Redash\", `${query.name} failed to run: ${queryResult.getError()}`);\n          }\n\n          setExecutionState({\n            queryResult,\n            loadedInitialResults: true,\n            error: queryResult.getError(),\n            isExecuting: false,\n            isCancelling: false,\n            executionStatus: ExecutionStatus.FAILED,\n          });\n        }\n      });\n  });\n\n  const queryRef = useRef(query);\n  queryRef.current = query;\n\n  useEffect(() => {\n    // TODO: this belongs on the query page?\n    // loadedInitialResults can be removed if so\n    if (queryRef.current.hasResult() || queryRef.current.paramsRequired()) {\n      executeQuery(getMaxAge());\n    } else {\n      setExecutionState({ loadedInitialResults: true });\n    }\n  }, [executeQuery]);\n\n  return { ...executionState, ...{ executeQuery } };\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useQueryFlags.js",
    "content": "import { isNil, isEmpty } from \"lodash\";\nimport { useMemo } from \"react\";\nimport { currentUser } from \"@/services/auth\";\nimport { policy } from \"@/services/policy\";\n\nexport default function useQueryFlags(query, dataSource = null) {\n  dataSource = dataSource || { view_only: true };\n\n  return useMemo(\n    () => ({\n      // state flags\n      isNew: isNil(query.id),\n      isDraft: query.is_draft,\n      isArchived: query.is_archived,\n\n      // permissions flags\n      canCreate: currentUser.hasPermission(\"create_query\"),\n      canView: currentUser.hasPermission(\"view_query\"),\n      canEdit: currentUser.hasPermission(\"edit_query\") && policy.canEdit(query),\n      canViewSource: currentUser.hasPermission(\"view_source\"),\n      canExecute:\n        !isEmpty(query.query) &&\n        policy.canRun(query) &&\n        (query.is_safe || (currentUser.hasPermission(\"execute_query\") && !dataSource.view_only)),\n      canFork: currentUser.hasPermission(\"edit_query\") && !dataSource.view_only,\n      canSchedule: currentUser.hasPermission(\"schedule_query\"),\n    }),\n    [query, dataSource.view_only]\n  );\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useQueryParameters.js",
    "content": "import { isUndefined } from \"lodash\";\nimport { useEffect, useMemo, useState, useCallback } from \"react\";\n\nexport default function useQueryParameters(query) {\n  const parameters = useMemo(() => query.getParametersDefs(), [query]);\n  const [dirtyFlag, setDirtyFlag] = useState(query.getParameters().hasPendingValues());\n\n  const updateDirtyFlag = useCallback(\n    flag => {\n      flag = isUndefined(flag) ? query.getParameters().hasPendingValues() : flag;\n      setDirtyFlag(flag);\n    },\n    [query]\n  );\n\n  useEffect(() => {\n    const updatedDirtyParameters = query.getParameters().hasPendingValues();\n    if (updatedDirtyParameters !== dirtyFlag) {\n      setDirtyFlag(updatedDirtyParameters);\n    }\n  }, [query, parameters, dirtyFlag]);\n\n  return useMemo(() => [parameters, dirtyFlag, updateDirtyFlag], [parameters, dirtyFlag, updateDirtyFlag]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useRenameQuery.js",
    "content": "import { useCallback } from \"react\";\nimport useUpdateQuery from \"./useUpdateQuery\";\nimport recordEvent from \"@/services/recordEvent\";\nimport { clientConfig } from \"@/services/auth\";\n\nexport default function useRenameQuery(query, onChange) {\n  const updateQuery = useUpdateQuery(query, onChange);\n\n  return useCallback(\n    name => {\n      recordEvent(\"edit_name\", \"query\", query.id);\n      const changes = { name };\n      const options = {};\n\n      if (query.is_draft && clientConfig.autoPublishNamedQueries && name !== \"New Query\") {\n        changes.is_draft = false;\n        options.successMessage = \"Query saved and published\";\n      }\n\n      updateQuery(changes, options);\n    },\n    [query.id, query.is_draft, updateQuery]\n  );\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useUnpublishQuery.js",
    "content": "import { useCallback } from \"react\";\nimport useUpdateQuery from \"./useUpdateQuery\";\nimport recordEvent from \"@/services/recordEvent\";\n\nexport default function useUnpublishQuery(query, onChange) {\n  const updateQuery = useUpdateQuery(query, onChange);\n\n  return useCallback(() => {\n    recordEvent(\"toggle_published\", \"query\", query.id);\n    updateQuery({ is_draft: true });\n  }, [query.id, updateQuery]);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useUnsavedChangesAlert.js",
    "content": "import { useRef, useEffect } from \"react\";\nimport location from \"@/services/location\";\n\nexport default function useUnsavedChangesAlert(shouldShowAlert = false) {\n  const shouldShowAlertRef = useRef();\n  shouldShowAlertRef.current = shouldShowAlert;\n\n  useEffect(() => {\n    const unloadMessage = \"You will lose your changes if you leave\";\n    const confirmMessage = `${unloadMessage}\\n\\nAre you sure you want to leave this page?`;\n    // store original handler (if any)\n    const savedOnBeforeUnload = window.onbeforeunload;\n\n    window.onbeforeunload = function onbeforeunload() {\n      return shouldShowAlertRef.current ? unloadMessage : undefined;\n    };\n\n    const unsubscribe = location.confirmChange((nextLocation, currentLocation) => {\n      if (shouldShowAlertRef.current && nextLocation.path !== currentLocation.path) {\n        return confirmMessage;\n      }\n    });\n\n    return () => {\n      window.onbeforeunload = savedOnBeforeUnload;\n      unsubscribe();\n    };\n  }, []);\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useUpdateQuery.jsx",
    "content": "import { isNil, isObject, extend, keys, map, omit, pick, uniq, get } from \"lodash\";\nimport React, { useCallback } from \"react\";\nimport Modal from \"antd/lib/modal\";\nimport { Query } from \"@/services/query\";\nimport notification from \"@/services/notification\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\nimport { policy } from \"@/services/policy\";\n\nclass SaveQueryError extends Error {\n  constructor(message, detailedMessage = null) {\n    super(message);\n    this.detailedMessage = detailedMessage;\n  }\n}\n\nclass SaveQueryConflictError extends SaveQueryError {\n  constructor() {\n    super(\n      \"Changes not saved\",\n      <React.Fragment>\n        <div className=\"m-b-5\">It seems like the query has been modified by another user.</div>\n        <div>Please copy/backup your changes and reload this page.</div>\n      </React.Fragment>\n    );\n  }\n}\n\nfunction confirmOverwrite() {\n  return new Promise((resolve, reject) => {\n    Modal.confirm({\n      title: \"Overwrite Query\",\n      content: (\n        <React.Fragment>\n          <div className=\"m-b-5\">It seems like the query has been modified by another user.</div>\n          <div>Are you sure you want to overwrite the query with your version?</div>\n        </React.Fragment>\n      ),\n      okText: \"Overwrite\",\n      okType: \"danger\",\n      onOk: () => {\n        resolve();\n      },\n      onCancel: () => {\n        reject();\n      },\n      maskClosable: true,\n      autoFocusButton: null,\n    });\n  });\n}\n\nfunction doSaveQuery(data, { canOverwrite = false } = {}) {\n  // omit parameter properties that don't need to be stored\n  if (isObject(data.options) && data.options.parameters) {\n    data.options = {\n      ...data.options,\n      parameters: map(data.options.parameters, p => p.toSaveableObject()),\n    };\n  }\n\n  return Query.save(data).catch(error => {\n    if (get(error, \"response.status\") === 409) {\n      if (canOverwrite) {\n        return confirmOverwrite()\n          .then(() => Query.save(omit(data, [\"version\"])))\n          .catch(() => Promise.reject(new SaveQueryConflictError()));\n      }\n      return Promise.reject(new SaveQueryConflictError());\n    }\n    return Promise.reject(new SaveQueryError(\"Query could not be saved\"));\n  });\n}\n\nexport default function useUpdateQuery(query, onChange) {\n  const handleChange = useImmutableCallback(onChange);\n\n  return useCallback(\n    (data = null, { successMessage = \"Query saved\" } = {}) => {\n      if (isObject(data)) {\n        // Don't save new query with partial data\n        if (query.isNew()) {\n          handleChange(extend(query.clone(), data));\n          return;\n        }\n        data = { ...data, id: query.id, version: query.version };\n      } else {\n        data = pick(query, [\n          \"id\",\n          \"version\",\n          \"schedule\",\n          \"query\",\n          \"description\",\n          \"name\",\n          \"data_source_id\",\n          \"options\",\n          \"latest_query_data_id\",\n          \"is_draft\",\n          \"tags\",\n        ]);\n      }\n\n      return doSaveQuery(data, { canOverwrite: policy.canEdit(query) })\n        .then(updatedQuery => {\n          if (!isNil(successMessage)) {\n            notification.success(successMessage);\n          }\n          handleChange(\n            extend(\n              query.clone(),\n              // if server returned completely new object (currently possible only when saving new query) -\n              // update all fields; otherwise pick only changed fields\n              updatedQuery.id !== query.id ? updatedQuery : pick(updatedQuery, uniq([\"id\", \"version\", ...keys(data)]))\n            )\n          );\n        })\n        .catch(error => {\n          const notificationOptions = {};\n          if (error instanceof SaveQueryConflictError) {\n            notificationOptions.duration = null;\n          }\n          notification.error(error.message, error.detailedMessage, notificationOptions);\n        });\n    },\n    [query, handleChange]\n  );\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useUpdateQueryDescription.js",
    "content": "import { useCallback } from \"react\";\nimport useUpdateQuery from \"./useUpdateQuery\";\nimport recordEvent from \"@/services/recordEvent\";\n\nexport default function useUpdateQueryDescription(query, onChange) {\n  const updateQuery = useUpdateQuery(query, onChange);\n\n  return useCallback(\n    description => {\n      recordEvent(\"edit_description\", \"query\", query.id);\n      updateQuery({ description });\n    },\n    [query.id, updateQuery]\n  );\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useUpdateQueryTags.js",
    "content": "import { useCallback } from \"react\";\nimport useUpdateQuery from \"./useUpdateQuery\";\nimport recordEvent from \"@/services/recordEvent\";\n\nexport default function useUpdateQueryTags(query, onChange) {\n  const updateQuery = useUpdateQuery(query, onChange);\n\n  return useCallback(\n    tags => {\n      recordEvent(\"edit_tags\", \"query\", query.id);\n      updateQuery({ tags });\n    },\n    [query.id, updateQuery]\n  );\n}\n"
  },
  {
    "path": "client/app/pages/queries/hooks/useVisualizationTabHandler.js",
    "content": "import { useState, useEffect, useMemo } from \"react\";\nimport { first, orderBy, find } from \"lodash\";\nimport location from \"@/services/location\";\n\nexport default function useVisualizationTabHandler(visualizations) {\n  const firstVisualization = useMemo(() => first(orderBy(visualizations, [\"id\"])) || {}, [visualizations]);\n  const [selectedTab, setSelectedTab] = useState(+location.hash || firstVisualization.id);\n\n  useEffect(() => {\n    const hashValue = selectedTab !== firstVisualization.id ? `${selectedTab}` : null;\n    if (location.hash !== hashValue) {\n      location.setHash(hashValue);\n    }\n\n    const unlisten = location.listen(() => {\n      if (location.hash !== hashValue) {\n        setSelectedTab(+location.hash || firstVisualization.id);\n      }\n    });\n    return unlisten;\n  }, [firstVisualization.id, selectedTab]);\n\n  // make sure selectedTab is in visualizations\n  useEffect(() => {\n    if (!find(visualizations, { id: selectedTab })) {\n      setSelectedTab(firstVisualization.id);\n    }\n  }, [firstVisualization.id, selectedTab, visualizations]);\n\n  return useMemo(() => [selectedTab, setSelectedTab], [selectedTab]);\n}\n"
  },
  {
    "path": "client/app/pages/queries-list/QueriesList.jsx",
    "content": "import React, { useCallback, useEffect, useRef } from \"react\";\nimport cx from \"classnames\";\n\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport Link from \"@/components/Link\";\nimport PageHeader from \"@/components/PageHeader\";\nimport Paginator from \"@/components/Paginator\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { QueryTagsControl } from \"@/components/tags-control/TagsControl\";\nimport SchedulePhrase from \"@/components/queries/SchedulePhrase\";\n\nimport { wrap as itemsList, ControllerType } from \"@/components/items-list/ItemsList\";\nimport useItemsListExtraActions from \"@/components/items-list/hooks/useItemsListExtraActions\";\nimport { ResourceItemsSource } from \"@/components/items-list/classes/ItemsSource\";\nimport { UrlStateStorage } from \"@/components/items-list/classes/StateStorage\";\n\nimport * as Sidebar from \"@/components/items-list/components/Sidebar\";\nimport ItemsTable, { Columns } from \"@/components/items-list/components/ItemsTable\";\n\nimport Layout from \"@/components/layouts/ContentWithSidebar\";\n\nimport { Query } from \"@/services/query\";\nimport { clientConfig, currentUser } from \"@/services/auth\";\nimport location from \"@/services/location\";\nimport routes from \"@/services/routes\";\n\nimport QueriesListEmptyState from \"./QueriesListEmptyState\";\n\nimport \"./queries-list.css\";\n\nconst sidebarMenu = [\n  {\n    key: \"all\",\n    href: \"queries\",\n    title: \"All Queries\",\n    icon: () => <Sidebar.MenuIcon icon=\"fa fa-code\" />,\n  },\n  {\n    key: \"my\",\n    href: \"queries/my\",\n    title: \"My Queries\",\n    icon: () => <Sidebar.ProfileImage user={currentUser} />,\n  },\n  {\n    key: \"favorites\",\n    href: \"queries/favorites\",\n    title: \"Favorites\",\n    icon: () => <Sidebar.MenuIcon icon=\"fa fa-star\" />,\n  },\n  {\n    key: \"archive\",\n    href: \"queries/archive\",\n    title: \"Archived\",\n    icon: () => <Sidebar.MenuIcon icon=\"fa fa-archive\" />,\n  },\n];\n\nconst listColumns = [\n  Columns.favorites({ className: \"p-r-0\" }),\n  Columns.custom.sortable(\n    (text, item) => (\n      <React.Fragment>\n        <Link className=\"table-main-title\" href={\"queries/\" + item.id}>\n          {item.name}\n        </Link>\n        <QueryTagsControl className=\"d-block\" tags={item.tags} isDraft={item.is_draft} isArchived={item.is_archived} />\n      </React.Fragment>\n    ),\n    {\n      title: \"Name\",\n      field: \"name\",\n      width: null,\n    }\n  ),\n  Columns.custom((text, item) => item.user.name, { title: \"Created By\", width: \"1%\" }),\n  Columns.dateTime.sortable({ title: \"Created At\", field: \"created_at\", width: \"1%\" }),\n  Columns.dateTime.sortable({\n    title: \"Last Executed At\",\n    field: \"retrieved_at\",\n    orderByField: \"executed_at\",\n    width: \"1%\",\n  }),\n  Columns.custom.sortable((text, item) => <SchedulePhrase schedule={item.schedule} isNew={item.isNew()} />, {\n    title: \"Refresh Schedule\",\n    field: \"schedule\",\n    width: \"1%\",\n  }),\n];\n\nfunction QueriesListExtraActions(props) {\n  return <DynamicComponent name=\"QueriesList.Actions\" {...props} />;\n}\n\nfunction QueriesList({ controller }) {\n  const controllerRef = useRef();\n  controllerRef.current = controller;\n\n  const updateSearch = useCallback(\n    (searchTemm) => {\n      controller.updateSearch(searchTemm, { isServerSideFTS: !clientConfig.multiByteSearchEnabled });\n    },\n    [controller]\n  );\n\n  useEffect(() => {\n    const unlistenLocationChanges = location.listen((unused, action) => {\n      const searchTerm = location.search.q || \"\";\n      if (action === \"PUSH\" && searchTerm !== controllerRef.current.searchTerm) {\n        updateSearch(searchTerm);\n      }\n    });\n\n    return () => {\n      unlistenLocationChanges();\n    };\n  }, [updateSearch]);\n\n  let usedListColumns = listColumns;\n  if (controller.params.currentPage === \"favorites\") {\n    usedListColumns = [\n      ...usedListColumns,\n      Columns.dateTime.sortable({ title: \"Starred At\", field: \"starred_at\", width: \"1%\" }),\n    ];\n  }\n  const {\n    areExtraActionsAvailable,\n    listColumns: tableColumns,\n    Component: ExtraActionsComponent,\n    selectedItems,\n  } = useItemsListExtraActions(controller, usedListColumns, QueriesListExtraActions);\n\n  return (\n    <div className=\"page-queries-list\">\n      <div className=\"container\">\n        <PageHeader\n          title={controller.params.pageTitle}\n          actions={\n            currentUser.hasPermission(\"create_query\") ? (\n              <Link.Button block type=\"primary\" href=\"queries/new\">\n                <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n                New Query\n              </Link.Button>\n            ) : null\n          }\n        />\n        <Layout>\n          <Layout.Sidebar className=\"m-b-0\">\n            <Sidebar.SearchInput\n              placeholder=\"Search Queries...\"\n              label=\"Search queries\"\n              value={controller.searchTerm}\n              onChange={updateSearch}\n            />\n            <Sidebar.Menu items={sidebarMenu} selected={controller.params.currentPage} />\n            <Sidebar.Tags url=\"api/queries/tags\" onChange={controller.updateSelectedTags} showUnselectAll />\n          </Layout.Sidebar>\n          <Layout.Content>\n            {controller.isLoaded && controller.isEmpty ? (\n              <QueriesListEmptyState\n                page={controller.params.currentPage}\n                searchTerm={controller.searchTerm}\n                selectedTags={controller.selectedTags}\n              />\n            ) : (\n              <React.Fragment>\n                <div className={cx({ \"m-b-10\": areExtraActionsAvailable })}>\n                  <ExtraActionsComponent selectedItems={selectedItems} />\n                </div>\n                <div className=\"bg-white tiled table-responsive\">\n                  <ItemsTable\n                    items={controller.pageItems}\n                    loading={!controller.isLoaded}\n                    columns={tableColumns}\n                    orderByField={controller.orderByField}\n                    orderByReverse={controller.orderByReverse}\n                    toggleSorting={controller.toggleSorting}\n                    setSorting={controller.setSorting}\n                  />\n                  <Paginator\n                    showPageSizeSelect\n                    totalCount={controller.totalItemsCount}\n                    pageSize={controller.itemsPerPage}\n                    onPageSizeChange={(itemsPerPage) => controller.updatePagination({ itemsPerPage })}\n                    page={controller.page}\n                    onChange={(page) => controller.updatePagination({ page })}\n                  />\n                </div>\n              </React.Fragment>\n            )}\n          </Layout.Content>\n        </Layout>\n      </div>\n    </div>\n  );\n}\n\nQueriesList.propTypes = {\n  controller: ControllerType.isRequired,\n};\n\nconst QueriesListPage = itemsList(\n  QueriesList,\n  () =>\n    new ResourceItemsSource({\n      getResource({ params: { currentPage } }) {\n        return {\n          all: Query.query.bind(Query),\n          my: Query.myQueries.bind(Query),\n          favorites: Query.favorites.bind(Query),\n          archive: Query.archive.bind(Query),\n        }[currentPage];\n      },\n      getItemProcessor() {\n        return (item) => new Query(item);\n      },\n    }),\n  ({ ...props }) => new UrlStateStorage({ orderByField: props.orderByField ?? \"created_at\", orderByReverse: true })\n);\n\nroutes.register(\n  \"Queries.List\",\n  routeWithUserSession({\n    path: \"/queries\",\n    title: \"Queries\",\n    render: (pageProps) => <QueriesListPage {...pageProps} currentPage=\"all\" />,\n  })\n);\nroutes.register(\n  \"Queries.Favorites\",\n  routeWithUserSession({\n    path: \"/queries/favorites\",\n    title: \"Favorite Queries\",\n    render: (pageProps) => <QueriesListPage {...pageProps} currentPage=\"favorites\" orderByField=\"starred_at\" />,\n  })\n);\nroutes.register(\n  \"Queries.Archived\",\n  routeWithUserSession({\n    path: \"/queries/archive\",\n    title: \"Archived Queries\",\n    render: (pageProps) => <QueriesListPage {...pageProps} currentPage=\"archive\" />,\n  })\n);\nroutes.register(\n  \"Queries.My\",\n  routeWithUserSession({\n    path: \"/queries/my\",\n    title: \"My Queries\",\n    render: (pageProps) => <QueriesListPage {...pageProps} currentPage=\"my\" />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/queries-list/QueriesListEmptyState.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Link from \"@/components/Link\";\nimport BigMessage from \"@/components/BigMessage\";\nimport NoTaggedObjectsFound from \"@/components/NoTaggedObjectsFound\";\nimport EmptyState, { EmptyStateHelpMessage } from \"@/components/empty-state/EmptyState\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { currentUser } from \"@/services/auth\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\n\nexport default function QueriesListEmptyState({ page, searchTerm, selectedTags }) {\n  if (searchTerm !== \"\") {\n    return <BigMessage message=\"Sorry, we couldn't find anything.\" icon=\"fa-search\" />;\n  }\n  if (selectedTags.length > 0) {\n    return <NoTaggedObjectsFound objectType=\"queries\" tags={selectedTags} />;\n  }\n  switch (page) {\n    case \"favorites\":\n      return <BigMessage message=\"Mark queries as Favorite to list them here.\" icon=\"fa-star\" />;\n    case \"archive\":\n      return <BigMessage message=\"Archived queries will be listed here.\" icon=\"fa-archive\" />;\n    case \"my\":\n      const my_msg = currentUser.hasPermission(\"create_query\") ? (\n        <span>\n          <Link.Button href=\"queries/new\" type=\"primary\" size=\"small\">\n            Create your first query!\n          </Link.Button>{\" \"}\n          <HelpTrigger className=\"f-13\" type=\"QUERIES\" showTooltip={false}>\n            Need help?\n          </HelpTrigger>\n        </span>\n      ) : (\n        <span>Sorry, we couldn't find anything.</span>\n      );\n      return <BigMessage icon=\"fa-search\">{my_msg}</BigMessage>;\n    default:\n      return (\n        <DynamicComponent name=\"QueriesList.EmptyState\">\n          <EmptyState\n            icon=\"fa fa-code\"\n            illustration=\"query\"\n            description=\"Getting the data from your datasources.\"\n            helpMessage={<EmptyStateHelpMessage helpTriggerType=\"QUERIES\" />}\n          />\n        </DynamicComponent>\n      );\n  }\n}\n\nQueriesListEmptyState.propTypes = {\n  page: PropTypes.string.isRequired,\n  searchTerm: PropTypes.string.isRequired,\n  selectedTags: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types\n};\n"
  },
  {
    "path": "client/app/pages/queries-list/queries-list.css",
    "content": ".search input[type=\"text\"],\n.search button {\n  height: 35px;\n}\n\n\n/* same rule as for sidebar */\n@media (max-width: 990px) {\n  .page-queries-list .page-header-actions {\n    width: auto;\n  }\n}\n"
  },
  {
    "path": "client/app/pages/query-snippets/QuerySnippetsList.jsx",
    "content": "import { get } from \"lodash\";\nimport React from \"react\";\n\nimport Button from \"antd/lib/button\";\nimport Modal from \"antd/lib/modal\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\nimport Paginator from \"@/components/Paginator\";\nimport QuerySnippetDialog from \"@/components/query-snippets/QuerySnippetDialog\";\n\nimport { wrap as itemsList, ControllerType } from \"@/components/items-list/ItemsList\";\nimport { ResourceItemsSource } from \"@/components/items-list/classes/ItemsSource\";\nimport { StateStorage } from \"@/components/items-list/classes/StateStorage\";\n\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport ItemsTable, { Columns } from \"@/components/items-list/components/ItemsTable\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\nimport PlainButton from \"@/components/PlainButton\";\n\nimport QuerySnippet from \"@/services/query-snippet\";\nimport { currentUser } from \"@/services/auth\";\nimport { policy } from \"@/services/policy\";\nimport notification from \"@/services/notification\";\nimport routes from \"@/services/routes\";\n\nimport \"./QuerySnippetsList.less\";\n\nconst canEditQuerySnippet = querySnippet => currentUser.isAdmin || currentUser.id === get(querySnippet, \"user.id\");\n\nclass QuerySnippetsList extends React.Component {\n  static propTypes = {\n    controller: ControllerType.isRequired,\n  };\n\n  listColumns = [\n    Columns.custom.sortable(\n      (text, querySnippet) => (\n        <PlainButton type=\"link\" className=\"table-main-title\" onClick={() => this.showSnippetDialog(querySnippet)}>\n          {querySnippet.trigger}\n        </PlainButton>\n      ),\n      {\n        title: \"Trigger\",\n        field: \"trigger\",\n        className: \"text-nowrap\",\n      }\n    ),\n    Columns.custom.sortable(text => text, {\n      title: \"Description\",\n      field: \"description\",\n      className: \"text-nowrap\",\n    }),\n    Columns.custom(snippet => <code className=\"snippet-content\">{snippet}</code>, {\n      title: \"Snippet\",\n      field: \"snippet\",\n    }),\n    Columns.avatar({ field: \"user\", className: \"p-l-0 p-r-0\" }, name => `Created by ${name}`),\n    Columns.date.sortable({\n      title: \"Created At\",\n      field: \"created_at\",\n      className: \"text-nowrap\",\n      width: \"1%\",\n    }),\n    Columns.custom(\n      (text, querySnippet) =>\n        canEditQuerySnippet(querySnippet) && (\n          <Button type=\"danger\" className=\"w-100\" onClick={e => this.deleteQuerySnippet(e, querySnippet)}>\n            Delete\n          </Button>\n        ),\n      {\n        width: \"1%\",\n      }\n    ),\n  ];\n\n  componentDidMount() {\n    const { isNewOrEditPage, querySnippetId } = this.props.controller.params;\n\n    if (isNewOrEditPage) {\n      if (querySnippetId === \"new\") {\n        if (policy.isCreateQuerySnippetEnabled()) {\n          this.showSnippetDialog();\n        } else {\n          navigateTo(\"query_snippets\", true);\n        }\n      } else {\n        QuerySnippet.get({ id: querySnippetId })\n          .then(this.showSnippetDialog)\n          .catch(error => {\n            this.props.controller.handleError(error);\n          });\n      }\n    }\n  }\n\n  saveQuerySnippet = querySnippet => {\n    const saveSnippet = querySnippet.id ? QuerySnippet.save : QuerySnippet.create;\n    return saveSnippet(querySnippet);\n  };\n\n  deleteQuerySnippet = (event, querySnippet) => {\n    Modal.confirm({\n      title: \"Delete Query Snippet\",\n      content: \"Are you sure you want to delete this query snippet?\",\n      okText: \"Yes\",\n      okType: \"danger\",\n      cancelText: \"No\",\n      onOk: () => {\n        QuerySnippet.delete(querySnippet)\n          .then(() => {\n            notification.success(\"Query snippet deleted successfully.\");\n            this.props.controller.update();\n          })\n          .catch(() => {\n            notification.error(\"Failed deleting query snippet.\");\n          });\n      },\n    });\n  };\n\n  showSnippetDialog = (querySnippet = null) => {\n    const canSave = !querySnippet || canEditQuerySnippet(querySnippet);\n    navigateTo(\"query_snippets/\" + get(querySnippet, \"id\", \"new\"), true);\n    const goToSnippetsList = () => navigateTo(\"query_snippets\", true);\n    QuerySnippetDialog.showModal({\n      querySnippet,\n      readOnly: !canSave,\n    })\n      .onClose(querySnippet =>\n        this.saveQuerySnippet(querySnippet).then(() => {\n          this.props.controller.update();\n          goToSnippetsList();\n        })\n      )\n      .onDismiss(goToSnippetsList);\n  };\n\n  render() {\n    const { controller } = this.props;\n\n    return (\n      <div>\n        <div className=\"m-b-15\">\n          <Button\n            type=\"primary\"\n            onClick={() => this.showSnippetDialog()}\n            disabled={!policy.isCreateQuerySnippetEnabled()}>\n            <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n            New Query Snippet\n          </Button>\n        </div>\n\n        {!controller.isLoaded && <LoadingState className=\"\" />}\n        {controller.isLoaded && controller.isEmpty && (\n          <div className=\"text-center\">\n            There are no query snippets yet.\n            {policy.isCreateQuerySnippetEnabled() && (\n              <div className=\"m-t-5\">\n                <PlainButton type=\"link\" onClick={() => this.showSnippetDialog()}>\n                  Click here\n                </PlainButton>{\" \"}\n                to add one.\n              </div>\n            )}\n          </div>\n        )}\n        {controller.isLoaded && !controller.isEmpty && (\n          <div className=\"table-responsive query-snippets-table\">\n            <ItemsTable\n              items={controller.pageItems}\n              columns={this.listColumns}\n              context={this.actions}\n              orderByField={controller.orderByField}\n              orderByReverse={controller.orderByReverse}\n              toggleSorting={controller.toggleSorting}\n            />\n            <Paginator\n              showPageSizeSelect\n              totalCount={controller.totalItemsCount}\n              pageSize={controller.itemsPerPage}\n              onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}\n              page={controller.page}\n              onChange={page => controller.updatePagination({ page })}\n            />\n          </div>\n        )}\n      </div>\n    );\n  }\n}\n\nconst QuerySnippetsListPage = wrapSettingsTab(\n  \"QuerySnippets.List\",\n  {\n    permission: \"create_query\",\n    title: \"Query Snippets\",\n    path: \"query_snippets\",\n    order: 5,\n  },\n  itemsList(\n    QuerySnippetsList,\n    () =>\n      new ResourceItemsSource({\n        isPlainList: true,\n        getRequest() {\n          return {};\n        },\n        getResource() {\n          return QuerySnippet.query.bind(QuerySnippet);\n        },\n      }),\n    () => new StateStorage({ orderByField: \"trigger\", itemsPerPage: 10 })\n  )\n);\n\nroutes.register(\n  \"QuerySnippets.List\",\n  routeWithUserSession({\n    path: \"/query_snippets\",\n    title: \"Query Snippets\",\n    render: pageProps => <QuerySnippetsListPage {...pageProps} currentPage=\"query_snippets\" />,\n  })\n);\nroutes.register(\n  \"QuerySnippets.NewOrEdit\",\n  routeWithUserSession({\n    path: \"/query_snippets/:querySnippetId\",\n    title: \"Query Snippets\",\n    render: pageProps => <QuerySnippetsListPage {...pageProps} currentPage=\"query_snippets\" isNewOrEditPage />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/query-snippets/QuerySnippetsList.less",
    "content": ".snippet-content {\n  max-width: 500px;\n  max-height: 56px;\n  overflow: hidden;\n  white-space: pre-wrap;\n  /* autoprefixer: off */\n  display: -webkit-box;\n  -webkit-line-clamp: 3;\n  -webkit-box-orient: vertical;\n}\n\n.query-snippets-table {\n  table {\n    height: 1px;\n  }\n\n  .ant-table-row {\n    height: 100%;\n  }\n\n  .ant-table-cell {\n    height: 100%;\n\n    & > .table-main-title {\n      display: inline-flex;\n      align-items: center;\n      height: 100%;\n      width: 100%;\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/pages/settings/OrganizationSettings.jsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport Button from \"antd/lib/button\";\nimport Form from \"antd/lib/form\";\nimport Skeleton from \"antd/lib/skeleton\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\n\nimport routes from \"@/services/routes\";\nimport { getHorizontalFormProps, getHorizontalFormItemWithoutLabelProps } from \"@/styles/formStyle\";\n\nimport useOrganizationSettings from \"./hooks/useOrganizationSettings\";\nimport GeneralSettings from \"./components/GeneralSettings\";\nimport AuthSettings from \"./components/AuthSettings\";\n\nfunction OrganizationSettings({ onError }) {\n  const { settings, currentValues, isLoading, isSaving, handleSubmit, handleChange } = useOrganizationSettings(onError);\n  return (\n    <div className=\"row\" data-test=\"OrganizationSettings\">\n      <div className=\"m-r-20 m-l-20\">\n        <Form {...getHorizontalFormProps()} onFinish={handleSubmit}>\n          <GeneralSettings loading={isLoading} settings={settings} values={currentValues} onChange={handleChange} />\n          <AuthSettings loading={isLoading} settings={settings} values={currentValues} onChange={handleChange} />\n          <Form.Item {...getHorizontalFormItemWithoutLabelProps()}>\n            {isLoading ? (\n              <Skeleton.Button active />\n            ) : (\n              <Button type=\"primary\" htmlType=\"submit\" loading={isSaving} data-test=\"OrganizationSettingsSaveButton\">\n                Save\n              </Button>\n            )}\n          </Form.Item>\n        </Form>\n      </div>\n    </div>\n  );\n}\n\nOrganizationSettings.propTypes = {\n  onError: PropTypes.func,\n};\n\nOrganizationSettings.defaultProps = {\n  onError: () => {},\n};\n\nconst OrganizationSettingsPage = wrapSettingsTab(\n  \"Settings.Organization\",\n  {\n    permission: \"admin\",\n    title: \"General\",\n    path: \"settings/general\",\n    order: 6,\n  },\n  OrganizationSettings\n);\n\nroutes.register(\n  \"Settings.Organization\",\n  routeWithUserSession({\n    path: \"/settings/general\",\n    title: \"General Settings\",\n    render: pageProps => <OrganizationSettingsPage {...pageProps} />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/settings/components/AuthSettings/GoogleLoginSettings.jsx",
    "content": "import { isEmpty, join } from \"lodash\";\nimport React from \"react\";\nimport Form from \"antd/lib/form\";\nimport Select from \"antd/lib/select\";\nimport Alert from \"antd/lib/alert\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { clientConfig } from \"@/services/auth\";\nimport { SettingsEditorPropTypes, SettingsEditorDefaultProps } from \"../prop-types\";\n\nexport default function GoogleLoginSettings(props) {\n  const { values, onChange } = props;\n\n  if (!clientConfig.googleLoginEnabled) {\n    return null;\n  }\n\n  return (\n    <DynamicComponent name=\"OrganizationSettings.GoogleLoginSettings\" {...props}>\n      <h4>Google Login</h4>\n      <Form.Item label=\"Allowed Google Apps Domains\">\n        <Select\n          mode=\"tags\"\n          value={values.auth_google_apps_domains}\n          onChange={value => onChange({ auth_google_apps_domains: value })}\n        />\n        {!isEmpty(values.auth_google_apps_domains) && (\n          <Alert\n            message={\n              <p>\n                Any user registered with a <strong>{join(values.auth_google_apps_domains, \", \")}</strong> Google Apps\n                account will be able to login. If they don't have an existing user, a new user will be created and join\n                the <strong>Default</strong> group.\n              </p>\n            }\n            className=\"m-t-15\"\n          />\n        )}\n      </Form.Item>\n    </DynamicComponent>\n  );\n}\n\nGoogleLoginSettings.propTypes = SettingsEditorPropTypes;\n\nGoogleLoginSettings.defaultProps = SettingsEditorDefaultProps;\n"
  },
  {
    "path": "client/app/pages/settings/components/AuthSettings/PasswordLoginSettings.jsx",
    "content": "import React from \"react\";\nimport Alert from \"antd/lib/alert\";\nimport Form from \"antd/lib/form\";\nimport Checkbox from \"antd/lib/checkbox\";\nimport Tooltip from \"@/components/Tooltip\";\nimport Skeleton from \"antd/lib/skeleton\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { clientConfig } from \"@/services/auth\";\nimport { SettingsEditorPropTypes, SettingsEditorDefaultProps } from \"../prop-types\";\n\nexport default function PasswordLoginSettings(props) {\n  const { settings, values, onChange, loading } = props;\n\n  const isTheOnlyAuthMethod =\n    !clientConfig.googleLoginEnabled && !clientConfig.ldapLoginEnabled && !values.auth_saml_enabled;\n\n  return (\n    <DynamicComponent name=\"OrganizationSettings.PasswordLoginSettings\" {...props}>\n      {!loading && !settings.auth_password_login_enabled && (\n        <Alert\n          message=\"Password based login is currently disabled and users will\n            be able to login only with the enabled SSO options.\"\n          type=\"warning\"\n          className=\"m-t-15 m-b-15\"\n        />\n      )}\n      <Form.Item label=\"Password Login\">\n        {loading ? (\n          <Skeleton title={{ width: 300 }} paragraph={false} active />\n        ) : (\n          <Checkbox\n            checked={values.auth_password_login_enabled}\n            disabled={isTheOnlyAuthMethod}\n            onChange={e => onChange({ auth_password_login_enabled: e.target.checked })}>\n            <Tooltip\n              title={\n                isTheOnlyAuthMethod ? \"Password login can be disabled only if another login method is enabled.\" : null\n              }\n              placement=\"right\">\n              Password Login Enabled\n            </Tooltip>\n          </Checkbox>\n        )}\n      </Form.Item>\n    </DynamicComponent>\n  );\n}\n\nPasswordLoginSettings.propTypes = SettingsEditorPropTypes;\n\nPasswordLoginSettings.defaultProps = SettingsEditorDefaultProps;\n"
  },
  {
    "path": "client/app/pages/settings/components/AuthSettings/SAMLSettings.jsx",
    "content": "import React from \"react\";\nimport Form from \"antd/lib/form\";\nimport Input from \"antd/lib/input\";\nimport Skeleton from \"antd/lib/skeleton\";\nimport Radio from \"antd/lib/radio\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { SettingsEditorPropTypes, SettingsEditorDefaultProps } from \"../prop-types\";\n\nexport default function SAMLSettings(props) {\n  const { values, onChange, loading } = props;\n\n  const onChangeEnabledStatus = e => {\n    const updates = { auth_saml_enabled: !!e.target.value };\n    if (e.target.value) {\n      updates.auth_saml_type = e.target.value;\n    }\n    onChange(updates);\n  };\n\n  return (\n    <DynamicComponent name=\"OrganizationSettings.SAMLSettings\" {...props}>\n      <h4>SAML</h4>\n      <Form.Item label=\"SAML Enabled\">\n        {loading ? (\n          <Skeleton title={{ width: 300 }} paragraph={false} active />\n        ) : (\n          <Radio.Group\n            onChange={onChangeEnabledStatus}\n            value={values.auth_saml_enabled && (values.auth_saml_type || \"dynamic\")}>\n            <Radio value={false}>Disabled</Radio>\n            <Radio value={\"static\"}>Enabled (Static)</Radio>\n            <Radio value={\"dynamic\"}>Enabled (Dynamic)</Radio>\n          </Radio.Group>\n        )}\n      </Form.Item>\n      {values.auth_saml_enabled && (\n        <>\n          {values.auth_saml_type === \"static\" && (\n            <>\n              <Form.Item label=\"SAML Single Sign-on URL\">\n                <Input\n                  value={values.auth_saml_sso_url}\n                  onChange={e => onChange({ auth_saml_sso_url: e.target.value })}\n                />\n              </Form.Item>\n              <Form.Item label=\"SAML Entity ID\">\n                <Input\n                  value={values.auth_saml_entity_id}\n                  onChange={e => onChange({ auth_saml_entity_id: e.target.value })}\n                />\n              </Form.Item>\n              <Form.Item label=\"SAML x509 cert\">\n                <Input\n                  value={values.auth_saml_x509_cert}\n                  onChange={e => onChange({ auth_saml_x509_cert: e.target.value })}\n                />\n              </Form.Item>\n            </>\n          )}\n          {values.auth_saml_type === \"dynamic\" && (\n            <>\n              <Form.Item label=\"SAML Metadata URL\">\n                <Input\n                  value={values.auth_saml_metadata_url}\n                  onChange={e => onChange({ auth_saml_metadata_url: e.target.value })}\n                />\n              </Form.Item>\n              <Form.Item label=\"SAML Entity ID\">\n                <Input\n                  value={values.auth_saml_entity_id}\n                  onChange={e => onChange({ auth_saml_entity_id: e.target.value })}\n                />\n              </Form.Item>\n              <Form.Item label=\"SAML NameID Format\">\n                <Input\n                  value={values.auth_saml_nameid_format}\n                  onChange={e => onChange({ auth_saml_nameid_format: e.target.value })}\n                />\n              </Form.Item>\n            </>\n          )}\n        </>\n      )}\n    </DynamicComponent>\n  );\n}\n\nSAMLSettings.propTypes = SettingsEditorPropTypes;\n\nSAMLSettings.defaultProps = SettingsEditorDefaultProps;\n"
  },
  {
    "path": "client/app/pages/settings/components/AuthSettings/index.jsx",
    "content": "import React, { useCallback } from \"react\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { clientConfig } from \"@/services/auth\";\nimport { SettingsEditorPropTypes, SettingsEditorDefaultProps } from \"../prop-types\";\n\nimport PasswordLoginSettings from \"./PasswordLoginSettings\";\nimport GoogleLoginSettings from \"./GoogleLoginSettings\";\nimport SAMLSettings from \"./SAMLSettings\";\n\nexport default function AuthSettings(props) {\n  const { values, onChange } = props;\n  const handleChange = useCallback(\n    changes => {\n      const allSettings = { ...values, ...changes };\n      const allAuthMethodsDisabled =\n        !clientConfig.googleLoginEnabled && !clientConfig.ldapLoginEnabled && !allSettings.auth_saml_enabled;\n      if (allAuthMethodsDisabled) {\n        changes = { ...changes, auth_password_login_enabled: true };\n      }\n      onChange(changes);\n    },\n    [values, onChange]\n  );\n\n  return (\n    <DynamicComponent name=\"OrganizationSettings.AuthSettings\" {...props}>\n      <h3 className=\"m-t-0\">\n        Authentication <HelpTrigger type=\"AUTHENTICATION_OPTIONS\" />\n      </h3>\n      <hr />\n      <PasswordLoginSettings {...props} onChange={handleChange} />\n      <GoogleLoginSettings {...props} onChange={handleChange} />\n      <SAMLSettings {...props} onChange={handleChange} />\n    </DynamicComponent>\n  );\n}\n\nAuthSettings.propTypes = SettingsEditorPropTypes;\nAuthSettings.defaultProps = SettingsEditorDefaultProps;\n"
  },
  {
    "path": "client/app/pages/settings/components/GeneralSettings/BeaconConsentSettings.jsx",
    "content": "import React from \"react\";\nimport Form from \"antd/lib/form\";\nimport Checkbox from \"antd/lib/checkbox\";\nimport Skeleton from \"antd/lib/skeleton\";\nimport HelpTrigger from \"@/components/HelpTrigger\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { SettingsEditorPropTypes, SettingsEditorDefaultProps } from \"../prop-types\";\n\nexport default function BeaconConsentSettings(props) {\n  const { values, onChange, loading } = props;\n\n  return (\n    <DynamicComponent name=\"OrganizationSettings.BeaconConsentSettings\" {...props}>\n      <Form.Item\n        label={\n          <span>\n            Anonymous Usage Data Sharing\n            <HelpTrigger className=\"m-l-5 m-r-5\" type=\"USAGE_DATA_SHARING\" />\n          </span>\n        }\n      >\n        {loading ? (\n          <Skeleton title={{ width: 300 }} paragraph={false} active />\n        ) : (\n          <Checkbox\n            name=\"beacon_consent\"\n            checked={values.beacon_consent}\n            onChange={(e) => onChange({ beacon_consent: e.target.checked })}\n          >\n            Help Redash improve by automatically sending anonymous usage data\n          </Checkbox>\n        )}\n      </Form.Item>\n    </DynamicComponent>\n  );\n}\n\nBeaconConsentSettings.propTypes = SettingsEditorPropTypes;\n\nBeaconConsentSettings.defaultProps = SettingsEditorDefaultProps;\n"
  },
  {
    "path": "client/app/pages/settings/components/GeneralSettings/FeatureFlagsSettings.jsx",
    "content": "import React from \"react\";\nimport Checkbox from \"antd/lib/checkbox\";\nimport Form from \"antd/lib/form\";\nimport Row from \"antd/lib/row\";\nimport Skeleton from \"antd/lib/skeleton\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { SettingsEditorPropTypes, SettingsEditorDefaultProps } from \"../prop-types\";\n\nexport default function FeatureFlagsSettings(props) {\n  const { values, onChange, loading } = props;\n\n  return (\n    <DynamicComponent name=\"OrganizationSettings.FeatureFlagsSettings\" {...props}>\n      <Form.Item label=\"Feature Flags\">\n        {loading ? (\n          <>\n            <Row>\n              <Skeleton title={false} paragraph={{ width: [300, 300, 300], rows: 3 }} active />\n            </Row>\n          </>\n        ) : (\n          <>\n            <DynamicComponent name=\"OrganizationSettings.FeatureFlagsSettings.PermissionsControl\" {...props}>\n              <Row>\n                <Checkbox\n                  name=\"feature_show_permissions_control\"\n                  checked={values.feature_show_permissions_control}\n                  onChange={e => onChange({ feature_show_permissions_control: e.target.checked })}>\n                  Enable experimental multiple owners support\n                </Checkbox>\n              </Row>\n            </DynamicComponent>\n            <Row>\n              <Checkbox\n                name=\"send_email_on_failed_scheduled_queries\"\n                checked={values.send_email_on_failed_scheduled_queries}\n                onChange={e => onChange({ send_email_on_failed_scheduled_queries: e.target.checked })}>\n                Email query owners when scheduled queries fail\n              </Checkbox>\n            </Row>\n            <Row>\n              <Checkbox\n                name=\"multi_byte_search_enabled\"\n                checked={values.multi_byte_search_enabled}\n                onChange={e => onChange({ multi_byte_search_enabled: e.target.checked })}>\n                Enable multi-byte (Chinese, Japanese, and Korean) search for query names and descriptions (slower)\n              </Checkbox>\n            </Row>\n          </>\n        )}\n      </Form.Item>\n    </DynamicComponent>\n  );\n}\n\nFeatureFlagsSettings.propTypes = SettingsEditorPropTypes;\n\nFeatureFlagsSettings.defaultProps = SettingsEditorDefaultProps;\n"
  },
  {
    "path": "client/app/pages/settings/components/GeneralSettings/FormatSettings.jsx",
    "content": "import React from \"react\";\nimport { SettingsEditorPropTypes, SettingsEditorDefaultProps } from \"../prop-types\";\nimport Form from \"antd/lib/form\";\nimport Select from \"antd/lib/select\";\nimport Skeleton from \"antd/lib/skeleton\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { clientConfig } from \"@/services/auth\";\n\nexport default function FormatSettings(props) {\n  const { values, onChange, loading } = props;\n\n  return (\n    <DynamicComponent name=\"OrganizationSettings.FormatSettings\" {...props}>\n      <Form.Item label=\"Date Format\">\n        {loading ? (\n          <Skeleton.Input style={{ width: 300 }} active />\n        ) : (\n          <Select\n            value={values.date_format}\n            onChange={value => onChange({ date_format: value })}\n            data-test=\"DateFormatSelect\">\n            {clientConfig.dateFormatList.map(dateFormat => (\n              <Select.Option key={dateFormat} data-test={`DateFormatSelect:${dateFormat}`}>\n                {dateFormat}\n              </Select.Option>\n            ))}\n          </Select>\n        )}\n      </Form.Item>\n      <Form.Item label=\"Time Format\">\n        {loading ? (\n          <Skeleton.Input style={{ width: 300 }} active />\n        ) : (\n          <Select\n            value={values.time_format}\n            onChange={value => onChange({ time_format: value })}\n            data-test=\"TimeFormatSelect\">\n            {clientConfig.timeFormatList.map(timeFormat => (\n              <Select.Option key={timeFormat}>{timeFormat}</Select.Option>\n            ))}\n          </Select>\n        )}\n      </Form.Item>\n    </DynamicComponent>\n  );\n}\n\nFormatSettings.propTypes = SettingsEditorPropTypes;\n\nFormatSettings.defaultProps = SettingsEditorDefaultProps;\n"
  },
  {
    "path": "client/app/pages/settings/components/GeneralSettings/PlotlySettings.jsx",
    "content": "import React from \"react\";\nimport Checkbox from \"antd/lib/checkbox\";\nimport Form from \"antd/lib/form\";\nimport Skeleton from \"antd/lib/skeleton\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { SettingsEditorPropTypes, SettingsEditorDefaultProps } from \"../prop-types\";\n\nexport default function PlotlySettings(props) {\n  const { values, onChange, loading } = props;\n\n  return (\n    <DynamicComponent name=\"OrganizationSettings.PlotlySettings\" {...props}>\n      <Form.Item label=\"Chart Visualization\">\n        {loading ? (\n          <Skeleton title={{ width: 300 }} paragraph={false} active />\n        ) : (\n          <Checkbox\n            name=\"hide_plotly_mode_bar\"\n            checked={values.hide_plotly_mode_bar}\n            onChange={e => onChange({ hide_plotly_mode_bar: e.target.checked })}>\n            Hide Plotly mode bar\n          </Checkbox>\n        )}\n      </Form.Item>\n    </DynamicComponent>\n  );\n}\n\nPlotlySettings.propTypes = SettingsEditorPropTypes;\n\nPlotlySettings.defaultProps = SettingsEditorDefaultProps;\n"
  },
  {
    "path": "client/app/pages/settings/components/GeneralSettings/index.jsx",
    "content": "import React from \"react\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\n\nimport FormatSettings from \"./FormatSettings\";\nimport PlotlySettings from \"./PlotlySettings\";\nimport FeatureFlagsSettings from \"./FeatureFlagsSettings\";\nimport BeaconConsentSettings from \"./BeaconConsentSettings\";\n\nexport default function GeneralSettings(props) {\n  return (\n    <DynamicComponent name=\"OrganizationSettings.GeneralSettings\" {...props}>\n      <h3 className=\"m-t-0\">General</h3>\n      <hr />\n      <FormatSettings {...props} />\n      <PlotlySettings {...props} />\n      <FeatureFlagsSettings {...props} />\n      <BeaconConsentSettings {...props} />\n    </DynamicComponent>\n  );\n}\n"
  },
  {
    "path": "client/app/pages/settings/components/prop-types.js",
    "content": "import PropTypes from \"prop-types\";\n\nexport const SettingsEditorPropTypes = {\n  settings: PropTypes.object,\n  values: PropTypes.object,\n  onChange: PropTypes.func, // (key, value) => void\n  loading: PropTypes.bool,\n};\n\nexport const SettingsEditorDefaultProps = {\n  settings: {},\n  values: {},\n  onChange: () => {},\n  loading: false,\n};\n"
  },
  {
    "path": "client/app/pages/settings/hooks/useOrganizationSettings.js",
    "content": "import { get } from \"lodash\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport recordEvent from \"@/services/recordEvent\";\nimport OrgSettings from \"@/services/organizationSettings\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\nimport { updateClientConfig } from \"@/services/auth\";\n\nexport default function useOrganizationSettings({ onError }) {\n  const [settings, setSettings] = useState({});\n  const [currentValues, setCurrentValues] = useState({});\n  const [isLoading, setIsLoading] = useState(true);\n  const [isSaving, setIsSaving] = useState(false);\n\n  const handleError = useImmutableCallback(onError);\n\n  useEffect(() => {\n    recordEvent(\"view\", \"page\", \"org_settings\");\n\n    let isCancelled = false;\n\n    OrgSettings.get()\n      .then(response => {\n        if (!isCancelled) {\n          const settings = get(response, \"settings\");\n          setSettings(settings);\n          setCurrentValues({ ...settings });\n          setIsLoading(false);\n        }\n      })\n      .catch(error => {\n        if (!isCancelled) {\n          handleError(error);\n        }\n      });\n\n    return () => {\n      isCancelled = true;\n    };\n  }, [handleError]);\n\n  const handleChange = useCallback(changes => {\n    setCurrentValues(currentValues => ({ ...currentValues, ...changes }));\n  }, []);\n\n  const handleSubmit = useCallback(() => {\n    if (!isSaving) {\n      setIsSaving(true);\n      OrgSettings.save(currentValues)\n        .then(response => {\n          const settings = get(response, \"settings\");\n          setSettings(settings);\n          setCurrentValues({ ...settings });\n          updateClientConfig({\n            dateFormat: currentValues.date_format,\n            timeFormat: currentValues.time_format,\n            dateTimeFormat: `${currentValues.date_format} ${currentValues.time_format}`,\n          });\n        })\n        .catch(handleError)\n        .finally(() => setIsSaving(false));\n    }\n  }, [isSaving, currentValues, handleError]);\n\n  return { settings, currentValues, isLoading, isSaving, handleSubmit, handleChange };\n}\n"
  },
  {
    "path": "client/app/pages/users/UserProfile.jsx",
    "content": "import React, { useState, useEffect } from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport EmailSettingsWarning from \"@/components/EmailSettingsWarning\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\n\nimport User from \"@/services/user\";\nimport { currentUser } from \"@/services/auth\";\nimport routes from \"@/services/routes\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nimport EditableUserProfile from \"./components/EditableUserProfile\";\nimport ReadOnlyUserProfile from \"./components/ReadOnlyUserProfile\";\n\nimport \"./settings.less\";\n\nfunction UserProfile({ userId, onError }) {\n  const [user, setUser] = useState(null);\n\n  const handleError = useImmutableCallback(onError);\n\n  useEffect(() => {\n    let isCancelled = false;\n    User.get({ id: userId || currentUser.id })\n      .then(user => {\n        if (!isCancelled) {\n          setUser(User.convertUserInfo(user));\n        }\n      })\n      .catch(error => {\n        if (!isCancelled) {\n          handleError(error);\n        }\n      });\n\n    return () => {\n      isCancelled = true;\n    };\n  }, [userId, handleError]);\n\n  const canEdit = user && (currentUser.isAdmin || currentUser.id === user.id);\n  return (\n    <React.Fragment>\n      <EmailSettingsWarning featureName=\"invite emails\" className=\"m-b-20\" adminOnly />\n      <div className=\"row\">\n        {!user && <LoadingState className=\"\" />}\n        {user && (\n          <DynamicComponent name=\"UserProfile\" user={user}>\n            {!canEdit && <ReadOnlyUserProfile user={user} />}\n            {canEdit && <EditableUserProfile user={user} />}\n          </DynamicComponent>\n        )}\n      </div>\n    </React.Fragment>\n  );\n}\n\nUserProfile.propTypes = {\n  userId: PropTypes.string,\n  onError: PropTypes.func,\n};\n\nUserProfile.defaultProps = {\n  userId: null, // defaults to `currentUser.id`\n  onError: () => {},\n};\n\nconst UserProfilePage = wrapSettingsTab(\n  \"Users.Account\",\n  {\n    title: \"Account\",\n    path: \"users/me\",\n    order: 7,\n  },\n  UserProfile\n);\n\nroutes.register(\n  \"Users.Account\",\n  routeWithUserSession({\n    path: \"/users/me\",\n    title: \"Account\",\n    render: pageProps => <UserProfilePage {...pageProps} />,\n  })\n);\nroutes.register(\n  \"Users.ViewOrEdit\",\n  routeWithUserSession({\n    path: \"/users/:userId\",\n    title: \"Users\",\n    render: pageProps => <UserProfilePage {...pageProps} />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/users/UsersList.jsx",
    "content": "import { isString, map, get, find } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\n\nimport Button from \"antd/lib/button\";\nimport Modal from \"antd/lib/modal\";\nimport routeWithUserSession from \"@/components/ApplicationArea/routeWithUserSession\";\nimport Link from \"@/components/Link\";\nimport Paginator from \"@/components/Paginator\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { UserPreviewCard } from \"@/components/PreviewCard\";\nimport InputWithCopy from \"@/components/InputWithCopy\";\n\nimport { wrap as itemsList, ControllerType } from \"@/components/items-list/ItemsList\";\nimport { ResourceItemsSource } from \"@/components/items-list/classes/ItemsSource\";\nimport { UrlStateStorage } from \"@/components/items-list/classes/StateStorage\";\n\nimport LoadingState from \"@/components/items-list/components/LoadingState\";\nimport EmptyState from \"@/components/items-list/components/EmptyState\";\nimport * as Sidebar from \"@/components/items-list/components/Sidebar\";\nimport ItemsTable, { Columns } from \"@/components/items-list/components/ItemsTable\";\n\nimport Layout from \"@/components/layouts/ContentWithSidebar\";\nimport wrapSettingsTab from \"@/components/SettingsWrapper\";\n\nimport { currentUser } from \"@/services/auth\";\nimport { policy } from \"@/services/policy\";\nimport User from \"@/services/user\";\nimport navigateTo from \"@/components/ApplicationArea/navigateTo\";\nimport notification from \"@/services/notification\";\nimport { absoluteUrl } from \"@/services/utils\";\nimport routes from \"@/services/routes\";\n\nimport CreateUserDialog from \"./components/CreateUserDialog\";\n\nfunction UsersListActions({ user, enableUser, disableUser, deleteUser }) {\n  if (user.id === currentUser.id) {\n    return null;\n  }\n  if (user.is_invitation_pending) {\n    return (\n      <Button type=\"danger\" className=\"w-100\" onClick={event => deleteUser(event, user)}>\n        Delete\n      </Button>\n    );\n  }\n  return user.is_disabled ? (\n    <Button type=\"primary\" className=\"w-100\" onClick={event => enableUser(event, user)}>\n      Enable\n    </Button>\n  ) : (\n    <Button className=\"w-100\" onClick={event => disableUser(event, user)}>\n      Disable\n    </Button>\n  );\n}\n\nUsersListActions.propTypes = {\n  user: PropTypes.shape({\n    id: PropTypes.number,\n    is_invitation_pending: PropTypes.bool,\n    is_disabled: PropTypes.bool,\n  }).isRequired,\n  enableUser: PropTypes.func.isRequired,\n  disableUser: PropTypes.func.isRequired,\n  deleteUser: PropTypes.func.isRequired,\n};\n\nclass UsersList extends React.Component {\n  static propTypes = {\n    controller: ControllerType.isRequired,\n  };\n\n  sidebarMenu = [\n    {\n      key: \"active\",\n      href: \"users\",\n      title: \"Active Users\",\n    },\n    {\n      key: \"pending\",\n      href: \"users/pending\",\n      title: \"Pending Invitations\",\n    },\n    {\n      key: \"disabled\",\n      href: \"users/disabled\",\n      title: \"Disabled Users\",\n      isAvailable: () => policy.canCreateUser(),\n    },\n  ];\n\n  listColumns = [\n    Columns.custom.sortable((text, user) => <UserPreviewCard user={user} withLink />, {\n      title: \"Name\",\n      field: \"name\",\n      width: null,\n    }),\n    Columns.custom.sortable(\n      (text, user) =>\n        map(user.groups, group => (\n          <Link key={\"group\" + group.id} className=\"label label-tag\" href={\"groups/\" + group.id}>\n            {group.name}\n          </Link>\n        )),\n      {\n        title: \"Groups\",\n        field: \"groups\",\n      }\n    ),\n    Columns.timeAgo.sortable({\n      title: \"Joined\",\n      field: \"created_at\",\n      className: \"text-nowrap\",\n      width: \"1%\",\n    }),\n    Columns.timeAgo.sortable({\n      title: \"Last Active At\",\n      field: \"active_at\",\n      className: \"text-nowrap\",\n      width: \"1%\",\n    }),\n    Columns.custom(\n      (text, user) => (\n        <UsersListActions\n          user={user}\n          enableUser={this.enableUser}\n          disableUser={this.disableUser}\n          deleteUser={this.deleteUser}\n        />\n      ),\n      {\n        width: \"1%\",\n        isAvailable: () => policy.canCreateUser(),\n      }\n    ),\n  ];\n\n  componentDidMount() {\n    if (this.props.controller.params.isNewUserPage) {\n      this.showCreateUserDialog();\n    }\n  }\n\n  createUser = values =>\n    User.create(values)\n      .then(user => {\n        notification.success(\"Saved.\");\n        if (user.invite_link) {\n          Modal.warning({\n            title: \"Email not sent!\",\n            content: (\n              <React.Fragment>\n                <p>\n                  The mail server is not configured, please send the following link to <b>{user.name}</b>:\n                </p>\n                <InputWithCopy value={absoluteUrl(user.invite_link)} aria-label=\"Invite link\" readOnly />\n              </React.Fragment>\n            ),\n          });\n        }\n      })\n      .catch(error => {\n        const message = find([get(error, \"response.data.message\"), get(error, \"message\"), \"Failed saving.\"], isString);\n        return Promise.reject(new Error(message));\n      });\n\n  showCreateUserDialog = () => {\n    if (policy.isCreateUserEnabled()) {\n      const goToUsersList = () => {\n        if (this.props.controller.params.isNewUserPage) {\n          navigateTo(\"users\");\n        }\n      };\n      CreateUserDialog.showModal()\n        .onClose(values =>\n          this.createUser(values).then(() => {\n            this.props.controller.update();\n            goToUsersList();\n          })\n        )\n        .onDismiss(goToUsersList);\n    }\n  };\n\n  enableUser = (event, user) => User.enableUser(user).then(() => this.props.controller.update());\n\n  disableUser = (event, user) => User.disableUser(user).then(() => this.props.controller.update());\n\n  deleteUser = (event, user) => User.deleteUser(user).then(() => this.props.controller.update());\n\n  // eslint-disable-next-line class-methods-use-this\n  renderPageHeader() {\n    if (!policy.canCreateUser()) {\n      return null;\n    }\n    return (\n      <div className=\"m-b-15\">\n        <Button type=\"primary\" disabled={!policy.isCreateUserEnabled()} onClick={this.showCreateUserDialog}>\n          <i className=\"fa fa-plus m-r-5\" aria-hidden=\"true\" />\n          New User\n        </Button>\n        <DynamicComponent name=\"UsersListExtra\" />\n      </div>\n    );\n  }\n\n  render() {\n    const { controller } = this.props;\n    return (\n      <React.Fragment>\n        {this.renderPageHeader()}\n        <Layout>\n          <Layout.Sidebar className=\"m-b-0\">\n            <Sidebar.SearchInput\n              value={controller.searchTerm}\n              onChange={controller.updateSearch}\n              label=\"Search users\"\n            />\n            <Sidebar.Menu items={this.sidebarMenu} selected={controller.params.currentPage} />\n          </Layout.Sidebar>\n          <Layout.Content>\n            {!controller.isLoaded && <LoadingState className=\"\" />}\n            {controller.isLoaded && controller.isEmpty && <EmptyState className=\"\" />}\n            {controller.isLoaded && !controller.isEmpty && (\n              <div className=\"table-responsive\" data-test=\"UserList\">\n                <ItemsTable\n                  items={controller.pageItems}\n                  columns={this.listColumns}\n                  context={this.actions}\n                  orderByField={controller.orderByField}\n                  orderByReverse={controller.orderByReverse}\n                  toggleSorting={controller.toggleSorting}\n                />\n                <Paginator\n                  showPageSizeSelect\n                  totalCount={controller.totalItemsCount}\n                  pageSize={controller.itemsPerPage}\n                  onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}\n                  page={controller.page}\n                  onChange={page => controller.updatePagination({ page })}\n                />\n              </div>\n            )}\n          </Layout.Content>\n        </Layout>\n      </React.Fragment>\n    );\n  }\n}\n\nconst UsersListPage = wrapSettingsTab(\n  \"Users.List\",\n  {\n    permission: \"list_users\",\n    title: \"Users\",\n    path: \"users\",\n    isActive: path => path.startsWith(\"/users\") && path !== \"/users/me\",\n    order: 2,\n  },\n  itemsList(\n    UsersList,\n    () =>\n      new ResourceItemsSource({\n        getRequest(request, { params: { currentPage } }) {\n          switch (currentPage) {\n            case \"active\":\n              request.pending = false;\n              break;\n            case \"pending\":\n              request.pending = true;\n              break;\n            case \"disabled\":\n              request.disabled = true;\n              break;\n            // no default\n          }\n          return request;\n        },\n        getResource() {\n          return User.query.bind(User);\n        },\n      }),\n    () => new UrlStateStorage({ orderByField: \"created_at\", orderByReverse: true })\n  )\n);\n\nroutes.register(\n  \"Users.New\",\n  routeWithUserSession({\n    path: \"/users/new\",\n    title: \"Users\",\n    render: pageProps => <UsersListPage {...pageProps} currentPage=\"active\" isNewUserPage />,\n  })\n);\nroutes.register(\n  \"Users.List\",\n  routeWithUserSession({\n    path: \"/users\",\n    title: \"Users\",\n    render: pageProps => <UsersListPage {...pageProps} currentPage=\"active\" />,\n  })\n);\nroutes.register(\n  \"Users.Pending\",\n  routeWithUserSession({\n    path: \"/users/pending\",\n    title: \"Pending Invitations\",\n    render: pageProps => <UsersListPage {...pageProps} currentPage=\"pending\" />,\n  })\n);\nroutes.register(\n  \"Users.Disabled\",\n  routeWithUserSession({\n    path: \"/users/disabled\",\n    title: \"Disabled Users\",\n    render: pageProps => <UsersListPage {...pageProps} currentPage=\"disabled\" />,\n  })\n);\n"
  },
  {
    "path": "client/app/pages/users/components/ApiKeyForm.jsx",
    "content": "import React, { useState, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport Form from \"antd/lib/form\";\nimport Modal from \"antd/lib/modal\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport InputWithCopy from \"@/components/InputWithCopy\";\nimport { UserProfile } from \"@/components/proptypes\";\nimport User from \"@/services/user\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\nimport { useUniqueId } from \"@/lib/hooks/useUniqueId\";\n\nexport default function ApiKeyForm(props) {\n  const { user, onChange } = props;\n\n  const [loading, setLoading] = useState(false);\n  const handleChange = useImmutableCallback(onChange);\n  const apiKeyInputId = useUniqueId(\"apiKey\");\n\n  const regenerateApiKey = useCallback(() => {\n    const doRegenerate = () => {\n      setLoading(true);\n      User.regenerateApiKey(user)\n        .then(apiKey => {\n          if (apiKey) {\n            handleChange({ ...user, apiKey });\n          }\n        })\n        .finally(() => {\n          setLoading(false);\n        });\n    };\n\n    Modal.confirm({\n      title: \"Regenerate API Key\",\n      content: \"Are you sure you want to regenerate?\",\n      okText: \"Regenerate\",\n      onOk: doRegenerate,\n      maskClosable: true,\n      autoFocusButton: null,\n    });\n  }, [user, handleChange]);\n\n  return (\n    <DynamicComponent name=\"UserProfile.ApiKeyForm\" {...props}>\n      <Form layout=\"vertical\">\n        <hr />\n        <Form.Item label=\"API Key\" className=\"m-b-10\">\n          <InputWithCopy id={apiKeyInputId} className=\"hide-in-percy\" value={user.apiKey} data-test=\"ApiKey\" readOnly />\n        </Form.Item>\n        <Button className=\"w-100\" onClick={regenerateApiKey} loading={loading} data-test=\"RegenerateApiKey\">\n          Regenerate\n        </Button>\n      </Form>\n    </DynamicComponent>\n  );\n}\n\nApiKeyForm.propTypes = {\n  user: UserProfile.isRequired,\n  onChange: PropTypes.func,\n};\n\nApiKeyForm.defaultProps = {\n  onChange: () => {},\n};\n"
  },
  {
    "path": "client/app/pages/users/components/CreateUserDialog.jsx",
    "content": "import React, { useState, useEffect, useCallback } from \"react\";\nimport Button from \"antd/lib/button\";\nimport Modal from \"antd/lib/modal\";\nimport Alert from \"antd/lib/alert\";\nimport DynamicForm from \"@/components/dynamic-form/DynamicForm\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport recordEvent from \"@/services/recordEvent\";\nimport { useUniqueId } from \"@/lib/hooks/useUniqueId\";\n\nconst formFields = [\n  { required: true, name: \"name\", title: \"Name\", type: \"text\", autoFocus: true },\n  { required: true, name: \"email\", title: \"Email\", type: \"email\" },\n];\n\nfunction CreateUserDialog({ dialog }) {\n  const [error, setError] = useState(null);\n  useEffect(() => {\n    recordEvent(\"view\", \"page\", \"users/new\");\n  }, []);\n\n  const handleSubmit = useCallback(values => dialog.close(values).catch(setError), [dialog]);\n  const formId = useUniqueId(\"userForm\");\n\n  return (\n    <Modal\n      {...dialog.props}\n      title=\"Create a New User\"\n      footer={[\n        <Button key=\"cancel\" {...dialog.props.cancelButtonProps} onClick={dialog.dismiss}>\n          Cancel\n        </Button>,\n        <Button\n          key=\"submit\"\n          {...dialog.props.okButtonProps}\n          htmlType=\"submit\"\n          type=\"primary\"\n          form={formId}\n          data-test=\"SaveUserButton\">\n          Create\n        </Button>,\n      ]}\n      wrapProps={{\n        \"data-test\": \"CreateUserDialog\",\n      }}>\n      <DynamicForm id={formId} fields={formFields} onSubmit={handleSubmit} hideSubmitButton />\n      {error && <Alert message={error.message} type=\"error\" showIcon data-test=\"CreateUserErrorAlert\" />}\n    </Modal>\n  );\n}\n\nCreateUserDialog.propTypes = {\n  dialog: DialogPropType.isRequired,\n};\n\nexport default wrapDialog(CreateUserDialog);\n"
  },
  {
    "path": "client/app/pages/users/components/EditableUserProfile.jsx",
    "content": "import React, { useState, useEffect } from \"react\";\nimport { UserProfile } from \"@/components/proptypes\";\n\nimport UserInfoForm from \"./UserInfoForm\";\nimport ApiKeyForm from \"./ApiKeyForm\";\nimport PasswordForm from \"./PasswordForm\";\nimport ToggleUserForm from \"./ToggleUserForm\";\n\nexport default function EditableUserProfile(props) {\n  const [user, setUser] = useState(props.user);\n\n  useEffect(() => {\n    setUser(props.user);\n  }, [props.user]);\n\n  return (\n    <div className=\"col-md-4 col-md-offset-4\">\n      <img alt=\"Profile\" src={user.profileImageUrl} className=\"profile__image\" width=\"40\" />\n      <h3 className=\"profile__h3\">{user.name}</h3>\n      <hr />\n      <UserInfoForm user={user} onChange={setUser} />\n      {!user.isDisabled && (\n        <React.Fragment>\n          <ApiKeyForm user={user} onChange={setUser} />\n          <hr />\n          <PasswordForm user={user} />\n        </React.Fragment>\n      )}\n      <hr />\n      <ToggleUserForm user={user} onChange={setUser} />\n    </div>\n  );\n}\n\nEditableUserProfile.propTypes = {\n  user: UserProfile.isRequired,\n};\n"
  },
  {
    "path": "client/app/pages/users/components/PasswordForm/ChangePasswordDialog.jsx",
    "content": "import { isFunction, get } from \"lodash\";\nimport React from \"react\";\nimport Form from \"antd/lib/form\";\nimport Modal from \"antd/lib/modal\";\nimport Input from \"antd/lib/input\";\nimport { UserProfile } from \"@/components/proptypes\";\nimport { wrap as wrapDialog, DialogPropType } from \"@/components/DialogWrapper\";\nimport User from \"@/services/user\";\nimport notification from \"@/services/notification\";\n\nclass ChangePasswordDialog extends React.Component {\n  static propTypes = {\n    user: UserProfile.isRequired,\n    dialog: DialogPropType.isRequired,\n  };\n\n  constructor(props) {\n    super(props);\n    this.state = {\n      currentPassword: { value: \"\", error: null, touched: false },\n      newPassword: { value: \"\", error: null, touched: false },\n      repeatPassword: { value: \"\", error: null, touched: false },\n      updatingPassword: false,\n    };\n  }\n\n  fieldError = (name, value) => {\n    if (value.length === 0) return \"This field is required.\";\n    if (name !== \"currentPassword\" && value.length < 6) return \"This field is too short.\";\n    if (name === \"repeatPassword\" && value !== this.state.newPassword.value) return \"Passwords don't match\";\n    return null;\n  };\n\n  validateFields = callback => {\n    const { currentPassword, newPassword, repeatPassword } = this.state;\n\n    const errors = {\n      currentPassword: this.fieldError(\"currentPassword\", currentPassword.value),\n      newPassword: this.fieldError(\"newPassword\", newPassword.value),\n      repeatPassword: this.fieldError(\"repeatPassword\", repeatPassword.value),\n    };\n\n    this.setState({\n      currentPassword: { ...currentPassword, error: errors.currentPassword },\n      newPassword: { ...newPassword, error: errors.newPassword },\n      repeatPassword: { ...repeatPassword, error: errors.repeatPassword },\n    });\n\n    if (isFunction(callback)) {\n      if (errors.currentPassword || errors.newPassword || errors.repeatPassword) {\n        callback(errors);\n      } else callback(null);\n    }\n  };\n\n  updatePassword = () => {\n    const { currentPassword, newPassword, updatingPassword } = this.state;\n\n    if (!updatingPassword) {\n      this.validateFields(err => {\n        if (!err) {\n          const userData = {\n            id: this.props.user.id,\n            old_password: currentPassword.value,\n            password: newPassword.value,\n          };\n\n          this.setState({ updatingPassword: true });\n\n          User.save(userData)\n            .then(() => {\n              notification.success(\"Saved.\");\n              this.props.dialog.close({ success: true });\n            })\n            .catch(error => {\n              notification.error(get(error, \"response.data.message\", \"Failed saving.\"));\n              this.setState({ updatingPassword: false });\n            });\n        } else {\n          this.setState(prevState => ({\n            currentPassword: { ...prevState.currentPassword, touched: true },\n            newPassword: { ...prevState.newPassword, touched: true },\n            repeatPassword: { ...prevState.repeatPassword, touched: true },\n          }));\n        }\n      });\n    }\n  };\n\n  handleChange = e => {\n    const { name, value } = e.target;\n    const { error } = this.state[name];\n\n    this.setState({ [name]: { value, error, touched: true } }, () => {\n      this.validateFields();\n    });\n  };\n\n  render() {\n    const { dialog } = this.props;\n    const { currentPassword, newPassword, repeatPassword, updatingPassword } = this.state;\n\n    const formItemProps = { className: \"m-b-10\", required: true };\n\n    const inputProps = {\n      onChange: this.handleChange,\n      onPressEnter: this.updatePassword,\n    };\n\n    return (\n      <Modal\n        {...dialog.props}\n        okButtonProps={{ loading: updatingPassword }}\n        onOk={this.updatePassword}\n        title=\"Change Password\">\n        <Form layout=\"vertical\">\n          <Form.Item\n            {...formItemProps}\n            validateStatus={currentPassword.touched && currentPassword.error ? \"error\" : null}\n            help={currentPassword.touched ? currentPassword.error : null}\n            label=\"Current Password\">\n            <Input.Password {...inputProps} name=\"currentPassword\" data-test=\"CurrentPassword\" autoFocus />\n          </Form.Item>\n          <Form.Item\n            {...formItemProps}\n            validateStatus={newPassword.touched && newPassword.error ? \"error\" : null}\n            help={newPassword.touched ? newPassword.error : null}\n            label=\"New Password\">\n            <Input.Password {...inputProps} name=\"newPassword\" data-test=\"NewPassword\" />\n          </Form.Item>\n          <Form.Item\n            {...formItemProps}\n            validateStatus={repeatPassword.touched && repeatPassword.error ? \"error\" : null}\n            help={repeatPassword.touched ? repeatPassword.error : null}\n            label=\"Repeat New Password\">\n            <Input.Password {...inputProps} name=\"repeatPassword\" data-test=\"RepeatPassword\" />\n          </Form.Item>\n        </Form>\n      </Modal>\n    );\n  }\n}\n\nexport default wrapDialog(ChangePasswordDialog);\n"
  },
  {
    "path": "client/app/pages/users/components/PasswordForm/PasswordLinkAlert.jsx",
    "content": "import { isString } from \"lodash\";\nimport React from \"react\";\nimport PropTypes from \"prop-types\";\nimport Alert from \"antd/lib/alert\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport InputWithCopy from \"@/components/InputWithCopy\";\nimport { UserProfile } from \"@/components/proptypes\";\nimport { absoluteUrl } from \"@/services/utils\";\n\nexport default function PasswordLinkAlert(props) {\n  const { user, passwordLink, ...restProps } = props;\n\n  if (!isString(passwordLink)) {\n    return null;\n  }\n\n  return (\n    <DynamicComponent name=\"UserProfile.PasswordLinkAlert\" {...props}>\n      <Alert\n        message=\"Email not sent!\"\n        description={\n          <React.Fragment>\n            <p>\n              The mail server is not configured, please send the following link to <b>{user.name}</b>:\n            </p>\n            <InputWithCopy value={absoluteUrl(passwordLink)} aria-label=\"Password link\" readOnly />\n          </React.Fragment>\n        }\n        type=\"warning\"\n        className=\"m-t-20\"\n        closable\n        {...restProps}\n      />\n    </DynamicComponent>\n  );\n}\n\nPasswordLinkAlert.propTypes = {\n  user: UserProfile.isRequired,\n  passwordLink: PropTypes.string,\n};\n\nPasswordLinkAlert.defaultProps = {\n  passwordLink: null,\n};\n"
  },
  {
    "path": "client/app/pages/users/components/PasswordForm/PasswordResetForm.jsx",
    "content": "import React, { useState, useCallback } from \"react\";\nimport Button from \"antd/lib/button\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { UserProfile } from \"@/components/proptypes\";\nimport User from \"@/services/user\";\nimport PasswordLinkAlert from \"./PasswordLinkAlert\";\n\nexport default function PasswordResetForm(props) {\n  const { user } = props;\n\n  const [loading, setLoading] = useState(false);\n  const [passwordLink, setPasswordLink] = useState(null);\n\n  const sendPasswordReset = useCallback(() => {\n    setLoading(true);\n    User.sendPasswordReset(user)\n      .then(passwordLink => {\n        setPasswordLink(passwordLink);\n      })\n      .finally(() => {\n        setLoading(false);\n      });\n  }, [user]);\n\n  return (\n    <DynamicComponent name=\"UserProfile.PasswordResetForm\" {...props}>\n      <Button className=\"w-100 m-t-10\" onClick={sendPasswordReset} loading={loading}>\n        Send Password Reset Email\n      </Button>\n      <PasswordLinkAlert user={user} passwordLink={passwordLink} afterClose={() => setPasswordLink(null)} />\n    </DynamicComponent>\n  );\n}\n\nPasswordResetForm.propTypes = {\n  user: UserProfile.isRequired,\n};\n"
  },
  {
    "path": "client/app/pages/users/components/PasswordForm/ResendInvitationForm.jsx",
    "content": "import React, { useState, useCallback } from \"react\";\nimport Button from \"antd/lib/button\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { UserProfile } from \"@/components/proptypes\";\nimport User from \"@/services/user\";\nimport PasswordLinkAlert from \"./PasswordLinkAlert\";\n\nexport default function ResendInvitationForm(props) {\n  const { user } = props;\n\n  const [loading, setLoading] = useState(false);\n  const [passwordLink, setPasswordLink] = useState(null);\n\n  const resendInvitation = useCallback(() => {\n    setLoading(true);\n\n    User.resendInvitation(user)\n      .then(passwordLink => {\n        setPasswordLink(passwordLink);\n      })\n      .finally(() => {\n        setLoading(false);\n      });\n  }, [user]);\n\n  return (\n    <DynamicComponent name=\"UserProfile.ResendInvitationForm\" {...props}>\n      <Button className=\"w-100 m-t-10\" onClick={resendInvitation} loading={loading}>\n        Resend Invitation\n      </Button>\n      <PasswordLinkAlert user={user} passwordLink={passwordLink} afterClose={() => setPasswordLink(null)} />\n    </DynamicComponent>\n  );\n}\n\nResendInvitationForm.propTypes = {\n  user: UserProfile.isRequired,\n};\n"
  },
  {
    "path": "client/app/pages/users/components/PasswordForm/index.jsx",
    "content": "import React, { useCallback } from \"react\";\nimport Button from \"antd/lib/button\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { UserProfile } from \"@/components/proptypes\";\nimport { currentUser } from \"@/services/auth\";\n\nimport ChangePasswordDialog from \"./ChangePasswordDialog\";\nimport PasswordResetForm from \"./PasswordResetForm\";\nimport ResendInvitationForm from \"./ResendInvitationForm\";\n\nexport default function PasswordForm(props) {\n  const { user } = props;\n\n  const changePassword = useCallback(() => {\n    ChangePasswordDialog.showModal({ user });\n  }, [user]);\n\n  return (\n    <DynamicComponent name=\"UserProfile.PasswordForm\" {...props}>\n      <h5>Password</h5>\n      {user.id === currentUser.id && (\n        <Button className=\"w-100 m-t-10\" onClick={changePassword} data-test=\"ChangePassword\">\n          Change Password\n        </Button>\n      )}\n      {user.id !== currentUser.id && currentUser.isAdmin && (\n        <React.Fragment>\n          {user.isInvitationPending ? <ResendInvitationForm user={user} /> : <PasswordResetForm user={user} />}\n        </React.Fragment>\n      )}\n    </DynamicComponent>\n  );\n}\n\nPasswordForm.propTypes = {\n  user: UserProfile.isRequired,\n};\n"
  },
  {
    "path": "client/app/pages/users/components/ReadOnlyUserProfile.jsx",
    "content": "import React from \"react\";\nimport { UserProfile } from \"@/components/proptypes\";\nimport UserGroups from \"@/components/UserGroups\";\n\nimport useUserGroups from \"../hooks/useUserGroups\";\n\nexport default function ReadOnlyUserProfile({ user }) {\n  const { groups, isLoading: isLoadingGroups } = useUserGroups(user);\n\n  return (\n    <div className=\"col-md-4 col-md-offset-4 profile__container\">\n      <img alt=\"profile\" src={user.profileImageUrl} className=\"profile__image\" width=\"40\" />\n      <h3 className=\"profile__h3\">{user.name}</h3>\n      <hr />\n      <dl className=\"profile__dl\">\n        <dt>Name:</dt>\n        <dd>{user.name}</dd>\n        <dt>Email:</dt>\n        <dd>{user.email}</dd>\n        <dt className=\"m-b-5\">Groups:</dt>\n        <dd>{isLoadingGroups ? \"Loading...\" : <UserGroups groups={groups} />}</dd>\n      </dl>\n    </div>\n  );\n}\n\nReadOnlyUserProfile.propTypes = {\n  user: UserProfile.isRequired,\n};\n"
  },
  {
    "path": "client/app/pages/users/components/ReadOnlyUserProfile.test.js",
    "content": "import React from \"react\";\nimport renderer from \"react-test-renderer\";\nimport Group from \"@/services/group\";\nimport ReadOnlyUserProfile from \"./ReadOnlyUserProfile\";\n\nbeforeEach(() => {\n  Group.query = jest.fn().mockResolvedValue([]);\n});\n\ntest(\"renders correctly\", () => {\n  const user = {\n    id: 2,\n    name: \"John Doe\",\n    email: \"john@doe.com\",\n    groupIds: [],\n    profileImageUrl: \"http://www.images.com/llama.jpg\",\n  };\n\n  const component = renderer.create(<ReadOnlyUserProfile user={user} />);\n  const tree = component.toJSON();\n  expect(tree).toMatchSnapshot();\n});\n"
  },
  {
    "path": "client/app/pages/users/components/ToggleUserForm.jsx",
    "content": "import React, { useState, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport Button from \"antd/lib/button\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport { UserProfile } from \"@/components/proptypes\";\nimport { currentUser } from \"@/services/auth\";\nimport User from \"@/services/user\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nexport default function ToggleUserForm(props) {\n  const { user, onChange } = props;\n\n  const [loading, setLoading] = useState(false);\n  const handleChange = useImmutableCallback(onChange);\n\n  const toggleUser = useCallback(() => {\n    const action = user.isDisabled ? User.enableUser : User.disableUser;\n    setLoading(true);\n    action(user)\n      .then(data => {\n        if (data) {\n          handleChange(User.convertUserInfo(data));\n        }\n      })\n      .finally(() => {\n        setLoading(false);\n      });\n  }, [user, handleChange]);\n\n  if (!currentUser.isAdmin || user.id === currentUser.id) {\n    return null;\n  }\n\n  const buttonProps = {\n    type: user.isDisabled ? \"primary\" : \"danger\",\n    children: user.isDisabled ? \"Enable User\" : \"Disable User\",\n  };\n\n  return (\n    <DynamicComponent name=\"UserProfile.ToggleUserForm\">\n      <Button className=\"w-100 m-t-10\" onClick={toggleUser} loading={loading} {...buttonProps} />\n    </DynamicComponent>\n  );\n}\n\nToggleUserForm.propTypes = {\n  user: UserProfile.isRequired,\n  onChange: PropTypes.func,\n};\n\nToggleUserForm.defaultProps = {\n  onChange: () => {},\n};\n"
  },
  {
    "path": "client/app/pages/users/components/UserInfoForm.jsx",
    "content": "import { get, map } from \"lodash\";\nimport React, { useMemo, useCallback } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { UserProfile } from \"@/components/proptypes\";\nimport DynamicComponent from \"@/components/DynamicComponent\";\nimport DynamicForm from \"@/components/dynamic-form/DynamicForm\";\nimport UserGroups from \"@/components/UserGroups\";\n\nimport User from \"@/services/user\";\nimport { currentUser } from \"@/services/auth\";\nimport useImmutableCallback from \"@/lib/hooks/useImmutableCallback\";\n\nimport useUserGroups from \"../hooks/useUserGroups\";\n\nexport default function UserInfoForm(props) {\n  const { user, onChange } = props;\n\n  const { groups, allGroups, isLoading: isLoadingGroups } = useUserGroups(user);\n\n  const handleChange = useImmutableCallback(onChange);\n\n  const saveUser = useCallback(\n    (values, successCallback, errorCallback) => {\n      const data = {\n        ...values,\n        id: user.id,\n      };\n\n      User.save(data)\n        .then(user => {\n          successCallback(\"Saved.\");\n          handleChange(User.convertUserInfo(user));\n        })\n        .catch(error => {\n          errorCallback(get(error, \"response.data.message\", \"Failed saving.\"));\n        });\n    },\n    [user, handleChange]\n  );\n\n  const formFields = useMemo(\n    () =>\n      map(\n        [\n          {\n            name: \"name\",\n            title: \"Name\",\n            type: \"text\",\n            initialValue: user.name,\n          },\n          {\n            name: \"email\",\n            title: \"Email\",\n            type: \"email\",\n            initialValue: user.email,\n          },\n          !user.isDisabled && currentUser.id !== user.id\n            ? {\n                name: \"group_ids\",\n                title: \"Groups\",\n                type: \"select\",\n                mode: \"multiple\",\n                options: map(allGroups, group => ({ name: group.name, value: group.id })),\n                initialValue: user.groupIds,\n                loading: isLoadingGroups,\n                placeholder: isLoadingGroups ? \"Loading...\" : \"\",\n              }\n            : {\n                name: \"group_ids\",\n                title: \"Groups\",\n                type: \"content\",\n                required: false,\n                content: isLoadingGroups ? \"Loading...\" : <UserGroups data-test=\"Groups\" groups={groups} />,\n              },\n        ],\n        field => ({ readOnly: user.isDisabled, required: true, ...field })\n      ),\n    [user, groups, allGroups, isLoadingGroups]\n  );\n\n  return (\n    <DynamicComponent name=\"UserProfile.UserInfoForm\" {...props}>\n      <DynamicForm fields={formFields} onSubmit={saveUser} hideSubmitButton={user.isDisabled} />\n    </DynamicComponent>\n  );\n}\n\nUserInfoForm.propTypes = {\n  user: UserProfile.isRequired,\n  onChange: PropTypes.func,\n};\n\nUserInfoForm.defaultProps = {\n  onChange: () => {},\n};\n"
  },
  {
    "path": "client/app/pages/users/components/__snapshots__/ReadOnlyUserProfile.test.js.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`renders correctly 1`] = `\n<div\n  className=\"col-md-4 col-md-offset-4 profile__container\"\n>\n  <img\n    alt=\"profile\"\n    className=\"profile__image\"\n    src=\"http://www.images.com/llama.jpg\"\n    width=\"40\"\n  />\n  <h3\n    className=\"profile__h3\"\n  >\n    John Doe\n  </h3>\n  <hr />\n  <dl\n    className=\"profile__dl\"\n  >\n    <dt>\n      Name:\n    </dt>\n    <dd>\n      John Doe\n    </dd>\n    <dt>\n      Email:\n    </dt>\n    <dd>\n      john@doe.com\n    </dd>\n    <dt\n      className=\"m-b-5\"\n    >\n      Groups:\n    </dt>\n    <dd>\n      Loading...\n    </dd>\n  </dl>\n</div>\n`;\n"
  },
  {
    "path": "client/app/pages/users/hooks/useUserGroups.js",
    "content": "import { filter, includes, isArray } from \"lodash\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport Group from \"@/services/group\";\n\nexport default function useUserGroups(user) {\n  const [allGroups, setAllGroups] = useState([]);\n  const [isLoading, setIsLoading] = useState(true);\n  const groups = useMemo(() => filter(allGroups, group => includes(user.groupIds, group.id)), [allGroups, user]);\n\n  useEffect(() => {\n    let isCancelled = false;\n\n    Group.query().then(groups => {\n      if (!isCancelled) {\n        setAllGroups(isArray(groups) ? groups : []);\n        setIsLoading(false);\n      }\n    });\n  }, []);\n\n  return useMemo(() => ({ groups, allGroups, isLoading }), [groups, allGroups, isLoading]);\n}\n"
  },
  {
    "path": "client/app/pages/users/settings.less",
    "content": ".profile__image {\n  float: left;\n  margin-right: 10px;\n  border-radius: 100%;\n}\n\n.profile__h3 {\n  margin: 8px 0 0 0;\n}\n\n.profile__container {\n  .well {\n    .form-group:last-of-type {\n      margin-bottom: 0;\n    }\n  }\n}\n\n.profile__dl {\n    dd {\n        margin-bottom: 12px;\n    }\n}\n\n.alert-invited {\n  .form-control {\n    cursor: text !important;\n    background: #fff !important;\n  }\n}\n"
  },
  {
    "path": "client/app/redash-font/style.less",
    "content": "@import \"./variables\";\n\n@font-face {\n  font-family: \"@{icomoon-font-family}\";\n  src: url(\"@{icomoon-font-path}/@{icomoon-font-family}.eot?ehpufm\");\n  src: url(\"@{icomoon-font-path}/@{icomoon-font-family}.eot?ehpufm#iefix\") format(\"embedded-opentype\"),\n    url(\"@{icomoon-font-path}/@{icomoon-font-family}.ttf?ehpufm\") format(\"truetype\"),\n    url(\"@{icomoon-font-path}/@{icomoon-font-family}.woff?ehpufm\") format(\"woff\"),\n    url(\"@{icomoon-font-path}/@{icomoon-font-family}.svg?ehpufm#@{icomoon-font-family}\") format(\"svg\");\n  font-weight: normal;\n  font-style: normal;\n}\n\ni.icon {\n  /* use !important to prevent issues with browser extensions that change fonts */\n  font-family: \"@{icomoon-font-family}\" !important;\n  speak: none;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n\n  /* Better Font Rendering =========== */\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.icon-flash-off {\n  &:before {\n    content: @icon-flash-off;\n  }\n}\n.icon-flash {\n  &:before {\n    content: @icon-flash;\n  }\n}\n"
  },
  {
    "path": "client/app/redash-font/variables.less",
    "content": "@icomoon-font-family: \"redash-icons\";\n@icomoon-font-path: \"fonts\";\n\n@icon-flash-off: \"\\e900\";\n@icon-flash: \"\\e901\";\n\n"
  },
  {
    "path": "client/app/services/KeyboardShortcuts.js",
    "content": "import { each, filter, map, toLower, toString, trim, upperFirst, without } from \"lodash\";\nimport Mousetrap from \"mousetrap\";\nimport \"mousetrap/plugins/global-bind/mousetrap-global-bind\";\n\nconst modKey = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? \"Cmd\" : \"Ctrl\";\nconst altKey = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? \"Option\" : \"Alt\";\n\nexport function humanReadableShortcut(shortcut, limit = Infinity) {\n  const modifiers = {\n    mod: upperFirst(modKey),\n    alt: upperFirst(altKey),\n  };\n\n  shortcut = toLower(toString(shortcut));\n  shortcut = filter(map(shortcut.split(\",\"), trim), s => s !== \"\").slice(0, limit);\n  shortcut = map(shortcut, sc => {\n    sc = filter(map(sc.split(\"+\")), s => s !== \"\");\n    return map(sc, s => modifiers[s] || upperFirst(s)).join(\" + \");\n  }).join(\", \");\n\n  return shortcut !== \"\" ? shortcut : null;\n}\n\nconst handlers = {};\n\nfunction onShortcut(event, shortcut) {\n  event.preventDefault();\n  event.retunValue = false;\n  each(handlers[shortcut], fn => fn());\n}\n\nconst KeyboardShortcuts = {\n  modKey,\n  altKey,\n\n  bind: keymap => {\n    each(keymap, (fn, key) => {\n      const keys = key\n        .toLowerCase()\n        .split(\",\")\n        .map(trim);\n      each(keys, k => {\n        handlers[k] = [...without(handlers[k], fn), fn];\n        Mousetrap.bindGlobal(k, onShortcut);\n      });\n    });\n  },\n\n  unbind: keymap => {\n    each(keymap, (fn, key) => {\n      const keys = key\n        .toLowerCase()\n        .split(\",\")\n        .map(trim);\n      each(keys, k => {\n        handlers[k] = without(handlers[k], fn);\n        if (handlers[k].length === 0) {\n          handlers[k] = undefined;\n          Mousetrap.unbind(k);\n        }\n      });\n    });\n  },\n};\n\nexport default KeyboardShortcuts;\n"
  },
  {
    "path": "client/app/services/alert-subscription.js",
    "content": "import { axios } from \"@/services/axios\";\n\nconst AlertSubscription = {\n  query: ({ alertId }) => axios.get(`api/alerts/${alertId}/subscriptions`),\n  create: data => axios.post(`api/alerts/${data.alert_id}/subscriptions`, data),\n  delete: data => axios.delete(`api/alerts/${data.alert_id}/subscriptions/${data.id}`),\n};\n\nexport default AlertSubscription;\n"
  },
  {
    "path": "client/app/services/alert.js",
    "content": "import { axios } from \"@/services/axios\";\nimport { merge } from \"lodash\";\n\n// backwards compatibility\nconst normalizeCondition = {\n  \"greater than\": \">\",\n  \"less than\": \"<\",\n  equals: \"=\",\n};\n\nconst transformResponse = data =>\n  merge({}, data, {\n    options: {\n      op: normalizeCondition[data.options.op] || data.options.op,\n    },\n  });\n\nconst transformRequest = data => {\n  const newData = Object.assign({}, data);\n  if (newData.query_id === undefined) {\n    newData.query_id = newData.query.id;\n    newData.destination_id = newData.destinations;\n    delete newData.query;\n    delete newData.destinations;\n  }\n\n  return newData;\n};\n\nconst saveOrCreateUrl = data => (data.id ? `api/alerts/${data.id}` : \"api/alerts\");\n\nconst Alert = {\n  query: () => axios.get(\"api/alerts\"),\n  get: ({ id }) => axios.get(`api/alerts/${id}`).then(transformResponse),\n  save: data => axios.post(saveOrCreateUrl(data), transformRequest(data)),\n  delete: data => axios.delete(`api/alerts/${data.id}`),\n  mute: data => axios.post(`api/alerts/${data.id}/mute`),\n  unmute: data => axios.delete(`api/alerts/${data.id}/mute`),\n  evaluate: data => axios.post(`api/alerts/${data.id}/eval`),\n};\n\nexport default Alert;\n"
  },
  {
    "path": "client/app/services/auth.js",
    "content": "import debug from \"debug\";\nimport { includes, extend } from \"lodash\";\nimport location from \"@/services/location\";\nimport { axios } from \"@/services/axios\";\nimport { notifySessionRestored } from \"@/services/restoreSession\";\n\nexport const currentUser = {\n  _isAdmin: undefined,\n\n  canEdit(object) {\n    const userId = object.user_id || (object.user && object.user.id);\n    return this.isAdmin || (userId && userId === this.id);\n  },\n\n  canCreate() {\n    return (\n      this.hasPermission(\"create_query\") || this.hasPermission(\"create_dashboard\") || this.hasPermission(\"list_alerts\")\n    );\n  },\n\n  hasPermission(permission) {\n    if (permission === \"admin\" && this._isAdmin !== undefined) {\n      return this._isAdmin;\n    }\n    return includes(this.permissions, permission);\n  },\n\n  get isAdmin() {\n    return this.hasPermission(\"admin\");\n  },\n\n  set isAdmin(isAdmin) {\n    this._isAdmin = isAdmin;\n  },\n};\n\nexport const clientConfig = {};\nexport const messages = [];\n\nconst logger = debug(\"redash:auth\");\nconst session = { loaded: false };\n\nconst AuthUrls = {\n  Login: \"login\",\n};\n\nexport function updateClientConfig(newClientConfig) {\n  extend(clientConfig, newClientConfig);\n}\n\nfunction updateSession(sessionData) {\n  logger(\"Updating session to be:\", sessionData);\n  extend(session, sessionData, { loaded: true });\n  extend(currentUser, session.user);\n  extend(clientConfig, session.client_config);\n  extend(messages, session.messages);\n}\n\nexport const Auth = {\n  isAuthenticated() {\n    return session.loaded && session.user.id;\n  },\n  getLoginUrl() {\n    return AuthUrls.Login;\n  },\n  setLoginUrl(loginUrl) {\n    AuthUrls.Login = loginUrl;\n  },\n  login() {\n    const next = encodeURI(location.url);\n    logger(\"Calling login with next = %s\", next);\n    window.location.href = `${AuthUrls.Login}?next=${next}`;\n  },\n  logout() {\n    logger(\"Logout.\");\n    window.location.href = \"logout\";\n  },\n  loadSession() {\n    logger(\"Loading session\");\n    if (session.loaded && session.user.id) {\n      logger(\"Resolving with local value.\");\n      return Promise.resolve(session);\n    }\n\n    Auth.setApiKey(null);\n    return axios.get(\"api/session\").then(data => {\n      updateSession(data);\n      return session;\n    });\n  },\n  loadConfig() {\n    logger(\"Loading config\");\n    return axios.get(\"/api/config\").then(data => {\n      updateSession({ client_config: data.client_config, user: { permissions: [] }, messages: [] });\n      return data;\n    });\n  },\n  setApiKey(apiKey) {\n    logger(\"Set API key to: %s\", apiKey);\n    Auth.apiKey = apiKey;\n  },\n  getApiKey() {\n    return Auth.apiKey;\n  },\n  requireSession() {\n    logger(\"Requested authentication\");\n    if (Auth.isAuthenticated()) {\n      return Promise.resolve(session);\n    }\n    return Auth.loadSession()\n      .then(() => {\n        if (Auth.isAuthenticated()) {\n          logger(\"Loaded session\");\n          notifySessionRestored();\n          return session;\n        }\n        logger(\"Need to login, redirecting\");\n        Auth.login();\n      })\n      .catch(() => {\n        logger(\"Need to login, redirecting\");\n        Auth.login();\n      });\n  },\n};\n"
  },
  {
    "path": "client/app/services/auth.test.js",
    "content": "import { currentUser } from \"./auth\";\n\ndescribe(\"currentUser\", () => {\n  describe(\"currentUser.isAdmin\", () => {\n    it(\"returns state based on permission\", () => {\n      currentUser.permissions = [\"admin\"];\n\n      expect(currentUser.isAdmin).toBeTruthy();\n\n      currentUser.permissions = [];\n\n      expect(currentUser.isAdmin).toBeFalsy();\n    });\n\n    it(\"allows setting admin status explicitly\", () => {\n      currentUser.permissions = [];\n      currentUser.isAdmin = true;\n      expect(currentUser.isAdmin).toBeTruthy();\n      currentUser.permissions = [\"admin\"];\n      currentUser.isAdmin = true;\n      expect(currentUser.isAdmin).toBeTruthy();\n      currentUser.permissions = [\"admin\"];\n      currentUser.isAdmin = false;\n      expect(currentUser.isAdmin).toBeFalsy();\n      currentUser.permissions = [];\n      currentUser.isAdmin = false;\n      expect(currentUser.isAdmin).toBeFalsy();\n    });\n  });\n\n  describe(\"currentUser.hasPermission\", () => {\n    it(\"let's override admin status\", () => {\n      currentUser.permissions = [\"\"];\n      currentUser.isAdmin = true;\n      expect(currentUser.hasPermission(\"admin\")).toBeTruthy();\n      currentUser.permissions = [\"\"];\n      currentUser.isAdmin = false;\n      expect(currentUser.hasPermission(\"admin\")).toBeFalsy();\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/services/axios.js",
    "content": "import { get, includes } from \"lodash\";\nimport axiosLib from \"axios\";\nimport createAuthRefreshInterceptor from \"axios-auth-refresh\";\nimport { Auth } from \"@/services/auth\";\nimport qs from \"query-string\";\nimport { restoreSession } from \"@/services/restoreSession\";\n\nexport const axios = axiosLib.create({\n  paramsSerializer: params => qs.stringify(params),\n  xsrfCookieName: \"csrf_token\",\n  xsrfHeaderName: \"X-CSRF-TOKEN\",\n});\n\naxios.interceptors.response.use(response => response.data);\n\nexport const csrfRefreshInterceptor = createAuthRefreshInterceptor(\n  axios,\n  error => {\n    const message = get(error, \"response.data.message\");\n    if (error.isAxiosError && includes(message, \"CSRF\")) {\n      return axios.get(\"/ping\");\n    } else {\n      return Promise.reject(error);\n    }\n  },\n  { statusCodes: [400] }\n);\n\nexport const sessionRefreshInterceptor = createAuthRefreshInterceptor(\n  axios,\n  error => {\n    const status = parseInt(get(error, \"response.status\"));\n    const message = get(error, \"response.data.message\");\n    // TODO: In axios@0.9.1 this check could be replaced with { skipAuthRefresh: true } flag. See axios-auth-refresh docs\n    const requestUrl = get(error, \"config.url\");\n    if (error.isAxiosError && (status === 401 || includes(message, \"Please login\")) && requestUrl !== \"api/session\") {\n      return restoreSession();\n    }\n    return Promise.reject(error);\n  },\n  {\n    statusCodes: [401, 404],\n    pauseInstanceWhileRefreshing: false, // According to docs, `false` is default value, but in fact it's not :-)\n  }\n);\n\naxios.interceptors.request.use(config => {\n  const apiKey = Auth.getApiKey();\n  if (apiKey) {\n    config.headers.Authorization = `Key ${apiKey}`;\n  }\n\n  return config;\n});\n"
  },
  {
    "path": "client/app/services/dashboard.js",
    "content": "import _ from \"lodash\";\nimport { axios } from \"@/services/axios\";\nimport dashboardGridOptions from \"@/config/dashboard-grid-options\";\nimport Widget from \"./widget\";\nimport location from \"@/services/location\";\nimport { cloneParameter } from \"@/services/parameters\";\nimport { policy } from \"@/services/policy\";\n\nexport const urlForDashboard = ({ id, slug }) => `dashboards/${id}-${slug}`;\n\nexport function collectDashboardFilters(dashboard, queryResults, urlParams) {\n  const filters = {};\n  _.each(queryResults, (queryResult) => {\n    const queryFilters = queryResult && queryResult.getFilters ? queryResult.getFilters() : [];\n    _.each(queryFilters, (queryFilter) => {\n      const hasQueryStringValue = _.has(urlParams, queryFilter.name);\n\n      if (!(hasQueryStringValue || dashboard.dashboard_filters_enabled)) {\n        // If dashboard filters not enabled, or no query string value given,\n        // skip filters linking.\n        return;\n      }\n\n      if (hasQueryStringValue) {\n        queryFilter.current = urlParams[queryFilter.name];\n      }\n\n      const filter = { ...queryFilter };\n      if (!_.has(filters, queryFilter.name)) {\n        filters[filter.name] = filter;\n      } else {\n        filters[filter.name].values = _.union(filters[filter.name].values, filter.values);\n      }\n    });\n  });\n\n  return _.values(filters);\n}\n\nfunction prepareWidgetsForDashboard(widgets) {\n  // Default height for auto-height widgets.\n  // Compute biggest widget size and choose between it and some magic number.\n  // This value should be big enough so auto-height widgets will not overlap other ones.\n  const defaultWidgetSizeY =\n    Math.max(\n      _.chain(widgets)\n        .map((w) => w.options.position.sizeY)\n        .max()\n        .value(),\n      20\n    ) + 5;\n\n  // Fix layout:\n  // 1. sort and group widgets by row\n  // 2. update position of widgets in each row - place it right below\n  //    biggest widget from previous row\n  _.chain(widgets)\n    .sortBy((widget) => widget.options.position.row)\n    .groupBy((widget) => widget.options.position.row)\n    .reduce((row, widgetsAtRow) => {\n      let height = 1;\n      _.each(widgetsAtRow, (widget) => {\n        height = Math.max(\n          height,\n          widget.options.position.autoHeight ? defaultWidgetSizeY : widget.options.position.sizeY\n        );\n        widget.options.position.row = row;\n        if (widget.options.position.sizeY < 1) {\n          widget.options.position.sizeY = defaultWidgetSizeY;\n        }\n      });\n      return row + height;\n    }, 0)\n    .value();\n\n  // Sort widgets by updated column and row value\n  widgets = _.sortBy(widgets, (widget) => widget.options.position.col);\n  widgets = _.sortBy(widgets, (widget) => widget.options.position.row);\n\n  return widgets;\n}\n\nfunction calculateNewWidgetPosition(existingWidgets, newWidget) {\n  const width = _.extend({ sizeX: dashboardGridOptions.defaultSizeX }, _.extend({}, newWidget.options).position).sizeX;\n\n  // Find first free row for each column\n  const bottomLine = _.chain(existingWidgets)\n    .map((w) => {\n      const options = _.extend({}, w.options);\n      const position = _.extend({ row: 0, sizeY: 0 }, options.position);\n      return {\n        left: position.col,\n        top: position.row,\n        right: position.col + position.sizeX,\n        bottom: position.row + position.sizeY,\n        width: position.sizeX,\n        height: position.sizeY,\n      };\n    })\n    .reduce(\n      (result, item) => {\n        const from = Math.max(item.left, 0);\n        const to = Math.min(item.right, result.length + 1);\n        for (let i = from; i < to; i += 1) {\n          result[i] = Math.max(result[i], item.bottom);\n        }\n        return result;\n      },\n      _.map(new Array(dashboardGridOptions.columns), _.constant(0))\n    )\n    .value();\n\n  // Go through columns, pick them by count necessary to hold new block,\n  // and calculate bottom-most free row per group.\n  // Choose group with the top-most free row (comparing to other groups)\n  return _.chain(_.range(0, dashboardGridOptions.columns - width + 1))\n    .map((col) => ({\n      col,\n      row: _.chain(bottomLine)\n        .slice(col, col + width)\n        .max()\n        .value(),\n    }))\n    .sortBy(\"row\")\n    .first()\n    .value();\n}\n\nexport function Dashboard(dashboard) {\n  _.extend(this, dashboard);\n  Object.defineProperty(this, \"url\", {\n    get: function () {\n      return urlForDashboard(this);\n    },\n  });\n}\n\nfunction prepareDashboardWidgets(widgets) {\n  return prepareWidgetsForDashboard(_.map(widgets, (widget) => new Widget(widget)));\n}\n\nfunction transformSingle(dashboard) {\n  dashboard = new Dashboard(dashboard);\n  if (dashboard.widgets) {\n    dashboard.widgets = prepareDashboardWidgets(dashboard.widgets);\n  }\n  dashboard.publicAccessEnabled = dashboard.public_url !== undefined;\n  return dashboard;\n}\n\nfunction transformResponse(data) {\n  if (data.results) {\n    data = { ...data, results: _.map(data.results, transformSingle) };\n  } else {\n    data = transformSingle(data);\n  }\n  return data;\n}\n\nconst saveOrCreateUrl = (data) => (data.id ? `api/dashboards/${data.id}` : \"api/dashboards\");\nconst DashboardService = {\n  get: ({ id, slug }) => {\n    const params = {};\n    if (!id) {\n      params.legacy = null;\n    }\n    return axios.get(`api/dashboards/${id || slug}`, { params }).then(transformResponse);\n  },\n  getByToken: ({ token }) => axios.get(`api/dashboards/public/${token}`).then(transformResponse),\n  save: (data) => axios.post(saveOrCreateUrl(data), data).then(transformResponse),\n  delete: ({ id }) => axios.delete(`api/dashboards/${id}`).then(transformResponse),\n  query: (params) => axios.get(\"api/dashboards\", { params }).then(transformResponse),\n  recent: (params) => axios.get(\"api/dashboards/recent\", { params }).then(transformResponse),\n  myDashboards: (params) => axios.get(\"api/dashboards/my\", { params }).then(transformResponse),\n  favorites: (params) => axios.get(\"api/dashboards/favorites\", { params }).then(transformResponse),\n  favorite: ({ id }) => axios.post(`api/dashboards/${id}/favorite`),\n  unfavorite: ({ id }) => axios.delete(`api/dashboards/${id}/favorite`),\n  fork: ({ id }) => axios.post(`api/dashboards/${id}/fork`, { id }).then(transformResponse),\n};\n\n_.extend(Dashboard, DashboardService);\n\nDashboard.prepareDashboardWidgets = prepareDashboardWidgets;\nDashboard.prepareWidgetsForDashboard = prepareWidgetsForDashboard;\n\nDashboard.prototype.canEdit = function canEdit() {\n  return policy.canEdit(this);\n};\n\nDashboard.prototype.getParametersDefs = function getParametersDefs() {\n  const globalParams = {};\n  const queryParams = location.search;\n  _.each(this.widgets, (widget) => {\n    if (widget.getQuery()) {\n      const mappings = widget.getParameterMappings();\n      widget\n        .getQuery()\n        .getParametersDefs(false)\n        .forEach((param) => {\n          const mapping = mappings[param.name];\n          if (mapping.type === Widget.MappingType.DashboardLevel) {\n            // create global param\n            if (!globalParams[mapping.mapTo]) {\n              globalParams[mapping.mapTo] = cloneParameter(param);\n              globalParams[mapping.mapTo].name = mapping.mapTo;\n              globalParams[mapping.mapTo].title = mapping.title || param.title;\n              globalParams[mapping.mapTo].locals = [];\n            }\n\n            // add to locals list\n            globalParams[mapping.mapTo].locals.push(param);\n          }\n        });\n    }\n  });\n  const mergedValues = {\n    ..._.mapValues(globalParams, (p) => p.value),\n    ...Object.fromEntries((this.options.parameters || []).map((param) => [param.name, param.value])),\n  };\n  const resultingGlobalParams = _.values(\n    _.each(globalParams, (param) => {\n      param.setValue(mergedValues[param.name]); // apply merged value\n      param.fromUrlParams(queryParams); // allow param-specific parsing logic\n    })\n  );\n\n  // order dashboard params using paramOrder\n  return _.sortBy(resultingGlobalParams, (param) =>\n    _.includes(this.options.globalParamOrder, param.name)\n      ? _.indexOf(this.options.globalParamOrder, param.name)\n      : _.size(this.options.globalParamOrder)\n  );\n};\n\nDashboard.prototype.addWidget = function addWidget(textOrVisualization, options = {}) {\n  const props = {\n    dashboard_id: this.id,\n    options: {\n      ...options,\n      isHidden: false,\n      position: {},\n    },\n    text: \"\",\n    visualization_id: null,\n    visualization: null,\n  };\n\n  if (_.isString(textOrVisualization)) {\n    props.text = textOrVisualization;\n  } else if (_.isObject(textOrVisualization)) {\n    props.visualization_id = textOrVisualization.id;\n    props.visualization = textOrVisualization;\n  } else {\n    // TODO: Throw an error?\n  }\n\n  const widget = new Widget(props);\n\n  const position = calculateNewWidgetPosition(this.widgets, widget);\n  widget.options.position.col = position.col;\n  widget.options.position.row = position.row;\n\n  return widget.save().then(() => {\n    this.widgets = [...this.widgets, widget];\n    return widget;\n  });\n};\n\nDashboard.prototype.favorite = function favorite() {\n  return Dashboard.favorite(this);\n};\n\nDashboard.prototype.unfavorite = function unfavorite() {\n  return Dashboard.unfavorite(this);\n};\n\nDashboard.prototype.getUrl = function getUrl() {\n  return urlForDashboard(this);\n};\n"
  },
  {
    "path": "client/app/services/data-source.js",
    "content": "import { has, map, isObject } from \"lodash\";\nimport { axios } from \"@/services/axios\";\nimport { fetchDataFromJob } from \"@/services/query-result\";\n\nexport const SCHEMA_NOT_SUPPORTED = 1;\nexport const SCHEMA_LOAD_ERROR = 2;\nexport const IMG_ROOT = \"/static/images/db-logos\";\n\nfunction mapSchemaColumnsToObject(columns) {\n  return map(columns, (column) => (isObject(column) ? column : { name: column }));\n}\n\nconst DataSource = {\n  query: () => axios.get(\"api/data_sources\"),\n  get: ({ id }) => axios.get(`api/data_sources/${id}`),\n  types: () => axios.get(\"api/data_sources/types\"),\n  create: (data) => axios.post(`api/data_sources`, data),\n  save: (data) => axios.post(`api/data_sources/${data.id}`, data),\n  test: (data) => axios.post(`api/data_sources/${data.id}/test`),\n  delete: ({ id }) => axios.delete(`api/data_sources/${id}`),\n  fetchSchema: (data, refresh = false) => {\n    const params = {};\n\n    if (refresh) {\n      params.refresh = true;\n    }\n\n    return axios\n      .get(`api/data_sources/${data.id}/schema`, { params })\n      .then((data) => {\n        if (has(data, \"job\")) {\n          return fetchDataFromJob(data.job.id).catch((error) =>\n            error.code === SCHEMA_NOT_SUPPORTED ? [] : Promise.reject(new Error(data.job.error))\n          );\n        }\n        return has(data, \"schema\") ? data.schema : Promise.reject();\n      })\n      .then((tables) => map(tables, (table) => ({ ...table, columns: mapSchemaColumnsToObject(table.columns) })));\n  },\n};\n\nexport default DataSource;\n"
  },
  {
    "path": "client/app/services/databricks-data-source.js",
    "content": "import { has } from \"lodash\";\nimport { axios } from \"@/services/axios\";\nimport DataSource from \"@/services/data-source\";\nimport { fetchDataFromJob } from \"@/services/query-result\";\n\nfunction fetchDataFromJobOrReturnData(data) {\n  return has(data, \"job.id\") ? fetchDataFromJob(data.job.id, 1000) : data;\n}\n\nfunction rejectErrorResponse(data) {\n  return has(data, \"error\") ? Promise.reject(new Error(data.error.message)) : data;\n}\n\nexport default {\n  ...DataSource,\n  getDatabases: ({ id }, refresh = false) => {\n    const params = {};\n\n    if (refresh) {\n      params.refresh = true;\n    }\n    return axios\n      .get(`api/databricks/databases/${id}`, { params })\n      .then(fetchDataFromJobOrReturnData)\n      .then(rejectErrorResponse);\n  },\n  getDatabaseTables: (data, databaseName, refresh = false) => {\n    const params = {};\n\n    if (refresh) {\n      params.refresh = true;\n    }\n    return axios\n      .get(`api/databricks/databases/${data.id}/${databaseName}/tables`, { params })\n      .then(fetchDataFromJobOrReturnData)\n      .then(rejectErrorResponse);\n  },\n  getTableColumns: (data, databaseName, tableName) =>\n    axios\n      .get(`api/databricks/databases/${data.id}/${databaseName}/columns/${tableName}`)\n      .then(fetchDataFromJobOrReturnData)\n      .then(rejectErrorResponse),\n};\n"
  },
  {
    "path": "client/app/services/destination.js",
    "content": "import { axios } from \"@/services/axios\";\n\nexport const IMG_ROOT = \"static/images/destinations\";\n\nconst Destination = {\n  query: () => axios.get(\"api/destinations\"),\n  get: ({ id }) => axios.get(`api/destinations/${id}`),\n  types: () => axios.get(\"api/destinations/types\"),\n  create: data => axios.post(`api/destinations`, data),\n  save: data => axios.post(`api/destinations/${data.id}`, data),\n  delete: ({ id }) => axios.delete(`api/destinations/${id}`),\n};\n\nexport default Destination;\n"
  },
  {
    "path": "client/app/services/getTags.js",
    "content": "import { axios } from \"@/services/axios\";\n\nfunction processTags(data) {\n  return data.tags || [];\n}\n\nexport default function getTags(url) {\n  return axios.get(url).then(processTags);\n}\n"
  },
  {
    "path": "client/app/services/group.js",
    "content": "import { axios } from \"@/services/axios\";\n\nconst Group = {\n  query: () => axios.get(\"api/groups\"),\n  get: ({ id }) => axios.get(`api/groups/${id}`),\n  create: data => axios.post(`api/groups`, data),\n  save: data => axios.post(`api/groups/${data.id}`, data),\n  delete: data => axios.delete(`api/groups/${data.id}`),\n  members: ({ id }) => axios.get(`api/groups/${id}/members`),\n  addMember: ({ id }, data) => axios.post(`api/groups/${id}/members`, data),\n  removeMember: ({ id, userId }) => axios.delete(`api/groups/${id}/members/${userId}`),\n  dataSources: ({ id }) => axios.get(`api/groups/${id}/data_sources`),\n  addDataSource: ({ id }, data) => axios.post(`api/groups/${id}/data_sources`, data),\n  removeDataSource: ({ id, dataSourceId }) => axios.delete(`api/groups/${id}/data_sources/${dataSourceId}`),\n  updateDataSource: ({ id, dataSourceId }, data) => axios.post(`api/groups/${id}/data_sources/${dataSourceId}`, data),\n};\n\nexport default Group;\n"
  },
  {
    "path": "client/app/services/location.js",
    "content": "import { isNil, isUndefined, isFunction, isObject, trimStart, mapValues, omitBy, extend } from \"lodash\";\nimport qs from \"query-string\";\nimport { createBrowserHistory } from \"history\";\n\nconst history = createBrowserHistory();\n\nfunction normalizeLocation(rawLocation) {\n  const { pathname, search, hash } = rawLocation;\n  const result = {};\n\n  result.path = pathname;\n  result.search = mapValues(qs.parse(search), (value) => (isNil(value) ? true : value));\n  result.hash = trimStart(hash, \"#\");\n  result.url = `${pathname}${search}${hash}`;\n\n  return result;\n}\n\nconst location = {\n  listen(handler) {\n    if (isFunction(handler)) {\n      return history.listen((unused, action) => handler(location, action));\n    } else {\n      return () => {};\n    }\n  },\n\n  confirmChange(handler) {\n    if (isFunction(handler)) {\n      return history.block((nextLocation) => {\n        return handler(normalizeLocation(nextLocation), location);\n      });\n    } else {\n      return () => {};\n    }\n  },\n\n  update(newLocation, replace = false) {\n    if (isObject(newLocation)) {\n      // remap fields and remove undefined ones\n      newLocation = omitBy(\n        {\n          pathname: newLocation.path,\n          search: newLocation.search,\n          hash: newLocation.hash,\n        },\n        isUndefined\n      );\n\n      // keep existing fields (!)\n      newLocation = extend(\n        {\n          pathname: location.path,\n          search: location.search,\n          hash: location.hash,\n        },\n        newLocation\n      );\n\n      // serialize search and keep existing search parameters (!)\n      if (isObject(newLocation.search)) {\n        newLocation.search = omitBy(extend({}, location.search, newLocation.search), isNil);\n        newLocation.search = mapValues(newLocation.search, (value) => (value === true ? null : value));\n        newLocation.search = qs.stringify(newLocation.search);\n      }\n    }\n    if (replace) {\n      if (\n        newLocation.pathname !== location.path ||\n        newLocation.search !== qs.stringify(location.search) ||\n        newLocation.hash !== location.hash\n      ) {\n        history.replace(newLocation);\n      }\n    } else {\n      history.push(newLocation);\n    }\n  },\n\n  url: undefined,\n\n  path: undefined,\n  setPath(path, replace = false) {\n    location.update({ path }, replace);\n  },\n\n  search: undefined,\n  setSearch(search, replace = false) {\n    location.update({ search }, replace);\n  },\n\n  hash: undefined,\n  setHash(hash, replace = false) {\n    location.update({ hash }, replace);\n  },\n};\n\nfunction locationChanged() {\n  extend(location, normalizeLocation(history.location));\n}\n\nhistory.listen(locationChanged);\nlocationChanged(); // init service\n\nexport default location;\n"
  },
  {
    "path": "client/app/services/notification.d.ts",
    "content": "import { NotificationApi, ArgsProps } from \"antd/lib/notification\";\n\nexport type NotificationConfig = Omit<ArgsProps, \"message\" | \"description\"> | null;\n\ntype NotificationFunction = (\n  message: ArgsProps[\"message\"],\n  description?: ArgsProps[\"description\"],\n  args?: NotificationConfig\n) => void;\n\ndeclare const notification: NotificationApi & {\n  success: NotificationFunction;\n  error: NotificationFunction;\n  info: NotificationFunction;\n  warning: NotificationFunction;\n  warn: NotificationFunction;\n};\n\nexport default notification;\n"
  },
  {
    "path": "client/app/services/notification.js",
    "content": "import notification from \"antd/lib/notification\";\n\nnotification.config({\n  placement: \"bottomRight\",\n  duration: 3,\n});\n\nconst simpleNotification = {};\n\n[\"success\", \"error\", \"info\", \"warning\", \"warn\"].forEach(action => {\n  // eslint-disable-next-line arrow-body-style\n  simpleNotification[action] = (message, description = null, props = null) => {\n    return notification[action]({ ...props, message, description });\n  };\n});\n\nexport default {\n  // export Ant's notification and replace actions\n  ...notification,\n  ...simpleNotification,\n};\n"
  },
  {
    "path": "client/app/services/notifications.js",
    "content": "import { find } from \"lodash\";\nimport debug from \"debug\";\nimport recordEvent from \"@/services/recordEvent\";\nimport redashIconUrl from \"@/assets/images/redash_icon_small.png\";\n\nconst logger = debug(\"redash:notifications\");\n\nconst Notification = window.Notification || null;\nif (!Notification) {\n  logger(\"HTML5 notifications are not supported.\");\n}\n\nconst hidden = find([\"hidden\", \"webkitHidden\", \"mozHidden\", \"msHidden\"], prop => prop in document);\n\nfunction isPageVisible() {\n  return !document[hidden];\n}\n\nfunction getPermissions() {\n  if (Notification && Notification.permission === \"default\") {\n    Notification.requestPermission();\n  }\n}\n\nfunction showNotification(title, content) {\n  if (!Notification || isPageVisible() || Notification.permission !== \"granted\") {\n    return;\n  }\n\n  // using the 'tag' to avoid showing duplicate notifications\n  const notification = new Notification(title, {\n    tag: title + content,\n    body: content,\n    icon: redashIconUrl,\n  });\n  notification.onclick = function onClick() {\n    window.focus();\n    this.close();\n    recordEvent(\"click\", \"notification\");\n  };\n}\n\nexport default {\n  getPermissions,\n  showNotification,\n};\n"
  },
  {
    "path": "client/app/services/offline-listener.js",
    "content": "import notification from \"@/services/notification\";\n\nfunction addOnlineListener(notificationKey) {\n  function onlineStateHandler() {\n    notification.close(notificationKey);\n    window.removeEventListener(\"online\", onlineStateHandler);\n  }\n  window.addEventListener(\"online\", onlineStateHandler);\n}\n\nexport default {\n  init() {\n    window.addEventListener(\"offline\", () => {\n      notification.warning(\"Please check your Internet connection.\", null, {\n        key: \"connectionNotification\",\n        duration: null,\n      });\n      addOnlineListener(\"connectionNotification\");\n    });\n  },\n};\n"
  },
  {
    "path": "client/app/services/organizationSettings.js",
    "content": "import { axios } from \"@/services/axios\";\nimport notification from \"@/services/notification\";\n\nexport default {\n  get: () => axios.get(\"api/settings/organization\"),\n  save: (data, message = \"Settings changes saved.\") =>\n    axios\n      .post(\"api/settings/organization\", data)\n      .then(data => {\n        notification.success(message);\n        return data;\n      })\n      .catch(() => {\n        notification.error(\"Failed saving changes.\");\n      }),\n};\n"
  },
  {
    "path": "client/app/services/organizationStatus.js",
    "content": "import { axios } from \"@/services/axios\";\n\nclass OrganizationStatus {\n  constructor() {\n    this.objectCounters = {};\n  }\n\n  refresh() {\n    return axios.get(\"api/organization/status\").then(data => {\n      this.objectCounters = data.object_counters;\n      return this;\n    });\n  }\n}\n\nexport default new OrganizationStatus();\n"
  },
  {
    "path": "client/app/services/parameters/DateParameter.js",
    "content": "import { findKey, startsWith, has, includes, isNull, values } from \"lodash\";\nimport moment from \"moment\";\nimport PropTypes from \"prop-types\";\nimport Parameter from \"./Parameter\";\n\nconst DATETIME_FORMATS = {\n  // eslint-disable-next-line quote-props\n  date: \"YYYY-MM-DD\",\n  \"datetime-local\": \"YYYY-MM-DD HH:mm\",\n  \"datetime-with-seconds\": \"YYYY-MM-DD HH:mm:ss\",\n};\n\nconst DYNAMIC_PREFIX = \"d_\";\n\nconst DYNAMIC_DATES = {\n  now: {\n    name: \"Today/Now\",\n    value: () => moment(),\n  },\n  yesterday: {\n    name: \"Yesterday\",\n    value: () => moment().subtract(1, \"day\"),\n  },\n};\n\nexport const DynamicDateType = PropTypes.oneOf(values(DYNAMIC_DATES));\n\nfunction isDynamicDateString(value) {\n  return startsWith(value, DYNAMIC_PREFIX) && has(DYNAMIC_DATES, value.substring(DYNAMIC_PREFIX.length));\n}\n\nexport function isDynamicDate(value) {\n  return includes(DYNAMIC_DATES, value);\n}\n\nexport function getDynamicDateFromString(value) {\n  if (!isDynamicDateString(value)) {\n    return null;\n  }\n  return DYNAMIC_DATES[value.substring(DYNAMIC_PREFIX.length)];\n}\n\nclass DateParameter extends Parameter {\n  constructor(parameter, parentQueryId) {\n    super(parameter, parentQueryId);\n    this.useCurrentDateTime = parameter.useCurrentDateTime;\n    this.setValue(parameter.value);\n  }\n\n  get hasDynamicValue() {\n    return isDynamicDate(this.normalizedValue);\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  normalizeValue(value) {\n    if (isDynamicDateString(value)) {\n      return getDynamicDateFromString(value);\n    }\n\n    if (isDynamicDate(value)) {\n      return value;\n    }\n\n    const normalizedValue = moment(value, moment.ISO_8601, true);\n    return normalizedValue.isValid() ? normalizedValue : null;\n  }\n\n  setValue(value) {\n    const normalizedValue = this.normalizeValue(value);\n    if (isDynamicDate(normalizedValue)) {\n      this.value = DYNAMIC_PREFIX + findKey(DYNAMIC_DATES, normalizedValue);\n    } else if (moment.isMoment(normalizedValue)) {\n      this.value = normalizedValue.format(DATETIME_FORMATS[this.type]);\n    } else {\n      this.value = normalizedValue;\n    }\n    this.$$value = normalizedValue;\n\n    this.updateLocals();\n    this.clearPendingValue();\n    return this;\n  }\n\n  getExecutionValue() {\n    if (this.hasDynamicValue) {\n      return this.normalizedValue.value().format(DATETIME_FORMATS[this.type]);\n    }\n    if (isNull(this.value) && this.useCurrentDateTime) {\n      return moment().format(DATETIME_FORMATS[this.type]);\n    }\n    return this.value;\n  }\n}\n\nexport default DateParameter;\n"
  },
  {
    "path": "client/app/services/parameters/DateRangeParameter.js",
    "content": "import { startsWith, has, includes, findKey, values, isObject, isArray } from \"lodash\";\nimport moment from \"moment\";\nimport PropTypes from \"prop-types\";\nimport Parameter from \"./Parameter\";\n\nconst DATETIME_FORMATS = {\n  \"date-range\": \"YYYY-MM-DD\",\n  \"datetime-range\": \"YYYY-MM-DD HH:mm\",\n  \"datetime-range-with-seconds\": \"YYYY-MM-DD HH:mm:ss\",\n};\n\nconst DYNAMIC_PREFIX = \"d_\";\n\n/**\n * Dynamic date range preset value with end set to current time\n * @param from {function(): moment.Moment}\n * @param now {function(): moment.Moment=} moment - defaults to now\n * @returns {function(withNow: boolean): [moment.Moment, moment.Moment|undefined]}\n */\nconst untilNow =\n  (from, now = () => moment()) =>\n  (withNow = true) => [from(), withNow ? now() : undefined];\n\nconst DYNAMIC_DATE_RANGES = {\n  today: {\n    name: \"Today\",\n    value: () => [moment().startOf(\"day\"), moment().endOf(\"day\")],\n  },\n  yesterday: {\n    name: \"Yesterday\",\n    value: () => [moment().subtract(1, \"day\").startOf(\"day\"), moment().subtract(1, \"day\").endOf(\"day\")],\n  },\n  this_week: {\n    name: \"This week\",\n    value: () => [moment().startOf(\"week\"), moment().endOf(\"week\")],\n  },\n  this_month: {\n    name: \"This month\",\n    value: () => [moment().startOf(\"month\"), moment().endOf(\"month\")],\n  },\n  this_year: {\n    name: \"This year\",\n    value: () => [moment().startOf(\"year\"), moment().endOf(\"year\")],\n  },\n  last_week: {\n    name: \"Last week\",\n    value: () => [moment().subtract(1, \"week\").startOf(\"week\"), moment().subtract(1, \"week\").endOf(\"week\")],\n  },\n  last_month: {\n    name: \"Last month\",\n    value: () => [moment().subtract(1, \"month\").startOf(\"month\"), moment().subtract(1, \"month\").endOf(\"month\")],\n  },\n  last_year: {\n    name: \"Last year\",\n    value: () => [moment().subtract(1, \"year\").startOf(\"year\"), moment().subtract(1, \"year\").endOf(\"year\")],\n  },\n  last_hour: {\n    name: \"Last hour\",\n    value: untilNow(() => moment().subtract(1, \"hour\")),\n  },\n  last_8_hours: {\n    name: \"Last 8 hours\",\n    value: untilNow(() => moment().subtract(8, \"hour\")),\n  },\n  last_24_hours: {\n    name: \"Last 24 hours\",\n    value: untilNow(() => moment().subtract(24, \"hour\")),\n  },\n  last_7_days: {\n    name: \"Last 7 days\",\n    value: untilNow(() => moment().subtract(7, \"days\").startOf(\"day\")),\n  },\n  last_14_days: {\n    name: \"Last 14 days\",\n    value: untilNow(() => moment().subtract(14, \"days\").startOf(\"day\")),\n  },\n  last_30_days: {\n    name: \"Last 30 days\",\n    value: untilNow(() => moment().subtract(30, \"days\").startOf(\"day\")),\n  },\n  last_60_days: {\n    name: \"Last 60 days\",\n    value: untilNow(() => moment().subtract(60, \"days\").startOf(\"day\")),\n  },\n  last_90_days: {\n    name: \"Last 90 days\",\n    value: untilNow(() => moment().subtract(90, \"days\").startOf(\"day\")),\n  },\n  last_12_months: {\n    name: \"Last 12 months\",\n    value: untilNow(() => moment().subtract(12, \"months\").startOf(\"day\")),\n  },\n  last_2_years: {\n    name: \"Last 2 years\",\n    value: untilNow(() => moment().subtract(2, \"years\").startOf(\"day\")),\n  },\n  last_3_years: {\n    name: \"Last 3 years\",\n    value: untilNow(() => moment().subtract(3, \"years\").startOf(\"day\")),\n  },\n  last_10_years: {\n    name: \"Last 10 years\",\n    value: untilNow(() => moment().subtract(10, \"years\").startOf(\"day\")),\n  },\n};\n\nexport const DynamicDateRangeType = PropTypes.oneOf(values(DYNAMIC_DATE_RANGES));\n\nexport function isDynamicDateRangeString(value) {\n  if (!startsWith(value, DYNAMIC_PREFIX)) {\n    return false;\n  }\n  return !!DYNAMIC_DATE_RANGES[value.substring(DYNAMIC_PREFIX.length)];\n}\n\nexport function getDynamicDateRangeStringFromName(dynamicRangeName) {\n  const key = findKey(DYNAMIC_DATE_RANGES, (range) => range.name === dynamicRangeName);\n  return key ? DYNAMIC_PREFIX + key : undefined;\n}\n\nexport function isDynamicDateRange(value) {\n  return includes(DYNAMIC_DATE_RANGES, value);\n}\n\nexport function getDynamicDateRangeFromString(value) {\n  if (!isDynamicDateRangeString(value)) {\n    return null;\n  }\n  return DYNAMIC_DATE_RANGES[value.substring(DYNAMIC_PREFIX.length)];\n}\n\nclass DateRangeParameter extends Parameter {\n  constructor(parameter, parentQueryId) {\n    super(parameter, parentQueryId);\n    this.setValue(parameter.value);\n  }\n\n  get hasDynamicValue() {\n    return isDynamicDateRange(this.normalizedValue);\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  normalizeValue(value) {\n    if (isDynamicDateRangeString(value)) {\n      return getDynamicDateRangeFromString(value);\n    }\n\n    if (isDynamicDateRange(value)) {\n      return value;\n    }\n\n    if (isObject(value) && !isArray(value)) {\n      value = [value.start, value.end];\n    }\n\n    if (isArray(value) && value.length === 2) {\n      value = [moment(value[0]), moment(value[1])];\n      if (value[0].isValid() && value[1].isValid()) {\n        return value;\n      }\n    }\n    return null;\n  }\n\n  setValue(value) {\n    const normalizedValue = this.normalizeValue(value);\n    if (isDynamicDateRange(normalizedValue)) {\n      this.value = DYNAMIC_PREFIX + findKey(DYNAMIC_DATE_RANGES, normalizedValue);\n    } else if (isArray(normalizedValue)) {\n      this.value = {\n        start: normalizedValue[0].format(DATETIME_FORMATS[this.type]),\n        end: normalizedValue[1].format(DATETIME_FORMATS[this.type]),\n      };\n    } else {\n      this.value = normalizedValue;\n    }\n    this.$$value = normalizedValue;\n\n    this.updateLocals();\n    this.clearPendingValue();\n    return this;\n  }\n\n  getExecutionValue() {\n    if (this.hasDynamicValue) {\n      const format = (date) => date.format(DATETIME_FORMATS[this.type]);\n      const [start, end] = this.normalizedValue.value().map(format);\n      return { start, end };\n    }\n    return this.value;\n  }\n\n  toUrlParams() {\n    const prefix = this.urlPrefix;\n    if (isObject(this.value) && this.value.start && this.value.end) {\n      return {\n        [`${prefix}${this.name}`]: `${this.value.start}--${this.value.end}`,\n      };\n    }\n    return super.toUrlParams();\n  }\n\n  fromUrlParams(query) {\n    const prefix = this.urlPrefix;\n    const key = `${prefix}${this.name}`;\n\n    // backward compatibility\n    const keyStart = `${prefix}${this.name}.start`;\n    const keyEnd = `${prefix}${this.name}.end`;\n\n    if (has(query, key)) {\n      const dates = query[key].split(\"--\");\n      if (dates.length === 2) {\n        this.setValue(dates);\n      } else {\n        this.setValue(query[key]);\n      }\n    } else if (has(query, keyStart) && has(query, keyEnd)) {\n      this.setValue([query[keyStart], query[keyEnd]]);\n    }\n  }\n\n  toQueryTextFragment() {\n    return `{{ ${this.name}.start }} {{ ${this.name}.end }}`;\n  }\n}\n\nexport default DateRangeParameter;\n"
  },
  {
    "path": "client/app/services/parameters/EnumParameter.js",
    "content": "import { isArray, isEmpty, includes, intersection, get, map, join, has } from \"lodash\";\nimport Parameter from \"./Parameter\";\n\nclass EnumParameter extends Parameter {\n  constructor(parameter, parentQueryId) {\n    super(parameter, parentQueryId);\n    this.enumOptions = parameter.enumOptions;\n    this.multiValuesOptions = parameter.multiValuesOptions;\n    this.setValue(parameter.value);\n  }\n\n  normalizeValue(value) {\n    if (isEmpty(this.enumOptions)) {\n      return null;\n    }\n\n    const enumOptionsArray = this.enumOptions.split(\"\\n\") || [];\n    if (this.multiValuesOptions) {\n      if (!isArray(value)) {\n        value = [value];\n      }\n      value = intersection(value, enumOptionsArray);\n    } else if (!value || isArray(value) || !includes(enumOptionsArray, value)) {\n      value = enumOptionsArray[0];\n    }\n\n    if (isArray(value) && isEmpty(value)) {\n      return null;\n    }\n    return value;\n  }\n\n  getExecutionValue(extra = {}) {\n    const { joinListValues } = extra;\n    if (joinListValues && isArray(this.value)) {\n      const separator = get(this.multiValuesOptions, \"separator\", \",\");\n      const prefix = get(this.multiValuesOptions, \"prefix\", \"\");\n      const suffix = get(this.multiValuesOptions, \"suffix\", \"\");\n      const parameterValues = map(this.value, v => `${prefix}${v}${suffix}`);\n      return join(parameterValues, separator);\n    }\n    return this.value;\n  }\n\n  toUrlParams() {\n    const prefix = this.urlPrefix;\n\n    let urlParam = this.value;\n    if (this.multiValuesOptions && isArray(this.value)) {\n      urlParam = JSON.stringify(this.value);\n    }\n\n    return {\n      [`${prefix}${this.name}`]: !this.isEmpty ? urlParam : null,\n    };\n  }\n\n  fromUrlParams(query) {\n    const prefix = this.urlPrefix;\n    const key = `${prefix}${this.name}`;\n    if (has(query, key)) {\n      if (this.multiValuesOptions) {\n        try {\n          const valueFromJson = JSON.parse(query[key]);\n          this.setValue(isArray(valueFromJson) ? valueFromJson : query[key]);\n        } catch (e) {\n          this.setValue(query[key]);\n        }\n      } else {\n        this.setValue(query[key]);\n      }\n    }\n  }\n}\n\nexport default EnumParameter;\n"
  },
  {
    "path": "client/app/services/parameters/NumberParameter.js",
    "content": "import { toNumber, isNull } from \"lodash\";\nimport Parameter from \"./Parameter\";\n\nclass NumberParameter extends Parameter {\n  constructor(parameter, parentQueryId) {\n    super(parameter, parentQueryId);\n    this.setValue(parameter.value);\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  normalizeValue(value) {\n    if (isNull(value)) {\n      return null;\n    }\n    const normalizedValue = toNumber(value);\n    return !isNaN(normalizedValue) ? normalizedValue : null;\n  }\n}\n\nexport default NumberParameter;\n"
  },
  {
    "path": "client/app/services/parameters/Parameter.js",
    "content": "import { isNull, isObject, isFunction, isUndefined, isEqual, has, omit, isArray, each } from \"lodash\";\n\nclass Parameter {\n  constructor(parameter, parentQueryId) {\n    this.title = parameter.title;\n    this.name = parameter.name;\n    this.type = parameter.type;\n    this.global = parameter.global; // backward compatibility in Widget service\n    this.parentQueryId = parentQueryId;\n\n    // Used for meta-parameters (i.e. dashboard-level params)\n    this.locals = [];\n\n    // Used for URL serialization\n    this.urlPrefix = \"p_\";\n  }\n\n  static getExecutionValue(param, extra = {}) {\n    if (!isObject(param) || !isFunction(param.getExecutionValue)) {\n      return null;\n    }\n\n    return param.getExecutionValue(extra);\n  }\n\n  static setValue(param, value) {\n    if (!isObject(param) || !isFunction(param.setValue)) {\n      return null;\n    }\n\n    return param.setValue(value);\n  }\n\n  get isEmpty() {\n    return isNull(this.normalizedValue);\n  }\n\n  get hasPendingValue() {\n    return this.pendingValue !== undefined && !isEqual(this.pendingValue, this.normalizedValue);\n  }\n\n  /** Get normalized value to be used in inputs */\n  get normalizedValue() {\n    return this.$$value;\n  }\n\n  isEmptyValue(value) {\n    return isNull(this.normalizeValue(value));\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  normalizeValue(value) {\n    if (isUndefined(value)) {\n      return null;\n    }\n    return value;\n  }\n\n  updateLocals() {\n    if (isArray(this.locals)) {\n      each(this.locals, (local) => {\n        local.setValue(this.value);\n      });\n    }\n  }\n\n  setValue(value) {\n    const normalizedValue = this.normalizeValue(value);\n    this.value = normalizedValue;\n    this.$$value = normalizedValue;\n\n    this.updateLocals();\n    this.clearPendingValue();\n    return this;\n  }\n\n  /** Get execution value for a query */\n  getExecutionValue() {\n    return this.value;\n  }\n\n  setPendingValue(value) {\n    this.pendingValue = this.normalizeValue(value);\n  }\n\n  applyPendingValue() {\n    if (this.hasPendingValue) {\n      this.setValue(this.pendingValue);\n    }\n  }\n\n  clearPendingValue() {\n    this.pendingValue = undefined;\n  }\n\n  /** Update URL with Parameter value */\n  toUrlParams() {\n    const prefix = this.urlPrefix;\n    // `null` removes the parameter from the URL in case it exists\n    return {\n      [`${prefix}${this.name}`]: !this.isEmpty ? this.value : null,\n    };\n  }\n\n  /** Set parameter value from the URL */\n  fromUrlParams(query) {\n    const prefix = this.urlPrefix;\n    const key = `${prefix}${this.name}`;\n    if (has(query, key)) {\n      this.setValue(query[key]);\n    }\n  }\n\n  toQueryTextFragment() {\n    return `{{ ${this.name} }}`;\n  }\n\n  /** Get a saveable version of the Parameter by omitting unnecessary props */\n  toSaveableObject() {\n    return omit(this, [\"$$value\", \"urlPrefix\", \"pendingValue\", \"parentQueryId\", \"locals\"]);\n  }\n}\n\nexport default Parameter;\n"
  },
  {
    "path": "client/app/services/parameters/QueryBasedDropdownParameter.js",
    "content": "import { isNull, isUndefined, isArray, isEmpty, get, map, join, has } from \"lodash\";\nimport { Query } from \"@/services/query\";\nimport Parameter from \"./Parameter\";\n\nclass QueryBasedDropdownParameter extends Parameter {\n  constructor(parameter, parentQueryId) {\n    super(parameter, parentQueryId);\n    this.queryId = parameter.queryId;\n    this.multiValuesOptions = parameter.multiValuesOptions;\n    this.setValue(parameter.value);\n  }\n\n  normalizeValue(value) {\n    if (isUndefined(value) || isNull(value) || (isArray(value) && isEmpty(value))) {\n      return null;\n    }\n\n    if (this.multiValuesOptions) {\n      value = isArray(value) ? value : [value];\n    } else {\n      value = isArray(value) ? value[0] : value;\n    }\n    return value;\n  }\n\n  getExecutionValue(extra = {}) {\n    const { joinListValues } = extra;\n    if (joinListValues && isArray(this.value)) {\n      const separator = get(this.multiValuesOptions, \"separator\", \",\");\n      const prefix = get(this.multiValuesOptions, \"prefix\", \"\");\n      const suffix = get(this.multiValuesOptions, \"suffix\", \"\");\n      const parameterValues = map(this.value, v => `${prefix}${v}${suffix}`);\n      return join(parameterValues, separator);\n    }\n    return this.value;\n  }\n\n  toUrlParams() {\n    const prefix = this.urlPrefix;\n\n    let urlParam = this.value;\n    if (this.multiValuesOptions && isArray(this.value)) {\n      urlParam = JSON.stringify(this.value);\n    }\n\n    return {\n      [`${prefix}${this.name}`]: !this.isEmpty ? urlParam : null,\n    };\n  }\n\n  fromUrlParams(query) {\n    const prefix = this.urlPrefix;\n    const key = `${prefix}${this.name}`;\n    if (has(query, key)) {\n      if (this.multiValuesOptions) {\n        try {\n          const valueFromJson = JSON.parse(query[key]);\n          this.setValue(isArray(valueFromJson) ? valueFromJson : query[key]);\n        } catch (e) {\n          this.setValue(query[key]);\n        }\n      } else {\n        this.setValue(query[key]);\n      }\n    }\n  }\n\n  loadDropdownValues() {\n    if (this.parentQueryId) {\n      return Query.associatedDropdown({ queryId: this.parentQueryId, dropdownQueryId: this.queryId }).catch(() =>\n        Promise.resolve([])\n      );\n    }\n\n    return Query.asDropdown({ id: this.queryId }).catch(Promise.resolve([]));\n  }\n}\n\nexport default QueryBasedDropdownParameter;\n"
  },
  {
    "path": "client/app/services/parameters/TextParameter.js",
    "content": "import { toString, isEmpty } from \"lodash\";\nimport Parameter from \"./Parameter\";\n\nclass TextParameter extends Parameter {\n  constructor(parameter, parentQueryId) {\n    super(parameter, parentQueryId);\n    this.setValue(parameter.value);\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  normalizeValue(value) {\n    const normalizedValue = toString(value);\n    if (isEmpty(normalizedValue)) {\n      return null;\n    }\n    return normalizedValue;\n  }\n}\n\nexport default TextParameter;\n"
  },
  {
    "path": "client/app/services/parameters/TextPatternParameter.js",
    "content": "import { toString, isNull } from \"lodash\";\nimport Parameter from \"./Parameter\";\n\nclass TextPatternParameter extends Parameter {\n  constructor(parameter, parentQueryId) {\n    super(parameter, parentQueryId);\n    this.regex = parameter.regex;\n    this.setValue(parameter.value);\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  normalizeValue(value) {\n    const normalizedValue = toString(value);\n    if (isNull(normalizedValue)) {\n      return null;\n    }\n\n    var re = new RegExp(this.regex);\n\n    if (re !== null) {\n      if (re.test(normalizedValue)) {\n        return normalizedValue;\n      }\n    }\n    return null;\n  }\n}\n\nexport default TextPatternParameter;\n"
  },
  {
    "path": "client/app/services/parameters/index.js",
    "content": "import Parameter from \"./Parameter\";\nimport TextParameter from \"./TextParameter\";\nimport NumberParameter from \"./NumberParameter\";\nimport EnumParameter from \"./EnumParameter\";\nimport QueryBasedDropdownParameter from \"./QueryBasedDropdownParameter\";\nimport DateParameter from \"./DateParameter\";\nimport DateRangeParameter from \"./DateRangeParameter\";\nimport TextPatternParameter from \"./TextPatternParameter\";\n\nfunction createParameter(param, parentQueryId) {\n  switch (param.type) {\n    case \"number\":\n      return new NumberParameter(param, parentQueryId);\n    case \"enum\":\n      return new EnumParameter(param, parentQueryId);\n    case \"query\":\n      return new QueryBasedDropdownParameter(param, parentQueryId);\n    case \"date\":\n    case \"datetime-local\":\n    case \"datetime-with-seconds\":\n      return new DateParameter(param, parentQueryId);\n    case \"date-range\":\n    case \"datetime-range\":\n    case \"datetime-range-with-seconds\":\n      return new DateRangeParameter(param, parentQueryId);\n    case \"text-pattern\":\n      return new TextPatternParameter({ ...param, type: \"text-pattern\" }, parentQueryId);\n    default:\n      return new TextParameter({ ...param, type: \"text\" }, parentQueryId);\n  }\n}\n\nfunction cloneParameter(param) {\n  return createParameter(param, param.parentQueryId);\n}\n\nexport {\n  Parameter,\n  TextParameter,\n  TextPatternParameter,\n  NumberParameter,\n  EnumParameter,\n  QueryBasedDropdownParameter,\n  DateParameter,\n  DateRangeParameter,\n  createParameter,\n  cloneParameter,\n};\n"
  },
  {
    "path": "client/app/services/parameters/tests/DateParameter.test.js",
    "content": "import { createParameter } from \"..\";\nimport { getDynamicDateFromString } from \"../DateParameter\";\nimport moment from \"moment\";\n\ndescribe(\"DateParameter\", () => {\n  let type = \"date\";\n  let param;\n\n  beforeEach(() => {\n    param = createParameter({ name: \"param\", title: \"Param\", type });\n  });\n\n  describe(\"getExecutionValue\", () => {\n    beforeEach(() => {\n      param.setValue(moment(\"2019-10-06 10:00:00\"));\n    });\n\n    test(\"formats value as a string date\", () => {\n      const executionValue = param.getExecutionValue();\n      expect(executionValue).toBe(\"2019-10-06\");\n    });\n\n    describe(\"type is datetime-local\", () => {\n      beforeAll(() => {\n        type = \"datetime-local\";\n      });\n\n      test(\"formats value as a string datetime\", () => {\n        const executionValue = param.getExecutionValue();\n        expect(executionValue).toBe(\"2019-10-06 10:00\");\n      });\n    });\n\n    describe(\"type is datetime-with-seconds\", () => {\n      beforeAll(() => {\n        type = \"datetime-with-seconds\";\n      });\n\n      test(\"formats value as a string datetime with seconds\", () => {\n        const executionValue = param.getExecutionValue();\n        expect(executionValue).toBe(\"2019-10-06 10:00:00\");\n      });\n    });\n  });\n\n  describe(\"normalizeValue\", () => {\n    test(\"recognizes dates from strings\", () => {\n      const normalizedValue = param.normalizeValue(\"2019-10-06\");\n      expect(moment.isMoment(normalizedValue)).toBeTruthy();\n      expect(normalizedValue.format(\"YYYY-MM-DD\")).toBe(\"2019-10-06\");\n    });\n\n    test(\"recognizes dates from moment values\", () => {\n      const normalizedValue = param.normalizeValue(moment(\"2019-10-06\"));\n      expect(moment.isMoment(normalizedValue)).toBeTruthy();\n      expect(normalizedValue.format(\"YYYY-MM-DD\")).toBe(\"2019-10-06\");\n    });\n\n    test(\"normalizes unrecognized values as null\", () => {\n      const normalizedValue = param.normalizeValue(\"value\");\n      expect(normalizedValue).toBeNull();\n    });\n\n    describe(\"Dynamic values\", () => {\n      test(\"recognizes dynamic values from string index\", () => {\n        const normalizedValue = param.normalizeValue(\"d_now\");\n        expect(normalizedValue).not.toBeNull();\n        expect(normalizedValue).toEqual(getDynamicDateFromString(\"d_now\"));\n      });\n\n      test(\"recognizes dynamic values from a dynamic date\", () => {\n        const dynamicDate = getDynamicDateFromString(\"d_now\");\n        const normalizedValue = param.normalizeValue(dynamicDate);\n        expect(normalizedValue).not.toBeNull();\n        expect(normalizedValue).toEqual(dynamicDate);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/services/parameters/tests/DateRangeParameter.test.js",
    "content": "import { createParameter } from \"..\";\nimport { getDynamicDateRangeFromString } from \"../DateRangeParameter\";\nimport moment from \"moment\";\n\ndescribe(\"DateRangeParameter\", () => {\n  let type = \"date-range\";\n  let param;\n\n  beforeEach(() => {\n    param = createParameter({ name: \"param\", title: \"Param\", type });\n  });\n\n  describe(\"getExecutionValue\", () => {\n    beforeEach(() => {\n      param.setValue({ start: \"2019-10-05 10:00:00\", end: \"2019-10-06 09:59:59\" });\n    });\n\n    test(\"formats value as a string date\", () => {\n      const executionValue = param.getExecutionValue();\n      expect(executionValue).toEqual({ start: \"2019-10-05\", end: \"2019-10-06\" });\n    });\n\n    describe(\"type is datetime-range\", () => {\n      beforeAll(() => {\n        type = \"datetime-range\";\n      });\n\n      test(\"formats value as a string datetime\", () => {\n        const executionValue = param.getExecutionValue();\n        expect(executionValue).toEqual({ start: \"2019-10-05 10:00\", end: \"2019-10-06 09:59\" });\n      });\n    });\n\n    describe(\"type is datetime-range-with-seconds\", () => {\n      beforeAll(() => {\n        type = \"datetime-range-with-seconds\";\n      });\n\n      test(\"formats value as a string datetime with seconds\", () => {\n        const executionValue = param.getExecutionValue();\n        expect(executionValue).toEqual({ start: \"2019-10-05 10:00:00\", end: \"2019-10-06 09:59:59\" });\n      });\n    });\n  });\n\n  describe(\"normalizeValue\", () => {\n    test(\"recognizes dates from moment arrays\", () => {\n      const normalizedValue = param.normalizeValue([moment(\"2019-10-05\"), moment(\"2019-10-06\")]);\n      expect(normalizedValue).toHaveLength(2);\n      expect(normalizedValue[0].format(\"YYYY-MM-DD\")).toBe(\"2019-10-05\");\n      expect(normalizedValue[1].format(\"YYYY-MM-DD\")).toBe(\"2019-10-06\");\n    });\n\n    test(\"recognizes dates from object\", () => {\n      const normalizedValue = param.normalizeValue({ start: \"2019-10-05\", end: \"2019-10-06\" });\n      expect(normalizedValue).toHaveLength(2);\n      expect(normalizedValue[0].format(\"YYYY-MM-DD\")).toBe(\"2019-10-05\");\n      expect(normalizedValue[1].format(\"YYYY-MM-DD\")).toBe(\"2019-10-06\");\n    });\n\n    describe(\"Dynamic values\", () => {\n      test(\"recognizes dynamic values from string index\", () => {\n        const normalizedValue = param.normalizeValue(\"d_last_week\");\n        expect(normalizedValue).not.toBeNull();\n        expect(normalizedValue).toEqual(getDynamicDateRangeFromString(\"d_last_week\"));\n      });\n\n      test(\"recognizes dynamic values from a dynamic date range\", () => {\n        const dynamicDateRange = getDynamicDateRangeFromString(\"d_last_week\");\n        const normalizedValue = param.normalizeValue(dynamicDateRange);\n        expect(normalizedValue).not.toBeNull();\n        expect(normalizedValue).toEqual(dynamicDateRange);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/services/parameters/tests/EnumParameter.test.js",
    "content": "import { createParameter } from \"..\";\n\ndescribe(\"EnumParameter\", () => {\n  let param;\n  let multiValuesOptions = null;\n  const enumOptions = \"value1\\nvalue2\\nvalue3\\nvalue4\";\n\n  beforeEach(() => {\n    const paramOptions = {\n      name: \"param\",\n      title: \"Param\",\n      type: \"enum\",\n      enumOptions,\n      multiValuesOptions,\n    };\n    param = createParameter(paramOptions);\n  });\n\n  describe(\"normalizeValue\", () => {\n    test(\"returns the value when the input in the enum options\", () => {\n      const normalizedValue = param.normalizeValue(\"value2\");\n      expect(normalizedValue).toBe(\"value2\");\n    });\n\n    test(\"returns the first value when the input is not in the enum options\", () => {\n      const normalizedValue = param.normalizeValue(\"anything\");\n      expect(normalizedValue).toBe(\"value1\");\n    });\n  });\n\n  describe(\"Multi-valued\", () => {\n    beforeAll(() => {\n      multiValuesOptions = { prefix: '\"', suffix: '\"', separator: \",\" };\n    });\n\n    describe(\"normalizeValue\", () => {\n      test(\"returns only valid values\", () => {\n        const normalizedValue = param.normalizeValue([\"value3\", \"anything\", null]);\n        expect(normalizedValue).toEqual([\"value3\"]);\n      });\n\n      test(\"normalizes empty values as null\", () => {\n        const normalizedValue = param.normalizeValue([]);\n        expect(normalizedValue).toBeNull();\n      });\n    });\n\n    describe(\"getExecutionValue\", () => {\n      test(\"joins values when joinListValues is truthy\", () => {\n        param.setValue([\"value1\", \"value3\"]);\n        const executionValue = param.getExecutionValue({ joinListValues: true });\n        expect(executionValue).toBe('\"value1\",\"value3\"');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/services/parameters/tests/NumberParameter.test.js",
    "content": "import { createParameter } from \"..\";\n\ndescribe(\"NumberParameter\", () => {\n  let param;\n\n  beforeEach(() => {\n    param = createParameter({ name: \"param\", title: \"Param\", type: \"number\" });\n  });\n\n  describe(\"normalizeValue\", () => {\n    test(\"converts Strings\", () => {\n      const normalizedValue = param.normalizeValue(\"15\");\n      expect(normalizedValue).toBe(15);\n    });\n\n    test(\"converts Numbers\", () => {\n      const normalizedValue = param.normalizeValue(42);\n      expect(normalizedValue).toBe(42);\n    });\n\n    test(\"returns null when not possible to convert to number\", () => {\n      const normalizedValue = param.normalizeValue(\"notanumber\");\n      expect(normalizedValue).toBeNull();\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/services/parameters/tests/Parameter.test.js",
    "content": "import {\n  createParameter,\n  TextParameter,\n  TextPatternParameter,\n  NumberParameter,\n  EnumParameter,\n  QueryBasedDropdownParameter,\n  DateParameter,\n  DateRangeParameter,\n} from \"..\";\n\ndescribe(\"Parameter\", () => {\n  describe(\"create\", () => {\n    const parameterTypes = [\n      [\"text\", TextParameter],\n      [\"text-pattern\", TextPatternParameter],\n      [\"number\", NumberParameter],\n      [\"enum\", EnumParameter],\n      [\"query\", QueryBasedDropdownParameter],\n      [\"date\", DateParameter],\n      [\"datetime-local\", DateParameter],\n      [\"datetime-with-seconds\", DateParameter],\n      [\"date-range\", DateRangeParameter],\n      [\"datetime-range\", DateRangeParameter],\n      [\"datetime-range-with-seconds\", DateRangeParameter],\n      [null, TextParameter],\n    ];\n\n    test.each(parameterTypes)(\"when type is '%s' creates a %p\", (type, expectedClass) => {\n      const parameter = createParameter({ name: \"param\", title: \"Param\", type });\n      expect(parameter).toBeInstanceOf(expectedClass);\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/services/parameters/tests/QueryBasedDropdownParameter.test.js",
    "content": "import { createParameter } from \"..\";\n\ndescribe(\"QueryBasedDropdownParameter\", () => {\n  let param;\n  let multiValuesOptions = null;\n\n  beforeEach(() => {\n    const paramOptions = {\n      name: \"param\",\n      title: \"Param\",\n      type: \"query\",\n      queryId: 1,\n      multiValuesOptions,\n    };\n    param = createParameter(paramOptions);\n  });\n\n  describe(\"normalizeValue\", () => {\n    test(\"returns the value when the input in the enum options\", () => {\n      const normalizedValue = param.normalizeValue(\"value2\");\n      expect(normalizedValue).toBe(\"value2\");\n    });\n\n    describe(\"Empty values\", () => {\n      const emptyValues = [null, undefined, []];\n\n      test.each(emptyValues)(\"normalizes empty value '%s' as null\", emptyValue => {\n        const normalizedValue = param.normalizeValue(emptyValue);\n        expect(normalizedValue).toBeNull();\n      });\n    });\n  });\n\n  describe(\"Multi-valued\", () => {\n    beforeAll(() => {\n      multiValuesOptions = { prefix: '\"', suffix: '\"', separator: \",\" };\n    });\n\n    describe(\"normalizeValue\", () => {\n      test(\"returns an array with the input when input is not an array\", () => {\n        const normalizedValue = param.normalizeValue(\"value\");\n        expect(normalizedValue).toEqual([\"value\"]);\n      });\n    });\n\n    describe(\"getExecutionValue\", () => {\n      test(\"joins values when joinListValues is truthy\", () => {\n        param.setValue([\"value1\", \"value3\"]);\n        const executionValue = param.getExecutionValue({ joinListValues: true });\n        expect(executionValue).toBe('\"value1\",\"value3\"');\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/services/parameters/tests/TextParameter.test.js",
    "content": "import { createParameter } from \"..\";\n\ndescribe(\"TextParameter\", () => {\n  let param;\n\n  beforeEach(() => {\n    param = createParameter({ name: \"param\", title: \"Param\", type: \"text\" });\n  });\n\n  describe(\"normalizeValue\", () => {\n    test(\"converts Strings\", () => {\n      const normalizedValue = param.normalizeValue(\"exampleString\");\n      expect(normalizedValue).toBe(\"exampleString\");\n    });\n\n    test(\"converts Numbers\", () => {\n      const normalizedValue = param.normalizeValue(3);\n      expect(normalizedValue).toBe(\"3\");\n    });\n\n    describe(\"Empty values\", () => {\n      const emptyValues = [null, undefined, \"\"];\n\n      test.each(emptyValues)(\"normalizes empty value '%s' as null\", emptyValue => {\n        const normalizedValue = param.normalizeValue(emptyValue);\n        expect(normalizedValue).toBeNull();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/services/parameters/tests/TextPatternParameter.test.js",
    "content": "import { createParameter } from \"..\";\n\ndescribe(\"TextPatternParameter\", () => {\n  let param;\n\n  beforeEach(() => {\n    param = createParameter({ name: \"param\", title: \"Param\", type: \"text-pattern\", regex: \"a+\" });\n  });\n\n  describe(\"noramlizeValue\", () => {\n    test(\"converts matching strings\", () => {\n      const normalizedValue = param.normalizeValue(\"art\");\n      expect(normalizedValue).toBe(\"art\");\n    });\n\n    test(\"returns null when string does not match pattern\", () => {\n      const normalizedValue = param.normalizeValue(\"brt\");\n      expect(normalizedValue).toBeNull();\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/services/policy/DefaultPolicy.js",
    "content": "import { get, isArray } from \"lodash\";\nimport { currentUser, clientConfig } from \"@/services/auth\";\n\n/* eslint-disable class-methods-use-this */\n\nexport default class DefaultPolicy {\n  refresh() {\n    return Promise.resolve(this);\n  }\n\n  canCreateDataSource() {\n    return currentUser.isAdmin;\n  }\n\n  isCreateDataSourceEnabled() {\n    return currentUser.isAdmin;\n  }\n\n  canCreateDestination() {\n    return currentUser.isAdmin;\n  }\n\n  isCreateDestinationEnabled() {\n    return currentUser.isAdmin;\n  }\n\n  canCreateDashboard() {\n    return currentUser.hasPermission(\"create_dashboard\");\n  }\n\n  isCreateDashboardEnabled() {\n    return currentUser.hasPermission(\"create_dashboard\");\n  }\n\n  canCreateAlert() {\n    return true;\n  }\n\n  canCreateUser() {\n    return currentUser.isAdmin;\n  }\n\n  isCreateUserEnabled() {\n    return currentUser.isAdmin;\n  }\n\n  isCreateQuerySnippetEnabled() {\n    return true;\n  }\n\n  getDashboardRefreshIntervals() {\n    const result = clientConfig.dashboardRefreshIntervals;\n    return isArray(result) ? result : null;\n  }\n\n  getQueryRefreshIntervals() {\n    const result = clientConfig.queryRefreshIntervals;\n    return isArray(result) ? result : null;\n  }\n\n  canEdit(object) {\n    return get(object, \"can_edit\", false);\n  }\n\n  canRun() {\n    return true;\n  }\n}\n"
  },
  {
    "path": "client/app/services/policy/index.js",
    "content": "import DefaultPolicy from \"./DefaultPolicy\";\n\n// eslint-disable-next-line import/no-mutable-exports\nexport let policy = new DefaultPolicy();\n\nexport function setPolicy(newPolicy) {\n  policy = newPolicy;\n}\n"
  },
  {
    "path": "client/app/services/query-result.js",
    "content": "import debug from \"debug\";\nimport moment from \"moment\";\nimport { axios } from \"@/services/axios\";\nimport { QueryResultError } from \"@/services/query\";\nimport { Auth } from \"@/services/auth\";\nimport { isString, uniqBy, each, isNumber, includes, extend, forOwn, get } from \"lodash\";\n\nconst logger = debug(\"redash:services:QueryResult\");\nconst filterTypes = [\"filter\", \"multi-filter\", \"multiFilter\"];\n\nfunction defer() {\n  const result = { onStatusChange: (status) => {} };\n  result.promise = new Promise((resolve, reject) => {\n    result.resolve = resolve;\n    result.reject = reject;\n  });\n  return result;\n}\n\nfunction getColumnNameWithoutType(column) {\n  let typeSplit;\n  if (column.indexOf(\"::\") !== -1) {\n    typeSplit = \"::\";\n  } else if (column.indexOf(\"__\") !== -1) {\n    typeSplit = \"__\";\n  } else {\n    return column;\n  }\n\n  const parts = column.split(typeSplit);\n  if (parts[0] === \"\" && parts.length === 2) {\n    return parts[1];\n  }\n\n  if (!includes(filterTypes, parts[1])) {\n    return column;\n  }\n\n  return parts[0];\n}\n\nfunction getColumnFriendlyName(column) {\n  return getColumnNameWithoutType(column).replace(/(?:^|\\s)\\S/g, (a) => a.toUpperCase());\n}\n\nconst createOrSaveUrl = (data) => (data.id ? `api/query_results/${data.id}` : \"api/query_results\");\nconst QueryResultResource = {\n  get: ({ id }) => axios.get(`api/query_results/${id}`),\n  post: (data) => axios.post(createOrSaveUrl(data), data),\n};\n\nexport const ExecutionStatus = {\n  WAITING: \"waiting\",\n  PROCESSING: \"processing\",\n  DONE: \"done\",\n  FAILED: \"failed\",\n  LOADING_RESULT: \"loading-result\",\n};\n\nconst statuses = {\n  1: ExecutionStatus.WAITING,\n  2: ExecutionStatus.PROCESSING,\n  3: ExecutionStatus.DONE,\n  4: ExecutionStatus.FAILED,\n};\n\nfunction handleErrorResponse(queryResult, error) {\n  const status = get(error, \"response.status\");\n  switch (status) {\n    case 403:\n      queryResult.update(error.response.data);\n      return;\n    case 400:\n      if (\"job\" in error.response.data) {\n        queryResult.update(error.response.data);\n        return;\n      }\n      break;\n    case 404:\n      queryResult.update({\n        job: {\n          error: \"cached query result unavailable, please execute again.\",\n          status: 4,\n        },\n      });\n      return;\n    // no default\n  }\n\n  logger(\"Unknown error\", error);\n  queryResult.update({\n    job: {\n      error: get(error, \"response.data.message\", \"Unknown error occurred. Please try again later.\"),\n      status: 4,\n    },\n  });\n}\n\nfunction sleep(ms) {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function fetchDataFromJob(jobId, interval = 1000) {\n  return axios.get(`api/jobs/${jobId}`).then((data) => {\n    const status = statuses[data.job.status];\n    if (status === ExecutionStatus.WAITING || status === ExecutionStatus.PROCESSING) {\n      return sleep(interval).then(() => fetchDataFromJob(data.job.id));\n    } else if (status === ExecutionStatus.DONE) {\n      return data.job.result;\n    } else if (status === ExecutionStatus.FAILED) {\n      return Promise.reject(data.job.error);\n    }\n  });\n}\n\nexport function isDateTime(v) {\n  return isString(v) && moment(v, moment.ISO_8601, true).isValid() && /^\\d{4}-\\d{2}-\\d{2}T/.test(v);\n}\n\nclass QueryResult {\n  constructor(props) {\n    this.deferred = defer();\n    this.job = {};\n    this.query_result = {};\n    this.status = \"waiting\";\n\n    this.updatedAt = moment();\n\n    // extended status flags\n    this.isLoadingResult = false;\n\n    if (props) {\n      this.update(props);\n    }\n  }\n\n  update(props) {\n    extend(this, props);\n\n    if (\"query_result\" in props) {\n      this.status = ExecutionStatus.DONE;\n      this.deferred.onStatusChange(ExecutionStatus.DONE);\n\n      const columnTypes = {};\n\n      // TODO: we should stop manipulating incoming data, and switch to relaying\n      // on the column type set by the backend. This logic is prone to errors,\n      // and better be removed. Kept for now, for backward compatability.\n      each(this.query_result.data.rows, (row) => {\n        forOwn(row, (v, k) => {\n          let newType = null;\n          if (isNumber(v)) {\n            newType = \"float\";\n          } else if (isDateTime(v)) {\n            row[k] = moment.utc(v);\n            newType = \"datetime\";\n          } else if (isString(v) && v.match(/^\\d{4}-\\d{2}-\\d{2}$/)) {\n            row[k] = moment.utc(v);\n            newType = \"date\";\n          } else if (typeof v === \"object\" && v !== null) {\n            row[k] = JSON.stringify(v);\n          } else {\n            newType = \"string\";\n          }\n\n          if (newType !== null) {\n            if (columnTypes[k] !== undefined && columnTypes[k] !== newType) {\n              columnTypes[k] = \"string\";\n            } else {\n              columnTypes[k] = newType;\n            }\n          }\n        });\n      });\n\n      each(this.query_result.data.columns, (column) => {\n        column.name = \"\" + column.name;\n        if (columnTypes[column.name]) {\n          if (column.type == null || column.type === \"string\") {\n            column.type = columnTypes[column.name];\n          }\n        }\n      });\n\n      this.deferred.resolve(this);\n    } else if (this.job.status === 3 || this.job.status === 2) {\n      this.deferred.onStatusChange(ExecutionStatus.PROCESSING);\n      this.status = \"processing\";\n    } else if (this.job.status === 4) {\n      this.status = statuses[this.job.status];\n      this.deferred.reject(new QueryResultError(this.job.error));\n    } else {\n      this.deferred.onStatusChange(undefined);\n      this.status = undefined;\n    }\n  }\n\n  getId() {\n    let id = null;\n    if (\"query_result\" in this) {\n      id = this.query_result.id;\n    }\n    return id;\n  }\n\n  cancelExecution() {\n    axios.delete(`api/jobs/${this.job.id}`);\n  }\n\n  getStatus() {\n    if (this.isLoadingResult) {\n      return ExecutionStatus.LOADING_RESULT;\n    }\n    return this.status || statuses[this.job.status];\n  }\n\n  getError() {\n    // TODO: move this logic to the server...\n    if (this.job.error === \"None\") {\n      return undefined;\n    }\n\n    return this.job.error;\n  }\n\n  getLog() {\n    if (!this.query_result.data || !this.query_result.data.log || this.query_result.data.log.length === 0) {\n      return null;\n    }\n\n    return this.query_result.data.log;\n  }\n\n  getUpdatedAt() {\n    return this.query_result.retrieved_at || this.job.updated_at * 1000.0 || this.updatedAt;\n  }\n\n  getRuntime() {\n    return this.query_result.runtime;\n  }\n\n  getRawData() {\n    if (!this.query_result.data) {\n      return null;\n    }\n\n    return this.query_result.data.rows;\n  }\n\n  getData() {\n    return this.query_result.data ? this.query_result.data.rows : null;\n  }\n\n  isEmpty() {\n    return this.getData() === null || this.getData().length === 0;\n  }\n\n  getColumns() {\n    if (this.columns === undefined && this.query_result.data) {\n      this.columns = this.query_result.data.columns;\n    }\n\n    return this.columns;\n  }\n\n  getColumnNames() {\n    if (this.columnNames === undefined && this.query_result.data) {\n      this.columnNames = this.query_result.data.columns.map((v) => v.name);\n    }\n\n    return this.columnNames;\n  }\n\n  getColumnFriendlyNames() {\n    return this.getColumnNames().map((col) => getColumnFriendlyName(col));\n  }\n\n  getTruncated() {\n    return this.query_result.data ? this.query_result.data.truncated : null;\n  }\n\n  getFilters() {\n    if (!this.getColumns()) {\n      return [];\n    }\n\n    const filters = [];\n\n    this.getColumns().forEach((col) => {\n      const name = col.name;\n      const type = name.split(\"::\")[1] || name.split(\"__\")[1];\n      if (includes(filterTypes, type)) {\n        // filter found\n        const filter = {\n          name,\n          friendlyName: getColumnFriendlyName(name),\n          column: col,\n          values: [],\n          multiple: type === \"multiFilter\" || type === \"multi-filter\",\n        };\n        filters.push(filter);\n      }\n    }, this);\n\n    this.getRawData().forEach((row) => {\n      filters.forEach((filter) => {\n        filter.values.push(row[filter.name]);\n        if (filter.values.length === 1) {\n          if (filter.multiple) {\n            filter.current = [row[filter.name]];\n          } else {\n            filter.current = row[filter.name];\n          }\n        }\n      });\n    });\n\n    filters.forEach((filter) => {\n      filter.values = uniqBy(filter.values, (v) => {\n        if (moment.isMoment(v)) {\n          return v.unix();\n        }\n        return v;\n      });\n      if (filter.values.length > 1 && filter.multiple) {\n        filter.current = filter.values.slice();\n      }\n    });\n\n    return filters;\n  }\n\n  toPromise(statusCallback) {\n    if (statusCallback) {\n      this.deferred.onStatusChange = statusCallback;\n    }\n    return this.deferred.promise;\n  }\n\n  static getById(queryId, id) {\n    const queryResult = new QueryResult();\n\n    queryResult.isLoadingResult = true;\n    queryResult.deferred.onStatusChange(ExecutionStatus.LOADING_RESULT);\n\n    axios\n      .get(`api/queries/${queryId}/results/${id}.json`)\n      .then((response) => {\n        // Success handler\n        queryResult.isLoadingResult = false;\n        queryResult.update(response);\n      })\n      .catch((error) => {\n        // Error handler\n        queryResult.isLoadingResult = false;\n        handleErrorResponse(queryResult, error);\n      });\n\n    return queryResult;\n  }\n\n  loadLatestCachedResult(queryId, parameters) {\n    axios\n      .post(`api/queries/${queryId}/results`, { queryId, parameters })\n      .then((response) => {\n        this.update(response);\n      })\n      .catch((error) => {\n        handleErrorResponse(this, error);\n      });\n  }\n\n  loadResult(tryCount) {\n    this.isLoadingResult = true;\n    this.deferred.onStatusChange(ExecutionStatus.LOADING_RESULT);\n\n    QueryResultResource.get({ id: this.job.query_result_id })\n      .then((response) => {\n        this.update(response);\n        this.isLoadingResult = false;\n      })\n      .catch((error) => {\n        if (tryCount === undefined) {\n          tryCount = 0;\n        }\n\n        if (tryCount > 3) {\n          logger(\"Connection error while trying to load result\", error);\n          this.update({\n            job: {\n              error: \"failed communicating with server. Please check your Internet connection and try again.\",\n              status: 4,\n            },\n          });\n          this.isLoadingResult = false;\n        } else {\n          setTimeout(\n            () => {\n              this.loadResult(tryCount + 1);\n            },\n            1000 * Math.pow(2, tryCount)\n          );\n        }\n      });\n  }\n\n  refreshStatus(query, parameters, tryNumber = 1) {\n    const loadResult = () =>\n      Auth.isAuthenticated() ? this.loadResult() : this.loadLatestCachedResult(query, parameters);\n\n    const request = Auth.isAuthenticated()\n      ? axios.get(`api/jobs/${this.job.id}`)\n      : axios.get(`api/queries/${query}/jobs/${this.job.id}`);\n\n    request\n      .then((jobResponse) => {\n        this.update(jobResponse);\n\n        if (this.getStatus() === \"processing\" && this.job.query_result_id && this.job.query_result_id !== \"None\") {\n          loadResult();\n        } else if (this.getStatus() !== \"failed\") {\n          let waitTime;\n          if (tryNumber <= 10) {\n            waitTime = 500;\n          } else if (tryNumber <= 50) {\n            waitTime = 1000;\n          } else {\n            waitTime = 3000;\n          }\n          setTimeout(() => {\n            this.refreshStatus(query, parameters, tryNumber + 1);\n          }, waitTime);\n        }\n      })\n      .catch((error) => {\n        logger(\"Connection error\", error);\n        // TODO: use QueryResultError, or better yet: exception/reject of promise.\n        this.update({\n          job: {\n            error: \"failed communicating with server. Please check your Internet connection and try again.\",\n            status: 4,\n          },\n        });\n      });\n  }\n\n  getLink(queryId, fileType, apiKey) {\n    let link = `api/queries/${queryId}/results/${this.getId()}.${fileType}`;\n    if (apiKey) {\n      link = `${link}?api_key=${apiKey}`;\n    }\n    return link;\n  }\n\n  getName(queryName, fileType) {\n    return `${queryName.replace(/ /g, \"_\") + moment(this.getUpdatedAt()).format(\"_YYYY_MM_DD\")}.${fileType}`;\n  }\n\n  static getByQueryId(id, parameters, applyAutoLimit, maxAge) {\n    const queryResult = new QueryResult();\n\n    axios\n      .post(`api/queries/${id}/results`, { id, parameters, apply_auto_limit: applyAutoLimit, max_age: maxAge })\n      .then((response) => {\n        queryResult.update(response);\n\n        if (\"job\" in response) {\n          queryResult.refreshStatus(id, parameters);\n        }\n      })\n      .catch((error) => {\n        handleErrorResponse(queryResult, error);\n      });\n\n    return queryResult;\n  }\n\n  static get(dataSourceId, query, parameters, applyAutoLimit, maxAge, queryId) {\n    const queryResult = new QueryResult();\n\n    const params = {\n      data_source_id: dataSourceId,\n      parameters,\n      query,\n      apply_auto_limit: applyAutoLimit,\n      max_age: maxAge,\n    };\n\n    if (queryId !== undefined) {\n      params.query_id = queryId;\n    }\n\n    QueryResultResource.post(params)\n      .then((response) => {\n        queryResult.update(response);\n\n        if (\"job\" in response) {\n          queryResult.refreshStatus(query, parameters);\n        }\n      })\n      .catch((error) => {\n        handleErrorResponse(queryResult, error);\n      });\n\n    return queryResult;\n  }\n}\n\nexport default QueryResult;\n"
  },
  {
    "path": "client/app/services/query-result.test.js",
    "content": "import { isDateTime } from \"@/services/query-result\";\n\ndescribe(\"isDateTime\", () => {\n  it.each([\n    [\"2022-01-01T00:00:00\", true],\n    [\"2022-01-01T00:00:00+09:00\", true],\n    [\"2021-01-27T00:00:01.733983944+03:00 stderr F {\", false],\n    [\"2021-01-27Z00:00:00+09:00\", false],\n    [\"2021-01-27\", false],\n    [\"foo bar\", false],\n    [2022, false],\n    [null, false],\n    [\"\", false],\n  ])(\"isDateTime('%s'). expected '%s'.\", (value, expected) => {\n    expect(isDateTime(value)).toBe(expected);\n  });\n});\n"
  },
  {
    "path": "client/app/services/query-snippet.js",
    "content": "import { axios } from \"@/services/axios\";\nimport { extend, map } from \"lodash\";\n\nclass QuerySnippet {\n  constructor(querySnippet) {\n    extend(this, querySnippet);\n  }\n\n  getSnippet() {\n    let name = this.trigger;\n    if (this.description !== \"\") {\n      name = `${this.trigger}: ${this.description}`;\n    }\n\n    return {\n      name,\n      content: this.snippet,\n      tabTrigger: this.trigger,\n    };\n  }\n}\n\nconst getQuerySnippet = querySnippet => new QuerySnippet(querySnippet);\n\nconst QuerySnippetService = {\n  get: data => axios.get(`api/query_snippets/${data.id}`).then(getQuerySnippet),\n  query: () => axios.get(\"api/query_snippets\").then(data => map(data, getQuerySnippet)),\n  create: data => axios.post(\"api/query_snippets\", data).then(getQuerySnippet),\n  save: data => axios.post(`api/query_snippets/${data.id}`, data).then(getQuerySnippet),\n  delete: data => axios.delete(`api/query_snippets/${data.id}`),\n};\n\nexport default QuerySnippetService;\n"
  },
  {
    "path": "client/app/services/query.js",
    "content": "import moment from \"moment\";\nimport debug from \"debug\";\nimport Mustache from \"mustache\";\nimport { axios } from \"@/services/axios\";\nimport {\n  zipObject,\n  isEmpty,\n  isArray,\n  map,\n  filter,\n  includes,\n  union,\n  uniq,\n  has,\n  identity,\n  extend,\n  each,\n  some,\n  clone,\n  find,\n} from \"lodash\";\nimport location from \"@/services/location\";\n\nimport { Parameter, createParameter } from \"./parameters\";\nimport { currentUser } from \"./auth\";\nimport QueryResult from \"./query-result\";\nimport localOptions from \"@/lib/localOptions\";\n\nMustache.escape = identity; // do not html-escape values\n\nconst logger = debug(\"redash:services:query\");\n\nfunction collectParams(parts) {\n  let parameters = [];\n\n  parts.forEach(part => {\n    if (part[0] === \"name\" || part[0] === \"&\") {\n      parameters.push(part[1].split(\".\")[0]);\n    } else if (part[0] === \"#\") {\n      parameters = union(parameters, collectParams(part[4]));\n    }\n  });\n\n  return parameters;\n}\n\nexport class Query {\n  constructor(query) {\n    extend(this, query);\n\n    if (!has(this, \"options\")) {\n      this.options = {};\n    }\n    this.options.apply_auto_limit = !!this.options.apply_auto_limit;\n\n    if (!isArray(this.options.parameters)) {\n      this.options.parameters = [];\n    }\n  }\n\n  isNew() {\n    return this.id === undefined;\n  }\n\n  hasDailySchedule() {\n    return this.schedule && this.schedule.match(/\\d\\d:\\d\\d/) !== null;\n  }\n\n  scheduleInLocalTime() {\n    const parts = this.schedule.split(\":\");\n    return moment\n      .utc()\n      .hour(parts[0])\n      .minute(parts[1])\n      .local()\n      .format(\"HH:mm\");\n  }\n\n  hasResult() {\n    return !!(this.latest_query_data || this.latest_query_data_id);\n  }\n\n  paramsRequired() {\n    return this.getParameters().isRequired();\n  }\n\n  hasParameters() {\n    return this.getParametersDefs().length > 0;\n  }\n\n  prepareQueryResultExecution(execute, maxAge) {\n    const parameters = this.getParameters();\n    const missingParams = parameters.getMissing();\n\n    if (missingParams.length > 0) {\n      let paramsWord = \"parameter\";\n      let valuesWord = \"value\";\n      if (missingParams.length > 1) {\n        paramsWord = \"parameters\";\n        valuesWord = \"values\";\n      }\n\n      return new QueryResult({\n        job: {\n          error: `missing ${valuesWord} for ${missingParams.join(\", \")} ${paramsWord}.`,\n          status: 4,\n        },\n      });\n    }\n\n    if (parameters.isRequired()) {\n      // Need to clear latest results, to make sure we don't use results for different params.\n      this.latest_query_data = null;\n      this.latest_query_data_id = null;\n    }\n\n    if (this.latest_query_data && maxAge !== 0) {\n      if (!this.queryResult) {\n        this.queryResult = new QueryResult({\n          query_result: this.latest_query_data,\n        });\n      }\n    } else if (this.latest_query_data_id && maxAge !== 0) {\n      if (!this.queryResult) {\n        this.queryResult = QueryResult.getById(this.id, this.latest_query_data_id);\n      }\n    } else {\n      this.queryResult = execute();\n    }\n\n    return this.queryResult;\n  }\n\n  getQueryResult(maxAge) {\n    const execute = () =>\n      QueryResult.getByQueryId(this.id, this.getParameters().getExecutionValues(), this.getAutoLimit(), maxAge);\n    return this.prepareQueryResultExecution(execute, maxAge);\n  }\n\n  getQueryResultByText(maxAge, selectedQueryText) {\n    const queryText = selectedQueryText || this.query;\n    if (!queryText) {\n      return new QueryResultError(\"Can't execute empty query.\");\n    }\n\n    const parameters = this.getParameters().getExecutionValues({ joinListValues: true });\n    const execute = () =>\n      QueryResult.get(this.data_source_id, queryText, parameters, this.getAutoLimit(), maxAge, this.id);\n    return this.prepareQueryResultExecution(execute, maxAge);\n  }\n\n  getUrl(source, hash) {\n    let url = `queries/${this.id}`;\n\n    if (source) {\n      url += \"/source\";\n    }\n\n    let params = {};\n    if (this.getParameters().isRequired()) {\n      this.getParametersDefs().forEach(param => {\n        extend(params, param.toUrlParams());\n      });\n    }\n    Object.keys(params).forEach(key => params[key] == null && delete params[key]);\n    params = map(params, (value, name) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`).join(\"&\");\n\n    if (params !== \"\") {\n      url += `?${params}`;\n    }\n\n    if (hash) {\n      url += `#${hash}`;\n    }\n\n    return url;\n  }\n\n  getQueryResultPromise() {\n    return this.getQueryResult().toPromise();\n  }\n\n  getParameters() {\n    if (!this.$parameters) {\n      this.$parameters = new Parameters(this, location.search);\n    }\n\n    return this.$parameters;\n  }\n\n  getAutoLimit() {\n    return this.options.apply_auto_limit;\n  }\n\n  getParametersDefs(update = true) {\n    return this.getParameters().get(update);\n  }\n\n  favorite() {\n    return Query.favorite(this);\n  }\n\n  unfavorite() {\n    return Query.unfavorite(this);\n  }\n\n  clone() {\n    const newQuery = clone(this);\n    newQuery.$parameters = null;\n    newQuery.getParameters();\n    return newQuery;\n  }\n}\n\nclass Parameters {\n  constructor(query, queryString) {\n    this.query = query;\n    this.updateParameters();\n    this.initFromQueryString(queryString);\n  }\n\n  parseQuery() {\n    const fallback = () => map(this.query.options.parameters, i => i.name);\n\n    let parameters = [];\n    if (this.query.query !== undefined) {\n      try {\n        const parts = Mustache.parse(this.query.query);\n        parameters = uniq(collectParams(parts));\n      } catch (e) {\n        logger(\"Failed parsing parameters: \", e);\n        // Return current parameters so we don't reset the list\n        parameters = fallback();\n      }\n    } else {\n      parameters = fallback();\n    }\n\n    return parameters;\n  }\n\n  updateParameters(update) {\n    if (this.query.query === this.cachedQueryText) {\n      const parameters = this.query.options.parameters;\n      const hasUnprocessedParameters = find(parameters, p => !(p instanceof Parameter));\n      if (hasUnprocessedParameters) {\n        this.query.options.parameters = map(parameters, p =>\n          p instanceof Parameter ? p : createParameter(p, this.query.id)\n        );\n      }\n      return;\n    }\n\n    this.cachedQueryText = this.query.query;\n    const parameterNames = update ? this.parseQuery() : map(this.query.options.parameters, p => p.name);\n\n    this.query.options.parameters = this.query.options.parameters || [];\n\n    const parametersMap = {};\n    this.query.options.parameters.forEach(param => {\n      parametersMap[param.name] = param;\n    });\n\n    parameterNames.forEach(param => {\n      if (!has(parametersMap, param)) {\n        this.query.options.parameters.push(\n          createParameter({\n            title: param,\n            name: param,\n            type: \"text\",\n            value: null,\n            global: false,\n          })\n        );\n      }\n    });\n\n    const parameterExists = p => includes(parameterNames, p.name);\n    const parameters = this.query.options.parameters;\n    this.query.options.parameters = parameters\n      .filter(parameterExists)\n      .map(p => (p instanceof Parameter ? p : createParameter(p, this.query.id)));\n  }\n\n  initFromQueryString(query) {\n    this.get().forEach(param => {\n      param.fromUrlParams(query);\n    });\n  }\n\n  get(update = true) {\n    this.updateParameters(update);\n    return this.query.options.parameters;\n  }\n\n  add(parameterDef) {\n    this.query.options.parameters = this.query.options.parameters.filter(p => p.name !== parameterDef.name);\n    const param = createParameter(parameterDef);\n    this.query.options.parameters.push(param);\n    return param;\n  }\n\n  getMissing() {\n    return map(\n      filter(this.get(), p => p.isEmpty),\n      i => i.title\n    );\n  }\n\n  isRequired() {\n    return !isEmpty(this.get());\n  }\n\n  getExecutionValues(extra = {}) {\n    const params = this.get();\n    return zipObject(\n      map(params, i => i.name),\n      map(params, i => i.getExecutionValue(extra))\n    );\n  }\n\n  hasPendingValues() {\n    return some(this.get(), p => p.hasPendingValue);\n  }\n\n  applyPendingValues() {\n    each(this.get(), p => p.applyPendingValue());\n  }\n\n  toUrlParams() {\n    if (this.get().length === 0) {\n      return \"\";\n    }\n\n    const params = Object.assign(...this.get().map(p => p.toUrlParams()));\n    Object.keys(params).forEach(key => params[key] == null && delete params[key]);\n    return Object.keys(params)\n      .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)\n      .join(\"&\");\n  }\n}\n\nexport class QueryResultError {\n  constructor(errorMessage) {\n    this.errorMessage = errorMessage;\n    this.updatedAt = moment.utc();\n  }\n\n  getUpdatedAt() {\n    return this.updatedAt;\n  }\n\n  getError() {\n    return this.errorMessage;\n  }\n\n  toPromise() {\n    return Promise.reject(this);\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  getStatus() {\n    return \"failed\";\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  getData() {\n    return null;\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  getLog() {\n    return null;\n  }\n}\n\nconst getQuery = query => new Query(query);\nconst saveOrCreateUrl = data => (data.id ? `api/queries/${data.id}` : \"api/queries\");\nconst mapResults = data => ({ ...data, results: map(data.results, getQuery) });\n\nconst QueryService = {\n  query: params => axios.get(\"api/queries\", { params }).then(mapResults),\n  get: data => axios.get(`api/queries/${data.id}`, data).then(getQuery),\n  save: data => axios.post(saveOrCreateUrl(data), data).then(getQuery),\n  delete: data => axios.delete(`api/queries/${data.id}`),\n  recent: params => axios.get(`api/queries/recent`, { params }).then(data => map(data, getQuery)),\n  archive: params => axios.get(`api/queries/archive`, { params }).then(mapResults),\n  myQueries: params => axios.get(\"api/queries/my\", { params }).then(mapResults),\n  fork: ({ id }) => axios.post(`api/queries/${id}/fork`, { id }).then(getQuery),\n  resultById: data => axios.get(`api/queries/${data.id}/results.json`),\n  asDropdown: data => axios.get(`api/queries/${data.id}/dropdown`),\n  associatedDropdown: ({ queryId, dropdownQueryId }) =>\n    axios.get(`api/queries/${queryId}/dropdowns/${dropdownQueryId}`),\n  favorites: params => axios.get(\"api/queries/favorites\", { params }).then(mapResults),\n  favorite: data => axios.post(`api/queries/${data.id}/favorite`),\n  unfavorite: data => axios.delete(`api/queries/${data.id}/favorite`),\n};\n\nQueryService.newQuery = function newQuery() {\n  return new Query({\n    query: \"\",\n    name: \"New Query\",\n    schedule: null,\n    user: currentUser,\n    options: { apply_auto_limit: localOptions.get(\"applyAutoLimit\", true) },\n    tags: [],\n    can_edit: true,\n  });\n};\n\nextend(Query, QueryService);\n"
  },
  {
    "path": "client/app/services/recordEvent.js",
    "content": "import { axios } from \"@/services/axios\";\nimport { debounce, extend } from \"lodash\";\n\nlet events = [];\n\nconst post = debounce(() => {\n  const eventsToSend = events;\n  events = [];\n\n  axios.post(\"api/events\", eventsToSend);\n}, 1000);\n\nexport default function recordEvent(action, objectType, objectId, additionalProperties) {\n  const event = {\n    action,\n    object_type: objectType,\n    object_id: objectId,\n    timestamp: Date.now() / 1000.0,\n    screen_resolution: `${window.screen.width}x${window.screen.height}`,\n  };\n  extend(event, additionalProperties);\n  events.push(event);\n\n  post();\n}\n"
  },
  {
    "path": "client/app/services/resizeObserver.js",
    "content": "const items = new Map();\n\nfunction checkItems() {\n  if (items.size > 0) {\n    items.forEach((item, node) => {\n      const bounds = node.getBoundingClientRect();\n      // convert to int (because these numbers needed for comparisons), but preserve 1 decimal point\n      const width = Math.round(bounds.width * 10);\n      const height = Math.round(bounds.height * 10);\n\n      if (item.width !== width || item.height !== height) {\n        item.width = width;\n        item.height = height;\n        item.callback(node);\n      }\n    });\n\n    setTimeout(checkItems, 100);\n  }\n}\n\nexport default function observe(node, callback) {\n  if (node && !items.has(node)) {\n    const shouldTrigger = items.size === 0;\n    items.set(node, { callback });\n    if (shouldTrigger) {\n      checkItems();\n    }\n    return () => items.delete(node);\n  }\n  return () => {};\n}\n"
  },
  {
    "path": "client/app/services/restoreSession.jsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport Modal from \"antd/lib/modal\";\nimport { Auth } from \"@/services/auth\";\n\nconst SESSION_RESTORED_MESSAGE = \"redash_session_restored\";\n\nexport function notifySessionRestored() {\n  if (window.opener) {\n    window.opener.postMessage({ type: SESSION_RESTORED_MESSAGE }, window.location.origin);\n  }\n}\n\nfunction getPopupPosition(width, height) {\n  const windowLeft = window.screenX;\n  const windowTop = window.screenY;\n  const windowWidth = window.innerWidth;\n  const windowHeight = window.innerHeight;\n\n  return {\n    left: Math.floor((windowWidth - width) / 2 + windowLeft),\n    top: Math.floor((windowHeight - height) / 2 + windowTop),\n    width: Math.floor(width),\n    height: Math.floor(height),\n  };\n}\n\nfunction showRestoreSessionPrompt(loginUrl, onSuccess) {\n  let popup = null;\n\n  Modal.warning({\n    content: \"Your session has expired. Please login to continue.\",\n    okText: (\n      <React.Fragment>\n        Login <i className=\"fa fa-external-link m-r-5\" aria-hidden=\"true\" />\n        <span className=\"sr-only\">(opens in a new tab)</span>\n      </React.Fragment>\n    ),\n    centered: true,\n    mask: true,\n    maskClosable: false,\n    keyboard: false,\n    onOk: closeModal => {\n      if (popup && !popup.closed) {\n        popup.focus();\n        return; // popup already shown\n      }\n\n      const popupOptions = {\n        ...getPopupPosition(640, 640),\n        menubar: \"no\",\n        toolbar: \"no\",\n        location: \"yes\",\n        resizable: \"yes\",\n        scrollbars: \"yes\",\n        status: \"yes\",\n      };\n\n      popup = window.open(loginUrl, \"Restore Session\", map(popupOptions, (value, key) => `${key}=${value}`).join(\",\"));\n\n      const handlePostMessage = event => {\n        if (event.data.type === SESSION_RESTORED_MESSAGE) {\n          if (popup) {\n            popup.close();\n          }\n          popup = null;\n          window.removeEventListener(\"message\", handlePostMessage);\n          closeModal();\n          onSuccess();\n        }\n      };\n\n      window.addEventListener(\"message\", handlePostMessage, false);\n    },\n  });\n}\n\nlet restoreSessionPromise = null;\n\nexport function restoreSession() {\n  if (!restoreSessionPromise) {\n    restoreSessionPromise = new Promise(resolve => {\n      showRestoreSessionPrompt(Auth.getLoginUrl(), () => {\n        restoreSessionPromise = null;\n        resolve();\n      });\n    });\n  }\n\n  return restoreSessionPromise;\n}\n"
  },
  {
    "path": "client/app/services/routes.ts",
    "content": "import { isString, isObject, filter, sortBy } from \"lodash\";\nimport React from \"react\";\nimport { Context, Route as UniversalRouterRoute } from \"universal-router\";\nimport pathToRegexp from \"path-to-regexp\";\n\nexport interface CurrentRoute<P> {\n  id: string | null;\n  key?: string;\n  title: string;\n  routeParams: P;\n}\n\nexport interface RedashRoute<P = {}, C extends Context = Context, R = any> extends UniversalRouterRoute<C, R> {\n  path: string; // we don't use other UniversalRouterRoute options, path should be available and should be a string\n  key?: string; // generated in Router.jsx\n  title: string;\n  render?: (currentRoute: CurrentRoute<P>) => React.ReactNode;\n  getApiKey?: () => string;\n}\n\ninterface RouteItem extends RedashRoute<any> {\n  id: string | null;\n}\n\nfunction getRouteParamsCount(path: string) {\n  const tokens = pathToRegexp.parse(path);\n  return filter(tokens, isObject).length;\n}\n\nclass Routes {\n  _items: RouteItem[] = [];\n  _sorted = false;\n\n  get items(): RouteItem[] {\n    if (!this._sorted) {\n      this._items = sortBy(this._items, [\n        item => getRouteParamsCount(item.path), // simple definitions first, with more params - last\n        item => -item.path.length, // longer first\n        item => item.path, // if same type and length - sort alphabetically\n      ]);\n      this._sorted = true;\n    }\n    return this._items;\n  }\n\n  public register<P>(id: string, route: RedashRoute<P>) {\n    const idOrNull = isString(id) ? id : null;\n    this.unregister(idOrNull);\n    if (isObject(route)) {\n      this._items = [...this.items, { ...route, id: idOrNull }];\n      this._sorted = false;\n    }\n  }\n\n  public unregister(id: string | null) {\n    if (isString(id)) {\n      // removing item does not break their order (if already sorted)\n      this._items = filter(this.items, item => item.id !== id);\n    }\n  }\n}\n\nexport default new Routes();\n"
  },
  {
    "path": "client/app/services/sanitize.js",
    "content": "import { isString } from \"lodash\";\nimport DOMPurify from \"dompurify\";\n\nDOMPurify.setConfig({\n  ADD_ATTR: [\"target\"],\n});\n\nDOMPurify.addHook(\"afterSanitizeAttributes\", function(node) {\n  // Fix elements with `target` attribute:\n  // - allow only `target=\"_blank\"\n  // - add `rel=\"noopener noreferrer\"` to prevent https://www.owasp.org/index.php/Reverse_Tabnabbing\n\n  const target = node.getAttribute(\"target\");\n  if (isString(target) && target.toLowerCase() === \"_blank\") {\n    node.setAttribute(\"rel\", \"noopener noreferrer\");\n  } else {\n    node.removeAttribute(\"target\");\n  }\n});\n\nexport { DOMPurify };\n\nexport default DOMPurify.sanitize;\n"
  },
  {
    "path": "client/app/services/settingsMenu.js",
    "content": "import { isString, isObject, isFunction, extend, omit, sortBy, find, filter } from \"lodash\";\nimport { stripBase } from \"@/components/ApplicationArea/Router\";\nimport { currentUser } from \"@/services/auth\";\n\nclass SettingsMenuItem {\n  constructor(menuItem) {\n    extend(this, { pathPrefix: `/${menuItem.path}` }, omit(menuItem, [\"isActive\", \"isAvailable\"]));\n    if (isFunction(menuItem.isActive)) {\n      this.isActive = menuItem.isActive;\n    }\n    if (isFunction(menuItem.isAvailable)) {\n      this.isAvailable = menuItem.isAvailable;\n    }\n  }\n\n  isActive(path) {\n    return path.startsWith(this.pathPrefix);\n  }\n\n  isAvailable() {\n    return this.permission === undefined || currentUser.hasPermission(this.permission);\n  }\n}\n\nclass SettingsMenu {\n  items = [];\n\n  add(id, item) {\n    id = isString(id) ? id : null;\n    this.remove(id);\n    if (isObject(item)) {\n      this.items.push(new SettingsMenuItem({ ...item, id }));\n      this.items = sortBy(this.items, \"order\");\n    }\n  }\n\n  remove(id) {\n    if (isString(id)) {\n      this.items = filter(this.items, item => item.id !== id);\n      // removing item does not change order of other items, so no need to sort\n    }\n  }\n\n  getAvailableItems() {\n    return filter(this.items, item => item.isAvailable());\n  }\n\n  getActiveItem(path) {\n    const strippedPath = stripBase(path);\n    return find(this.items, item => item.isActive(strippedPath));\n  }\n}\n\nexport default new SettingsMenu();\n"
  },
  {
    "path": "client/app/services/settingsMenu.test.js",
    "content": "import settingsMenu from \"./settingsMenu\";\n\nconst dataSourcesItem = {\n  permission: \"admin\",\n  title: \"Data Sources\",\n  path: \"data_sources\",\n};\n\nconst usersItem = {\n  title: \"Users\",\n  path: \"users\",\n};\n\nsettingsMenu.add(null, dataSourcesItem);\nsettingsMenu.add(null, usersItem);\n\ndescribe(\"SettingsMenu\", () => {\n  describe(\"isActive\", () => {\n    test(\"works with non multi org paths\", () => {\n      expect(settingsMenu.getActiveItem(\"/data_sources/\").title).toBe(dataSourcesItem.title);\n    });\n\n    test(\"works with multi org paths\", () => {\n      // Set base href:\n      const base = document.createElement(\"base\");\n      base.setAttribute(\"href\", \"http://localhost/acme/\");\n      document.head.appendChild(base);\n\n      expect(settingsMenu.getActiveItem(\"/acme/data_sources/\")).toBeTruthy();\n      expect(settingsMenu.getActiveItem(\"/acme/data_sources/\").title).toBe(dataSourcesItem.title);\n    });\n  });\n});\n"
  },
  {
    "path": "client/app/services/url.js",
    "content": "import { pick, extend } from \"lodash\";\n\nconst link = document.createElement(\"a\"); // the only way to get an instance of Location class\n// add to document to apply <base> href\nlink.style.display = \"none\";\ndocument.body.appendChild(link);\n\nconst fragmentProps = [\"origin\", \"protocol\", \"host\", \"hostname\", \"port\", \"pathname\", \"search\", \"hash\", \"href\"];\n\nexport function parse(url) {\n  link.setAttribute(\"href\", url);\n  return pick(link, fragmentProps);\n}\n\nexport function stringify(fragments) {\n  extend(link, pick(fragments, fragmentProps));\n  return link.href; // absolute URL\n}\n\nexport function normalize(url) {\n  link.setAttribute(\"href\", url);\n  return link.href; // absolute URL\n}\n\nexport default { parse, stringify, normalize };\n"
  },
  {
    "path": "client/app/services/user.js",
    "content": "import { isString, get, find } from \"lodash\";\nimport sanitize from \"@/services/sanitize\";\nimport { axios } from \"@/services/axios\";\nimport notification from \"@/services/notification\";\nimport { clientConfig } from \"@/services/auth\";\n\nfunction getErrorMessage(error) {\n  return find([get(error, \"response.data.message\"), get(error, \"response.statusText\"), \"Unknown error\"], isString);\n}\n\nfunction disableResource(user) {\n  return `api/users/${user.id}/disable`;\n}\n\nfunction enableUser(user) {\n  const userName = sanitize(user.name);\n\n  return axios\n    .delete(disableResource(user))\n    .then(data => {\n      notification.success(`User ${userName} is now enabled.`);\n      user.is_disabled = false;\n      user.profile_image_url = data.profile_image_url;\n      return data;\n    })\n    .catch(error => {\n      notification.error(\"Cannot enable user\", getErrorMessage(error));\n    });\n}\n\nfunction disableUser(user) {\n  const userName = sanitize(user.name);\n  return axios\n    .post(disableResource(user))\n    .then(data => {\n      notification.warning(`User ${userName} is now disabled.`);\n      user.is_disabled = true;\n      user.profile_image_url = data.profile_image_url;\n      return data;\n    })\n    .catch(error => {\n      notification.error(\"Cannot disable user\", getErrorMessage(error));\n    });\n}\n\nfunction deleteUser(user) {\n  const userName = sanitize(user.name);\n  return axios\n    .delete(`api/users/${user.id}`)\n    .then(data => {\n      notification.warning(`User ${userName} has been deleted.`);\n      return data;\n    })\n    .catch(error => {\n      notification.error(\"Cannot delete user\", getErrorMessage(error));\n    });\n}\n\nfunction convertUserInfo(user) {\n  return {\n    id: user.id,\n    name: user.name,\n    email: user.email,\n    profileImageUrl: user.profile_image_url,\n    apiKey: user.api_key,\n    groupIds: user.groups,\n    isDisabled: user.is_disabled,\n    isInvitationPending: user.is_invitation_pending,\n  };\n}\n\nfunction regenerateApiKey(user) {\n  return axios\n    .post(`api/users/${user.id}/regenerate_api_key`)\n    .then(data => {\n      notification.success(\"The API Key has been updated.\");\n      return data.api_key;\n    })\n    .catch(error => {\n      notification.error(\"Failed regenerating API Key\", getErrorMessage(error));\n    });\n}\n\nfunction sendPasswordReset(user) {\n  return axios\n    .post(`api/users/${user.id}/reset_password`)\n    .then(data => {\n      if (clientConfig.mailSettingsMissing) {\n        notification.warning(\"The mail server is not configured.\");\n        return data.reset_link;\n      }\n      notification.success(\"Password reset email sent.\");\n    })\n    .catch(error => {\n      notification.error(\"Failed to send password reset email\", getErrorMessage(error));\n    });\n}\n\nfunction resendInvitation(user) {\n  return axios\n    .post(`api/users/${user.id}/invite`)\n    .then(data => {\n      if (clientConfig.mailSettingsMissing) {\n        notification.warning(\"The mail server is not configured.\");\n        return data.invite_link;\n      }\n      notification.success(\"Invitation sent.\");\n    })\n    .catch(error => {\n      notification.error(\"Failed to resend invitation\", getErrorMessage(error));\n    });\n}\n\nconst User = {\n  query: params => axios.get(\"api/users\", { params }),\n  get: ({ id }) => axios.get(`api/users/${id}`),\n  create: data => axios.post(`api/users`, data),\n  save: data => axios.post(`api/users/${data.id}`, data),\n  enableUser,\n  disableUser,\n  deleteUser,\n  convertUserInfo,\n  regenerateApiKey,\n  sendPasswordReset,\n  resendInvitation,\n};\n\nexport default User;\n"
  },
  {
    "path": "client/app/services/utils.js",
    "content": "// eslint-disable-next-line import/prefer-default-export\nexport function absoluteUrl(url) {\n  const urlObj = new URL(url, window.location);\n  urlObj.protocol = window.location.protocol;\n  urlObj.host = window.location.host;\n  return urlObj.toString();\n}\n"
  },
  {
    "path": "client/app/services/visualization.js",
    "content": "import { axios } from \"@/services/axios\";\n\nconst saveOrCreateUrl = data => (data.id ? `api/visualizations/${data.id}` : \"api/visualizations\");\n\nconst Visualization = {\n  save: data => axios.post(saveOrCreateUrl(data), data),\n  delete: data => axios.delete(`api/visualizations/${data.id}`),\n};\n\nexport default Visualization;\n"
  },
  {
    "path": "client/app/services/widget.js",
    "content": "import moment from \"moment\";\nimport { axios } from \"@/services/axios\";\nimport {\n  each,\n  pick,\n  extend,\n  isObject,\n  truncate,\n  keys,\n  difference,\n  filter,\n  map,\n  merge,\n  sortBy,\n  indexOf,\n  size,\n  includes,\n} from \"lodash\";\nimport location from \"@/services/location\";\nimport { cloneParameter } from \"@/services/parameters\";\nimport dashboardGridOptions from \"@/config/dashboard-grid-options\";\nimport { registeredVisualizations } from \"@redash/viz/lib\";\nimport { Query } from \"./query\";\n\nexport const WidgetTypeEnum = {\n  TEXTBOX: \"textbox\",\n  VISUALIZATION: \"visualization\",\n  RESTRICTED: \"restricted\",\n};\n\nfunction calculatePositionOptions(widget) {\n  widget.width = 1; // Backward compatibility, user on back-end\n\n  const visualizationOptions = {\n    autoHeight: false,\n    sizeX: Math.round(dashboardGridOptions.columns / 2),\n    sizeY: dashboardGridOptions.defaultSizeY,\n    minSizeX: dashboardGridOptions.minSizeX,\n    maxSizeX: dashboardGridOptions.maxSizeX,\n    minSizeY: dashboardGridOptions.minSizeY,\n    maxSizeY: dashboardGridOptions.maxSizeY,\n  };\n\n  const config = widget.visualization ? registeredVisualizations[widget.visualization.type] : null;\n  if (isObject(config)) {\n    if (Object.prototype.hasOwnProperty.call(config, \"autoHeight\")) {\n      visualizationOptions.autoHeight = config.autoHeight;\n    }\n\n    // Width constraints\n    const minColumns = parseInt(config.minColumns, 10);\n    if (isFinite(minColumns) && minColumns >= 0) {\n      visualizationOptions.minSizeX = minColumns;\n    }\n    const maxColumns = parseInt(config.maxColumns, 10);\n    if (isFinite(maxColumns) && maxColumns >= 0) {\n      visualizationOptions.maxSizeX = Math.min(maxColumns, dashboardGridOptions.columns);\n    }\n\n    // Height constraints\n    // `minRows` is preferred, but it should be kept for backward compatibility\n    const height = parseInt(config.height, 10);\n    if (isFinite(height)) {\n      visualizationOptions.minSizeY = Math.ceil(height / dashboardGridOptions.rowHeight);\n    }\n    const minRows = parseInt(config.minRows, 10);\n    if (isFinite(minRows)) {\n      visualizationOptions.minSizeY = minRows;\n    }\n    const maxRows = parseInt(config.maxRows, 10);\n    if (isFinite(maxRows) && maxRows >= 0) {\n      visualizationOptions.maxSizeY = maxRows;\n    }\n\n    // Default dimensions\n    const defaultWidth = parseInt(config.defaultColumns, 10);\n    if (isFinite(defaultWidth) && defaultWidth > 0) {\n      visualizationOptions.sizeX = defaultWidth;\n    }\n    const defaultHeight = parseInt(config.defaultRows, 10);\n    if (isFinite(defaultHeight) && defaultHeight > 0) {\n      visualizationOptions.sizeY = defaultHeight;\n    }\n  }\n\n  return visualizationOptions;\n}\n\nexport const ParameterMappingType = {\n  DashboardLevel: \"dashboard-level\",\n  WidgetLevel: \"widget-level\",\n  StaticValue: \"static-value\",\n};\n\nclass Widget {\n  static MappingType = ParameterMappingType;\n\n  constructor(data) {\n    // Copy properties\n    extend(this, data);\n\n    const visualizationOptions = calculatePositionOptions(this);\n\n    this.options = this.options || {};\n    this.options.position = extend(\n      {},\n      visualizationOptions,\n      pick(this.options.position, [\"col\", \"row\", \"sizeX\", \"sizeY\", \"autoHeight\"])\n    );\n\n    if (this.options.position.sizeY < 0) {\n      this.options.position.autoHeight = true;\n    }\n  }\n\n  get type() {\n    if (this.visualization) {\n      return WidgetTypeEnum.VISUALIZATION;\n    } else if (this.restricted) {\n      return WidgetTypeEnum.RESTRICTED;\n    }\n    return WidgetTypeEnum.TEXTBOX;\n  }\n\n  getQuery() {\n    if (!this.query && this.visualization) {\n      this.query = new Query(this.visualization.query);\n    }\n\n    return this.query;\n  }\n\n  getQueryResult() {\n    return this.data;\n  }\n\n  getName() {\n    if (this.visualization) {\n      return `${this.visualization.query.name} (${this.visualization.name})`;\n    }\n    return truncate(this.text, 20);\n  }\n\n  load(force, maxAge) {\n    if (!this.visualization) {\n      return Promise.resolve();\n    }\n\n    // Both `this.data` and `this.queryResult` are query result objects;\n    // `this.data` is last loaded query result;\n    // `this.queryResult` is currently loading query result;\n    // while widget is refreshing, `this.data` !== `this.queryResult`\n\n    if (force || this.queryResult === undefined) {\n      this.loading = true;\n      this.refreshStartedAt = moment();\n\n      if (maxAge === undefined || force) {\n        maxAge = force ? 0 : undefined;\n      }\n\n      const queryResult = this.getQuery().getQueryResult(maxAge);\n      this.queryResult = queryResult;\n\n      queryResult\n        .toPromise()\n        .then(result => {\n          if (this.queryResult === queryResult) {\n            this.loading = false;\n            this.data = result;\n          }\n          return result;\n        })\n        .catch(error => {\n          if (this.queryResult === queryResult) {\n            this.loading = false;\n            this.data = error;\n          }\n          return error;\n        });\n    }\n\n    return this.queryResult.toPromise();\n  }\n\n  save(key, value) {\n    const data = pick(this, \"options\", \"text\", \"id\", \"width\", \"dashboard_id\", \"visualization_id\");\n    if (key && value) {\n      data[key] = merge({}, data[key], value); // done like this so `this.options` doesn't get updated by side-effect\n    }\n\n    let url = \"api/widgets\";\n    if (this.id) {\n      url = `${url}/${this.id}`;\n    }\n\n    return axios.post(url, data).then(data => {\n      each(data, (v, k) => {\n        this[k] = v;\n      });\n\n      return this;\n    });\n  }\n\n  delete() {\n    const url = `api/widgets/${this.id}`;\n    return axios.delete(url);\n  }\n\n  isStaticParam(param) {\n    const mappings = this.getParameterMappings();\n    const mappingType = mappings[param.name].type;\n    return mappingType === Widget.MappingType.StaticValue;\n  }\n\n  getParametersDefs() {\n    const mappings = this.getParameterMappings();\n    // textboxes does not have query\n    const params = this.getQuery() ? this.getQuery().getParametersDefs() : [];\n\n    const queryParams = location.search;\n\n    const localTypes = [Widget.MappingType.WidgetLevel, Widget.MappingType.StaticValue];\n    const localParameters = map(\n      filter(params, param => localTypes.indexOf(mappings[param.name].type) >= 0),\n      param => {\n        const mapping = mappings[param.name];\n        const result = cloneParameter(param);\n        result.title = mapping.title || param.title;\n        result.locals = [param];\n        result.urlPrefix = `p_w${this.id}_`;\n        if (mapping.type === Widget.MappingType.StaticValue) {\n          result.setValue(mapping.value);\n        } else {\n          result.fromUrlParams(queryParams);\n        }\n        return result;\n      }\n    );\n\n    // order widget params using paramOrder\n    return sortBy(localParameters, param =>\n      includes(this.options.paramOrder, param.name)\n        ? indexOf(this.options.paramOrder, param.name)\n        : size(this.options.paramOrder)\n    );\n  }\n\n  getParameterMappings() {\n    if (!isObject(this.options.parameterMappings)) {\n      this.options.parameterMappings = {};\n    }\n\n    const existingParams = {};\n    // textboxes does not have query\n    const params = this.getQuery() ? this.getQuery().getParametersDefs(false) : [];\n    each(params, param => {\n      existingParams[param.name] = true;\n      if (!isObject(this.options.parameterMappings[param.name])) {\n        // \"migration\" for old dashboards: parameters with `global` flag\n        // should be mapped to a dashboard-level parameter with the same name\n        this.options.parameterMappings[param.name] = {\n          name: param.name,\n          type: param.global ? Widget.MappingType.DashboardLevel : Widget.MappingType.WidgetLevel,\n          mapTo: param.name, // map to param with the same name\n          value: null, // for StaticValue\n          title: \"\", // Use parameter's title\n        };\n      }\n    });\n\n    // Remove mappings for parameters that do not exists anymore\n    const removedParams = difference(keys(this.options.parameterMappings), keys(existingParams));\n    each(removedParams, name => {\n      delete this.options.parameterMappings[name];\n    });\n\n    return this.options.parameterMappings;\n  }\n\n  getLocalParameters() {\n    return filter(this.getParametersDefs(), param => !this.isStaticParam(param));\n  }\n}\n\nexport default Widget;\n"
  },
  {
    "path": "client/app/styles/formStyle.less",
    "content": ".ant-form-horizontal--labels-left {\n  .ant-form-item-label {\n    text-align: left;\n    white-space: normal;\n\n    > label::after {\n      content: none; // Do not show \":\" next to label when they are aligned on left side\n    }\n  }\n}\n"
  },
  {
    "path": "client/app/styles/formStyle.ts",
    "content": "import { FormProps } from \"antd/lib/form/Form\";\nimport { FormItemProps } from \"antd/lib/form/FormItem\";\nimport \"./formStyle.less\";\n\nexport function getHorizontalFormProps(): FormProps {\n  return {\n    labelCol: { xs: { span: 24 }, sm: { span: 6 }, lg: { span: 4 } },\n    wrapperCol: { xs: { span: 24 }, sm: { span: 12 }, lg: { span: 10 } },\n    layout: \"horizontal\",\n    className: \"ant-form-horizontal--labels-left\",\n  };\n}\n\nexport function getHorizontalFormItemWithoutLabelProps(): FormItemProps {\n  return {\n    wrapperCol: { xs: { span: 24 }, sm: { span: 12, offset: 6 }, lg: { span: 12, offset: 4 } },\n  };\n}\n"
  },
  {
    "path": "client/app/unsupported.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Redash doesn't support your browser</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <style>\n    body {\n      padding-top: 100px;\n      background: #F6F8F9;\n      text-align: center;\n      color: #333;\n      font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n    }\n\n    a {\n      color: #2196F3;\n      text-decoration: none;\n    }\n\n    h3 {\n      font-weight: 500;\n      margin-bottom: 25px;\n      font-size: 23px;\n    }\n\n    h4 {\n      font-weight: 500;\n    }\n\n    .tiled {\n      background: white;\n      max-width: 500px;\n      padding: 25px;\n      border-radius: 3px;\n      box-shadow: rgba(102, 136, 153, 0.15) 0px 4px 9px -3px;\n      margin: auto;\n      text-align: left;\n    }\n\n    ul {\n      font-size: 14px;\n      line-height: 25px;\n      padding-left: 20px;\n    }\n  </style>\n</head>\n<body>\n  <div>\n    <a href=\"https://redash.io\"><img src=\"images/redash_icon_small.png\" width=\"40\" height=\"40\"></a>\n    <h3>Whoops... Redash doesn't support your browser</h3>\n  </div>\n  <div class=\"tiled\">\n    <h4>Download one of these free and up-to-date browsers:</h4>\n    <ul>\n      <li><a href=\"https://www.google.com/chrome/browser/desktop/\" target=\"_blank\">Chrome</a></li>\n      <li><a href=\"https://www.mozilla.com/firefox/\" target=\"_blank\">Firefox</a></li>\n      <li><a href=\"https://support.apple.com/en-us/HT204416\" target=\"_blank\">Safari</a></li>\n    </ul>\n  </div>\n</body>\n</html>"
  },
  {
    "path": "client/app/unsupportedRedirect.js",
    "content": "if (\n  navigator.appVersion.match(\"Trident/\") || // IE8-11\n  \"ActiveXObject\" in window // IE<11\n) {\n  window.location.href = \"/static/unsupported.html\";\n}\n"
  },
  {
    "path": "client/app/version.json",
    "content": "\"dev\"\n"
  },
  {
    "path": "client/cypress/.eslintrc.js",
    "content": "module.exports = {\n  extends: [\"plugin:cypress/recommended\"],\n  plugins: [\"cypress\", \"chai-friendly\"],\n  env: {\n    \"cypress/globals\": true,\n  },\n  rules: {\n    \"func-names\": [\"error\", \"never\"],\n    \"no-unused-expressions\": 0,\n    \"chai-friendly/no-unused-expressions\": 2,\n    \"no-redeclare\": \"off\",\n    \"cypress/unsafe-to-chain-command\": \"off\",\n  },\n};\n"
  },
  {
    "path": "client/cypress/cypress.js",
    "content": "/* eslint-disable import/no-extraneous-dependencies, no-console */\nconst { find } = require(\"lodash\");\nconst { execSync } = require(\"child_process\");\nconst { get, post } = require(\"request\").defaults({ jar: true });\nconst { seedData } = require(\"./seed-data\");\nconst fs = require(\"fs\");\nvar Cookie = require(\"request-cookies\").Cookie;\n\nlet cypressConfigBaseUrl;\ntry {\n  const cypressConfig = JSON.parse(fs.readFileSync(\"cypress.json\"));\n  cypressConfigBaseUrl = cypressConfig.baseUrl;\n} catch (e) {}\n\nconst baseUrl = process.env.CYPRESS_baseUrl || cypressConfigBaseUrl || \"http://localhost:5001\";\n\nfunction seedDatabase(seedValues) {\n  get(baseUrl + \"/login\", (_, { headers }) => {\n    const request = seedValues.shift();\n    const data = request.type === \"form\" ? { formData: request.data } : { json: request.data };\n\n    if (headers[\"set-cookie\"]) {\n      const cookies = headers[\"set-cookie\"].map((cookie) => new Cookie(cookie));\n      const csrfCookie = find(cookies, { key: \"csrf_token\" });\n      if (csrfCookie) {\n        if (request.type === \"form\") {\n          data[\"formData\"] = { ...data[\"formData\"], csrf_token: csrfCookie.value };\n        } else {\n          data[\"headers\"] = { \"X-CSRFToken\": csrfCookie.value };\n        }\n      }\n    }\n\n    post(baseUrl + request.route, data, (err, response) => {\n      const result = response ? response.statusCode : err;\n      console.log(\"POST \" + request.route + \" - \" + result);\n      if (seedValues.length) {\n        seedDatabase(seedValues);\n      }\n    });\n  });\n}\n\nfunction buildServer() {\n  console.log(\"Building the server...\");\n  execSync(\"docker compose -p cypress build\", { stdio: \"inherit\" });\n}\n\nfunction startServer() {\n  console.log(\"Starting the server...\");\n  execSync(\"docker compose -p cypress up -d\", { stdio: \"inherit\" });\n  execSync(\"docker compose -p cypress run server create_db\", { stdio: \"inherit\" });\n}\n\nfunction stopServer() {\n  console.log(\"Stopping the server...\");\n  execSync(\"docker compose -p cypress down\", { stdio: \"inherit\" });\n}\n\nfunction runCypressCI() {\n  const {\n    GITHUB_REPOSITORY,\n    CYPRESS_OPTIONS, // eslint-disable-line no-unused-vars\n  } = process.env;\n\n  if (GITHUB_REPOSITORY === \"getredash/redash\" && process.env.CYPRESS_RECORD_KEY) {\n    process.env.CYPRESS_OPTIONS = \"--record\";\n  }\n\n  execSync(\n    \"COMMIT_INFO_MESSAGE=$(git show -s --format=%s) docker compose run --name cypress cypress ./node_modules/.bin/percy exec -t 300 -- ./node_modules/.bin/cypress run $CYPRESS_OPTIONS\",\n    { stdio: \"inherit\" }\n  );\n}\n\nconst command = process.argv[2] || \"all\";\n\nswitch (command) {\n  case \"build\":\n    buildServer();\n    break;\n  case \"start\":\n    startServer();\n    if (!process.argv.includes(\"--skip-db-seed\")) {\n      seedDatabase(seedData);\n    }\n    break;\n  case \"db-seed\":\n    seedDatabase(seedData);\n    break;\n  case \"run\":\n    execSync(\"cypress run\", { stdio: \"inherit\" });\n    break;\n  case \"open\":\n    execSync(\"cypress open\", { stdio: \"inherit\" });\n    break;\n  case \"run-ci\":\n    runCypressCI();\n    break;\n  case \"stop\":\n    stopServer();\n    break;\n  case \"all\":\n    startServer();\n    seedDatabase(seedData);\n    execSync(\"cypress run\", { stdio: \"inherit\" });\n    stopServer();\n    break;\n  default:\n    console.log(\"Usage: pnpm run cypress [build|start|db-seed|open|run|stop]\");\n    break;\n}\n"
  },
  {
    "path": "client/cypress/integration/alert/create_alert_spec.js",
    "content": "describe(\"Create Alert\", () => {\n  beforeEach(() => {\n    cy.login();\n  });\n\n  it(\"renders the initial page and takes a screenshot\", () => {\n    cy.visit(\"/alerts/new\");\n    cy.getByTestId(\"QuerySelector\").should(\"exist\");\n    cy.percySnapshot(\"Create Alert initial screen\");\n  });\n\n  it(\"selects query and takes a screenshot\", () => {\n    cy.createQuery({ name: \"Create Alert Query\" }).then(({ id: queryId }) => {\n      cy.visit(\"/alerts/new\");\n      cy.getByTestId(\"QuerySelector\")\n        .click()\n        .type(\"Create Alert Query\");\n      cy.get(`.query-selector-result[data-test=\"QueryId${queryId}\"]`).click();\n      cy.getByTestId(\"Criteria\").should(\"exist\");\n      cy.percySnapshot(\"Create Alert second screen\");\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/alert/edit_alert_spec.js",
    "content": "describe(\"Edit Alert\", () => {\n  beforeEach(() => {\n    cy.login();\n  });\n\n  it(\"renders the page and takes a screenshot\", () => {\n    cy.createQuery({ query: \"select 1 as col_name\" })\n      .then(({ id: queryId }) => cy.createAlert(queryId, { column: \"col_name\" }))\n      .then(({ id: alertId }) => {\n        cy.visit(`/alerts/${alertId}/edit`);\n        cy.getByTestId(\"Criteria\").should(\"exist\");\n        cy.percySnapshot(\"Edit Alert screen\");\n      });\n  });\n\n  it(\"edits the notification template and takes a screenshot\", () => {\n    cy.createQuery()\n      .then(({ id: queryId }) => cy.createAlert(queryId, { custom_subject: \"FOO\", custom_body: \"BAR\" }))\n      .then(({ id: alertId }) => {\n        cy.visit(`/alerts/${alertId}/edit`);\n        cy.getByTestId(\"AlertCustomTemplate\").should(\"exist\");\n        cy.percySnapshot(\"Alert Custom Template screen\");\n      });\n  });\n\n  it(\"previews rendered template correctly\", () => {\n    const options = {\n      value: \"123\",\n      op: \"==\",\n      custom_subject: \"{{ ALERT_CONDITION }}\",\n      custom_body: \"{{ ALERT_THRESHOLD }}\",\n    };\n\n    cy.createQuery()\n      .then(({ id: queryId }) => cy.createAlert(queryId, options))\n      .then(({ id: alertId }) => {\n        cy.visit(`/alerts/${alertId}/edit`);\n        cy.get(\".alert-template-preview\").click();\n        cy.getByTestId(\"CustomSubject\").should(\"have.value\", options.op);\n        cy.getByTestId(\"CustomBody\").should(\"have.value\", options.value);\n      });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/alert/view_alert_spec.js",
    "content": "describe(\"View Alert\", () => {\n  beforeEach(function() {\n    cy.login().then(() => {\n      cy.createQuery({ query: \"select 1 as col_name\" })\n        .then(({ id: queryId }) => cy.createAlert(queryId, { column: \"col_name\" }))\n        .then(({ id: alertId }) => {\n          this.alertId = alertId;\n          this.alertUrl = `/alerts/${alertId}`;\n        });\n    });\n  });\n\n  it(\"renders the page and takes a screenshot\", function() {\n    cy.visit(this.alertUrl);\n    cy.getByTestId(\"Criteria\").should(\"exist\");\n    cy.percySnapshot(\"View Alert screen\");\n  });\n\n  it(\"allows adding new destinations\", function() {\n    cy.visit(this.alertUrl);\n    cy.getByTestId(\"AlertDestinations\")\n      .contains(\"Test Email Destination\")\n      .should(\"not.exist\");\n\n    cy.server();\n    cy.route(\"GET\", \"**/api/destinations\").as(\"Destinations\");\n    cy.route(\"GET\", \"**/api/alerts/*/subscriptions\").as(\"Subscriptions\");\n\n    cy.visit(this.alertUrl);\n\n    cy.wait([\"@Destinations\", \"@Subscriptions\"]);\n    cy.getByTestId(\"ShowAddAlertSubDialog\").click();\n    cy.contains(\"Test Email Destination\").click();\n    cy.contains(\"Save\").click();\n\n    cy.getByTestId(\"AlertDestinations\")\n      .contains(\"Test Email Destination\")\n      .should(\"exist\");\n  });\n\n  describe(\"Alert Destination permissions\", () => {\n    before(() => {\n      cy.login();\n      cy.createUser({\n        name: \"Example User\",\n        email: \"user@redash.io\",\n        password: \"password\",\n      });\n    });\n\n    it(\"hides remove button from non-author\", function() {\n      cy.server();\n      cy.route(\"GET\", \"**/api/alerts/*/subscriptions\").as(\"Subscriptions\");\n\n      cy.logout()\n        .then(() => cy.login()) // as admin\n        .then(() => cy.addDestinationSubscription(this.alertId, \"Test Email Destination\"))\n        .then(() => {\n          cy.visit(this.alertUrl);\n\n          // verify remove button appears for author\n          cy.wait([\"@Subscriptions\"]);\n          cy.getByTestId(\"AlertDestinations\")\n            .contains(\"Test Email Destination\")\n            .parent()\n            .within(() => {\n              cy.get(\".remove-button\")\n                .as(\"RemoveButton\")\n                .should(\"exist\");\n            });\n\n          return cy.logout().then(() => cy.login(\"user@redash.io\", \"password\"));\n        })\n        .then(() => {\n          cy.visit(this.alertUrl);\n\n          // verify remove button not shown for non-author\n          cy.wait([\"@Subscriptions\"]);\n          cy.get(\"@RemoveButton\").should(\"not.exist\");\n        });\n    });\n\n    it(\"shows remove button for non-author admin\", function() {\n      cy.server();\n      cy.route(\"GET\", \"**/api/alerts/*/subscriptions\").as(\"Subscriptions\");\n\n      cy.logout()\n        .then(() => cy.login(\"user@redash.io\", \"password\"))\n        .then(() => cy.addDestinationSubscription(this.alertId, \"Test Email Destination\"))\n        .then(() => {\n          cy.visit(this.alertUrl);\n\n          // verify remove button appears for author\n          cy.wait([\"@Subscriptions\"]);\n          cy.getByTestId(\"AlertDestinations\")\n            .contains(\"Test Email Destination\")\n            .parent()\n            .within(() => {\n              cy.get(\".remove-button\")\n                .as(\"RemoveButton\")\n                .should(\"exist\");\n            });\n\n          return cy.logout().then(() => cy.login()); // as admin\n        })\n        .then(() => {\n          cy.visit(this.alertUrl);\n\n          // verify remove button also appears for admin\n          cy.wait([\"@Subscriptions\"]);\n          cy.get(\"@RemoveButton\").should(\"exist\");\n        });\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/dashboard/dashboard_list.js",
    "content": "describe(\"Dashboard list sort\", () => {\n  beforeEach(() => {\n    cy.login();\n  });\n\n  it(\"creates one dashboard\", () => {\n    cy.visit(\"/dashboards\");\n    cy.getByTestId(\"CreateButton\").click();\n    cy.getByTestId(\"CreateDashboardMenuItem\").click();\n    cy.getByTestId(\"CreateDashboardDialog\").within(() => {\n      cy.get(\"input\").type(\"A Foo Bar\");\n      cy.getByTestId(\"DashboardSaveButton\").click();\n    });\n  });\n\n  describe(\"Sorting table does not crash page \", () => {\n    it(\"sorts\", () => {\n      cy.visit(\"/dashboards\");\n      cy.contains(\"Name\").click();\n      cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting\n      cy.getByTestId(\"ErrorMessage\").should(\"not.exist\");\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/dashboard/dashboard_spec.js",
    "content": "/* global cy, Cypress */\n\nimport { getWidgetTestId } from \"../../support/dashboard\";\n\nconst menuWidth = 80;\n\ndescribe(\"Dashboard\", () => {\n  beforeEach(() => {\n    cy.login();\n  });\n\n  it(\"creates new dashboard\", () => {\n    cy.visit(\"/dashboards\");\n    cy.getByTestId(\"CreateButton\").click();\n    cy.getByTestId(\"CreateDashboardMenuItem\").click();\n\n    cy.server();\n    cy.route(\"POST\", \"**/api/dashboards\").as(\"NewDashboard\");\n\n    cy.getByTestId(\"CreateDashboardDialog\").within(() => {\n      cy.getByTestId(\"DashboardSaveButton\").should(\"be.disabled\");\n      cy.get(\"input\").type(\"Foo Bar\");\n      cy.getByTestId(\"DashboardSaveButton\").click();\n    });\n\n    cy.wait(\"@NewDashboard\").then((xhr) => {\n      const id = Cypress._.get(xhr, \"response.body.id\");\n      assert.isDefined(id, \"Dashboard api call returns id\");\n\n      cy.visit(\"/dashboards\");\n      cy.getByTestId(\"DashboardLayoutContent\").within(() => {\n        cy.getByTestId(`DashboardId${id}`).should(\"exist\");\n      });\n    });\n  });\n\n  it(\"archives dashboard\", () => {\n    cy.createDashboard(\"Foo Bar\").then(({ id }) => {\n      cy.visit(`/dashboards/${id}`);\n\n      cy.getByTestId(\"DashboardMoreButton\").click();\n\n      cy.getByTestId(\"DashboardMoreButtonMenu\").contains(\"Archive\").click();\n\n      cy.get(\".ant-modal .ant-btn\").contains(\"Archive\").click({ force: true });\n      cy.get(\".label-tag-archived\").should(\"exist\");\n\n      cy.visit(\"/dashboards\");\n      cy.getByTestId(\"DashboardLayoutContent\").within(() => {\n        cy.getByTestId(`DashboardId${id}`).should(\"not.exist\");\n      });\n    });\n  });\n\n  it(\"is accessible through multiple urls\", () => {\n    cy.server();\n    cy.route(\"GET\", \"**/api/dashboards/*\").as(\"LoadDashboard\");\n    cy.createDashboard(\"Dashboard multiple urls\").then(({ id, slug }) => {\n      [`/dashboards/${id}`, `/dashboards/${id}-anything-here`, `/dashboard/${slug}`].forEach((url) => {\n        cy.visit(url);\n        cy.wait(\"@LoadDashboard\");\n        cy.getByTestId(`DashboardId${id}Container`).should(\"exist\");\n\n        // assert it always use the \"/dashboards/{id}\" path\n        cy.location(\"pathname\").should(\"contain\", `/dashboards/${id}`);\n      });\n    });\n  });\n\n  context(\"viewport width is at 800px\", () => {\n    before(function () {\n      cy.login();\n      cy.createDashboard(\"Foo Bar\")\n        .then(({ id }) => {\n          this.dashboardUrl = `/dashboards/${id}`;\n          this.dashboardEditUrl = `/dashboards/${id}?edit`;\n          return cy.addTextbox(id, \"Hello World!\").then(getWidgetTestId);\n        })\n        .then((elTestId) => {\n          cy.visit(this.dashboardUrl);\n          cy.getByTestId(elTestId).as(\"textboxEl\");\n        });\n    });\n\n    beforeEach(function () {\n      cy.login();\n      cy.visit(this.dashboardUrl);\n      cy.viewport(800 + menuWidth, 800);\n    });\n\n    it(\"shows widgets with full width\", () => {\n      cy.get(\"@textboxEl\").should(($el) => {\n        expect($el.width()).to.eq(770);\n      });\n\n      cy.viewport(801 + menuWidth, 800);\n      cy.get(\"@textboxEl\").should(($el) => {\n        expect($el.width()).to.eq(182);\n      });\n    });\n\n    it(\"hides edit option\", () => {\n      cy.getByTestId(\"DashboardMoreButton\").click().should(\"be.visible\");\n\n      cy.getByTestId(\"DashboardMoreButtonMenu\").contains(\"Edit\").as(\"editButton\").should(\"not.be.visible\");\n\n      cy.viewport(801 + menuWidth, 800);\n      cy.get(\"@editButton\").should(\"be.visible\");\n    });\n\n    it(\"disables edit mode\", function () {\n      cy.viewport(801 + menuWidth, 800);\n      cy.visit(this.dashboardEditUrl);\n      cy.contains(\"button\", \"Done Editing\").as(\"saveButton\").should(\"exist\");\n\n      cy.viewport(800 + menuWidth, 800);\n      cy.contains(\"button\", \"Done Editing\").should(\"not.exist\");\n    });\n  });\n\n  context(\"viewport width is at 767px\", () => {\n    before(function () {\n      cy.login();\n      cy.createDashboard(\"Foo Bar\").then(({ id }) => {\n        this.dashboardUrl = `/dashboards/${id}`;\n      });\n    });\n\n    beforeEach(function () {\n      cy.visit(this.dashboardUrl);\n      cy.viewport(767, 800);\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/dashboard/dashboard_tags_spec.js",
    "content": "import { expectTagsToContain, typeInTagsSelectAndSave } from \"../../support/tags\";\n\ndescribe(\"Dashboard Tags\", () => {\n  beforeEach(function() {\n    cy.login();\n    cy.createDashboard(\"Foo Bar\").then(({ id }) => cy.visit(`/dashboards/${id}`));\n  });\n\n  it(\"is possible to add and edit tags\", () => {\n    cy.server();\n    cy.route(\"POST\", \"**/api/dashboards/*\").as(\"DashboardSave\");\n\n    cy.getByTestId(\"TagsControl\").contains(\".label\", \"Unpublished\");\n\n    cy.getByTestId(\"EditTagsButton\")\n      .should(\"contain\", \"Add tag\")\n      .click();\n\n    typeInTagsSelectAndSave(\"tag1{enter}tag2{enter}tag3{enter}\");\n\n    cy.wait(\"@DashboardSave\");\n    expectTagsToContain([\"tag1\", \"tag2\", \"tag3\"]);\n\n    cy.getByTestId(\"EditTagsButton\").click();\n    typeInTagsSelectAndSave(\"tag4{enter}\");\n\n    cy.wait(\"@DashboardSave\");\n    cy.reload();\n    expectTagsToContain([\"tag1\", \"tag2\", \"tag3\", \"tag4\"]);\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/dashboard/filters_spec.js",
    "content": "import { createQueryAndAddWidget, editDashboard } from \"../../support/dashboard\";\nimport { expectTableToHaveLength, expectFirstColumnToHaveMembers } from \"../../support/visualizations/table\";\n\nconst SQL = `\nSELECT 'a' AS stage1, 'a1' AS stage2, 11 AS value UNION ALL\nSELECT 'a' AS stage1, 'a2' AS stage2, 12 AS value UNION ALL\nSELECT 'a' AS stage1, 'a3' AS stage2, 45 AS value UNION ALL\nSELECT 'a' AS stage1, 'a4' AS stage2, 54 AS value UNION ALL\nSELECT 'b' AS stage1, 'b1' AS stage2, 33 AS value UNION ALL\nSELECT 'b' AS stage1, 'b2' AS stage2, 73 AS value UNION ALL\nSELECT 'b' AS stage1, 'b3' AS stage2, 90 AS value UNION ALL\nSELECT 'c' AS stage1, 'c1' AS stage2, 19 AS value UNION ALL\nSELECT 'c' AS stage1, 'c2' AS stage2, 92 AS value UNION ALL\nSELECT 'c' AS stage1, 'c3' AS stage2, 63 AS value UNION ALL\nSELECT 'c' AS stage1, 'c4' AS stage2, 44 AS value\\\n`;\n\ndescribe(\"Dashboard Filters\", () => {\n  beforeEach(() => {\n    cy.login();\n\n    const queryData = {\n      name: \"Query Filters\",\n      query: `SELECT stage1 AS \"stage1::filter\", stage2, value FROM (${SQL}) q`,\n    };\n    cy.createDashboard(\"Dashboard Filters\").then((dashboard) => {\n      createQueryAndAddWidget(dashboard.id, queryData)\n        .as(\"widget1TestId\")\n        .then(() => createQueryAndAddWidget(dashboard.id, queryData, { position: { col: 4 } }))\n        .as(\"widget2TestId\")\n        .then(() => cy.visit(`/dashboards/${dashboard.id}`));\n    });\n  });\n\n  it(\"filters rows in a Table Visualization\", function () {\n    editDashboard();\n    cy.getByTestId(\"DashboardFilters\").should(\"not.exist\");\n    cy.getByTestId(\"DashboardFiltersCheckbox\").click();\n\n    cy.getByTestId(\"DashboardFilters\").within(() => {\n      cy.getByTestId(\"FilterName-stage1::filter\").find(\".ant-select-selection-item\").should(\"have.text\", \"a\");\n    });\n\n    cy.getByTestId(this.widget1TestId).within(() => {\n      expectTableToHaveLength(4);\n      expectFirstColumnToHaveMembers([\"a\", \"a\", \"a\", \"a\"]);\n\n      cy.getByTestId(\"FilterName-stage1::filter\").find(\".ant-select\").click();\n    });\n\n    cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.contains(\".ant-select-item-option-content:visible\", \"b\").click();\n\n    cy.getByTestId(this.widget1TestId).within(() => {\n      expectTableToHaveLength(3);\n      expectFirstColumnToHaveMembers([\"b\", \"b\", \"b\"]);\n    });\n\n    // assert that changing one widget filter doesn't affect another\n\n    cy.getByTestId(this.widget2TestId).within(() => {\n      expectTableToHaveLength(4);\n      expectFirstColumnToHaveMembers([\"a\", \"a\", \"a\", \"a\"]);\n    });\n\n    // assert that changing a global filter affects all widgets\n\n    cy.getByTestId(\"DashboardFilters\").within(() => {\n      cy.getByTestId(\"FilterName-stage1::filter\").find(\".ant-select\").click();\n    });\n\n    cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.contains(\".ant-select-item-option-content:visible\", \"c\").click();\n\n    [this.widget1TestId, this.widget2TestId].forEach((widgetTestId) =>\n      cy.getByTestId(widgetTestId).within(() => {\n        expectTableToHaveLength(4);\n        expectFirstColumnToHaveMembers([\"c\", \"c\", \"c\", \"c\"]);\n      })\n    );\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/dashboard/grid_compliant_widgets_spec.js",
    "content": "/* global cy */\n\nimport { getWidgetTestId, editDashboard, resizeBy } from \"../../support/dashboard\";\n\nconst menuWidth = 80;\n\ndescribe(\"Grid compliant widgets\", () => {\n  beforeEach(function () {\n    cy.login();\n    cy.viewport(1215 + menuWidth, 800);\n    cy.createDashboard(\"Foo Bar\")\n      .then(({ id }) => {\n        this.dashboardUrl = `/dashboards/${id}`;\n        return cy.addTextbox(id, \"Hello World!\").then(getWidgetTestId);\n      })\n      .then((elTestId) => {\n        cy.visit(this.dashboardUrl);\n        cy.getByTestId(elTestId).as(\"textboxEl\");\n      });\n  });\n\n  describe(\"Draggable\", () => {\n    describe(\"Grid snap\", () => {\n      beforeEach(() => {\n        editDashboard();\n      });\n\n      it(\"stays put when dragged under snap threshold\", () => {\n        cy.get(\"@textboxEl\")\n          .dragBy(30)\n          .invoke(\"offset\")\n          .should(\"have.property\", \"left\", 15 + menuWidth); // no change, 15 -> 15\n      });\n\n      it(\"moves one column when dragged over snap threshold\", () => {\n        cy.get(\"@textboxEl\")\n          .dragBy(110)\n          .invoke(\"offset\")\n          .should(\"have.property\", \"left\", 115 + menuWidth); //  moved by 100, 15 -> 115\n      });\n\n      it(\"moves two columns when dragged over snap threshold\", () => {\n        cy.get(\"@textboxEl\")\n          .dragBy(200)\n          .invoke(\"offset\")\n          .should(\"have.property\", \"left\", 215 + menuWidth); //  moved by 200, 15 -> 215\n      });\n    });\n\n    it(\"auto saves after drag\", () => {\n      cy.server();\n      cy.route(\"POST\", \"**/api/widgets/*\").as(\"WidgetSave\");\n\n      editDashboard();\n      cy.get(\"@textboxEl\").dragBy(100);\n      cy.wait(\"@WidgetSave\");\n    });\n  });\n\n  describe(\"Resizeable\", () => {\n    describe(\"Column snap\", () => {\n      beforeEach(() => {\n        editDashboard();\n      });\n\n      it(\"stays put when dragged under snap threshold\", () => {\n        resizeBy(cy.get(\"@textboxEl\"), 30)\n          .then(() => cy.get(\"@textboxEl\"))\n          .invoke(\"width\")\n          .should(\"eq\", 285); // no change, 285 -> 285\n      });\n\n      it(\"moves one column when dragged over snap threshold\", () => {\n        resizeBy(cy.get(\"@textboxEl\"), 110)\n          .then(() => cy.get(\"@textboxEl\"))\n          .invoke(\"width\")\n          .should(\"eq\", 385); // resized by 200, 185 -> 385\n      });\n\n      it(\"moves two columns when dragged over snap threshold\", () => {\n        resizeBy(cy.get(\"@textboxEl\"), 400)\n          .then(() => cy.get(\"@textboxEl\"))\n          .invoke(\"width\")\n          .should(\"eq\", 685); // resized by 400, 285 -> 685\n      });\n    });\n\n    describe(\"Row snap\", () => {\n      beforeEach(() => {\n        editDashboard();\n      });\n\n      it(\"stays put when dragged under snap threshold\", () => {\n        resizeBy(cy.get(\"@textboxEl\"), 0, 10)\n          .then(() => cy.get(\"@textboxEl\"))\n          .invoke(\"height\")\n          .should(\"eq\", 135); // no change, 135 -> 135\n      });\n\n      it(\"moves one row when dragged over snap threshold\", () => {\n        resizeBy(cy.get(\"@textboxEl\"), 0, 30)\n          .then(() => cy.get(\"@textboxEl\"))\n          .invoke(\"height\")\n          .should(\"eq\", 185);\n      });\n\n      it(\"shrinks to minimum\", () => {\n        cy.get(\"@textboxEl\")\n          .then(($el) => resizeBy(cy.get(\"@textboxEl\"), -$el.width(), -$el.height())) // resize to 0,0\n          .then(() => cy.get(\"@textboxEl\"))\n          .should(($el) => {\n            expect($el.width()).to.eq(185); // min textbox width\n            expect($el.height()).to.eq(85); // min textbox height\n          });\n      });\n    });\n\n    it(\"auto saves after resize\", () => {\n      cy.server();\n      cy.route(\"POST\", \"**/api/widgets/*\").as(\"WidgetSave\");\n\n      editDashboard();\n      resizeBy(cy.get(\"@textboxEl\"), 200);\n      cy.wait(\"@WidgetSave\");\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/dashboard/parameter_spec.js",
    "content": "import { createQueryAndAddWidget } from \"../../support/dashboard\";\n\ndescribe(\"Dashboard Parameters\", () => {\n  const parameters = [\n    { name: \"param1\", title: \"Parameter 1\", type: \"text\", value: \"example1\" },\n    { name: \"param2\", title: \"Parameter 2\", type: \"text\", value: \"example2\" },\n  ];\n\n  beforeEach(function() {\n    cy.login();\n    cy.createDashboard(\"Foo Bar\")\n      .then(({ id }) => {\n        this.dashboardId = id;\n        this.dashboardUrl = `/dashboards/${id}`;\n      })\n      .then(() => {\n        const queryData = {\n          name: \"Text Parameter\",\n          query: \"SELECT '{{param1}}', '{{param2}}' AS parameter\",\n          options: {\n            parameters,\n          },\n        };\n        const widgetOptions = { position: { col: 0, row: 0, sizeX: 3, sizeY: 10, autoHeight: false } };\n        createQueryAndAddWidget(this.dashboardId, queryData, widgetOptions).then(widgetTestId => {\n          cy.visit(this.dashboardUrl);\n          this.widgetTestId = widgetTestId;\n        });\n      });\n  });\n\n  const openMappingOptions = widgetTestId => {\n    cy.getByTestId(widgetTestId).within(() => {\n      cy.getByTestId(\"WidgetDropdownButton\").click();\n    });\n\n    cy.getByTestId(\"WidgetDropdownButtonMenu\")\n      .contains(\"Edit Parameters\")\n      .click();\n  };\n\n  const saveMappingOptions = (closeMappingMenu = false) => {\n    return cy\n      .getByTestId(\"EditParamMappingPopover\")\n      .filter(\":visible\")\n      .as(\"Popover\")\n      .within(() => {\n        // This is needed to grant the element will have finished loading\n        // eslint-disable-next-line cypress/no-unnecessary-waiting\n        cy.wait(500);\n        cy.contains(\"button\", \"OK\").click();\n      })\n      .then(() => {\n        if (closeMappingMenu) {\n          cy.contains(\"button\", \"OK\").click();\n        }\n        return cy.get(\"@Popover\").should(\"not.be.visible\");\n      });\n  };\n\n  it(\"supports widget parameters\", function() {\n    // widget parameter mapping is the default for the API\n    cy.getByTestId(this.widgetTestId).within(() => {\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", \"example1\");\n\n      cy.getByTestId(\"ParameterName-param1\")\n        .find(\"input\")\n        .type(\"{selectall}Redash\");\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", \"Redash\");\n    });\n\n    cy.getByTestId(\"DashboardParameters\").should(\"not.exist\");\n  });\n\n  it(\"supports static values for parameters\", function() {\n    openMappingOptions(this.widgetTestId);\n    cy.getByTestId(\"EditParamMappingButton-param1\").click();\n\n    cy.getByTestId(\"StaticValueOption\").click();\n\n    cy.getByTestId(\"EditParamMappingPopover\").within(() => {\n      cy.getByTestId(\"ParameterValueInput\")\n        .find(\"input\")\n        .type(\"{selectall}StaticValue\");\n    });\n\n    saveMappingOptions(true);\n\n    cy.getByTestId(this.widgetTestId).within(() => {\n      cy.getByTestId(\"ParameterName-param1\").should(\"not.exist\");\n    });\n\n    cy.getByTestId(\"DashboardParameters\").should(\"not.exist\");\n\n    cy.getByTestId(this.widgetTestId).within(() => {\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", \"StaticValue\");\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/dashboard/sharing_spec.js",
    "content": "/* global cy */\n\nimport { editDashboard, shareDashboard, createQueryAndAddWidget } from \"../../support/dashboard\";\n\ndescribe(\"Dashboard Sharing\", () => {\n  beforeEach(function() {\n    cy.login();\n    cy.createDashboard(\"Foo Bar\").then(({ id }) => {\n      this.dashboardId = id;\n      this.dashboardUrl = `/dashboards/${id}`;\n    });\n    cy.updateOrgSettings({ disable_public_urls: false });\n  });\n\n  it(\"is unavailable when public urls feature is disabled\", function() {\n    const queryData = {\n      query: \"select 1\",\n    };\n\n    const position = { autoHeight: false, sizeY: 6 };\n    createQueryAndAddWidget(this.dashboardId, queryData, { position })\n      .then(() => {\n        cy.visit(this.dashboardUrl);\n        return shareDashboard();\n      })\n      .then(secretAddress => {\n        // disable the feature\n        cy.updateOrgSettings({ disable_public_urls: true });\n\n        // check the feature is disabled\n        cy.visit(this.dashboardUrl);\n        cy.getByTestId(\"DashboardMoreButton\").should(\"exist\");\n        cy.getByTestId(\"OpenShareForm\").should(\"not.exist\");\n\n        cy.logout();\n        cy.visit(secretAddress);\n        cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n        cy.getByTestId(\"TableVisualization\").should(\"not.exist\");\n\n        cy.login();\n        cy.updateOrgSettings({ disable_public_urls: false });\n      });\n  });\n\n  it(\"is possible if all queries are safe\", function() {\n    const options = {\n      parameters: [\n        {\n          name: \"foo\",\n          type: \"number\",\n        },\n      ],\n    };\n\n    const dashboardUrl = this.dashboardUrl;\n    cy.createQuery({ options }).then(({ id: queryId }) => {\n      cy.visit(dashboardUrl);\n      editDashboard();\n      cy.getByTestId(\"AddWidgetButton\").click();\n      cy.getByTestId(\"AddWidgetDialog\").within(() => {\n        cy.get(`.query-selector-result[data-test=\"QueryId${queryId}\"]`).click();\n      });\n      cy.contains(\"button\", \"Add to Dashboard\").click();\n      cy.getByTestId(\"AddWidgetDialog\").should(\"not.exist\");\n      cy.clickThrough(\n        {\n          button: `\n        Done Editing\n        Publish\n      `,\n        },\n        `OpenShareForm\n      PublicAccessEnabled`\n      );\n\n      cy.getByTestId(\"SecretAddress\").should(\"exist\");\n    });\n  });\n\n  describe(\"is available to unauthenticated users\", () => {\n    it(\"when there are no parameters\", function() {\n      const queryData = {\n        query: \"select 1\",\n      };\n\n      const position = { autoHeight: false, sizeY: 6 };\n      createQueryAndAddWidget(this.dashboardId, queryData, { position }).then(() => {\n        cy.visit(this.dashboardUrl);\n\n        shareDashboard().then(secretAddress => {\n          cy.logout();\n          cy.visit(secretAddress);\n          cy.getByTestId(\"TableVisualization\", { timeout: 10000 }).should(\"exist\");\n          cy.percySnapshot(\"Successfully Shared Unparameterized Dashboard\");\n        });\n      });\n    });\n\n    it(\"when there are only safe parameters\", function() {\n      const queryData = {\n        query: \"select '{{foo}}'\",\n        options: {\n          parameters: [\n            {\n              name: \"foo\",\n              type: \"number\",\n              value: 1,\n            },\n          ],\n        },\n      };\n\n      const position = { autoHeight: false, sizeY: 6 };\n      createQueryAndAddWidget(this.dashboardId, queryData, { position }).then(() => {\n        cy.visit(this.dashboardUrl);\n\n        shareDashboard().then(secretAddress => {\n          cy.logout();\n          cy.visit(secretAddress);\n          cy.getByTestId(\"TableVisualization\", { timeout: 10000 }).should(\"exist\");\n          cy.percySnapshot(\"Successfully Shared Parameterized Dashboard\");\n        });\n      });\n    });\n\n    it(\"even when there are suddenly some unsafe parameters\", function() {\n      const queryData = {\n        query: \"select 1\",\n      };\n\n      // start out by creating a dashboard with no parameters & share it\n      const position = { autoHeight: false, sizeY: 6 };\n      createQueryAndAddWidget(this.dashboardId, queryData, { position })\n        .then(() => {\n          cy.visit(this.dashboardUrl);\n          return shareDashboard();\n        })\n        .then(secretAddress => {\n          const unsafeQueryData = {\n            query: \"select '{{foo}}'\",\n            options: {\n              parameters: [\n                {\n                  name: \"foo\",\n                  type: \"text\",\n                  value: \"oh snap!\",\n                },\n              ],\n            },\n          };\n\n          // then, after it is shared, add an unsafe parameterized query to it\n          const secondWidgetPos = { autoHeight: false, col: 3, sizeY: 6 };\n          createQueryAndAddWidget(this.dashboardId, unsafeQueryData, { position: secondWidgetPos }).then(() => {\n            cy.logout();\n            cy.title().should(\"eq\", \"Login to Redash\"); // Make sure it's logged out\n            cy.visit(secretAddress);\n            cy.getByTestId(\"TableVisualization\", { timeout: 10000 }).should(\"exist\");\n            cy.contains(\n              \".alert\",\n              \"This query contains potentially unsafe parameters\" +\n                \" and cannot be executed on a shared dashboard or an embedded visualization.\"\n            );\n            cy.percySnapshot(\"Successfully Shared Parameterized Dashboard With Some Unsafe Queries\");\n          });\n        });\n    });\n  });\n\n  it(\"is not possible if some queries are not safe\", function() {\n    const options = {\n      parameters: [\n        {\n          name: \"foo\",\n          type: \"text\",\n        },\n      ],\n    };\n\n    const dashboardUrl = this.dashboardUrl;\n    cy.createQuery({ options }).then(({ id: queryId }) => {\n      cy.visit(dashboardUrl);\n      editDashboard();\n      cy.getByTestId(\"AddWidgetButton\").click();\n      cy.getByTestId(\"AddWidgetDialog\").within(() => {\n        cy.get(`.query-selector-result[data-test=\"QueryId${queryId}\"]`).click();\n      });\n      cy.contains(\"button\", \"Add to Dashboard\").click();\n      cy.getByTestId(\"AddWidgetDialog\").should(\"not.exist\");\n      cy.clickThrough(\n        {\n          button: `\n        Done Editing\n        Publish\n      `,\n        },\n        \"OpenShareForm\"\n      );\n\n      cy.getByTestId(\"PublicAccessEnabled\").should(\"be.disabled\");\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/dashboard/textbox_spec.js",
    "content": "/* global cy */\n\nimport { getWidgetTestId, editDashboard } from \"../../support/dashboard\";\n\ndescribe(\"Textbox\", () => {\n  beforeEach(function () {\n    cy.login();\n    cy.createDashboard(\"Foo Bar\").then(({ id }) => {\n      this.dashboardId = id;\n      this.dashboardUrl = `/dashboards/${id}`;\n    });\n  });\n\n  const confirmDeletionInModal = () => {\n    cy.get(\".ant-modal .ant-btn\").contains(\"Delete\").click({ force: true });\n  };\n\n  it(\"adds textbox\", function () {\n    cy.visit(this.dashboardUrl);\n    editDashboard();\n    cy.getByTestId(\"AddTextboxButton\").click();\n    cy.getByTestId(\"TextboxDialog\").within(() => {\n      cy.get(\"textarea\").type(\"Hello World!\");\n    });\n    cy.contains(\"button\", \"Add to Dashboard\").click();\n    cy.getByTestId(\"TextboxDialog\").should(\"not.exist\");\n    cy.get(\".widget-text\").should(\"exist\");\n  });\n\n  it(\"removes textbox by X button\", function () {\n    cy.addTextbox(this.dashboardId, \"Hello World!\")\n      .then(getWidgetTestId)\n      .then((elTestId) => {\n        cy.visit(this.dashboardUrl);\n        editDashboard();\n\n        cy.getByTestId(elTestId).within(() => {\n          cy.getByTestId(\"WidgetDeleteButton\").click();\n        });\n\n        confirmDeletionInModal();\n        cy.getByTestId(elTestId).should(\"not.exist\");\n      });\n  });\n\n  it(\"removes textbox by menu\", function () {\n    cy.addTextbox(this.dashboardId, \"Hello World!\")\n      .then(getWidgetTestId)\n      .then((elTestId) => {\n        cy.visit(this.dashboardUrl);\n        cy.getByTestId(elTestId).within(() => {\n          cy.getByTestId(\"WidgetDropdownButton\").click();\n        });\n        cy.getByTestId(\"WidgetDropdownButtonMenu\").contains(\"Remove from Dashboard\").click();\n\n        confirmDeletionInModal();\n        cy.getByTestId(elTestId).should(\"not.exist\");\n      });\n  });\n\n  it(\"allows opening menu after removal\", function () {\n    let elTestId1;\n    cy.addTextbox(this.dashboardId, \"txb 1\")\n      .then(getWidgetTestId)\n      .then((elTestId) => {\n        elTestId1 = elTestId;\n        return cy.addTextbox(this.dashboardId, \"txb 2\").then(getWidgetTestId);\n      })\n      .then((elTestId2) => {\n        cy.visit(this.dashboardUrl);\n        editDashboard();\n\n        // remove 1st textbox and make sure it's gone\n        cy.getByTestId(elTestId1)\n          .as(\"textbox1\")\n          .within(() => {\n            cy.getByTestId(\"WidgetDeleteButton\").click();\n          });\n\n        confirmDeletionInModal();\n        cy.get(\"@textbox1\").should(\"not.exist\");\n\n        // remove 2nd textbox and make sure it's gone\n        cy.getByTestId(elTestId2)\n          .as(\"textbox2\")\n          .within(() => {\n            // unclickable https://github.com/getredash/redash/issues/3202\n            cy.getByTestId(\"WidgetDeleteButton\").click();\n          });\n\n        confirmDeletionInModal();\n        cy.get(\"@textbox2\").should(\"not.exist\"); // <-- fails because of the bug\n      });\n  });\n\n  it(\"edits textbox\", function () {\n    cy.addTextbox(this.dashboardId, \"Hello World!\")\n      .then(getWidgetTestId)\n      .then((elTestId) => {\n        cy.visit(this.dashboardUrl);\n        cy.getByTestId(elTestId)\n          .as(\"textboxEl\")\n          .within(() => {\n            cy.getByTestId(\"WidgetDropdownButton\").click();\n          });\n\n        cy.getByTestId(\"WidgetDropdownButtonMenu\").contains(\"Edit\").click();\n\n        const newContent = \"[edited]\";\n        cy.getByTestId(\"TextboxDialog\")\n          .should(\"exist\")\n          .within(() => {\n            cy.get(\"textarea\").clear().type(newContent);\n            cy.contains(\"button\", \"Save\").click();\n          });\n\n        cy.get(\"@textboxEl\").should(\"contain\", newContent);\n      });\n  });\n\n  it(\"renders textbox according to position configuration\", function () {\n    const id = this.dashboardId;\n    const txb1Pos = { col: 0, row: 0, sizeX: 3, sizeY: 2 };\n    const txb2Pos = { col: 1, row: 1, sizeX: 3, sizeY: 4 };\n\n    cy.viewport(1215, 800);\n    cy.addTextbox(id, \"x\", { position: txb1Pos })\n      .then(() => cy.addTextbox(id, \"x\", { position: txb2Pos }))\n      .then(getWidgetTestId)\n      .then((elTestId) => {\n        cy.visit(this.dashboardUrl);\n        return cy.getByTestId(elTestId);\n      })\n      .should(($el) => {\n        const { top, left } = $el.offset();\n        expect(top).to.be.oneOf([162, 162.015625]);\n        expect(left).to.eq(188);\n        expect($el.width()).to.eq(265);\n        expect($el.height()).to.eq(185);\n      });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/dashboard/widget_spec.js",
    "content": "/* global cy */\n\nimport { createQueryAndAddWidget, editDashboard, resizeBy } from \"../../support/dashboard\";\n\ndescribe(\"Widget\", () => {\n  beforeEach(function() {\n    cy.login();\n    cy.createDashboard(\"Foo Bar\").then(({ id }) => {\n      this.dashboardId = id;\n      this.dashboardUrl = `/dashboards/${id}`;\n    });\n  });\n\n  const confirmDeletionInModal = () => {\n    cy.get(\".ant-modal .ant-btn\")\n      .contains(\"Delete\")\n      .click({ force: true });\n  };\n\n  it(\"adds widget\", function() {\n    cy.createQuery().then(({ id: queryId }) => {\n      cy.visit(this.dashboardUrl);\n      editDashboard();\n      cy.getByTestId(\"AddWidgetButton\").click();\n      cy.getByTestId(\"AddWidgetDialog\").within(() => {\n        cy.get(`.query-selector-result[data-test=\"QueryId${queryId}\"]`).click();\n      });\n      cy.contains(\"button\", \"Add to Dashboard\").click();\n      cy.getByTestId(\"AddWidgetDialog\").should(\"not.exist\");\n      cy.get(\".widget-wrapper\").should(\"exist\");\n    });\n  });\n\n  it(\"removes widget\", function() {\n    createQueryAndAddWidget(this.dashboardId).then(elTestId => {\n      cy.visit(this.dashboardUrl);\n      editDashboard();\n      cy.getByTestId(elTestId).within(() => {\n        cy.getByTestId(\"WidgetDeleteButton\").click();\n      });\n\n      confirmDeletionInModal();\n      cy.getByTestId(elTestId).should(\"not.exist\");\n    });\n  });\n\n  describe(\"Auto height for table visualization\", () => {\n    it(\"renders correct height for 2 table rows\", function() {\n      const queryData = {\n        query: \"select s.a FROM generate_series(1,2) AS s(a)\",\n      };\n\n      createQueryAndAddWidget(this.dashboardId, queryData).then(elTestId => {\n        cy.visit(this.dashboardUrl);\n        cy.getByTestId(elTestId)\n          .its(\"0.offsetHeight\")\n          .should(\"eq\", 235);\n      });\n    });\n\n    it(\"renders correct height for 5 table rows\", function() {\n      const queryData = {\n        query: \"select s.a FROM generate_series(1,5) AS s(a)\",\n      };\n\n      createQueryAndAddWidget(this.dashboardId, queryData).then(elTestId => {\n        cy.visit(this.dashboardUrl);\n        cy.getByTestId(elTestId)\n          .its(\"0.offsetHeight\")\n          .should(\"eq\", 335);\n      });\n    });\n\n    describe(\"Height behavior on refresh\", () => {\n      const paramName = \"count\";\n      const queryData = {\n        query: `select s.a FROM generate_series(1,{{ ${paramName} }}) AS s(a)`,\n        options: {\n          parameters: [\n            {\n              title: paramName,\n              name: paramName,\n              type: \"text\",\n            },\n          ],\n        },\n      };\n\n      beforeEach(function() {\n        createQueryAndAddWidget(this.dashboardId, queryData).then(elTestId => {\n          cy.visit(this.dashboardUrl);\n          cy.getByTestId(elTestId)\n            .as(\"widget\")\n            .within(() => {\n              cy.getByTestId(\"RefreshButton\").as(\"refreshButton\");\n            });\n          cy.getByTestId(`ParameterName-${paramName}`).within(() => {\n            cy.getByTestId(\"TextParamInput\").as(\"paramInput\");\n          });\n        });\n      });\n\n      it(\"grows when dynamically adding table rows\", () => {\n        // listen to results\n        cy.server();\n        cy.route(\"GET\", \"**/api/query_results/*\").as(\"FreshResults\");\n\n        // start with 1 table row\n        cy.get(\"@paramInput\")\n          .clear()\n          .type(\"1\");\n        cy.getByTestId(\"ParameterApplyButton\").click();\n        cy.wait(\"@FreshResults\", { timeout: 10000 });\n        cy.get(\"@widget\")\n          .invoke(\"height\")\n          .should(\"eq\", 285);\n\n        // add 4 table rows\n        cy.get(\"@paramInput\")\n          .clear()\n          .type(\"5\");\n        cy.getByTestId(\"ParameterApplyButton\").click();\n        cy.wait(\"@FreshResults\", { timeout: 10000 });\n\n        // expect to height to grow by 1 grid grow\n        cy.get(\"@widget\")\n          .invoke(\"height\")\n          .should(\"eq\", 435);\n      });\n\n      it(\"revokes auto height after manual height adjustment\", () => {\n        // listen to results\n        cy.server();\n        cy.route(\"GET\", \"**/api/query_results/*\").as(\"FreshResults\");\n\n        editDashboard();\n\n        // start with 1 table row\n        cy.get(\"@paramInput\")\n          .clear()\n          .type(\"1\");\n        cy.getByTestId(\"ParameterApplyButton\").click();\n        cy.wait(\"@FreshResults\");\n        cy.get(\"@widget\")\n          .invoke(\"height\")\n          .should(\"eq\", 285);\n\n        // resize height by 1 grid row\n        resizeBy(cy.get(\"@widget\"), 0, 50)\n          .then(() => cy.get(\"@widget\"))\n          .invoke(\"height\")\n          .should(\"eq\", 335); // resized by 50, , 135 -> 185\n\n        // add 4 table rows\n        cy.get(\"@paramInput\")\n          .clear()\n          .type(\"5\");\n        cy.getByTestId(\"ParameterApplyButton\").click();\n        cy.wait(\"@FreshResults\");\n\n        // expect height to stay unchanged (would have been 435)\n        cy.get(\"@widget\")\n          .invoke(\"height\")\n          .should(\"eq\", 335);\n      });\n    });\n  });\n\n  it(\"sets the correct height of table visualization\", function() {\n    const queryData = {\n      query: `select '${\"loremipsum\".repeat(15)}' FROM generate_series(1,15)`,\n    };\n\n    const widgetOptions = { position: { col: 0, row: 0, sizeX: 3, sizeY: 10, autoHeight: false } };\n\n    createQueryAndAddWidget(this.dashboardId, queryData, widgetOptions).then(() => {\n      cy.visit(this.dashboardUrl);\n      cy.getByTestId(\"TableVisualization\")\n        .its(\"0.offsetHeight\")\n        .should(\"be.oneOf\", [380, 381]);\n      cy.percySnapshot(\"Shows correct height of table visualization\");\n    });\n  });\n\n  it(\"shows fixed pagination for overflowing tabular content \", function() {\n    const queryData = {\n      query: \"select 'lorem ipsum' FROM generate_series(1,50)\",\n    };\n\n    const widgetOptions = { position: { col: 0, row: 0, sizeX: 3, sizeY: 10, autoHeight: false } };\n\n    createQueryAndAddWidget(this.dashboardId, queryData, widgetOptions).then(() => {\n      cy.visit(this.dashboardUrl);\n      cy.getByTestId(\"TableVisualization\")\n        .next(\".ant-pagination.mini\")\n        .should(\"be.visible\");\n      cy.percySnapshot(\"Shows fixed mini pagination for overflowing tabular content\");\n    });\n  });\n\n  it(\"keeps results on screen while refreshing\", function() {\n    const queryData = {\n      query: \"select pg_sleep({{sleep-time}}), 'sleep time: {{sleep-time}}' as sleeptime\",\n      options: { parameters: [{ name: \"sleep-time\", title: \"Sleep time\", type: \"number\", value: 0 }] },\n    };\n\n    createQueryAndAddWidget(this.dashboardId, queryData).then(elTestId => {\n      cy.visit(this.dashboardUrl);\n      cy.getByTestId(elTestId).within(() => {\n        cy.getByTestId(\"TableVisualization\").should(\"contain\", \"sleep time: 0\");\n        cy.get(\".refresh-indicator\").should(\"not.be.visible\");\n\n        cy.getByTestId(\"ParameterName-sleep-time\").type(\"10\");\n        cy.getByTestId(\"ParameterApplyButton\").click();\n        cy.get(\".refresh-indicator\").should(\"be.visible\");\n        cy.getByTestId(\"TableVisualization\").should(\"contain\", \"sleep time: 0\");\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/data-source/create_data_source_spec.js",
    "content": "describe(\"Create Data Source\", () => {\n  beforeEach(() => {\n    cy.login();\n  });\n\n  it(\"opens the creation dialog when clicking in the create link or button\", () => {\n    cy.visit(\"/data_sources\");\n    cy.server();\n    cy.route(\"**/api/data_sources\", []); // force an empty response\n\n    [\"CreateDataSourceButton\", \"CreateDataSourceLink\"].forEach(createElementTestId => {\n      cy.getByTestId(createElementTestId).click();\n      cy.getByTestId(\"CreateSourceDialog\").should(\"exist\");\n      cy.getByTestId(\"CreateSourceCancelButton\").click();\n      cy.getByTestId(\"CreateSourceDialog\").should(\"not.exist\");\n    });\n  });\n\n  it(\"renders the page and takes a screenshot\", function() {\n    cy.visit(\"/data_sources/new\");\n    cy.server();\n    cy.route(\"**/api/data_sources/types\").as(\"DataSourceTypesRequest\");\n\n    cy.wait(\"@DataSourceTypesRequest\")\n      .then(({ response }) => response.body.filter(type => type.deprecated))\n      .then(deprecatedTypes => deprecatedTypes.map(type => type.type))\n      .as(\"deprecatedTypes\");\n\n    cy.getByTestId(\"PreviewItem\")\n      .then($previewItems => Cypress.$.map($previewItems, item => Cypress.$(item).attr(\"data-test-type\")))\n      .then(availableTypes => expect(availableTypes).not.to.contain.members(this.deprecatedTypes));\n\n    cy.getByTestId(\"CreateSourceDialog\").should(\"contain\", \"PostgreSQL\");\n    cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Create Data Source - Types\");\n  });\n\n  it(\"creates a new PostgreSQL data source\", () => {\n    cy.visit(\"/data_sources/new\");\n    cy.getByTestId(\"SearchSource\").type(\"PostgreSQL\");\n    cy.getByTestId(\"CreateSourceDialog\")\n      .contains(\"PostgreSQL\")\n      .click();\n\n    cy.getByTestId(\"Name\").type(\"Redash\");\n    cy.getByTestId(\"Host\").type(\"postgres\");\n    cy.getByTestId(\"User\").type(\"postgres\");\n    cy.getByTestId(\"Password\").type(\"postgres\");\n    cy.getByTestId(\"Database Name\").type(\"postgres{enter}\");\n    cy.getByTestId(\"CreateSourceSaveButton\").click({ force: true });\n\n    cy.contains(\"Saved.\");\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/data-source/edit_data_source_spec.js",
    "content": "describe(\"Edit Data Source\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.visit(\"/data_sources/1\");\n  });\n\n  it(\"renders the page and takes a screenshot\", () => {\n    cy.getByTestId(\"DataSource\").within(() => {\n      cy.getByTestId(\"Name\").should(\"have.value\", \"Test PostgreSQL\");\n      cy.getByTestId(\"Host\").should(\"have.value\", \"postgres\");\n    });\n\n    cy.percySnapshot(\"Edit Data Source - PostgreSQL\");\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/destination/create_destination_spec.js",
    "content": "describe(\"Create Destination\", () => {\n  beforeEach(() => {\n    cy.login();\n  });\n\n  it(\"renders the page and takes a screenshot\", function() {\n    cy.visit(\"/destinations/new\");\n    cy.server();\n    cy.route(\"**/api/destinations/types\").as(\"DestinationTypesRequest\");\n\n    cy.wait(\"@DestinationTypesRequest\")\n      .then(({ response }) => response.body.filter(type => type.deprecated))\n      .then(deprecatedTypes => deprecatedTypes.map(type => type.type))\n      .as(\"deprecatedTypes\");\n\n    cy.getByTestId(\"PreviewItem\")\n      .then($previewItems => Cypress.$.map($previewItems, item => Cypress.$(item).attr(\"data-test-type\")))\n      .then(availableTypes => expect(availableTypes).not.to.contain.oneOf(this.deprecatedTypes));\n\n    cy.getByTestId(\"CreateSourceDialog\").should(\"contain\", \"Email\");\n    cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Create Destination - Types\");\n  });\n\n  it(\"shows a custom error message when destination name is already taken\", () => {\n    cy.createDestination(\"Slack Destination\", \"slack\").then(() => {\n      cy.visit(\"/destinations/new\");\n\n      cy.getByTestId(\"SearchSource\").type(\"Slack\");\n      cy.getByTestId(\"CreateSourceDialog\")\n        .contains(\"Slack\")\n        .click();\n\n      cy.getByTestId(\"Name\").type(\"Slack Destination\");\n      cy.getByTestId(\"CreateSourceSaveButton\").click();\n\n      cy.contains(\"Alert Destination with the name Slack Destination already exists.\");\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/embed/share_embed_spec.js",
    "content": "describe(\"Embedded Queries\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.updateOrgSettings({ disable_public_urls: false });\n  });\n\n  it(\"is unavailable when public urls feature is disabled\", () => {\n    cy.createQuery({ query: \"select name from users order by name\" }).then((query) => {\n      cy.visit(`/queries/${query.id}/source`);\n      cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n      cy.getByTestId(\"ExecuteButton\").click();\n      cy.getByTestId(\"QueryPageVisualizationTabs\", { timeout: 10000 }).should(\"exist\");\n      cy.clickThrough(`\n          QueryControlDropdownButton\n          ShowEmbedDialogButton\n        `);\n      cy.getByTestId(\"EmbedIframe\")\n        .invoke(\"text\")\n        .then((embedUrl) => {\n          // disable the feature\n          cy.updateOrgSettings({ disable_public_urls: true });\n\n          // check the feature is disabled\n          cy.visit(`/queries/${query.id}/source`);\n          cy.getByTestId(\"QueryPageVisualizationTabs\", { timeout: 10000 }).should(\"exist\");\n          cy.getByTestId(\"QueryPageHeaderMoreButton\").click();\n          cy.get(\".ant-dropdown-menu-item\").should(\"exist\").should(\"not.contain\", \"Show API Key\");\n          cy.getByTestId(\"QueryControlDropdownButton\").click();\n          cy.get(\".ant-dropdown-menu-item\").should(\"exist\");\n          cy.getByTestId(\"ShowEmbedDialogButton\").should(\"not.exist\");\n\n          cy.logout();\n          cy.visit(embedUrl);\n          cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n          cy.getByTestId(\"TableVisualization\").should(\"not.exist\");\n\n          cy.login();\n          cy.updateOrgSettings({ disable_public_urls: false });\n        });\n    });\n  });\n\n  it(\"can be shared without parameters\", () => {\n    cy.createQuery({ query: \"select name from users order by name\" }).then((query) => {\n      cy.visit(`/queries/${query.id}/source`);\n      cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n      cy.getByTestId(\"ExecuteButton\").click();\n      cy.getByTestId(\"QueryPageVisualizationTabs\", { timeout: 10000 }).should(\"exist\");\n      cy.clickThrough(`\n          QueryControlDropdownButton\n          ShowEmbedDialogButton\n        `);\n      cy.getByTestId(\"EmbedIframe\")\n        .invoke(\"text\")\n        .then((embedUrl) => {\n          cy.logout();\n          cy.visit(embedUrl);\n          cy.getByTestId(\"VisualizationEmbed\", { timeout: 10000 }).should(\"exist\");\n          cy.getByTestId(\"TimeAgo\", { timeout: 10000 }).should(\"exist\");\n          cy.getByTestId(\"TableVisualization\").should(\"exist\");\n          cy.percySnapshot(\"Successfully Embedded Non-Parameterized Query\");\n        });\n    });\n  });\n\n  it(\"can be shared with safe parameters\", () => {\n    cy.visit(\"/queries/new\");\n    cy.getByTestId(\"QueryEditor\")\n      .get(\".ace_text-input\")\n      .type(\"SELECT name, slug FROM organizations WHERE id='{{}{{}id}}'{esc}\", { force: true });\n\n    cy.getByTestId(\"TextParamInput\").type(\"1\");\n    cy.getByTestId(\"ParameterApplyButton\").click();\n    cy.clickThrough(`\n      ParameterSettings-id\n      ParameterTypeSelect\n      NumberParameterTypeOption\n      SaveParameterSettings\n      SaveButton\n    `);\n\n    // Add a little waiting - page is not updated fast enough\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n\n    cy.location(\"search\").should(\"eq\", \"?p_id=1\");\n    cy.clickThrough(`\n      QueryControlDropdownButton\n      ShowEmbedDialogButton\n    `);\n\n    cy.getByTestId(\"EmbedIframe\")\n      .invoke(\"text\")\n      .then((embedUrl) => {\n        cy.logout();\n        cy.visit(embedUrl);\n        cy.getByTestId(\"VisualizationEmbed\", { timeout: 10000 }).should(\"exist\");\n        cy.getByTestId(\"TimeAgo\", { timeout: 10000 }).should(\"exist\");\n        cy.getByTestId(\"TableVisualization\").should(\"exist\");\n        cy.percySnapshot(\"Successfully Embedded Parameterized Query\");\n      });\n  });\n\n  it(\"cannot be shared with unsafe parameters\", () => {\n    cy.visit(\"/queries/new\");\n    cy.getByTestId(\"QueryEditor\")\n      .get(\".ace_text-input\")\n      .type(\"SELECT name, slug FROM organizations WHERE name='{{}{{}name}}'{esc}\", { force: true });\n\n    cy.getByTestId(\"TextParamInput\").type(\"Redash\");\n    cy.getByTestId(\"ParameterApplyButton\").click();\n    cy.clickThrough(`\n      ParameterSettings-name\n      ParameterTypeSelect\n      TextParameterTypeOption\n      SaveParameterSettings\n      SaveButton\n    `);\n\n    // Add a little waiting - page is not updated fast enough\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n\n    cy.location(\"search\").should(\"eq\", \"?p_name=Redash\");\n    cy.clickThrough(`\n      QueryControlDropdownButton\n      ShowEmbedDialogButton\n    `);\n\n    cy.getByTestId(\"EmbedIframe\").should(\"not.exist\");\n    cy.getByTestId(\"EmbedErrorAlert\").should(\"exist\");\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/group/edit_group_spec.js",
    "content": "describe(\"Edit Group\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.visit(\"/groups/1\");\n  });\n\n  it(\"renders the page and takes a screenshot\", () => {\n    cy.getByTestId(\"Group\").within(() => {\n      cy.get(\"h3\").should(\"contain\", \"admin\");\n      cy.get(\"td\").should(\"contain\", \"Example Admin\");\n    });\n\n    cy.percySnapshot(\"Group\");\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/group/group_list_spec.js",
    "content": "describe(\"Group List\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.visit(\"/groups\");\n  });\n\n  it(\"renders the page and takes a screenshot\", () => {\n    cy.getByTestId(\"GroupList\")\n      .should(\"exist\")\n      .and(\"contain\", \"admin\")\n      .and(\"contain\", \"default\");\n\n    cy.percySnapshot(\"Groups\");\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/query/create_query_spec.js",
    "content": "describe(\"Create Query\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.visit(\"/queries/new\");\n  });\n\n  it(\"executes and saves a query\", () => {\n    cy.clickThrough(`\n      SelectDataSource\n      SelectDataSource${Cypress.env(\"dataSourceId\")}\n    `);\n\n    cy.getByTestId(\"QueryEditor\")\n      .get(\".ace_text-input\")\n      .type(\"SELECT id, name FROM organizations{esc}\", { force: true });\n\n    cy.getByTestId(\"ExecuteButton\")\n      .should(\"be.enabled\")\n      .click();\n\n    cy.getByTestId(\"TableVisualization\").should(\"exist\");\n    cy.percySnapshot(\"Edit Query\");\n\n    cy.getByTestId(\"SaveButton\").click();\n    cy.url().should(\"match\", /\\/queries\\/.+\\/source/);\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/query/filters_spec.js",
    "content": "import { expectTableToHaveLength, expectFirstColumnToHaveMembers } from \"../../support/visualizations/table\";\n\nconst SQL = `\nSELECT 'a' AS stage1, 'a1' AS stage2, 11 AS value UNION ALL\nSELECT 'a' AS stage1, 'a2' AS stage2, 12 AS value UNION ALL\nSELECT 'a' AS stage1, 'a3' AS stage2, 45 AS value UNION ALL\nSELECT 'a' AS stage1, 'a4' AS stage2, 54 AS value UNION ALL\nSELECT 'b' AS stage1, 'b1' AS stage2, 33 AS value UNION ALL\nSELECT 'b' AS stage1, 'b2' AS stage2, 73 AS value UNION ALL\nSELECT 'b' AS stage1, 'b3' AS stage2, 90 AS value UNION ALL\nSELECT 'c' AS stage1, 'c1' AS stage2, 19 AS value UNION ALL\nSELECT 'c' AS stage1, 'c2' AS stage2, 92 AS value UNION ALL\nSELECT 'c' AS stage1, 'c3' AS stage2, 63 AS value UNION ALL\nSELECT 'c' AS stage1, 'c4' AS stage2, 44 AS value\\\n`;\n\ndescribe(\"Query Filters\", () => {\n  beforeEach(() => {\n    cy.login();\n  });\n\n  describe(\"Simple Filter\", () => {\n    beforeEach(() => {\n      const queryData = {\n        name: \"Query Filters\",\n        query: `SELECT stage1 AS \"stage1::filter\", stage2, value FROM (${SQL}) q`,\n      };\n\n      cy.createQuery(queryData).then(({ id }) => cy.visit(`/queries/${id}`));\n      cy.getByTestId(\"ExecuteButton\").click();\n    });\n\n    it(\"filters rows in a Table Visualization\", () => {\n      cy.getByTestId(\"FilterName-stage1::filter\")\n        .find(\".ant-select-selection-item\")\n        .should(\"have.text\", \"a\");\n\n      expectTableToHaveLength(4);\n      expectFirstColumnToHaveMembers([\"a\", \"a\", \"a\", \"a\"]);\n\n      cy.getByTestId(\"FilterName-stage1::filter\")\n        .find(\".ant-select\")\n        .click();\n\n      cy.contains(\".ant-select-item-option-content\", \"b\").click();\n\n      expectTableToHaveLength(3);\n      expectFirstColumnToHaveMembers([\"b\", \"b\", \"b\"]);\n    });\n  });\n\n  describe(\"Multi Filter\", () => {\n    beforeEach(() => {\n      const queryData = {\n        name: \"Query Filters\",\n        query: `SELECT stage1 AS \"stage1::multi-filter\", stage2, value FROM (${SQL}) q`,\n      };\n\n      cy.createQuery(queryData).then(({ id }) => cy.visit(`/queries/${id}`));\n      cy.getByTestId(\"ExecuteButton\").click();\n    });\n\n    function expectSelectedOptionsToHaveMembers(values) {\n      cy.getByTestId(\"FilterName-stage1::multi-filter\")\n        .find(\".ant-select-selection-item-content\")\n        .then($selectedOptions => Cypress.$.map($selectedOptions, item => Cypress.$(item).text()))\n        .then(selectedOptions => expect(selectedOptions).to.have.members(values));\n    }\n\n    it(\"filters rows in a Table Visualization\", () => {\n      // Defaults to All Options Selected\n\n      expectSelectedOptionsToHaveMembers([\"a\", \"b\", \"c\"]);\n      expectTableToHaveLength(11);\n      expectFirstColumnToHaveMembers([\"a\", \"a\", \"a\", \"a\", \"b\", \"b\", \"b\", \"c\", \"c\", \"c\", \"c\"]);\n\n      // Clear Option\n\n      cy.getByTestId(\"FilterName-stage1::multi-filter\")\n        .find(\".ant-select-selector\")\n        .click();\n      cy.getByTestId(\"ClearOption\").click();\n      cy.getByTestId(\"FilterName-stage1::multi-filter\").click(); // close dropdown\n\n      cy.getByTestId(\"TableVisualization\").should(\"not.exist\");\n\n      // Single Option selected\n\n      cy.getByTestId(\"FilterName-stage1::multi-filter\")\n        .find(\".ant-select-selector\")\n        .click();\n      cy.contains(\".ant-select-item-option-grouped > .ant-select-item-option-content\", \"a\").click();\n      cy.getByTestId(\"FilterName-stage1::multi-filter\").click(); // close dropdown\n\n      expectSelectedOptionsToHaveMembers([\"a\"]);\n      expectTableToHaveLength(4);\n      expectFirstColumnToHaveMembers([\"a\", \"a\", \"a\", \"a\"]);\n\n      // Two Options selected\n\n      cy.getByTestId(\"FilterName-stage1::multi-filter\")\n        .find(\".ant-select-selector\")\n        .click();\n      cy.contains(\".ant-select-item-option-content\", \"b\").click();\n      cy.getByTestId(\"FilterName-stage1::multi-filter\").click(); // close dropdown\n\n      expectSelectedOptionsToHaveMembers([\"a\", \"b\"]);\n      expectTableToHaveLength(7);\n      expectFirstColumnToHaveMembers([\"a\", \"a\", \"a\", \"a\", \"b\", \"b\", \"b\"]);\n\n      // Select All Option\n\n      cy.getByTestId(\"FilterName-stage1::multi-filter\")\n        .find(\".ant-select-selector\")\n        .click();\n      cy.getByTestId(\"SelectAllOption\").click();\n      cy.getByTestId(\"FilterName-stage1::multi-filter\").click(); // close dropdown\n\n      expectSelectedOptionsToHaveMembers([\"a\", \"b\", \"c\"]);\n      expectTableToHaveLength(11);\n      expectFirstColumnToHaveMembers([\"a\", \"a\", \"a\", \"a\", \"b\", \"b\", \"b\", \"c\", \"c\", \"c\", \"c\"]);\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/query/parameter_spec.js",
    "content": "import { dragParam } from \"../../support/parameters\";\nimport dayjs from \"dayjs\";\n\nfunction openAndSearchAntdDropdown(testId, paramOption) {\n  cy.getByTestId(testId).find(\".ant-select-selection-search-input\").type(paramOption, { force: true });\n}\n\ndescribe(\"Parameter\", () => {\n  const expectDirtyStateChange = (edit) => {\n    cy.getByTestId(\"ParameterName-test-parameter\")\n      .find(\".parameter-input\")\n      .should(($el) => {\n        assert.isUndefined($el.data(\"dirty\"));\n      });\n\n    edit();\n\n    cy.getByTestId(\"ParameterName-test-parameter\")\n      .find(\".parameter-input\")\n      .should(($el) => {\n        assert.isTrue($el.data(\"dirty\"));\n      });\n  };\n\n  beforeEach(() => {\n    cy.login();\n  });\n\n  describe(\"Text Parameter\", () => {\n    beforeEach(() => {\n      const queryData = {\n        name: \"Text Parameter\",\n        query: \"SELECT '{{test-parameter}}' AS parameter\",\n        options: {\n          parameters: [{ name: \"test-parameter\", title: \"Test Parameter\", type: \"text\" }],\n        },\n      };\n\n      cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}`));\n    });\n\n    it(\"updates the results after clicking Apply\", () => {\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").type(\"Redash\");\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", \"Redash\");\n    });\n\n    it(\"sets dirty state when edited\", () => {\n      expectDirtyStateChange(() => {\n        cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").type(\"Redash\");\n      });\n    });\n  });\n\n  describe(\"Text Pattern Parameter\", () => {\n    beforeEach(() => {\n      const queryData = {\n        name: \"Text Pattern Parameter\",\n        query: \"SELECT '{{test-parameter}}' AS parameter\",\n        options: {\n          parameters: [{ name: \"test-parameter\", title: \"Test Parameter\", type: \"text-pattern\", regex: \"a.*a\" }],\n        },\n      };\n\n      cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));\n    });\n\n    it(\"updates the results after clicking Apply\", () => {\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").type(\"{selectall}arta\");\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", \"arta\");\n\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").type(\"{selectall}arounda\");\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", \"arounda\");\n    });\n\n    it(\"throws error message with invalid query request\", () => {\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").type(\"{selectall}arta\");\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").type(\"{selectall}abcab\");\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"QueryExecutionStatus\").should(\"exist\");\n    });\n\n    it(\"sets dirty state when edited\", () => {\n      expectDirtyStateChange(() => {\n        cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").type(\"{selectall}arta\");\n      });\n    });\n\n    it(\"doesn't let user save invalid regex\", () => {\n      cy.get(\".fa-cog\").click();\n      cy.getByTestId(\"RegexPatternInput\").type(\"{selectall}[\");\n      cy.contains(\"Invalid Regex Pattern\").should(\"exist\");\n      cy.getByTestId(\"SaveParameterSettings\").click();\n      cy.get(\".fa-cog\").click();\n      cy.getByTestId(\"RegexPatternInput\").should(\"not.equal\", \"[\");\n    });\n  });\n\n  describe(\"Number Parameter\", () => {\n    beforeEach(() => {\n      const queryData = {\n        name: \"Number Parameter\",\n        query: \"SELECT '{{test-parameter}}' AS parameter\",\n        options: {\n          parameters: [{ name: \"test-parameter\", title: \"Test Parameter\", type: \"number\" }],\n        },\n      };\n\n      cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}`));\n    });\n\n    it(\"updates the results after clicking Apply\", () => {\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").type(\"{selectall}42\");\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", 42);\n\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").type(\"{selectall}31415\");\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", 31415);\n    });\n\n    it(\"sets dirty state when edited\", () => {\n      expectDirtyStateChange(() => {\n        cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").type(\"{selectall}42\");\n      });\n    });\n  });\n\n  describe(\"Dropdown Parameter\", () => {\n    beforeEach(() => {\n      const queryData = {\n        name: \"Dropdown Parameter\",\n        query: \"SELECT '{{test-parameter}}' AS parameter\",\n        options: {\n          parameters: [\n            { name: \"test-parameter\", title: \"Test Parameter\", type: \"enum\", enumOptions: \"value1\\nvalue2\\nvalue3\" },\n          ],\n        },\n      };\n\n      cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));\n    });\n\n    it(\"updates the results after selecting a value\", () => {\n      openAndSearchAntdDropdown(\"ParameterName-test-parameter\", \"value2\"); // asserts option filter prop\n\n      // only the filtered option should be on the DOM\n      cy.get(\".ant-select-item-option\").should(\"have.length\", 1).and(\"contain\", \"value2\").click();\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n      // ensure that query is being executed\n      cy.getByTestId(\"QueryExecutionStatus\").should(\"exist\");\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", \"value2\");\n    });\n\n    it(\"supports multi-selection\", () => {\n      cy.clickThrough(`\n        ParameterSettings-test-parameter\n        AllowMultipleValuesCheckbox\n        QuotationSelect\n        DoubleQuotationMarkOption\n        SaveParameterSettings\n      `);\n\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\".ant-select-selection-search\").click();\n\n      // select all unselected options\n      cy.get(\".ant-select-item-option\").each(($option) => {\n        if (!$option.hasClass(\"ant-select-item-option-selected\")) {\n          cy.wrap($option).click();\n        }\n      });\n\n      cy.getByTestId(\"QueryEditor\").click(); // just to close the select menu\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", '\"value1\",\"value2\",\"value3\"');\n    });\n\n    it(\"sets dirty state when edited\", () => {\n      expectDirtyStateChange(() => {\n        cy.getByTestId(\"ParameterName-test-parameter\").find(\".ant-select\").click();\n\n        cy.contains(\".ant-select-item-option\", \"value2\").click();\n      });\n    });\n  });\n\n  describe(\"Query Based Dropdown Parameter\", () => {\n    context(\"based on a query with no results\", () => {\n      beforeEach(() => {\n        const dropdownQueryData = {\n          name: \"Dropdown Query\",\n          query: \"\",\n        };\n        cy.createQuery(dropdownQueryData, true).then((dropdownQuery) => {\n          const queryData = {\n            name: \"Query Based Dropdown Parameter\",\n            query: \"SELECT '{{test-parameter}}' AS parameter\",\n            options: {\n              parameters: [\n                { name: \"test-parameter\", title: \"Test Parameter\", type: \"query\", queryId: dropdownQuery.id },\n              ],\n            },\n          };\n\n          cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));\n        });\n      });\n\n      it(\"should show a 'No options available' message when you click\", () => {\n        cy.getByTestId(\"ParameterName-test-parameter\")\n          .find(\".ant-select:not(.ant-select-disabled) .ant-select-selector\")\n          .click();\n\n        cy.contains(\".ant-select-item-empty\", \"No options available\");\n      });\n    });\n\n    context(\"based on a query with 3 results\", () => {\n      beforeEach(() => {\n        const dropdownQueryData = {\n          name: \"Dropdown Query\",\n          query: `SELECT 'value1' AS name, 1 AS value UNION ALL\n                  SELECT 'value2' AS name, 2 AS value UNION ALL\n                  SELECT 'value3' AS name, 3 AS value`,\n        };\n        cy.createQuery(dropdownQueryData, true).then((dropdownQuery) => {\n          const queryData = {\n            name: \"Query Based Dropdown Parameter\",\n            query: \"SELECT '{{test-parameter}}' AS parameter\",\n            options: {\n              parameters: [\n                { name: \"test-parameter\", title: \"Test Parameter\", type: \"query\", queryId: dropdownQuery.id },\n              ],\n            },\n          };\n\n          cy.visit(`/queries/${dropdownQuery.id}`);\n          cy.getByTestId(\"ExecuteButton\").click();\n          cy.getByTestId(\"TableVisualization\")\n            .should(\"contain\", \"value1\")\n            .and(\"contain\", \"value2\")\n            .and(\"contain\", \"value3\");\n\n          cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));\n        });\n      });\n\n      it(\"updates the results after selecting a value\", () => {\n        openAndSearchAntdDropdown(\"ParameterName-test-parameter\", \"value2\"); // asserts option filter prop\n\n        // only the filtered option should be on the DOM\n        cy.get(\".ant-select-item-option\").should(\"have.length\", 1).and(\"contain\", \"value2\").click();\n\n        cy.getByTestId(\"ParameterApplyButton\").click();\n        // ensure that query is being executed\n        cy.getByTestId(\"QueryExecutionStatus\").should(\"exist\");\n\n        cy.getByTestId(\"TableVisualization\").should(\"contain\", \"2\");\n      });\n\n      it(\"supports multi-selection\", () => {\n        cy.clickThrough(`\n          ParameterSettings-test-parameter\n          AllowMultipleValuesCheckbox\n          QuotationSelect\n          DoubleQuotationMarkOption\n          SaveParameterSettings\n        `);\n\n        cy.getByTestId(\"ParameterName-test-parameter\").find(\".ant-select\").click();\n\n        // make sure all options are unselected and select all\n        cy.get(\".ant-select-item-option\").each(($option) => {\n          expect($option).not.to.have.class(\"ant-select-dropdown-menu-item-selected\");\n          cy.wrap($option).click();\n        });\n\n        cy.getByTestId(\"QueryEditor\").click(); // just to close the select menu\n\n        cy.getByTestId(\"ParameterApplyButton\").click();\n\n        cy.getByTestId(\"TableVisualization\").should(\"contain\", '\"1\",\"2\",\"3\"');\n      });\n    });\n  });\n\n  const selectCalendarDate = (date) => {\n    cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").click();\n\n    cy.get(\".ant-picker-panel\").contains(\".ant-picker-cell-inner\", date).click();\n  };\n\n  describe(\"Date Parameter\", () => {\n    beforeEach(() => {\n      const queryData = {\n        name: \"Date Parameter\",\n        query: \"SELECT '{{test-parameter}}' AS parameter\",\n        options: {\n          parameters: [{ name: \"test-parameter\", title: \"Test Parameter\", type: \"date\", value: null }],\n        },\n      };\n\n      const now = new Date();\n      now.setDate(1);\n      cy.wrap(now.getTime()).as(\"now\");\n      cy.clock(now.getTime(), [\"Date\"]);\n\n      cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}`));\n    });\n\n    afterEach(() => {\n      cy.clock().then((clock) => clock.restore());\n    });\n\n    it(\"updates the results after selecting a date\", function () {\n      selectCalendarDate(\"15\");\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", dayjs(this.now).format(\"15/MM/YY\"));\n    });\n\n    it(\"allows picking a dynamic date\", function () {\n      cy.getByTestId(\"DynamicButton\").click();\n\n      cy.getByTestId(\"DynamicButtonMenu\").contains(\"Today/Now\").click();\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", dayjs(this.now).format(\"DD/MM/YY\"));\n    });\n\n    it(\"sets dirty state when edited\", () => {\n      expectDirtyStateChange(() => selectCalendarDate(\"15\"));\n    });\n  });\n\n  describe(\"Date and Time Parameter\", () => {\n    beforeEach(() => {\n      const queryData = {\n        name: \"Date and Time Parameter\",\n        query: \"SELECT '{{test-parameter}}' AS parameter\",\n        options: {\n          parameters: [{ name: \"test-parameter\", title: \"Test Parameter\", type: \"datetime-local\", value: null }],\n        },\n      };\n\n      const now = new Date();\n      now.setDate(1);\n      cy.wrap(now.getTime()).as(\"now\");\n      cy.clock(now.getTime(), [\"Date\"]);\n\n      cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}`));\n    });\n\n    afterEach(() => {\n      cy.clock().then((clock) => clock.restore());\n    });\n\n    it(\"updates the results after selecting a date and clicking in ok\", function () {\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").as(\"Input\").click();\n\n      selectCalendarDate(\"15\");\n\n      cy.get(\".ant-picker-ok button\").click();\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", dayjs(this.now).format(\"YYYY-MM-15 HH:mm\"));\n    });\n\n    it(\"shows the current datetime after clicking in Now\", function () {\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").as(\"Input\").click();\n\n      cy.get(\".ant-picker-panel\").contains(\"Now\").click();\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", dayjs(this.now).format(\"YYYY-MM-DD HH:mm\"));\n    });\n\n    it(\"allows picking a dynamic date\", function () {\n      cy.getByTestId(\"DynamicButton\").click();\n\n      cy.getByTestId(\"DynamicButtonMenu\").contains(\"Today/Now\").click();\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      cy.getByTestId(\"TableVisualization\").should(\"contain\", dayjs(this.now).format(\"YYYY-MM-DD HH:mm\"));\n    });\n\n    it(\"sets dirty state when edited\", () => {\n      expectDirtyStateChange(() => {\n        cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").click();\n\n        cy.get(\".ant-picker-panel\").contains(\"Now\").click();\n      });\n    });\n  });\n\n  describe(\"Date Range Parameter\", () => {\n    const selectCalendarDateRange = (startDate, endDate) => {\n      cy.getByTestId(\"ParameterName-test-parameter\").find(\"input\").first().click();\n\n      cy.get(\".ant-picker-panel\").contains(\".ant-picker-cell-inner\", startDate).click();\n\n      cy.get(\".ant-picker-panel\").contains(\".ant-picker-cell-inner\", endDate).click();\n    };\n\n    beforeEach(() => {\n      const queryData = {\n        name: \"Date Range Parameter\",\n        query: \"SELECT '{{test-parameter.start}} - {{test-parameter.end}}' AS parameter\",\n        options: {\n          parameters: [{ name: \"test-parameter\", title: \"Test Parameter\", type: \"date-range\" }],\n        },\n      };\n\n      const now = new Date();\n      now.setDate(1);\n      cy.wrap(now.getTime()).as(\"now\");\n      cy.clock(now.getTime(), [\"Date\"]);\n\n      cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));\n    });\n\n    afterEach(() => {\n      cy.clock().then((clock) => clock.restore());\n    });\n\n    it(\"updates the results after selecting a date range\", function () {\n      selectCalendarDateRange(\"15\", \"20\");\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      const now = dayjs(this.now);\n      cy.getByTestId(\"TableVisualization\").should(\n        \"contain\",\n        now.format(\"YYYY-MM-15\") + \" - \" + now.format(\"YYYY-MM-20\")\n      );\n    });\n\n    it(\"allows picking a dynamic date range\", function () {\n      cy.getByTestId(\"DynamicButton\").click();\n\n      cy.getByTestId(\"DynamicButtonMenu\").contains(\"Last month\").click();\n\n      cy.getByTestId(\"ParameterApplyButton\").click();\n\n      const lastMonth = dayjs(this.now).subtract(1, \"month\");\n      cy.getByTestId(\"TableVisualization\").should(\n        \"contain\",\n        lastMonth.startOf(\"month\").format(\"YYYY-MM-DD\") + \" - \" + lastMonth.endOf(\"month\").format(\"YYYY-MM-DD\")\n      );\n    });\n\n    it(\"sets dirty state when edited\", () => {\n      expectDirtyStateChange(() => selectCalendarDateRange(\"15\", \"20\"));\n    });\n  });\n\n  describe(\"Apply Changes\", () => {\n    const expectAppliedChanges = (apply) => {\n      cy.getByTestId(\"ParameterName-test-parameter-1\").find(\"input\").as(\"Input\").type(\"Redash\");\n\n      cy.getByTestId(\"ParameterName-test-parameter-2\").find(\"input\").type(\"Redash\");\n\n      cy.location(\"search\").should(\"not.contain\", \"Redash\");\n\n      cy.server();\n      cy.route(\"POST\", \"**/api/queries/*/results\").as(\"Results\");\n\n      apply(cy.get(\"@Input\"));\n\n      cy.location(\"search\").should(\"contain\", \"Redash\");\n      cy.wait(\"@Results\");\n    };\n\n    beforeEach(() => {\n      const queryData = {\n        name: \"Testing Apply Button\",\n        query: \"SELECT '{{test-parameter-1}} {{ test-parameter-2 }}'\",\n        options: {\n          parameters: [\n            { name: \"test-parameter-1\", title: \"Test Parameter 1\", type: \"text\" },\n            { name: \"test-parameter-2\", title: \"Test Parameter 2\", type: \"text\" },\n          ],\n        },\n      };\n\n      cy.server();\n      cy.route(\"GET\", \"**/api/data_sources/*/schema\").as(\"Schema\");\n\n      cy.createQuery(queryData, false)\n        .then(({ id }) => cy.visit(`/queries/${id}/source`))\n        .then(() => cy.wait(\"@Schema\"));\n    });\n\n    it(\"shows and hides according to parameter dirty state\", () => {\n      cy.getByTestId(\"ParameterApplyButton\").should(\"not.be\", \"visible\");\n\n      cy.getByTestId(\"ParameterName-test-parameter-1\").find(\"input\").as(\"Param\").type(\"Redash\");\n\n      cy.getByTestId(\"ParameterApplyButton\").should(\"be.visible\");\n\n      cy.get(\"@Param\").clear();\n\n      cy.getByTestId(\"ParameterApplyButton\").should(\"not.be\", \"visible\");\n    });\n\n    it(\"updates dirty counter\", () => {\n      cy.getByTestId(\"ParameterName-test-parameter-1\").find(\"input\").type(\"Redash\");\n\n      cy.getByTestId(\"ParameterApplyButton\").find(\".ant-badge-count p.current\").should(\"contain\", \"1\");\n\n      cy.getByTestId(\"ParameterName-test-parameter-2\").find(\"input\").type(\"Redash\");\n\n      cy.getByTestId(\"ParameterApplyButton\").find(\".ant-badge-count p.current\").should(\"contain\", \"2\");\n    });\n\n    it('applies changes from \"Apply Changes\" button', () => {\n      expectAppliedChanges(() => {\n        cy.getByTestId(\"ParameterApplyButton\").click();\n      });\n    });\n\n    it('applies changes from \"alt+enter\" keyboard shortcut', () => {\n      expectAppliedChanges((input) => {\n        input.type(\"{alt}{enter}\");\n      });\n    });\n\n    it('disables \"Execute\" button', () => {\n      cy.getByTestId(\"ParameterName-test-parameter-1\").find(\"input\").as(\"Input\").type(\"Redash\");\n      cy.getByTestId(\"ExecuteButton\").should(\"be.disabled\");\n\n      cy.get(\"@Input\").clear();\n      cy.getByTestId(\"ExecuteButton\").should(\"be.enabled\");\n    });\n  });\n\n  describe(\"Draggable\", () => {\n    beforeEach(() => {\n      const queryData = {\n        name: \"Draggable\",\n        query: \"SELECT '{{param1}}', '{{param2}}', '{{param3}}', '{{param4}}' AS parameter\",\n        options: {\n          parameters: [\n            { name: \"param1\", title: \"Parameter 1\", type: \"text\" },\n            { name: \"param2\", title: \"Parameter 2\", type: \"text\" },\n            { name: \"param3\", title: \"Parameter 3\", type: \"text\" },\n            { name: \"param4\", title: \"Parameter 4\", type: \"text\" },\n          ],\n        },\n      };\n\n      cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));\n\n      cy.get(\".parameter-block\").first().invoke(\"width\").as(\"paramWidth\");\n\n      cy.get(\"body\").type(\"{alt}D\"); // hide schema browser\n    });\n\n    it(\"is possible to rearrange parameters\", function () {\n      cy.server();\n      cy.route(\"POST\", \"**/api/queries/*\").as(\"QuerySave\");\n\n      dragParam(\"param1\", this.paramWidth, 1);\n      cy.wait(\"@QuerySave\");\n      dragParam(\"param4\", -this.paramWidth, 1);\n      cy.wait(\"@QuerySave\");\n\n      cy.reload();\n\n      const expectedOrder = [\"Parameter 2\", \"Parameter 1\", \"Parameter 4\", \"Parameter 3\"];\n      cy.get(\".parameter-container label\").each(($label, index) => expect($label).to.have.text(expectedOrder[index]));\n    });\n  });\n\n  describe(\"Parameter Settings\", () => {\n    beforeEach(() => {\n      const queryData = {\n        name: \"Draggable\",\n        query: \"SELECT '{{parameter}}' AS parameter\",\n        options: {\n          parameters: [{ name: \"parameter\", title: \"Parameter\", type: \"text\" }],\n        },\n      };\n\n      cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}/source`));\n\n      cy.getByTestId(\"ParameterSettings-parameter\").click();\n    });\n\n    it(\"changes the parameter title\", () => {\n      cy.getByTestId(\"ParameterTitleInput\").type(\"{selectall}New Parameter Name\");\n      cy.getByTestId(\"SaveParameterSettings\").click();\n\n      cy.contains(\"Query saved\");\n      cy.reload();\n\n      cy.getByTestId(\"ParameterName-parameter\").contains(\"label\", \"New Parameter Name\");\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/query/query_tags_spec.js",
    "content": "import { expectTagsToContain, typeInTagsSelectAndSave } from \"../../support/tags\";\n\ndescribe(\"Query Tags\", () => {\n  beforeEach(() => {\n    cy.login();\n\n    const queryData = {\n      name: \"Query Tags\",\n      query: \"SELECT 1 as value\",\n    };\n\n    cy.createQuery(queryData, false).then(({ id }) => cy.visit(`/queries/${id}`));\n  });\n\n  it(\"is possible to add and edit tags\", () => {\n    cy.server();\n    cy.route(\"POST\", \"**/api/queries/*\").as(\"QuerySave\");\n\n    cy.getByTestId(\"TagsControl\").contains(\".label\", \"Unpublished\");\n\n    cy.getByTestId(\"EditTagsButton\")\n      .should(\"contain\", \"Add tag\")\n      .click();\n\n    typeInTagsSelectAndSave(\"tag1{enter}tag2{enter}tag3{enter}\");\n\n    cy.wait(\"@QuerySave\");\n    expectTagsToContain([\"tag1\", \"tag2\", \"tag3\"]);\n\n    cy.getByTestId(\"EditTagsButton\").click();\n    typeInTagsSelectAndSave(\"tag4{enter}\");\n\n    cy.wait(\"@QuerySave\");\n    cy.reload();\n    expectTagsToContain([\"tag1\", \"tag2\", \"tag3\", \"tag4\"]);\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/query-snippets/create_query_snippet_spec.js",
    "content": "describe(\"Create Query Snippet\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.visit(\"/query_snippets/new\");\n  });\n\n  it(\"creates a query snippet with an empty description\", () => {\n    // delete existing \"example-snippet\"\n    cy.request(\"GET\", \"api/query_snippets\")\n      .then(({ body }) => body.filter(snippet => snippet.trigger === \"example-snippet\"))\n      .each(snippet => cy.request(\"DELETE\", `api/query_snippets/${snippet.id}`));\n\n    cy.getByTestId(\"QuerySnippetDialog\").within(() => {\n      cy.getByTestId(\"Trigger\").type(\"example-snippet\");\n      cy.getByTestId(\"Snippet\")\n        .find(\".ace_text-input\")\n        .type(\"SELECT 1\", { force: true });\n    });\n\n    cy.getByTestId(\"SaveQuerySnippetButton\").click();\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/settings/organization_settings_spec.js",
    "content": "describe(\"Settings\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.visit(\"/settings/general\");\n  });\n\n  it(\"renders the page and takes a screenshot\", () => {\n    cy.getByTestId(\"OrganizationSettings\").within(() => {\n      cy.getByTestId(\"TimeFormatSelect\").should(\"contain\", \"HH:mm\");\n    });\n\n    cy.percySnapshot(\"Organization Settings\");\n  });\n\n  it(\"can set date format setting\", () => {\n    cy.getByTestId(\"DateFormatSelect\").click();\n    cy.getByTestId(\"DateFormatSelect:YYYY-MM-DD\").click();\n    cy.getByTestId(\"OrganizationSettingsSaveButton\").click();\n\n    cy.createQuery({\n      name: \"test date format\",\n      query: \"SELECT NOW()\",\n    }).then(({ id: queryId }) => {\n      cy.visit(`/queries/${queryId}`);\n      cy.findByText(\"Refresh Now\").click();\n\n      // \"created at\" field is formatted with the date format.\n      cy.getByTestId(\"TableVisualization\")\n        .findAllByText(/\\d{4}-\\d{2}-\\d{2}/)\n        .should(\"exist\");\n\n      // set to a different format and expect a different result in the table\n      cy.visit(\"/settings/general\");\n      cy.getByTestId(\"DateFormatSelect\").click();\n      cy.getByTestId(\"DateFormatSelect:MM/DD/YY\").click();\n      cy.getByTestId(\"OrganizationSettingsSaveButton\").click();\n\n      cy.visit(`/queries/${queryId}`);\n\n      cy.getByTestId(\"TableVisualization\")\n        .findAllByText(/\\d{2}\\/\\d{2}\\/\\d{2}/)\n        .should(\"exist\");\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/settings/settings_tabs_spec.js",
    "content": "describe(\"Settings Tabs\", () => {\n  const regularUser = {\n    name: \"Example User\",\n    email: \"user@redash.io\",\n    password: \"password\",\n  };\n\n  const userTabs = [\"Users\", \"Groups\", \"Query Snippets\", \"Account\"];\n  const adminTabs = [\"Data Sources\", \"Alert Destinations\", \"General\"];\n\n  const expectSettingsTabsToBe = expectedTabs =>\n    cy.getByTestId(\"SettingsScreenItem\").then($list => {\n      const listedPages = $list.toArray().map(el => el.text);\n      expect(listedPages).to.have.members(expectedTabs);\n    });\n\n  before(() => {\n    cy.login().then(() => cy.createUser(regularUser));\n  });\n\n  describe(\"For admin user\", () => {\n    beforeEach(() => {\n      cy.logout();\n      cy.login();\n      cy.visit(\"/\");\n    });\n\n    it(\"settings link should lead to Data Sources settings\", () => {\n      cy.getByTestId(\"SettingsLink\")\n        .should(\"exist\")\n        .should(\"have.attr\", \"href\", \"data_sources\");\n    });\n\n    it(\"all tabs should be available\", () => {\n      cy.getByTestId(\"SettingsLink\").click();\n      expectSettingsTabsToBe([...userTabs, ...adminTabs]);\n    });\n  });\n\n  describe(\"For regular user\", () => {\n    beforeEach(() => {\n      cy.logout();\n      cy.login(regularUser.email, regularUser.password);\n      cy.visit(\"/\");\n    });\n\n    it(\"settings link should lead to Users settings\", () => {\n      cy.getByTestId(\"SettingsLink\")\n        .should(\"exist\")\n        .should(\"have.attr\", \"href\", \"users\");\n    });\n\n    it(\"limited set of settings tabs should be available\", () => {\n      cy.getByTestId(\"SettingsLink\").click();\n      expectSettingsTabsToBe(userTabs);\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/user/create_user_spec.js",
    "content": "describe(\"Create User\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.visit(\"/users/new\");\n  });\n\n  const fillUserFormAndSubmit = (name, email) => {\n    cy.getByTestId(\"CreateUserDialog\").within(() => {\n      cy.getByTestId(\"Name\").type(name);\n      cy.getByTestId(\"Email\").type(email);\n    });\n    cy.getByTestId(\"SaveUserButton\").click();\n  };\n\n  it(\"creates a new user\", () => {\n    // delete existing \"new-user@redash.io\"\n    cy.request(\"GET\", \"api/users?q=new-user\")\n      .then(({ body }) => body.results.filter(user => user.email === \"new-user@redash.io\"))\n      .each(user => cy.request(\"DELETE\", `api/users/${user.id}`));\n\n    fillUserFormAndSubmit(\"New User\", \"admin@redash.io\");\n\n    cy.getByTestId(\"CreateUserErrorAlert\").should(\"contain\", \"Email already taken\");\n\n    fillUserFormAndSubmit(\"{selectall}New User\", \"{selectall}new-user@redash.io\");\n    cy.contains(\"Saved.\");\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/user/edit_profile_spec.js",
    "content": "function fillProfileDataAndSave(name, email) {\n  cy.getByTestId(\"Name\").type(`{selectall}${name}`);\n  cy.getByTestId(\"Email\").type(`{selectall}${email}{enter}`);\n  cy.contains(\"Saved.\");\n}\n\nfunction fillChangePasswordAndSave(currentPassword, newPassword, repeatPassword) {\n  cy.getByTestId(\"CurrentPassword\").type(currentPassword);\n  cy.getByTestId(\"NewPassword\").type(newPassword);\n  cy.getByTestId(\"RepeatPassword\").type(`${repeatPassword}{enter}`);\n}\n\ndescribe(\"Edit Profile\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.visit(\"/users/me\");\n  });\n\n  it(\"updates the user after Save\", () => {\n    fillProfileDataAndSave(\"Jian Yang\", \"jian.yang@redash.io\");\n    cy.logout();\n    cy.login(\"jian.yang@redash.io\")\n      .its(\"status\")\n      .should(\"eq\", 200);\n    cy.visit(\"/users/me\");\n    cy.contains(\"Jian Yang\");\n    fillProfileDataAndSave(\"Example Admin\", \"admin@redash.io\");\n  });\n\n  it(\"regenerates API Key\", () => {\n    cy.getByTestId(\"ApiKey\").then($apiKey => {\n      const previousApiKey = $apiKey.val();\n\n      cy.getByTestId(\"RegenerateApiKey\").click();\n      cy.get(\".ant-btn-primary\")\n        .contains(\"Regenerate\")\n        .click({ force: true });\n\n      cy.getByTestId(\"ApiKey\").should(\"not.eq\", previousApiKey);\n    });\n  });\n\n  it(\"renders the page and takes a screenshot\", () => {\n    cy.getByTestId(\"Groups\").should(\"contain\", \"admin\");\n    cy.percySnapshot(\"User Profile\");\n  });\n\n  context(\"changing password\", () => {\n    beforeEach(() => {\n      cy.getByTestId(\"ChangePassword\").click();\n    });\n\n    it(\"updates user password when password is correct\", () => {\n      fillChangePasswordAndSave(\"password\", \"newpassword\", \"newpassword\");\n      cy.contains(\"Saved.\");\n      cy.logout();\n      cy.login(undefined, \"newpassword\")\n        .its(\"status\")\n        .should(\"eq\", 200);\n      cy.visit(\"/users/me\");\n      cy.getByTestId(\"ChangePassword\").click();\n      fillChangePasswordAndSave(\"newpassword\", \"password\", \"password\");\n      cy.contains(\"Saved.\");\n    });\n\n    it(\"shows an error when current password is wrong\", () => {\n      fillChangePasswordAndSave(\"wrongpassword\", \"newpassword\", \"newpassword\");\n      cy.contains(\"Incorrect current password.\");\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/user/login_spec.js",
    "content": "describe(\"Login\", () => {\n  beforeEach(() => {\n    cy.visit(\"/login\");\n  });\n\n  it(\"greets the user and take a screenshot\", () => {\n    cy.contains(\"h3\", \"Login to Redash\");\n\n    cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Login\");\n  });\n\n  it(\"shows message on failed login\", () => {\n    cy.getByTestId(\"Email\").type(\"admin@redash.io\");\n    cy.getByTestId(\"Password\").type(\"wrongpassword{enter}\");\n\n    cy.getByTestId(\"ErrorMessage\").should(\"contain\", \"Wrong email or password.\");\n  });\n\n  it(\"navigates to homepage with successful login\", () => {\n    cy.getByTestId(\"Email\").type(\"admin@redash.io\");\n    cy.getByTestId(\"Password\").type(\"password{enter}\");\n\n    cy.title().should(\"eq\", \"Redash\");\n    cy.get(`img.profile__image_thumb[alt=\"Example Admin\"]`).should(\"exist\");\n\n    cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Homepage\");\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/user/logout_spec.js",
    "content": "describe(\"Logout\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.visit(\"/\");\n  });\n\n  it(\"shows login page after logout\", () => {\n    cy.getByTestId(\"ProfileDropdown\").click();\n    // Wait until submenu appears and become interactive\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"LogOutButton\")\n      .should(\"be.visible\")\n      .click();\n\n    cy.title().should(\"eq\", \"Login to Redash\");\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/user/user_list_spec.js",
    "content": "describe(\"User List\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.visit(\"/users\");\n  });\n\n  it(\"renders the page and takes a screenshot\", () => {\n    cy.getByTestId(\"UserList\")\n      .should(\"exist\")\n      .and(\"contain\", \"Example Admin\");\n\n    cy.percySnapshot(\"Users\");\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/box_plot_spec.js",
    "content": "/* global cy, Cypress */\n\nconst SQL = `\n  SELECT 12 AS mn, 4967 AS mx UNION ALL\n  SELECT 10 AS mn, 19430 AS mx UNION ALL\n  SELECT 3132 AS mn, 3275 AS mx UNION ALL\n  SELECT 2 AS mn, 19429 AS mx UNION ALL\n  SELECT 7 AS mn, 19433 AS mx UNION ALL\n  SELECT 4824 AS mn, 4824 AS mx UNION ALL\n  SELECT 11353 AS mn, 16565 AS mx UNION ALL\n  SELECT 551 AS mn, 19415 AS mx UNION ALL\n  SELECT 307 AS mn, 17918 AS mx UNION ALL\n  SELECT 25 AS mn, 19436 AS mx UNION ALL\n  SELECT 98 AS mn, 19230 AS mx UNION ALL\n  SELECT 1652 AS mn, 1667 AS mx UNION ALL\n  SELECT 4486 AS mn, 4486 AS mx UNION ALL\n  SELECT 5113 AS mn, 5120 AS mx UNION ALL\n  SELECT 1642 AS mn, 1678 AS mx UNION ALL\n  SELECT 1632 AS mn, 16183 AS mx UNION ALL\n  SELECT 8 AS mn, 19434 AS mx UNION ALL\n  SELECT 13149 AS mn, 16945 AS mx UNION ALL\n  SELECT 340 AS mn, 340 AS mx UNION ALL\n  SELECT 15495 AS mn, 16559 AS mx UNION ALL\n  SELECT 24 AS mn, 19266 AS mx UNION ALL\n  SELECT 532 AS mn, 19283 AS mx UNION ALL\n  SELECT 4958 AS mn, 4958 AS mx UNION ALL\n  SELECT 10078 AS mn, 10079 AS mx UNION ALL\n  SELECT 102 AS mn, 17895 AS mx UNION ALL\n  SELECT 5366 AS mn, 18463 AS mx UNION ALL\n  SELECT 11363 AS mn, 16552 AS mx UNION ALL\n  SELECT 1 AS mn, 5211 AS mx UNION ALL\n  SELECT 6 AS mn, 19431 AS mx UNION ALL\n  SELECT 11378 AS mn, 16946 AS mx UNION ALL\n  SELECT 4676 AS mn, 4944 AS mx UNION ALL\n  SELECT 5228 AS mn, 18466 AS mx\n`;\n\ndescribe(\"Box Plot\", () => {\n  const viewportWidth = Cypress.config(\"viewportWidth\");\n\n  beforeEach(() => {\n    cy.login();\n    cy.createQuery({ query: SQL })\n      .then(({ id }) => cy.createVisualization(id, \"BOXPLOT\", \"Boxplot (Deprecated)\", {}))\n      .then(({ id: visualizationId, query_id: queryId }) => {\n        cy.visit(`queries/${queryId}/source#${visualizationId}`);\n        cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n        cy.getByTestId(\"ExecuteButton\").click();\n      });\n  });\n\n  it(\"creates visualization\", () => {\n    cy.clickThrough(`\n      EditVisualization\n    `);\n\n    cy.fillInputs({\n      \"BoxPlot.XAxisLabel\": \"X Axis\",\n      \"BoxPlot.YAxisLabel\": \"Y Axis\",\n    });\n\n    // Wait for proper initialization of visualization\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n\n    cy.getByTestId(\"VisualizationPreview\").find(\"svg\").should(\"exist\");\n\n    cy.percySnapshot(\"Visualizations - Box Plot\", { widths: [viewportWidth] });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/chart_spec.js",
    "content": "/* global cy */\n\nimport { getWidgetTestId } from \"../../support/dashboard\";\nimport {\n  assertAxesAndAddLabels,\n  assertPlotPreview,\n  assertTabbedEditor,\n  createChartThroughUI,\n  createDashboardWithCharts,\n} from \"../../support/visualizations/chart\";\n\nconst SQL = `\n  SELECT 'a' AS stage, 11 AS value1, 22 AS value2 UNION ALL\n  SELECT 'a' AS stage, 12 AS value1, 41 AS value2 UNION ALL\n  SELECT 'a' AS stage, 45 AS value1, 93 AS value2 UNION ALL\n  SELECT 'a' AS stage, 54 AS value1, 79 AS value2 UNION ALL\n  SELECT 'b' AS stage, 33 AS value1, 65 AS value2 UNION ALL\n  SELECT 'b' AS stage, 73 AS value1, 50 AS value2 UNION ALL\n  SELECT 'b' AS stage, 90 AS value1, 40 AS value2 UNION ALL\n  SELECT 'c' AS stage, 19 AS value1, 33 AS value2 UNION ALL\n  SELECT 'c' AS stage, 92 AS value1, 14 AS value2 UNION ALL\n  SELECT 'c' AS stage, 63 AS value1, 65 AS value2 UNION ALL\n  SELECT 'c' AS stage, 44 AS value1, 27 AS value2\\\n`;\n\ndescribe(\"Chart\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.createQuery({ name: \"Chart Visualization\", query: SQL }).its(\"id\").as(\"queryId\");\n  });\n\n  it(\"creates Bar charts\", function () {\n    cy.visit(`queries/${this.queryId}/source`);\n    cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"ExecuteButton\").click();\n\n    const getBarChartAssertionFunction =\n      (specificBarChartAssertionFn = () => {}) =>\n      () => {\n        // checks for TabbedEditor standard tabs\n        assertTabbedEditor();\n\n        // standard chart should be bar\n        cy.getByTestId(\"Chart.GlobalSeriesType\").contains(\".ant-select-selection-item\", \"Bar\");\n\n        // checks the plot canvas exists and is empty\n        assertPlotPreview(\"not.exist\");\n\n        // creates a chart and checks it is plotted\n        cy.getByTestId(\"Chart.ColumnMapping.x\").selectAntdOption(\"Chart.ColumnMapping.x.stage\");\n        cy.getByTestId(\"Chart.ColumnMapping.y\").selectAntdOption(\"Chart.ColumnMapping.y.value1\");\n        cy.getByTestId(\"Chart.ColumnMapping.y\").selectAntdOption(\"Chart.ColumnMapping.y.value2\");\n        assertPlotPreview(\"exist\");\n\n        specificBarChartAssertionFn();\n      };\n\n    const chartTests = [\n      {\n        name: \"Basic Bar Chart\",\n        alias: \"basicBarChart\",\n        assertionFn: () => {\n          assertAxesAndAddLabels(\"Stage\", \"Value\");\n        },\n      },\n      {\n        name: \"Horizontal Bar Chart\",\n        alias: \"horizontalBarChart\",\n        assertionFn: () => {\n          cy.getByTestId(\"Chart.SwappedAxes\").check();\n          cy.getByTestId(\"VisualizationEditor.Tabs.XAxis\").should(\"have.text\", \"Y Axis\");\n          cy.getByTestId(\"VisualizationEditor.Tabs.YAxis\").should(\"have.text\", \"X Axis\");\n        },\n      },\n      {\n        name: \"Stacked Bar Chart\",\n        alias: \"stackedBarChart\",\n        assertionFn: () => {\n          cy.getByTestId(\"Chart.Stacking\").selectAntdOption(\"Chart.Stacking.Stack\");\n        },\n      },\n      {\n        name: \"Normalized Bar Chart\",\n        alias: \"normalizedBarChart\",\n        assertionFn: () => {\n          cy.getByTestId(\"Chart.NormalizeValues\").check();\n        },\n      },\n    ];\n\n    chartTests.forEach(({ name, alias, assertionFn }) => {\n      createChartThroughUI(name, getBarChartAssertionFunction(assertionFn)).as(alias);\n    });\n\n    const chartGetters = chartTests.map(({ alias }) => alias);\n\n    const withDashboardWidgetsAssertionFn = (widgetGetters, dashboardUrl) => {\n      cy.visit(dashboardUrl);\n      widgetGetters.forEach((widgetGetter) => {\n        cy.get(`@${widgetGetter}`).then((widget) => {\n          cy.getByTestId(getWidgetTestId(widget)).within(() => {\n            cy.get(\"g.points\").should(\"exist\");\n          });\n        });\n      });\n    };\n\n    createDashboardWithCharts(\"Bar chart visualizations\", chartGetters, withDashboardWidgetsAssertionFn);\n    cy.percySnapshot(\"Visualizations - Charts - Bar\");\n  });\n  it(\"colors Bar charts\", function () {\n    cy.visit(`queries/${this.queryId}/source`);\n    cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"ExecuteButton\").click();\n    cy.getByTestId(\"NewVisualization\").click();\n    cy.getByTestId(\"Chart.ColumnMapping.x\").selectAntdOption(\"Chart.ColumnMapping.x.stage\");\n    cy.getByTestId(\"Chart.ColumnMapping.y\").selectAntdOption(\"Chart.ColumnMapping.y.value1\");\n    cy.getByTestId(\"VisualizationEditor.Tabs.Colors\").click();\n    cy.getByTestId(\"ColorScheme\").click();\n    cy.getByTestId(\"ColorOptionViridis\").click();\n    cy.getByTestId(\"ColorScheme\").click();\n    cy.getByTestId(\"ColorOptionTableau 10\").click();\n    cy.getByTestId(\"ColorScheme\").click();\n    cy.getByTestId(\"ColorOptionD3 Category 10\").click();\n  });\n  it(\"colors Pie charts\", function () {\n    cy.visit(`queries/${this.queryId}/source`);\n    cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"ExecuteButton\").click();\n    cy.getByTestId(\"NewVisualization\").click();\n    cy.getByTestId(\"Chart.GlobalSeriesType\").click();\n    cy.getByTestId(\"Chart.ChartType.pie\").click();\n    cy.getByTestId(\"Chart.ColumnMapping.x\").selectAntdOption(\"Chart.ColumnMapping.x.stage\");\n    cy.getByTestId(\"Chart.ColumnMapping.y\").selectAntdOption(\"Chart.ColumnMapping.y.value1\");\n    cy.getByTestId(\"VisualizationEditor.Tabs.Colors\").click();\n    cy.getByTestId(\"ColorScheme\").click();\n    cy.getByTestId(\"ColorOptionViridis\").click();\n    cy.getByTestId(\"ColorScheme\").click();\n    cy.getByTestId(\"ColorOptionTableau 10\").click();\n    cy.getByTestId(\"ColorScheme\").click();\n    cy.getByTestId(\"ColorOptionD3 Category 10\").click();\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/choropleth_spec.js",
    "content": "/* global cy */\n\nconst SQL = `\n  SELECT 'AR' AS \"code\", 'Argentina' AS \"name\", 37.62 AS \"value\" UNION ALL\n  SELECT 'AU' AS \"code\", 'Australia' AS \"name\", 37.62 AS \"value\" UNION ALL\n  SELECT 'AT' AS \"code\", 'Austria' AS \"name\", 42.62 AS \"value\" UNION ALL\n  SELECT 'BE' AS \"code\", 'Belgium' AS \"name\", 37.62 AS \"value\" UNION ALL\n  SELECT 'BR' AS \"code\", 'Brazil' AS \"name\", 190.10 AS \"value\" UNION ALL\n  SELECT 'CA' AS \"code\", 'Canada' AS \"name\", 303.96 AS \"value\" UNION ALL\n  SELECT 'CL' AS \"code\", 'Chile' AS \"name\", 46.62 AS \"value\" UNION ALL\n  SELECT 'CZ' AS \"code\", 'Czech Republic' AS \"name\", 90.24 AS \"value\" UNION ALL\n  SELECT 'DK' AS \"code\", 'Denmark' AS \"name\", 37.62 AS \"value\" UNION ALL\n  SELECT 'FI' AS \"code\", 'Finland' AS \"name\", 41.62 AS \"value\" UNION ALL\n  SELECT 'FR' AS \"code\", 'France' AS \"name\", 195.10 AS \"value\" UNION ALL\n  SELECT 'DE' AS \"code\", 'Germany' AS \"name\", 156.48 AS \"value\" UNION ALL\n  SELECT 'HU' AS \"code\", 'Hungary' AS \"name\", 45.62 AS \"value\" UNION ALL\n  SELECT 'IN' AS \"code\", 'India' AS \"name\", 75.26 AS \"value\" UNION ALL\n  SELECT 'IE' AS \"code\", 'Ireland' AS \"name\", 45.62 AS \"value\" UNION ALL\n  SELECT 'IT' AS \"code\", 'Italy' AS \"name\", 37.62 AS \"value\" UNION ALL\n  SELECT 'NL' AS \"code\", 'Netherlands' AS \"name\", 40.62 AS \"value\" UNION ALL\n  SELECT 'NO' AS \"code\", 'Norway' AS \"name\", 39.62 AS \"value\" UNION ALL\n  SELECT 'PL' AS \"code\", 'Poland' AS \"name\", 37.62 AS \"value\" UNION ALL\n  SELECT 'PT' AS \"code\", 'Portugal' AS \"name\", 77.24 AS \"value\" UNION ALL\n  SELECT 'ES' AS \"code\", 'Spain' AS \"name\", 37.62 AS \"value\" UNION ALL\n  SELECT 'SE' AS \"code\", 'Sweden' AS \"name\", 38.62 AS \"value\" UNION ALL\n  SELECT 'US' AS \"code\", 'USA' AS \"name\", 523.06 AS \"value\" UNION ALL\n  SELECT 'GB' AS \"code\", 'United Kingdom' AS \"name\", 112.86 AS \"value\"\n`;\n\ndescribe(\"Choropleth\", () => {\n  const viewportWidth = Cypress.config(\"viewportWidth\");\n\n  beforeEach(() => {\n    cy.login();\n    cy.createQuery({ query: SQL }).then(({ id }) => {\n      cy.visit(`queries/${id}/source`);\n      cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n      cy.getByTestId(\"ExecuteButton\").click();\n    });\n    cy.getByTestId(\"NewVisualization\").click();\n    cy.getByTestId(\"VisualizationType\").selectAntdOption(\"VisualizationType.CHOROPLETH\");\n  });\n\n  it(\"creates visualization\", () => {\n    cy.clickThrough(`\n      VisualizationEditor.Tabs.General\n      Choropleth.Editor.MapType\n      Choropleth.Editor.MapType.countries\n      Choropleth.Editor.KeyColumn\n      Choropleth.Editor.KeyColumn.name\n      Choropleth.Editor.TargetField\n      Choropleth.Editor.TargetField.name\n      Choropleth.Editor.ValueColumn\n      Choropleth.Editor.ValueColumn.value\n    `);\n\n    cy.clickThrough(\"VisualizationEditor.Tabs.Colors\");\n    cy.clickThrough(\"Choropleth.Editor.Colors.Min\");\n    cy.fillInputs({ \"ColorPicker.CustomColor\": \"yellow{enter}\" });\n    cy.getByTestId(\"ColorPicker.CustomColor\").should(\"not.be.visible\");\n    cy.clickThrough(\"Choropleth.Editor.Colors.Max\");\n    cy.fillInputs({ \"ColorPicker.CustomColor\": \"red{enter}\" });\n    cy.getByTestId(\"ColorPicker.CustomColor\").should(\"not.be.visible\");\n    cy.clickThrough(\"Choropleth.Editor.Colors.Borders\");\n    cy.fillInputs({ \"ColorPicker.CustomColor\": \"black{enter}\" });\n    cy.getByTestId(\"ColorPicker.CustomColor\").should(\"not.be.visible\");\n\n    cy.clickThrough(`\n      VisualizationEditor.Tabs.Format\n      Choropleth.Editor.LegendPosition\n      Choropleth.Editor.LegendPosition.TopRight\n    `);\n\n    cy.getByTestId(\"Choropleth.Editor.LegendTextAlignment\")\n      .find('[data-test=\"TextAlignmentSelect.Left\"]')\n      .check({ force: true });\n\n    // Wait for proper initialization of visualization\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"VisualizationPreview\").find(\".map-visualization-container.leaflet-container\").should(\"exist\");\n\n    cy.percySnapshot(\"Visualizations - Choropleth\", { widths: [viewportWidth] });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/cohort_spec.js",
    "content": "/* global cy, Cypress */\n\nconst SQL = `\n  SELECT '2019-01-01' AS \"date\", 21 AS \"bucket\", 5 AS \"value\", 1 AS \"stage\" UNION ALL\n  SELECT '2019-01-01' AS \"date\", 21 AS \"bucket\", 8 AS \"value\", 2 AS \"stage\" UNION ALL\n  SELECT '2019-01-01' AS \"date\", 21 AS \"bucket\", 2 AS \"value\", 3 AS \"stage\" UNION ALL\n  SELECT '2019-01-01' AS \"date\", 21 AS \"bucket\", 6 AS \"value\", 4 AS \"stage\" UNION ALL\n\n  SELECT '2019-02-01' AS \"date\", 10 AS \"bucket\", 7 AS \"value\", 1 AS \"stage\" UNION ALL\n  SELECT '2019-02-01' AS \"date\", 10 AS \"bucket\", 3 AS \"value\", 3 AS \"stage\" UNION ALL\n\n  SELECT '2019-03-01' AS \"date\", 19 AS \"bucket\", 4 AS \"value\", 1 AS \"stage\" UNION ALL\n  SELECT '2019-03-01' AS \"date\", 19 AS \"bucket\", 7 AS \"value\", 2 AS \"stage\" UNION ALL\n  SELECT '2019-03-01' AS \"date\", 19 AS \"bucket\", 8 AS \"value\", 3 AS \"stage\" UNION ALL\n\n  SELECT '2019-05-01' AS \"date\", 15 AS \"bucket\", 13 AS \"value\", 1 AS \"stage\" UNION ALL\n  SELECT '2019-05-01' AS \"date\", 15 AS \"bucket\", 2 AS \"value\", 4 AS \"stage\"\n`;\n\ndescribe(\"Cohort\", () => {\n  const viewportWidth = Cypress.config(\"viewportWidth\");\n\n  beforeEach(() => {\n    cy.login();\n    cy.createQuery({ query: SQL }).then(({ id }) => {\n      cy.visit(`queries/${id}/source`);\n      cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n      cy.getByTestId(\"ExecuteButton\").click();\n    });\n    cy.getByTestId(\"NewVisualization\").click();\n    cy.getByTestId(\"VisualizationType\").selectAntdOption(\"VisualizationType.COHORT\");\n  });\n\n  it(\"creates visualization\", () => {\n    cy.clickThrough(`\n      VisualizationEditor.Tabs.Options\n      Cohort.TimeInterval\n      Cohort.TimeInterval.monthly\n      Cohort.Mode\n      Cohort.Mode.simple\n\n      VisualizationEditor.Tabs.Columns\n      Cohort.DateColumn\n      Cohort.DateColumn.date\n      Cohort.StageColumn\n      Cohort.StageColumn.stage\n      Cohort.TotalColumn\n      Cohort.TotalColumn.bucket\n      Cohort.ValueColumn\n      Cohort.ValueColumn.value\n    `);\n\n    // Wait for proper initialization of visualization\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"VisualizationPreview\").find(\"table\").should(\"exist\");\n    cy.percySnapshot(\"Visualizations - Cohort (simple)\", { widths: [viewportWidth] });\n\n    cy.clickThrough(`\n      VisualizationEditor.Tabs.Options\n      Cohort.Mode\n      Cohort.Mode.diagonal\n    `);\n\n    // Wait for proper initialization of visualization\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"VisualizationPreview\").find(\"table\").should(\"exist\");\n    cy.percySnapshot(\"Visualizations - Cohort (diagonal)\", { widths: [viewportWidth] });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/counter_spec.js",
    "content": "/* global cy, Cypress */\n\nconst SQL = `\n  SELECT 27182.8182846 AS a, 20000 AS b, 'lorem' AS c UNION ALL\n  SELECT 31415.9265359 AS a, 40000 AS b, 'ipsum' AS c\n`;\n\ndescribe(\"Counter\", () => {\n  const viewportWidth = Cypress.config(\"viewportWidth\");\n\n  beforeEach(() => {\n    cy.login();\n    cy.createQuery({ query: SQL }).then(({ id }) => {\n      cy.visit(`queries/${id}/source`);\n      cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n      cy.getByTestId(\"ExecuteButton\").click();\n    });\n    cy.getByTestId(\"NewVisualization\").click();\n    cy.getByTestId(\"VisualizationType\").selectAntdOption(\"VisualizationType.COUNTER\");\n  });\n\n  it(\"creates simple Counter\", () => {\n    cy.clickThrough(`\n      Counter.General.ValueColumn\n      Counter.General.ValueColumn.a\n    `);\n\n    cy.getByTestId(\"VisualizationPreview\").find(\".counter-visualization-container\").should(\"exist\");\n\n    // wait a bit before taking snapshot\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Visualizations - Counter (with defaults)\", { widths: [viewportWidth] });\n  });\n\n  it(\"creates Counter with custom label\", () => {\n    cy.clickThrough(`\n      Counter.General.ValueColumn\n      Counter.General.ValueColumn.a\n    `);\n\n    cy.fillInputs({\n      \"Counter.General.Label\": \"Custom Label\",\n    });\n\n    cy.getByTestId(\"VisualizationPreview\").find(\".counter-visualization-container\").should(\"exist\");\n\n    // wait a bit before taking snapshot\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Visualizations - Counter (custom label)\", { widths: [viewportWidth] });\n  });\n\n  it(\"creates Counter with non-numeric value\", () => {\n    cy.clickThrough(`\n      Counter.General.ValueColumn\n      Counter.General.ValueColumn.c\n\n      Counter.General.TargetValueColumn\n      Counter.General.TargetValueColumn.c\n    `);\n\n    cy.fillInputs({\n      \"Counter.General.TargetValueRowNumber\": \"2\",\n    });\n\n    cy.getByTestId(\"VisualizationPreview\").find(\".counter-visualization-container\").should(\"exist\");\n\n    // wait a bit before taking snapshot\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Visualizations - Counter (non-numeric value)\", { widths: [viewportWidth] });\n  });\n\n  it(\"creates Counter with target value (trend positive)\", () => {\n    cy.clickThrough(`\n      Counter.General.ValueColumn\n      Counter.General.ValueColumn.a\n\n      Counter.General.TargetValueColumn\n      Counter.General.TargetValueColumn.b\n    `);\n\n    cy.getByTestId(\"VisualizationPreview\").find(\".counter-visualization-container\").should(\"exist\");\n\n    // wait a bit before taking snapshot\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Visualizations - Counter (target value + trend positive)\", { widths: [viewportWidth] });\n  });\n\n  it(\"creates Counter with custom row number (trend negative)\", () => {\n    cy.clickThrough(`\n      Counter.General.ValueColumn\n      Counter.General.ValueColumn.a\n\n      Counter.General.TargetValueColumn\n      Counter.General.TargetValueColumn.b\n    `);\n\n    cy.fillInputs({\n      \"Counter.General.ValueRowNumber\": \"2\",\n      \"Counter.General.TargetValueRowNumber\": \"2\",\n    });\n\n    cy.getByTestId(\"VisualizationPreview\").find(\".counter-visualization-container\").should(\"exist\");\n\n    // wait a bit before taking snapshot\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Visualizations - Counter (row number + trend negative)\", { widths: [viewportWidth] });\n  });\n\n  it(\"creates Counter with count rows\", () => {\n    cy.clickThrough(`\n      Counter.General.ValueColumn\n      Counter.General.ValueColumn.a\n\n      Counter.General.CountRows\n    `);\n\n    cy.getByTestId(\"VisualizationPreview\").find(\".counter-visualization-container\").should(\"exist\");\n\n    // wait a bit before taking snapshot\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Visualizations - Counter (count rows)\", { widths: [viewportWidth] });\n  });\n\n  it(\"creates Counter with formatting\", () => {\n    cy.clickThrough(`\n      Counter.General.ValueColumn\n      Counter.General.ValueColumn.a\n\n      Counter.General.TargetValueColumn\n      Counter.General.TargetValueColumn.b\n\n      VisualizationEditor.Tabs.Format\n    `);\n\n    cy.fillInputs({\n      \"Counter.Formatting.DecimalPlace\": \"4\",\n      \"Counter.Formatting.DecimalCharacter\": \",\",\n      \"Counter.Formatting.ThousandsSeparator\": \"`\",\n      \"Counter.Formatting.StringPrefix\": \"$\",\n      \"Counter.Formatting.StringSuffix\": \"%\",\n    });\n\n    cy.getByTestId(\"VisualizationPreview\").find(\".counter-visualization-container\").should(\"exist\");\n\n    // wait a bit before taking snapshot\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Visualizations - Counter (custom formatting)\", { widths: [viewportWidth] });\n  });\n\n  it(\"creates Counter with target value formatting\", () => {\n    cy.clickThrough(`\n      Counter.General.ValueColumn\n      Counter.General.ValueColumn.a\n\n      Counter.General.TargetValueColumn\n      Counter.General.TargetValueColumn.b\n\n      VisualizationEditor.Tabs.Format\n      Counter.Formatting.FormatTargetValue\n    `);\n\n    cy.fillInputs({\n      \"Counter.Formatting.DecimalPlace\": \"4\",\n      \"Counter.Formatting.DecimalCharacter\": \",\",\n      \"Counter.Formatting.ThousandsSeparator\": \"`\",\n      \"Counter.Formatting.StringPrefix\": \"$\",\n      \"Counter.Formatting.StringSuffix\": \"%\",\n    });\n\n    cy.getByTestId(\"VisualizationPreview\").find(\".counter-visualization-container\").should(\"exist\");\n\n    // wait a bit before taking snapshot\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Visualizations - Counter (format target value)\", { widths: [viewportWidth] });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/edit_visualization_dialog_spec.js",
    "content": "/* global cy */\n\ndescribe(\"Edit visualization dialog\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.createQuery().then(({ id }) => {\n      cy.visit(`queries/${id}/source`);\n      cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n      cy.getByTestId(\"ExecuteButton\").click();\n    });\n  });\n\n  it(\"opens New Visualization dialog\", () => {\n    cy.getByTestId(\"NewVisualization\").should(\"exist\").click();\n    cy.getByTestId(\"EditVisualizationDialog\").should(\"exist\");\n    // Default visualization should be selected\n    cy.getByTestId(\"VisualizationType\").should(\"exist\").should(\"contain\", \"Chart\");\n    cy.getByTestId(\"VisualizationName\").should(\"exist\").should(\"have.value\", \"Chart\");\n  });\n\n  it(\"opens Edit Visualization dialog\", () => {\n    cy.getByTestId(\"EditVisualization\").click();\n    cy.getByTestId(\"EditVisualizationDialog\").should(\"exist\");\n    // Default `Table` visualization should be selected\n    cy.getByTestId(\"VisualizationType\").should(\"exist\").should(\"contain\", \"Table\");\n    cy.getByTestId(\"VisualizationName\").should(\"exist\").should(\"have.value\", \"Table\");\n  });\n\n  it(\"creates visualization with custom name\", () => {\n    const visualizationName = \"Custom name\";\n\n    cy.clickThrough(`\n      NewVisualization\n      VisualizationType\n      VisualizationType.TABLE\n    `);\n\n    cy.getByTestId(\"VisualizationName\").clear().type(visualizationName);\n\n    cy.getByTestId(\"EditVisualizationDialog\").contains(\"button\", \"Save\").click();\n    cy.getByTestId(\"QueryPageVisualizationTabs\").contains(\"span\", visualizationName).should(\"exist\");\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/funnel_spec.js",
    "content": "/* global cy, Cypress */\n\nconst SQL = `\n  SELECT 'a.01' AS a, 1.758831600227 AS b UNION ALL\n  SELECT 'a.02' AS a, 613.4456936572 AS b UNION ALL\n  SELECT 'a.03' AS a, 9.045647090023 AS b UNION ALL\n  SELECT 'a.04' AS a, 29.37836413439 AS b UNION ALL\n  SELECT 'a.05' AS a, 642.9434910444 AS b UNION ALL\n  SELECT 'a.06' AS a, 176.7634164480 AS b UNION ALL\n  SELECT 'a.07' AS a, 279.4880059198 AS b UNION ALL\n  SELECT 'a.08' AS a, 78.48128609207 AS b UNION ALL\n  SELECT 'a.09' AS a, 14.10443892662 AS b UNION ALL\n  SELECT 'a.10' AS a, 59.25097112438 AS b UNION ALL\n  SELECT 'a.11' AS a, 61.58610868125 AS b UNION ALL\n  SELECT 'a.12' AS a, 277.8473055268 AS b UNION ALL\n  SELECT 'a.13' AS a, 621.1535090415 AS b UNION ALL\n  SELECT 'a.14' AS a, 261.1409234646 AS b UNION ALL\n  SELECT 'a.15' AS a, 72.94883358030 AS b\n`;\n\ndescribe(\"Funnel\", () => {\n  const viewportWidth = Cypress.config(\"viewportWidth\");\n\n  beforeEach(() => {\n    cy.login();\n    cy.createQuery({ query: SQL }).then(({ id }) => {\n      cy.visit(`queries/${id}/source`);\n      cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n      cy.getByTestId(\"ExecuteButton\").click();\n    });\n  });\n\n  it(\"creates visualization\", () => {\n    cy.clickThrough(`\n      NewVisualization\n    `);\n    cy.getByTestId(\"VisualizationType\").selectAntdOption(\"VisualizationType.FUNNEL\");\n    cy.clickThrough(`\n      VisualizationEditor.Tabs.General\n\n      Funnel.StepColumn\n      Funnel.StepColumn.a\n      Funnel.ValueColumn\n      Funnel.ValueColumn.b\n\n      Funnel.CustomSort\n      Funnel.SortColumn\n      Funnel.SortColumn.b\n      Funnel.SortDirection\n      Funnel.SortDirection.Ascending\n    `);\n\n    cy.fillInputs(\n      {\n        \"Funnel.StepColumnTitle\": \"Column A\",\n        \"Funnel.ValueColumnTitle\": \"Column B\",\n      },\n      { wait: 200 }\n    ); // inputs are debounced\n\n    // Wait for proper initialization of visualization\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"VisualizationPreview\").find(\"table\").should(\"exist\");\n    cy.percySnapshot(\"Visualizations - Funnel (basic)\", { widths: [viewportWidth] });\n\n    cy.clickThrough(`\n      VisualizationEditor.Tabs.Appearance\n    `);\n\n    cy.fillInputs(\n      {\n        \"Funnel.NumberFormat\": \"0[.]00\",\n        \"Funnel.PercentFormat\": \"0[.]0000%\",\n        \"Funnel.ItemsLimit\": \"10\",\n        \"Funnel.PercentRangeMin\": \"10\",\n        \"Funnel.PercentRangeMax\": \"90\",\n      },\n      { wait: 200 }\n    ); // inputs are debounced\n\n    // Wait for proper initialization of visualization\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"VisualizationPreview\").find(\"table\").should(\"exist\");\n    cy.percySnapshot(\"Visualizations - Funnel (extra options)\", { widths: [viewportWidth] });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/map_spec.js",
    "content": "/* global cy */\n\nconst SQL = `\n  SELECT 'Israel' AS country, 32.0808800 AS lat, 34.7805700 AS lng UNION ALL\n  SELECT 'Israel' AS country, 31.7690400 AS lat, 35.2163300 AS lng UNION ALL\n  SELECT 'Israel' AS country, 32.8184100 AS lat, 34.9885000 AS lng UNION ALL\n\n  SELECT 'Ukraine' AS country, 50.4546600 AS lat, 30.5238000 AS lng UNION ALL\n  SELECT 'Ukraine' AS country, 49.8382600 AS lat, 24.0232400 AS lng UNION ALL\n  SELECT 'Ukraine' AS country, 49.9808100 AS lat, 36.2527200 AS lng UNION ALL\n\n  SELECT 'Hungary' AS country, 47.4980100 AS lat, 19.0399100 AS lng\n`;\n\ndescribe(\"Map (Markers)\", () => {\n  const viewportWidth = Cypress.config(\"viewportWidth\");\n\n  beforeEach(() => {\n    cy.login();\n\n    const mapTileUrl = \"/static/images/fixtures/map-tile.png\";\n\n    cy.createQuery({ query: SQL })\n      .then(({ id }) => cy.createVisualization(id, \"MAP\", \"Map (Markers)\", { mapTileUrl }))\n      .then(({ id: visualizationId, query_id: queryId }) => {\n        cy.visit(`queries/${queryId}/source#${visualizationId}`);\n        cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n        cy.getByTestId(\"ExecuteButton\").click();\n      });\n  });\n\n  it(\"creates Map with groups\", () => {\n    cy.clickThrough(`\n      EditVisualization\n      VisualizationEditor.Tabs.General\n      Map.Editor.LatitudeColumnName\n      Map.Editor.LatitudeColumnName.lat\n      Map.Editor.LongitudeColumnName\n      Map.Editor.LongitudeColumnName.lng\n      Map.Editor.GroupBy\n      Map.Editor.GroupBy.country\n    `);\n\n    cy.clickThrough(\"VisualizationEditor.Tabs.Groups\");\n    cy.clickThrough(\"Map.Editor.Groups.Israel.Color\");\n    cy.fillInputs({ \"ColorPicker.CustomColor\": \"red{enter}\" });\n    cy.getByTestId(\"ColorPicker.CustomColor\").should(\"not.be.visible\");\n    cy.clickThrough(\"Map.Editor.Groups.Ukraine.Color\");\n    cy.fillInputs({ \"ColorPicker.CustomColor\": \"green{enter}\" });\n    cy.getByTestId(\"ColorPicker.CustomColor\").should(\"not.be.visible\");\n    cy.clickThrough(\"Map.Editor.Groups.Hungary.Color\");\n    cy.fillInputs({ \"ColorPicker.CustomColor\": \"blue{enter}\" });\n    cy.getByTestId(\"ColorPicker.CustomColor\").should(\"not.be.visible\");\n\n    cy.getByTestId(\"VisualizationPreview\").find(\".leaflet-control-zoom-in\").click();\n\n    // Wait for proper initialization of visualization\n    cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Visualizations - Map (Markers) with groups\", { widths: [viewportWidth] });\n  });\n\n  it(\"creates Map with custom markers\", () => {\n    cy.clickThrough(`\n      EditVisualization\n      VisualizationEditor.Tabs.General\n      Map.Editor.LatitudeColumnName\n      Map.Editor.LatitudeColumnName.lat\n      Map.Editor.LongitudeColumnName\n      Map.Editor.LongitudeColumnName.lng\n    `);\n\n    cy.clickThrough(`\n      VisualizationEditor.Tabs.Style\n      Map.Editor.ClusterMarkers\n      Map.Editor.CustomizeMarkers\n    `);\n\n    cy.fillInputs({ \"Map.Editor.MarkerIcon\": \"home\" }, { wait: 250 }); // this input is debounced\n\n    cy.clickThrough(\"Map.Editor.MarkerBackgroundColor\");\n    cy.fillInputs({ \"ColorPicker.CustomColor\": \"red{enter}\" });\n    cy.getByTestId(\"ColorPicker.CustomColor\").should(\"not.be.visible\");\n    cy.clickThrough(\"Map.Editor.MarkerBorderColor\");\n    cy.fillInputs({ \"ColorPicker.CustomColor\": \"maroon{enter}\" });\n    cy.getByTestId(\"ColorPicker.CustomColor\").should(\"not.be.visible\");\n\n    cy.getByTestId(\"VisualizationPreview\").find(\".leaflet-control-zoom-in\").click();\n\n    // Wait for proper initialization of visualization\n    cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.percySnapshot(\"Visualizations - Map (Markers) with custom markers\", { widths: [viewportWidth] });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/pivot_spec.js",
    "content": "/* global cy */\n\nimport { getWidgetTestId } from \"../../support/dashboard\";\n\nconst SQL = `\n  SELECT 'a' AS stage1, 'a1' AS stage2, 11 AS value UNION ALL\n  SELECT 'a' AS stage1, 'a2' AS stage2, 12 AS value UNION ALL\n  SELECT 'a' AS stage1, 'a3' AS stage2, 45 AS value UNION ALL\n  SELECT 'a' AS stage1, 'a4' AS stage2, 54 AS value UNION ALL\n  SELECT 'b' AS stage1, 'b1' AS stage2, 33 AS value UNION ALL\n  SELECT 'b' AS stage1, 'b2' AS stage2, 73 AS value UNION ALL\n  SELECT 'b' AS stage1, 'b3' AS stage2, 90 AS value UNION ALL\n  SELECT 'c' AS stage1, 'c1' AS stage2, 19 AS value UNION ALL\n  SELECT 'c' AS stage1, 'c2' AS stage2, 92 AS value UNION ALL\n  SELECT 'c' AS stage1, 'c3' AS stage2, 63 AS value UNION ALL\n  SELECT 'c' AS stage1, 'c4' AS stage2, 44 AS value\\\n`;\n\nfunction createPivotThroughUI(visualizationName, options = {}) {\n  cy.getByTestId(\"NewVisualization\").click();\n  cy.getByTestId(\"VisualizationType\").selectAntdOption(\"VisualizationType.PIVOT\");\n  cy.getByTestId(\"VisualizationName\").clear().type(visualizationName);\n  if (options.hideControls) {\n    cy.getByTestId(\"PivotEditor.HideControls\").click();\n    cy.getByTestId(\"VisualizationPreview\")\n      .find(\"table\")\n      .find(\".pvtAxisContainer, .pvtRenderer, .pvtVals\")\n      .should(\"be.not.visible\");\n  }\n  cy.getByTestId(\"VisualizationPreview\").find(\"table\").should(\"exist\");\n  cy.getByTestId(\"EditVisualizationDialog\").contains(\"button\", \"Save\").click();\n}\n\ndescribe(\"Pivot\", () => {\n  beforeEach(() => {\n    cy.login();\n    cy.createQuery({ name: \"Pivot Visualization\", query: SQL }).its(\"id\").as(\"queryId\");\n  });\n\n  it(\"creates Pivot with controls\", function () {\n    cy.visit(`queries/${this.queryId}/source`);\n    cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"ExecuteButton\").click();\n\n    const visualizationName = \"Pivot\";\n    createPivotThroughUI(visualizationName);\n\n    cy.getByTestId(\"QueryPageVisualizationTabs\").contains(\"span\", visualizationName).should(\"exist\");\n  });\n\n  it(\"creates Pivot without controls\", function () {\n    cy.visit(`queries/${this.queryId}/source`);\n    cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n    cy.getByTestId(\"ExecuteButton\").click();\n\n    const visualizationName = \"Pivot\";\n\n    cy.server();\n    cy.route(\"POST\", \"**/api/visualizations\").as(\"SaveVisualization\");\n\n    createPivotThroughUI(visualizationName, { hideControls: true });\n\n    cy.wait(\"@SaveVisualization\");\n    // Added visualization should also have hidden controls\n    cy.getByTestId(\"PivotTableVisualization\")\n      .find(\"table\")\n      .find(\".pvtAxisContainer, .pvtRenderer, .pvtVals\")\n      .should(\"be.not.visible\");\n  });\n\n  it(\"updates the visualization when results change\", function () {\n    const options = {\n      aggregatorName: \"Count\",\n      data: [], // force it to have a data object, although it shouldn't\n      controls: { enabled: false },\n      cols: [\"stage1\"],\n      rows: [\"stage2\"],\n      vals: [\"value\"],\n    };\n\n    cy.createVisualization(this.queryId, \"PIVOT\", \"Pivot\", options).then((visualization) => {\n      cy.visit(`queries/${this.queryId}/source#${visualization.id}`);\n      cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n      cy.getByTestId(\"ExecuteButton\").click();\n\n      // assert number of rows is 11\n      cy.getByTestId(\"PivotTableVisualization\").contains(\".pvtGrandTotal\", \"11\");\n\n      cy.getByTestId(\"QueryEditor\")\n        .get(\".ace_text-input\")\n        .first()\n        .focus()\n        .type(\" UNION ALL {enter}SELECT 'c' AS stage1, 'c5' AS stage2, 55 AS value\");\n\n      // wait for the query text change to propagate (it's debounced in QuerySource.jsx)\n      // eslint-disable-next-line cypress/no-unnecessary-waiting\n      cy.wait(200);\n\n      cy.getByTestId(\"SaveButton\").click();\n      cy.getByTestId(\"ExecuteButton\").should(\"be.enabled\").click();\n\n      // assert number of rows is 12\n      cy.getByTestId(\"PivotTableVisualization\").contains(\".pvtGrandTotal\", \"12\");\n    });\n  });\n\n  it(\"takes a snapshot with different configured Pivots\", function () {\n    const options = {\n      aggregatorName: \"Sum\",\n      controls: { enabled: true },\n      cols: [\"stage1\"],\n      rows: [\"stage2\"],\n      vals: [\"value\"],\n    };\n\n    const pivotTables = [\n      { name: \"Pivot\", options, position: { autoHeight: false, sizeY: 10, sizeX: 2 } },\n      {\n        name: \"Pivot without Row Totals\",\n        options: { ...options, rendererOptions: { table: { rowTotals: false } } },\n        position: { autoHeight: false, col: 2, sizeY: 10, sizeX: 2 },\n      },\n      {\n        name: \"Pivot without Col Totals\",\n        options: { ...options, rendererOptions: { table: { colTotals: false } } },\n        position: { autoHeight: false, col: 4, sizeY: 10, sizeX: 2 },\n      },\n      {\n        name: \"Pivot with Controls\",\n        options: { ...options, controls: { enabled: false } },\n        position: { autoHeight: false, row: 9, sizeY: 13 },\n      },\n    ];\n\n    cy.createDashboard(\"Pivot Visualization\")\n      .then((dashboard) => {\n        this.dashboardUrl = `/dashboards/${dashboard.id}`;\n        return cy.all(\n          pivotTables.map(\n            (pivot) => () =>\n              cy\n                .createVisualization(this.queryId, \"PIVOT\", pivot.name, pivot.options)\n                .then((visualization) => cy.addWidget(dashboard.id, visualization.id, { position: pivot.position }))\n          )\n        );\n      })\n      .then((widgets) => {\n        cy.visit(this.dashboardUrl);\n        widgets.forEach((widget) => {\n          cy.getByTestId(getWidgetTestId(widget)).within(() =>\n            cy.getByTestId(\"PivotTableVisualization\").should(\"exist\")\n          );\n        });\n        cy.percySnapshot(\"Visualizations - Pivot Table\");\n      });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/sankey_sunburst_spec.js",
    "content": "/* global cy */\n\nimport { getWidgetTestId } from \"../../support/dashboard\";\n\nconst SQL = `\n  SELECT 'a' AS s1, 'a1' AS s2, 'a2' AS s3, null AS s4, null AS s5, 11 AS value UNION ALL\n  SELECT 'a' AS s1, 'a2' AS s2, null AS s3, null AS s4, null AS s5, 12 AS value UNION ALL\n  SELECT 'a' AS s1, 'a3' AS s2, null AS s3, null AS s4, null AS s5, 45 AS value UNION ALL\n  SELECT 'a' AS s1, 'a4' AS s2, null AS s3, null AS s4, null AS s5, 54 AS value UNION ALL\n  SELECT 'b' AS s1, 'b1' AS s2, 'a2' AS s3, 'c1' AS s4, null AS s5, 33 AS value UNION ALL\n  SELECT 'b' AS s1, 'b2' AS s2, 'a4' AS s3, 'c3' AS s4, null AS s5, 73 AS value UNION ALL\n  SELECT 'b' AS s1, 'b3' AS s2, null AS s3, null AS s4, null AS s5, 90 AS value UNION ALL\n  SELECT 'c' AS s1, 'c1' AS s2, null AS s3, null AS s4, null AS s5, 19 AS value UNION ALL\n  SELECT 'c' AS s1, 'c2' AS s2, 'b2' AS s3, 'a2' AS s4, 'a3' AS s5, 92 AS value UNION ALL\n  SELECT 'c' AS s1, 'c3' AS s2, 'c4' AS s3, null AS s4, null AS s5, 63 AS value UNION ALL\n  SELECT 'c' AS s1, 'c4' AS s2, null AS s3, null AS s4, null AS s5, 44 AS value\n`;\n\ndescribe(\"Sankey and Sunburst\", () => {\n  beforeEach(() => {\n    cy.login();\n  });\n\n  describe(\"Creation through UI\", () => {\n    beforeEach(() => {\n      cy.createQuery({ query: SQL }).then(({ id }) => {\n        cy.visit(`queries/${id}/source`);\n        cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n        cy.getByTestId(\"ExecuteButton\").click();\n        cy.getByTestId(\"NewVisualization\").click();\n        cy.getByTestId(\"VisualizationType\").selectAntdOption(\"VisualizationType.SUNBURST_SEQUENCE\");\n      });\n    });\n\n    it(\"creates Sunburst\", () => {\n      const visualizationName = \"Sunburst\";\n\n      cy.getByTestId(\"VisualizationName\").clear().type(visualizationName);\n      cy.getByTestId(\"VisualizationPreview\").find(\"svg\").should(\"exist\");\n\n      cy.getByTestId(\"EditVisualizationDialog\").contains(\"button\", \"Save\").click();\n      cy.getByTestId(\"QueryPageVisualizationTabs\").contains(\"span\", visualizationName).should(\"exist\");\n    });\n\n    it(\"creates Sankey\", () => {\n      const visualizationName = \"Sankey\";\n\n      cy.getByTestId(\"VisualizationName\").clear().type(visualizationName);\n      cy.getByTestId(\"VisualizationPreview\").find(\"svg\").should(\"exist\");\n\n      cy.getByTestId(\"EditVisualizationDialog\").contains(\"button\", \"Save\").click();\n      cy.getByTestId(\"QueryPageVisualizationTabs\").contains(\"span\", visualizationName).should(\"exist\");\n    });\n  });\n\n  const STAGES_WIDGETS = [\n    { name: \"1 stage\", query: `SELECT s1,value FROM (${SQL}) q`, position: { autoHeight: false, sizeY: 10, sizeX: 2 } },\n    {\n      name: \"2 stages\",\n      query: `SELECT s1,s2,value FROM (${SQL}) q`,\n      position: { autoHeight: false, col: 2, sizeY: 10, sizeX: 2 },\n    },\n    {\n      name: \"3 stages\",\n      query: `SELECT s1,s2,s3,value FROM (${SQL}) q`,\n      position: { autoHeight: false, col: 4, sizeY: 10, sizeX: 2 },\n    },\n    {\n      name: \"4 stages\",\n      query: `SELECT s1,s2,s3,s4,value FROM (${SQL}) q`,\n      position: { autoHeight: false, row: 9, sizeY: 10, sizeX: 2 },\n    },\n    {\n      name: \"5 stages\",\n      query: `SELECT s1,s2,s3,s4,s5,value FROM (${SQL}) q`,\n      position: { autoHeight: false, row: 9, col: 2, sizeY: 10, sizeX: 2 },\n    },\n  ];\n\n  it(\"takes a snapshot with Sunburst (1 - 5 stages)\", function () {\n    cy.createDashboard(\"Sunburst Visualization\").then((dashboard) => {\n      this.dashboardUrl = `/dashboards/${dashboard.id}`;\n      return cy\n        .all(\n          STAGES_WIDGETS.map(\n            (sunburst) => () =>\n              cy\n                .createQuery({ name: `Sunburst with ${sunburst.name}`, query: sunburst.query })\n                .then((queryData) => cy.createVisualization(queryData.id, \"SUNBURST_SEQUENCE\", \"Sunburst\", {}))\n                .then((visualization) => cy.addWidget(dashboard.id, visualization.id, { position: sunburst.position }))\n          )\n        )\n        .then((widgets) => {\n          cy.visit(this.dashboardUrl);\n          widgets.forEach((widget) => {\n            cy.getByTestId(getWidgetTestId(widget)).within(() => cy.get(\"svg\").should(\"exist\"));\n          });\n\n          // wait a bit before taking snapshot\n          cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n          cy.percySnapshot(\"Visualizations - Sunburst\");\n        });\n    });\n  });\n\n  it(\"takes a snapshot with Sankey (1 - 5 stages)\", function () {\n    cy.createDashboard(\"Sankey Visualization\").then((dashboard) => {\n      this.dashboardUrl = `/dashboards/${dashboard.id}`;\n      return cy\n        .all(\n          STAGES_WIDGETS.map(\n            (sankey) => () =>\n              cy\n                .createQuery({ name: `Sankey with ${sankey.name}`, query: sankey.query })\n                .then((queryData) => cy.createVisualization(queryData.id, \"SANKEY\", \"Sankey\", {}))\n                .then((visualization) => cy.addWidget(dashboard.id, visualization.id, { position: sankey.position }))\n          )\n        )\n        .then((widgets) => {\n          cy.visit(this.dashboardUrl);\n          widgets.forEach((widget) => {\n            cy.getByTestId(getWidgetTestId(widget)).within(() => cy.get(\"svg\").should(\"exist\"));\n          });\n\n          // wait a bit before taking snapshot\n          cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n          cy.percySnapshot(\"Visualizations - Sankey\");\n        });\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/table/.mocks/all-cell-types.js",
    "content": "export const query = `\n  SELECT\n    314159.265359 AS num,\n    'test' AS str,\n    'hello, <b>world</b>' AS html,\n    'hello, <b>world</b>' AS html2,\n    'Link: http://example.com' AS html3,\n    '1995-09-03T07:45' AS \"date\",\n    true AS bool,\n    '[{\"a\": 3.14, \"b\": \"test\", \"c\": [], \"d\": {}}, false, [null, 123], \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\"]' AS json,\n    'ukr' AS img,\n    'redash' AS link\n`;\n\nexport const config = {\n  itemsPerPage: 25,\n  columns: [\n    {\n      name: \"num\",\n      displayAs: \"number\",\n      numberFormat: \"0.000\",\n    },\n    {\n      name: \"str\",\n      displayAs: \"string\",\n      allowHTML: true,\n      highlightLinks: false,\n    },\n    {\n      name: \"html\",\n      displayAs: \"string\",\n      allowHTML: true,\n      highlightLinks: false,\n    },\n    {\n      name: \"html2\",\n      displayAs: \"string\",\n      allowHTML: false,\n      highlightLinks: false,\n    },\n    {\n      name: \"html3\",\n      displayAs: \"string\",\n      allowHTML: true,\n      highlightLinks: true,\n    },\n    {\n      name: \"date\",\n      displayAs: \"datetime\",\n      dateTimeFormat: \"D MMMM YYYY, h:mm A\",\n    },\n    {\n      name: \"bool\",\n      displayAs: \"boolean\",\n      booleanValues: [\"No\", \"Yes\"],\n    },\n    {\n      name: \"json\",\n      displayAs: \"json\",\n    },\n    {\n      name: \"img\",\n      displayAs: \"image\",\n      imageUrlTemplate: \"https://raw.githubusercontent.com/linssen/country-flag-icons/master/images/png/{{ @ }}.png\",\n      imageTitleTemplate: \"ISO: {{ @ }}\",\n      imageWidth: \"30\",\n      imageHeight: \"\",\n    },\n    {\n      name: \"link\",\n      displayAs: \"link\",\n      linkUrlTemplate: \"https://www.google.com.ua/search?q={{ @ }}\",\n      linkTextTemplate: \"Search for '{{ @ }}'\",\n      linkTitleTemplate: \"Search for '{{ @ }}'\",\n      linkOpenInNewTab: true,\n    },\n  ],\n};\n"
  },
  {
    "path": "client/cypress/integration/visualizations/table/.mocks/large-dataset.js",
    "content": "const loremIpsum =\n  \"Lorem ipsum dolor sit amet consectetur adipiscing elit\" +\n  \"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua\";\n\nfunction pseudoRandom(seed) {\n  const x = Math.sin(seed) * 10000;\n  return x - Math.floor(x);\n}\n\nfunction randomString(index) {\n  const n = pseudoRandom(index);\n  const offset = Math.floor(n * loremIpsum.length);\n  const length = Math.floor(n * 15) + 1;\n  return loremIpsum.substr(offset, length).trim();\n}\n\nexport const query = new Array(400)\n  .fill(null) // we actually don't need these values, but `.map()` ignores undefined elements\n  .map((unused, index) => `SELECT ${index} AS a, '${randomString(index)}' as b`)\n  .join(\" UNION ALL\\n\");\n\nexport const config = {\n  itemsPerPage: 10,\n  columns: [\n    {\n      name: \"a\",\n      displayAs: \"number\",\n      numberFormat: \"0\",\n    },\n    {\n      name: \"b\",\n      displayAs: \"string\",\n    },\n  ],\n};\n"
  },
  {
    "path": "client/cypress/integration/visualizations/table/.mocks/multi-column-sort.js",
    "content": "export const query = `\n  SELECT 3 AS a, 1 AS b, 'h' AS c UNION ALL\n  SELECT 1 AS a, 1 AS b, 'b' AS c UNION ALL\n  SELECT 2 AS a, 1 AS b, 'e' AS c UNION ALL\n  SELECT 1 AS a, 3 AS b, 'd' AS c UNION ALL\n  SELECT 2 AS a, 2 AS b, 'f' AS c UNION ALL\n  SELECT 1 AS a, 1 AS b, 'a' AS c UNION ALL\n  SELECT 3 AS a, 2 AS b, 'i' AS c UNION ALL\n  SELECT 2 AS a, 3 AS b, 'g' AS c UNION ALL\n  SELECT 1 AS a, 2 AS b, 'c' AS c UNION ALL\n  SELECT 3 AS a, 3 AS b, 'j' AS c\n`;\n\nexport const config = {\n  itemsPerPage: 25,\n  columns: [\n    {\n      name: \"a\",\n      displayAs: \"number\",\n      numberFormat: \"0\",\n    },\n    {\n      name: \"b\",\n      displayAs: \"number\",\n      numberFormat: \"0\",\n    },\n    {\n      name: \"c\",\n      displayAs: \"string\",\n    },\n  ],\n};\n"
  },
  {
    "path": "client/cypress/integration/visualizations/table/.mocks/search-in-data.js",
    "content": "export const query = `\n  SELECT 'contains test' AS a, 'random string' AS b, 'another string' AS c UNION ALL\n  SELECT 'contains test' AS a, 'also contains Test' AS b, '' AS c UNION ALL\n  SELECT 'lorem ipsum' AS a, 'but TEST is here' AS b, 'none' AS c UNION ALL\n  SELECT 'should not appear' AS a, 'because' AS b, '\"test\" is here' AS c\n`;\n\nexport const config = {\n  itemsPerPage: 25,\n  columns: [\n    {\n      name: \"a\",\n      displayAs: \"string\",\n      allowSearch: true,\n    },\n    {\n      name: \"b\",\n      displayAs: \"string\",\n      allowSearch: true,\n    },\n    {\n      name: \"c\",\n      allowSearch: false,\n    },\n  ],\n};\n"
  },
  {
    "path": "client/cypress/integration/visualizations/table/table_spec.js",
    "content": "/* global cy, Cypress */\n\n/*\n  This test suite relies on Percy (does not validate rendered visualizations)\n*/\n\nimport * as AllCellTypes from \"./.mocks/all-cell-types\";\nimport * as MultiColumnSort from \"./.mocks/multi-column-sort\";\nimport * as SearchInData from \"./.mocks/search-in-data\";\nimport * as LargeDataset from \"./.mocks/large-dataset\";\n\nfunction prepareVisualization(query, type, name, options) {\n  return cy\n    .createQuery({ query })\n    .then(({ id }) => cy.createVisualization(id, type, name, options))\n    .then(({ id: visualizationId, query_id: queryId }) => {\n      // use data-only view because we don't need editor features, but it will\n      // free more space for visualizations. Also, we'll hide schema browser (via shortcut)\n      cy.visit(`queries/${queryId}#${visualizationId}`);\n\n      cy.getByTestId(\"ExecuteButton\").click();\n      cy.get(\"body\").type(\"{alt}D\");\n\n      // do some pre-checks here to ensure that visualization was created and is visible\n      cy.getByTestId(\"TableVisualization\").should(\"exist\").find(\"table\").should(\"exist\");\n\n      return cy.then(() => ({ queryId, visualizationId }));\n    });\n}\n\ndescribe(\"Table\", () => {\n  const viewportWidth = Cypress.config(\"viewportWidth\");\n\n  beforeEach(() => {\n    cy.login();\n  });\n\n  it(\"renders all cell types\", () => {\n    const { query, config } = AllCellTypes;\n    prepareVisualization(query, \"TABLE\", \"All cell types\", config).then(() => {\n      // eslint-disable-next-line cypress/no-unnecessary-waiting\n      cy.wait(500); // add some waiting to avoid an async update error from .jvi-toggle\n\n      // expand JSON cell\n      cy.get(\".jvi-item.jvi-root .jvi-toggle\").click();\n      cy.get(\".jvi-item.jvi-root .jvi-item .jvi-toggle\").click({ multiple: true });\n\n      cy.percySnapshot(\"Visualizations - Table (All cell types)\", { widths: [viewportWidth] });\n    });\n  });\n\n  describe(\"Sorting data\", () => {\n    beforeEach(function () {\n      const { query, config } = MultiColumnSort;\n      prepareVisualization(query, \"TABLE\", \"Sort data\", config).then(({ queryId, visualizationId }) => {\n        this.queryId = queryId;\n        this.visualizationId = visualizationId;\n      });\n    });\n\n    it(\"sorts data by a single column\", function () {\n      cy.getByTestId(\"TableVisualization\").find(\"table th\").contains(\"c\").should(\"exist\").click();\n      cy.percySnapshot(\"Visualizations - Table (Single-column sort)\", { widths: [viewportWidth] });\n    });\n\n    it(\"sorts data by a multiple columns\", function () {\n      cy.getByTestId(\"TableVisualization\").find(\"table th\").contains(\"a\").should(\"exist\").click();\n\n      cy.get(\"body\").type(\"{shift}\", { release: false });\n      cy.getByTestId(\"TableVisualization\").find(\"table th\").contains(\"b\").should(\"exist\").click();\n\n      cy.percySnapshot(\"Visualizations - Table (Multi-column sort)\", { widths: [viewportWidth] });\n    });\n\n    it(\"sorts data in reverse order\", function () {\n      cy.getByTestId(\"TableVisualization\").find(\"table th\").contains(\"c\").should(\"exist\").click().click();\n      cy.percySnapshot(\"Visualizations - Table (Single-column reverse sort)\", { widths: [viewportWidth] });\n    });\n  });\n\n  it(\"searches in multiple columns\", () => {\n    const { query, config } = SearchInData;\n    prepareVisualization(query, \"TABLE\", \"Search\", config).then(({ visualizationId }) => {\n      cy.getByTestId(\"TableVisualization\").find(\"table input\").should(\"exist\").type(\"test\");\n      cy.percySnapshot(\"Visualizations - Table (Search in data)\", { widths: [viewportWidth] });\n    });\n  });\n\n  it(\"shows pagination and navigates to third page\", () => {\n    const { query, config } = LargeDataset;\n    prepareVisualization(query, \"TABLE\", \"With pagination\", config).then(({ visualizationId }) => {\n      cy.get(\".visualization-renderer\")\n        .find(\".ant-table-pagination\")\n        .should(\"exist\")\n        .find(\"li\")\n        .contains(\"3\")\n        .should(\"exist\")\n        .click();\n\n      cy.percySnapshot(\"Visualizations - Table (Pagination)\", { widths: [viewportWidth] });\n    });\n  });\n});\n"
  },
  {
    "path": "client/cypress/integration/visualizations/word_cloud_spec.js",
    "content": "/* global cy, Cypress */\n\nconst { map } = Cypress._;\n\nconst SQL = `\n  SELECT 'Lorem ipsum dolor'     AS a, 'ipsum'  AS b, 2 AS c UNION ALL\n  SELECT 'Lorem sit amet'        AS a, 'amet'   AS b, 2 AS c UNION ALL\n  SELECT 'dolor adipiscing elit' AS a, 'elit'   AS b, 4 AS c UNION ALL\n  SELECT 'sed do sed'            AS a, 'sed'    AS b, 5 AS c UNION ALL\n  SELECT 'sed eiusmod tempor'    AS a, 'tempor' AS b, 7 AS c\n`;\n\n// Hack to fix Cypress -> Percy communication\n// Word Cloud uses `font-family` defined in CSS with a lot of fallbacks, so\n// it's almost impossible to know which font will be used on particular machine/browser.\n// In Cypress browser it could be one font, in Percy - another.\n// The issue is in how Percy takes screenshots: it takes a DOM/CSS/assets snapshot in Cypress,\n// copies it to own servers and restores in own browsers. Word Cloud computes its layout\n// using Cypress font, sets absolute positions for elements (in pixels), and when it is restored\n// on Percy machines (with another font) - visualization gets messed up.\n// Solution: explicitly provide some font that will be 100% the same on all CI machines. In this\n// case, it's \"Roboto\" just because it's in the list of fallback fonts and we already have this\n// webfont in assets folder (so browser can load it).\nfunction injectFont(document) {\n  const style = document.createElement(\"style\");\n  style.setAttribute(\"id\", \"percy-fix\");\n  style.setAttribute(\"type\", \"text/css\");\n\n  const fonts = [\n    [\"Roboto\", \"Roboto-Light-webfont\", 300],\n    [\"Roboto\", \"Roboto-Regular-webfont\", 400],\n    [\"Roboto\", \"Roboto-Medium-webfont\", 500],\n    [\"Roboto\", \"Roboto-Bold-webfont\", 700],\n  ];\n\n  const basePath = \"/static/fonts/roboto/\";\n\n  // `insertRule` does not load font for some reason. Using text content works ¯\\_(ツ)_/¯\n  style.appendChild(\n    document.createTextNode(\n      map(\n        fonts,\n        ([fontFamily, fileName, fontWeight]) => `\n    @font-face {\n      font-family: \"${fontFamily}\";\n      font-weight: ${fontWeight};\n      src: url(\"${basePath}${fileName}.eot\");\n      src: url(\"${basePath}${fileName}.eot?#iefix\") format(\"embedded-opentype\"),\n           url(\"${basePath}${fileName}.woff\") format(\"woff\"),\n           url(\"${basePath}${fileName}.ttf\") format(\"truetype\"),\n           url(\"${basePath}${fileName}.svg\") format(\"svg\");\n    }\n  `\n      ).join(\"\\n\\n\")\n    )\n  );\n  document.getElementsByTagName(\"head\")[0].appendChild(style);\n}\n\ndescribe(\"Word Cloud\", () => {\n  const viewportWidth = Cypress.config(\"viewportWidth\");\n\n  beforeEach(() => {\n    cy.login();\n    cy.createQuery({ query: SQL }).then(({ id }) => {\n      cy.visit(`queries/${id}/source`);\n      cy.wait(1500); // eslint-disable-line cypress/no-unnecessary-waiting\n      cy.getByTestId(\"ExecuteButton\").click();\n    });\n    cy.document().then(injectFont);\n    cy.getByTestId(\"NewVisualization\").click();\n    cy.getByTestId(\"VisualizationType\").selectAntdOption(\"VisualizationType.WORD_CLOUD\");\n  });\n\n  it(\"creates visualization with automatic word frequencies\", () => {\n    cy.clickThrough(`\n      WordCloud.WordsColumn\n      WordCloud.WordsColumn.a\n    `);\n\n    // Wait for proper initialization of visualization\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n\n    cy.getByTestId(\"VisualizationPreview\").find(\"svg text\").should(\"have.length\", 11);\n\n    cy.percySnapshot(\"Visualizations - Word Cloud (Automatic word frequencies)\", { widths: [viewportWidth] });\n  });\n\n  it(\"creates visualization with word frequencies from another column\", () => {\n    cy.clickThrough(`\n      WordCloud.WordsColumn\n      WordCloud.WordsColumn.b\n\n      WordCloud.FrequenciesColumn\n      WordCloud.FrequenciesColumn.c\n    `);\n\n    // Wait for proper initialization of visualization\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n\n    cy.getByTestId(\"VisualizationPreview\").find(\"svg text\").should(\"have.length\", 5);\n\n    cy.percySnapshot(\"Visualizations - Word Cloud (Frequencies from another column)\", { widths: [viewportWidth] });\n  });\n\n  it(\"creates visualization with word length and frequencies limits\", () => {\n    cy.clickThrough(`\n      WordCloud.WordsColumn\n      WordCloud.WordsColumn.b\n\n      WordCloud.FrequenciesColumn\n      WordCloud.FrequenciesColumn.c\n    `);\n\n    cy.fillInputs({\n      \"WordCloud.WordLengthLimit.Min\": \"4\",\n      \"WordCloud.WordLengthLimit.Max\": \"5\",\n      \"WordCloud.WordCountLimit.Min\": \"1\",\n      \"WordCloud.WordCountLimit.Max\": \"3\",\n    });\n\n    // Wait for proper initialization of visualization\n    cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting\n\n    cy.getByTestId(\"VisualizationPreview\").find(\"svg text\").should(\"have.length\", 2);\n\n    cy.percySnapshot(\"Visualizations - Word Cloud (With filters)\", { widths: [viewportWidth] });\n  });\n});\n"
  },
  {
    "path": "client/cypress/seed-data.js",
    "content": "exports.seedData = [\n  {\n    route: \"/setup\",\n    type: \"form\",\n    data: {\n      name: \"Example Admin\",\n      email: \"admin@redash.io\",\n      password: \"password\",\n      org_name: \"Redash\",\n    },\n  },\n  {\n    route: \"/login\",\n    type: \"form\",\n    data: {\n      email: \"admin@redash.io\",\n      password: \"password\",\n    },\n  },\n  {\n    route: \"/api/data_sources\",\n    type: \"json\",\n    data: {\n      name: \"Test PostgreSQL\",\n      options: {\n        dbname: \"postgres\",\n        host: \"postgres\",\n        port: 5432,\n        sslmode: \"prefer\",\n        user: \"postgres\",\n      },\n      type: \"pg\",\n    },\n  },\n  {\n    route: \"/api/destinations\",\n    type: \"json\",\n    data: {\n      name: \"Test Email Destination\",\n      options: {\n        addresses: \"test@example.com\",\n      },\n      type: \"email\",\n    },\n  },\n];\n"
  },
  {
    "path": "client/cypress/support/commands.js",
    "content": "/* global Cypress */\n\nimport \"@percy/cypress\"; // eslint-disable-line import/no-extraneous-dependencies, import/no-unresolved\n\nimport \"@testing-library/cypress/add-commands\";\n\nconst { each } = Cypress._;\n\nCypress.Commands.add(\"login\", (email = \"admin@redash.io\", password = \"password\") => {\n  let csrf;\n  cy.visit(\"/login\");\n  cy.getCookie(\"csrf_token\")\n    .then(cookie => {\n      if (cookie) {\n        csrf = cookie.value;\n      } else {\n        cy.visit(\"/login\").then(() => {\n          cy.get('input[name=\"csrf_token\"]')\n            .invoke(\"val\")\n            .then(csrf_token => {\n              csrf = csrf_token;\n            });\n        });\n      }\n    })\n    .then(() => {\n      cy.request({\n        url: \"/login\",\n        method: \"POST\",\n        form: true,\n        body: {\n          email,\n          password,\n          csrf_token: csrf,\n        },\n      });\n    });\n});\n\nCypress.Commands.add(\"logout\", () => cy.visit(\"/logout\"));\nCypress.Commands.add(\"getByTestId\", element => cy.get('[data-test=\"' + element + '\"]'));\n\n/* Clicks a series of elements. Pass in a newline-seperated string in order to click all elements by their test id,\n or enclose the above string in an object with 'button' as key to click the buttons by name. For example:\n\n  cy.clickThrough(`\n    TestId1\n    TestId2\n    TestId3\n    `, { button: `\n    Label of button 4\n    Label of button 5\n    ` }, `\n    TestId6\n    TestId7`);\n*/\nCypress.Commands.add(\"clickThrough\", (...args) => {\n  args.forEach(elements => {\n    const names = elements.button || elements;\n\n    const click = element =>\n      (elements.button ? cy.contains(\"button\", element.trim()) : cy.getByTestId(element.trim())).click();\n\n    names\n      .trim()\n      .split(/\\n/)\n      .filter(Boolean)\n      .forEach(click);\n  });\n\n  return undefined;\n});\n\n/**\n * Selects ANTD selector option\n */\nCypress.Commands.add(\"selectAntdOption\", { prevSubject: \"element\" }, (subject, testId) => {\n  cy.wrap(subject).click();\n  return cy.getByTestId(testId).click({ force: true });\n});\n\nCypress.Commands.add(\"fillInputs\", (elements, { wait = 0 } = {}) => {\n  each(elements, (value, testId) => {\n    cy.getByTestId(testId)\n      .filter(\":visible\")\n      .clear()\n      .type(value);\n    if (wait > 0) {\n      cy.wait(wait); // eslint-disable-line cypress/no-unnecessary-waiting\n    }\n  });\n});\n\nCypress.Commands.add(\"dragBy\", { prevSubject: true }, (subject, offsetLeft, offsetTop, force = false) => {\n  if (!offsetLeft) {\n    offsetLeft = 1;\n  }\n  if (!offsetTop) {\n    offsetTop = 1;\n  }\n  return cy\n    .wrap(subject)\n    .trigger(\"mouseover\", { force })\n    .trigger(\"mousedown\", \"topLeft\", { force })\n    .trigger(\"mousemove\", 1, 1, { force }) // must have at least 2 mousemove events for react-grid-layout to trigger onLayoutChange\n    .trigger(\"mousemove\", offsetLeft, offsetTop, { force })\n    .trigger(\"mouseup\", { force });\n});\n\nCypress.Commands.add(\"all\", (...functions) => {\n  if (Cypress._.isEmpty(functions)) {\n    return [];\n  }\n\n  const fns = Cypress._.isArray(functions[0]) ? functions[0] : functions;\n  const results = [];\n\n  fns.reduce((prev, fn) => {\n    fn().then(result => results.push(result));\n    return results;\n  }, results);\n\n  return cy.wrap(results);\n});\n\nCypress.Commands.overwrite(\"percySnapshot\", (originalFn, ...args) => {\n  Cypress.$(\"*[data-test=TimeAgo]\").text(\"just now\");\n  return originalFn(...args);\n});\n"
  },
  {
    "path": "client/cypress/support/dashboard/index.js",
    "content": "/* global cy */\n\nconst { get } = Cypress._;\nconst RESIZE_HANDLE_SELECTOR = \".react-resizable-handle\";\n\nexport function getWidgetTestId(widget) {\n  return `WidgetId${widget.id}`;\n}\n\nexport function createQueryAndAddWidget(dashboardId, queryData = {}, widgetOptions = {}) {\n  return cy\n    .createQuery(queryData)\n    .then(query => {\n      const visualizationId = get(query, \"visualizations.0.id\");\n      assert.isDefined(visualizationId, \"Query api call returns at least one visualization with id\");\n      return cy.addWidget(dashboardId, visualizationId, widgetOptions);\n    })\n    .then(getWidgetTestId);\n}\n\nexport function editDashboard() {\n  cy.getByTestId(\"DashboardMoreButton\").click();\n\n  cy.getByTestId(\"DashboardMoreButtonMenu\")\n    .contains(\"Edit\")\n    .click();\n}\n\nexport function shareDashboard() {\n  cy.clickThrough(\n    { button: \"Publish\" },\n    `OpenShareForm\n    PublicAccessEnabled`\n  );\n\n  return cy.getByTestId(\"SecretAddress\").invoke(\"val\");\n}\n\nexport function resizeBy(wrapper, offsetLeft = 0, offsetTop = 0) {\n  return wrapper.within(() => {\n    cy.get(RESIZE_HANDLE_SELECTOR).dragBy(offsetLeft, offsetTop, true);\n  });\n}\n"
  },
  {
    "path": "client/cypress/support/index.js",
    "content": "/* global Cypress */\n\nimport \"@cypress/code-coverage/support\";\nimport \"./commands\";\nimport \"./redash-api/index.js\";\n\nCypress.env(\"dataSourceId\", 1);\n\nCypress.on(\"uncaught:exception\", err => {\n  // Prevent ResizeObserver error from failing tests\n  if (err && Cypress._.includes(err.message, \"ResizeObserver loop limit exceeded\")) {\n    return false;\n  }\n});\n"
  },
  {
    "path": "client/cypress/support/parameters.js",
    "content": "export function dragParam(paramName, offsetLeft, offsetTop) {\n  cy.getByTestId(`DragHandle-${paramName}`)\n    .trigger(\"mouseover\")\n    .trigger(\"mousedown\");\n\n  cy.get(\".parameter-dragged .drag-handle\")\n    .trigger(\"mousemove\", offsetLeft, offsetTop, { force: true })\n    .trigger(\"mouseup\", { force: true });\n}\n\nexport function expectParamOrder(expectedOrder) {\n  cy.get(\".parameter-container label\").each(($label, index) => expect($label).to.have.text(expectedOrder[index]));\n}\n"
  },
  {
    "path": "client/cypress/support/redash-api/index.js",
    "content": "/* global cy, Cypress */\n\nconst { extend, get, merge, find } = Cypress._;\n\nconst post = (options) =>\n  cy\n    .getCookie(\"csrf_token\")\n    .then((csrf) => cy.request({ ...options, method: \"POST\", headers: { \"X-CSRF-TOKEN\": csrf.value } }));\n\nCypress.Commands.add(\"createDashboard\", (name) => {\n  return post({ url: \"api/dashboards\", body: { name } }).then(({ body }) => body);\n});\n\nCypress.Commands.add(\"createQuery\", (data, shouldPublish = true) => {\n  const merged = extend(\n    {\n      name: \"Test Query\",\n      query: \"select 1\",\n      data_source_id: Cypress.env(\"dataSourceId\"),\n      options: {\n        parameters: [],\n      },\n      schedule: null,\n    },\n    data\n  );\n\n  // eslint-disable-next-line cypress/no-assigning-return-values\n  let request = post({ url: \"/api/queries\", body: merged }).then(({ body }) => body);\n  if (shouldPublish) {\n    request = request.then((query) =>\n      post({ url: `/api/queries/${query.id}`, body: { is_draft: false } }).then(() => query)\n    );\n  }\n\n  return request;\n});\n\nCypress.Commands.add(\"createVisualization\", (queryId, type, name, options) => {\n  const data = { query_id: queryId, type, name, options };\n  return post({ url: \"/api/visualizations\", body: data }).then(({ body }) => ({\n    query_id: queryId,\n    ...body,\n  }));\n});\n\nCypress.Commands.add(\"addTextbox\", (dashboardId, text = \"text\", options = {}) => {\n  const defaultOptions = {\n    position: { col: 0, row: 0, sizeX: 3, sizeY: 3 },\n  };\n\n  const data = {\n    width: 1,\n    dashboard_id: dashboardId,\n    visualization_id: null,\n    text,\n    options: merge(defaultOptions, options),\n  };\n\n  return post({ url: \"api/widgets\", body: data }).then(({ body }) => {\n    const id = get(body, \"id\");\n    assert.isDefined(id, \"Widget api call returns widget id\");\n    return body;\n  });\n});\n\nCypress.Commands.add(\"addWidget\", (dashboardId, visualizationId, options = {}) => {\n  const defaultOptions = {\n    position: { col: 0, row: 0, sizeX: 3, sizeY: 3 },\n  };\n\n  const data = {\n    width: 1,\n    dashboard_id: dashboardId,\n    visualization_id: visualizationId,\n    options: merge(defaultOptions, options),\n  };\n\n  return post({ url: \"api/widgets\", body: data }).then(({ body }) => {\n    const id = get(body, \"id\");\n    assert.isDefined(id, \"Widget api call returns widget id\");\n    return body;\n  });\n});\n\nCypress.Commands.add(\"createAlert\", (queryId, options = {}, name) => {\n  const defaultOptions = {\n    column: \"?column?\",\n    selector: \"first\",\n    op: \"greater than\",\n    rearm: 0,\n    value: 1,\n  };\n\n  const data = {\n    query_id: queryId,\n    name: name || \"Alert for query \" + queryId,\n    options: merge(defaultOptions, options),\n  };\n\n  return post({ url: \"api/alerts\", body: data }).then(({ body }) => {\n    const id = get(body, \"id\");\n    assert.isDefined(id, \"Alert api call retu ns alert id\");\n    return body;\n  });\n});\n\nCypress.Commands.add(\"createUser\", ({ name, email, password }) => {\n  return post({\n    url: \"api/users?no_invite=yes\",\n    body: { name, email },\n    failOnStatusCode: false,\n  }).then((xhr) => {\n    const { status, body } = xhr;\n    if (status < 200 || status > 400) {\n      throw new Error(xhr);\n    }\n\n    if (status === 400 && body.message === \"Email already taken.\") {\n      // all is good, do nothing\n      return;\n    }\n\n    const id = get(body, \"id\");\n    assert.isDefined(id, \"User api call returns user id\");\n\n    return post({\n      url: body.invite_link,\n      form: true,\n      body: { password },\n    });\n  });\n});\n\nCypress.Commands.add(\"createDestination\", (name, type, options = {}) => {\n  return post({\n    url: \"api/destinations\",\n    body: { name, type, options },\n    failOnStatusCode: false,\n  });\n});\n\nCypress.Commands.add(\"getDestinations\", () => {\n  return cy.request(\"GET\", \"api/destinations\").then(({ body }) => body);\n});\n\nCypress.Commands.add(\"addDestinationSubscription\", (alertId, destinationName) => {\n  return cy\n    .getDestinations()\n    .then((destinations) => {\n      const destination = find(destinations, { name: destinationName });\n      if (!destination) {\n        throw new Error(\"Destination not found\");\n      }\n      return post({\n        url: `api/alerts/${alertId}/subscriptions`,\n        body: {\n          alert_id: alertId,\n          destination_id: destination.id,\n        },\n      });\n    })\n    .then(({ body }) => {\n      const id = get(body, \"id\");\n      assert.isDefined(id, \"Subscription api call returns subscription id\");\n      return body;\n    });\n});\n\nCypress.Commands.add(\"updateOrgSettings\", (settings) => {\n  return post({ url: \"api/settings/organization\", body: settings }).then(({ body }) => body);\n});\n"
  },
  {
    "path": "client/cypress/support/tags/index.js",
    "content": "export function expectTagsToContain(tags = []) {\n  cy.getByTestId(\"TagsControl\").within(() => {\n    cy.getByTestId(\"TagLabel\")\n      .should(\"have.length\", tags.length)\n      .each($tag => expect(tags).to.contain($tag.text()));\n  });\n}\n\nexport function typeInTagsSelectAndSave(text) {\n  cy.getByTestId(\"EditTagsDialog\").within(() => {\n    cy.get(\".ant-select\")\n      .find(\"input\")\n      .type(text, { force: true });\n\n    cy.get(\".ant-modal-header\").click(); // hide dropdown options\n    cy.contains(\"OK\").click();\n  });\n}\n"
  },
  {
    "path": "client/cypress/support/visualizations/chart.js",
    "content": "/**\n * Asserts the preview canvas exists, then captures the g.points element, which should be generated by plotly and asserts whether it exists\n * @param should Passed to should expression after plot points are captured\n */\nexport function assertPlotPreview(should = \"exist\") {\n  cy.getByTestId(\"VisualizationPreview\").find(\"g.overplot\").should(\"exist\").find(\"g.points\").should(should);\n}\n\nexport function createChartThroughUI(chartName, chartSpecificAssertionFn = () => {}) {\n  cy.getByTestId(\"NewVisualization\").click();\n  cy.getByTestId(\"VisualizationType\").selectAntdOption(\"VisualizationType.CHART\");\n  cy.getByTestId(\"VisualizationName\").clear().type(chartName);\n\n  chartSpecificAssertionFn();\n\n  cy.server();\n  cy.route(\"POST\", \"**/api/visualizations\").as(\"SaveVisualization\");\n\n  cy.getByTestId(\"EditVisualizationDialog\").contains(\"button\", \"Save\").click();\n\n  cy.getByTestId(\"QueryPageVisualizationTabs\").contains(\"span\", chartName).should(\"exist\");\n\n  cy.wait(\"@SaveVisualization\").should(\"have.property\", \"status\", 200);\n\n  return cy.get(\"@SaveVisualization\").then((xhr) => {\n    const { id, name, options } = xhr.response.body;\n    return cy.wrap({ id, name, options });\n  });\n}\n\nexport function assertTabbedEditor(chartSpecificTabbedEditorAssertionFn = () => {}) {\n  cy.getByTestId(\"Chart.GlobalSeriesType\").should(\"exist\");\n\n  cy.getByTestId(\"VisualizationEditor.Tabs.Series\").click();\n  cy.getByTestId(\"VisualizationEditor\").find(\"table\").should(\"exist\");\n\n  cy.getByTestId(\"VisualizationEditor.Tabs.Colors\").click();\n  cy.getByTestId(\"VisualizationEditor\").find(\"table\").should(\"exist\");\n\n  cy.getByTestId(\"VisualizationEditor.Tabs.DataLabels\").click();\n  cy.getByTestId(\"VisualizationEditor\").getByTestId(\"Chart.DataLabels.ShowDataLabels\").should(\"exist\");\n\n  chartSpecificTabbedEditorAssertionFn();\n\n  cy.getByTestId(\"VisualizationEditor.Tabs.General\").click();\n}\n\nexport function assertAxesAndAddLabels(xaxisLabel, yaxisLabel) {\n  cy.getByTestId(\"VisualizationEditor.Tabs.XAxis\").click();\n  cy.getByTestId(\"Chart.XAxis.Type\").contains(\".ant-select-selection-item\", \"Auto Detect\").should(\"exist\");\n\n  cy.getByTestId(\"Chart.XAxis.Name\").clear().type(xaxisLabel);\n\n  cy.getByTestId(\"VisualizationEditor.Tabs.YAxis\").click();\n  cy.getByTestId(\"Chart.LeftYAxis.Type\").contains(\".ant-select-selection-item\", \"Linear\").should(\"exist\");\n\n  cy.getByTestId(\"Chart.LeftYAxis.Name\").clear().type(yaxisLabel);\n\n  cy.getByTestId(\"Chart.LeftYAxis.TickFormat\").clear().type(\"+\");\n\n  cy.getByTestId(\"VisualizationEditor.Tabs.General\").click();\n}\n\nexport function createDashboardWithCharts(title, chartGetters, widgetsAssertionFn = () => {}) {\n  cy.createDashboard(title).then((dashboard) => {\n    const dashboardUrl = `/dashboards/${dashboard.id}`;\n    const widgetGetters = chartGetters.map((chartGetter) => `${chartGetter}Widget`);\n\n    chartGetters.forEach((chartGetter, i) => {\n      const position = { autoHeight: false, sizeY: 8, sizeX: 3, col: (i % 2) * 3 };\n      cy.get(`@${chartGetter}`)\n        .then((chart) => cy.addWidget(dashboard.id, chart.id, { position }))\n        .as(widgetGetters[i]);\n    });\n\n    widgetsAssertionFn(widgetGetters, dashboardUrl);\n  });\n}\n"
  },
  {
    "path": "client/cypress/support/visualizations/table.js",
    "content": "export function expectTableToHaveLength(length) {\n  cy.getByTestId(\"TableVisualization\").find(\"tbody tr\").should(\"have.length\", length);\n}\n\nexport function expectFirstColumnToHaveMembers(values) {\n  cy.getByTestId(\"TableVisualization\")\n    .find(\"tbody tr td:first-child\")\n    .then(($cell) => Cypress.$.map($cell, (item) => Cypress.$(item).text()))\n    .then((firstColumnCells) => expect(firstColumnCells).to.have.members(values));\n}\n"
  },
  {
    "path": "client/cypress/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"types\": [\"cypress\", \"@percy/cypress\", \"@testing-library/cypress\"]\n  },\n  \"include\": [\"./**/*.ts\"]\n}\n"
  },
  {
    "path": "client/prettier.config.js",
    "content": "module.exports = {\n  printWidth: 120,\n  jsxBracketSameLine: true,\n  tabWidth: 2,\n  trailingComma: 'es5',\n};\n"
  },
  {
    "path": "client/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    // Target latest version of ECMAScript.\n    \"target\": \"es2019\",\n    // Search under node_modules for non-relative imports.\n    \"moduleResolution\": \"node\",\n    // Process & infer types from .js files.\n    \"allowJs\": true,\n    // Don't emit; allow Babel to transform files.\n    \"noEmit\": true,\n    // Enable strictest settings like strictNullChecks & noImplicitAny.\n    \"strict\": true,\n    // Import non-ES modules as default imports.\n    \"esModuleInterop\": true,\n    \"jsx\": \"react\",\n    \"allowSyntheticDefaultImports\": true,\n    \"noUnusedLocals\": true,\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"baseUrl\": \"./\",\n    \"paths\": {\n      \"@/*\": [\"./app/*\"]\n    },\n    \"skipLibCheck\": true,\n    \"typeRoots\": [\"../node_modules/@types\"],\n    \"types\": [\"jest\", \"node\", \"react\", \"react-dom\"]\n  },\n  \"include\": [\"app/**/*\"],\n  \"exclude\": [\"dist\"]\n}\n"
  },
  {
    "path": "codecov.yml",
    "content": "comment:\n  layout: \" diff, flags, files\"\n  behavior: default\n  require_changes: false\n  require_base: true\n  require_head: true\n"
  },
  {
    "path": "compose.yaml",
    "content": "# This configuration file is for the **development** setup.\n# For a production example please refer to getredash/setup repository on GitHub.\nx-redash-service: &redash-service\n  build:\n    context: .\n    args:\n      skip_frontend_build: \"true\"  # set to empty string to build\n  volumes:\n    - .:/app\n  env_file:\n    - .env\nx-redash-environment: &redash-environment\n  REDASH_HOST: http://localhost:5001\n  REDASH_LOG_LEVEL: \"INFO\"\n  REDASH_REDIS_URL: \"redis://redis:6379/0\"\n  REDASH_DATABASE_URL: \"postgresql://postgres@postgres/postgres\"\n  REDASH_RATELIMIT_ENABLED: \"false\"\n  REDASH_MAIL_DEFAULT_SENDER: \"redash@example.com\"\n  REDASH_MAIL_SERVER: \"email\"\n  REDASH_MAIL_PORT: 1025\n  REDASH_ENFORCE_CSRF: \"true\"\n  REDASH_GUNICORN_TIMEOUT: 60\n  # Set secret keys in the .env file\nservices:\n  server:\n    <<: *redash-service\n    command: dev_server\n    depends_on:\n      - postgres\n      - redis\n    ports:\n      - \"5001:5000\"\n      - \"5678:5678\"\n    environment:\n      <<: *redash-environment\n      PYTHONUNBUFFERED: 0\n  scheduler:\n    <<: *redash-service\n    command: dev_scheduler\n    depends_on:\n      - server\n    environment:\n      <<: *redash-environment\n  worker:\n    <<: *redash-service\n    command: dev_worker\n    depends_on:\n      - server\n    environment:\n      <<: *redash-environment\n      PYTHONUNBUFFERED: 0\n  redis:\n    image: redis:7-alpine\n    restart: unless-stopped\n  postgres:\n    image: postgres:18-alpine\n    ports:\n      - \"15432:5432\"\n    # The following turns the DB into less durable, but gains significant performance improvements for the tests run (x3\n    # improvement on my personal machine). We should consider moving this into a dedicated Docker Compose configuration for\n    # tests.\n    command: \"postgres -c fsync=off -c full_page_writes=off -c synchronous_commit=OFF\"\n    restart: unless-stopped\n    environment:\n      POSTGRES_HOST_AUTH_METHOD: \"trust\"\n  email:\n    image: maildev/maildev\n    ports:\n      - \"1080:1080\"\n      - \"1025:1025\"\n    restart: unless-stopped\n"
  },
  {
    "path": "cypress.config.js",
    "content": "const { defineConfig } = require('cypress')\n\nmodule.exports = defineConfig({\n  e2e: {\n    baseUrl: 'http://localhost:5001',\n    defaultCommandTimeout: 20000,\n    downloadsFolder: 'client/cypress/downloads',\n    fixturesFolder: 'client/cypress/fixtures',\n    requestTimeout: 15000,\n    screenshotsFolder: 'client/cypress/screenshots',\n    specPattern: 'client/cypress/integration/',\n    supportFile: 'client/cypress/support/index.js',\n    video: true,\n    videoUploadOnPasses: false,\n    videosFolder: 'client/cypress/videos',\n    viewportHeight: 1024,\n    viewportWidth: 1280,\n    env: {\n      coverage: false\n    }\n  },\n})\n"
  },
  {
    "path": "manage.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nCLI to manage redash.\n\"\"\"\n\nfrom redash.cli import manager\n\nif __name__ == \"__main__\":\n    manager()\n"
  },
  {
    "path": "migrations/0001_warning.py",
    "content": "from __future__ import print_function\n# This is here just to print a warning for users who use the old Fabric upgrade script.\n\nif __name__ == '__main__':\n    warning = \"You're using an outdated upgrade script that is running migrations the wrong way. Please upgrade to \" \\\n              \"newer version of the script before continuning the upgrade process.\"\n    print(\"*\" * 20)\n    print(warning)\n    print(\"*\" * 20)\n    exit(1)\n"
  },
  {
    "path": "migrations/README",
    "content": "Generic single-database configuration."
  },
  {
    "path": "migrations/alembic.ini",
    "content": "# A generic, single database configuration.\n\n[alembic]\n# template used to generate migration files\n# file_template = %%(rev)s_%%(slug)s\n\n# set to 'true' to run the environment during\n# the 'revision' command, regardless of autogenerate\n# revision_environment = false\n\n\n# Logging configuration\n[loggers]\nkeys = root,sqlalchemy,alembic\n\n[handlers]\nkeys = console\n\n[formatters]\nkeys = generic\n\n[logger_root]\nlevel = WARN\nhandlers = console\nqualname =\n\n[logger_sqlalchemy]\nlevel = WARN\nhandlers =\nqualname = sqlalchemy.engine\n\n[logger_alembic]\nlevel = INFO\nhandlers =\nqualname = alembic\n\n[handler_console]\nclass = StreamHandler\nargs = (sys.stderr,)\nlevel = NOTSET\nformatter = generic\n\n[formatter_generic]\nformat = [%(asctime)s][PID:%(process)d][%(levelname)s][%(name)s] %(message)s\n"
  },
  {
    "path": "migrations/env.py",
    "content": "from __future__ import with_statement\nfrom alembic import context\nfrom sqlalchemy import engine_from_config, pool\nfrom logging.config import fileConfig\nimport logging\n\n# this is the Alembic Config object, which provides\n# access to the values within the .ini file in use.\nconfig = context.config\n\n# Interpret the config file for Python logging.\n# This line sets up loggers basically.\nfileConfig(config.config_file_name)\nlogger = logging.getLogger(\"alembic.env\")\n\n# add your model's MetaData object here\n# for 'autogenerate' support\n# from myapp import mymodel\n# target_metadata = mymodel.Base.metadata\nfrom flask import current_app\n\ndb_url_escaped = current_app.config.get(\"SQLALCHEMY_DATABASE_URI\").replace(\"%\", \"%%\")\nconfig.set_main_option(\"sqlalchemy.url\", db_url_escaped)\ntarget_metadata = current_app.extensions[\"migrate\"].db.metadata\n\n# other values from the config, defined by the needs of env.py,\n# can be acquired:\n# my_important_option = config.get_main_option(\"my_important_option\")\n# ... etc.\n\n\ndef run_migrations_offline():\n    \"\"\"Run migrations in 'offline' mode.\n\n    This configures the context with just a URL\n    and not an Engine, though an Engine is acceptable\n    here as well.  By skipping the Engine creation\n    we don't even need a DBAPI to be available.\n\n    Calls to context.execute() here emit the given string to the\n    script output.\n\n    \"\"\"\n    url = config.get_main_option(\"sqlalchemy.url\")\n    context.configure(url=url)\n\n    with context.begin_transaction():\n        context.run_migrations()\n\n\ndef run_migrations_online():\n    \"\"\"Run migrations in 'online' mode.\n\n    In this scenario we need to create an Engine\n    and associate a connection with the context.\n\n    \"\"\"\n\n    # this callback is used to prevent an auto-migration from being generated\n    # when there are no changes to the schema\n    # reference: http://alembic.readthedocs.org/en/latest/cookbook.html\n    def process_revision_directives(context, revision, directives):\n        if getattr(config.cmd_opts, \"autogenerate\", False):\n            script = directives[0]\n            if script.upgrade_ops.is_empty():\n                directives[:] = []\n                logger.info(\"No changes in schema detected.\")\n\n    engine = engine_from_config(\n        config.get_section(config.config_ini_section),\n        prefix=\"sqlalchemy.\",\n        poolclass=pool.NullPool,\n    )\n\n    connection = engine.connect()\n    context.configure(\n        connection=connection,\n        target_metadata=target_metadata,\n        process_revision_directives=process_revision_directives,\n        **current_app.extensions[\"migrate\"].configure_args\n    )\n\n    try:\n        with context.begin_transaction():\n            context.run_migrations()\n    finally:\n        connection.close()\n\n\nif context.is_offline_mode():\n    run_migrations_offline()\nelse:\n    run_migrations_online()\n"
  },
  {
    "path": "migrations/script.py.mako",
    "content": "\"\"\"${message}\n\nRevision ID: ${up_revision}\nRevises: ${down_revision | comma,n}\nCreate Date: ${create_date}\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\n${imports if imports else \"\"}\n\n# revision identifiers, used by Alembic.\nrevision = ${repr(up_revision)}\ndown_revision = ${repr(down_revision)}\nbranch_labels = ${repr(branch_labels)}\ndepends_on = ${repr(depends_on)}\n\n\ndef upgrade():\n    ${upgrades if upgrades else \"pass\"}\n\n\ndef downgrade():\n    ${downgrades if downgrades else \"pass\"}\n"
  },
  {
    "path": "migrations/versions/0ec979123ba4_.py",
    "content": "\"\"\"empty message\n\nRevision ID: 0ec979123ba4\nRevises: e5c7a4e2df4d\nCreate Date: 2020-12-23 21:35:32.766354\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.dialects.postgresql import JSON\n\n# revision identifiers, used by Alembic.\nrevision = '0ec979123ba4'\ndown_revision = 'e5c7a4e2df4d'\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    # ### commands auto generated by Alembic - please adjust! ###\n    op.add_column('dashboards', sa.Column('options', JSON(astext_type=sa.Text()), server_default='{}', nullable=False))\n    # ### end Alembic commands ###\n\n\ndef downgrade():\n    # ### commands auto generated by Alembic - please adjust! ###\n    op.drop_column('dashboards', 'options')\n    # ### end Alembic commands ###\n"
  },
  {
    "path": "migrations/versions/0f740a081d20_inline_tags.py",
    "content": "\"\"\"inline_tags\n\nRevision ID: 0f740a081d20\nRevises: a92d92aa678e\nCreate Date: 2018-05-10 15:47:56.120338\n\n\"\"\"\nimport re\nfrom funcy import flatten, compact\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.sql import text\nfrom redash import models\n\n\n# revision identifiers, used by Alembic.\nrevision = \"0f740a081d20\"\ndown_revision = \"a92d92aa678e\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    tags_regex = re.compile(r\"^([\\w\\s]+):|#([\\w-]+)\", re.I | re.U)\n    connection = op.get_bind()\n\n    dashboards = connection.execute(\"SELECT id, name FROM dashboards\")\n\n    update_query = text(\"UPDATE dashboards SET tags = :tags WHERE id = :id\")\n\n    for dashboard in dashboards:\n        tags = compact(flatten(tags_regex.findall(dashboard[1])))\n        if tags:\n            connection.execute(update_query, tags=tags, id=dashboard[0])\n\n\ndef downgrade():\n    pass\n"
  },
  {
    "path": "migrations/versions/1038c2174f5d_make_case_insensitive_hash_of_query_text.py",
    "content": "\"\"\"Make case insensitive hash of query text\n\nRevision ID: 1038c2174f5d\nRevises: fd4fc850d7ea\nCreate Date: 2023-07-16 23:10:12.885949\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.sql import table\n\nfrom redash.utils import gen_query_hash\n\n# revision identifiers, used by Alembic.\nrevision = '1038c2174f5d'\ndown_revision = 'fd4fc850d7ea'\nbranch_labels = None\ndepends_on = None\n\n\n\ndef change_query_hash(conn, table, query_text_to):\n    for record in conn.execute(table.select()):\n        query_text = query_text_to(record.query)\n        conn.execute(\n            table\n            .update()\n            .where(table.c.id == record.id)\n            .values(query_hash=gen_query_hash(query_text)))\n\n\ndef upgrade():\n    queries = table(\n        'queries',\n        sa.Column('id', sa.Integer, primary_key=True),\n        sa.Column('query', sa.Text),\n        sa.Column('query_hash', sa.String(length=10)))\n\n    conn = op.get_bind()\n    change_query_hash(conn, queries, query_text_to=str)\n\n\ndef downgrade():\n    queries = table(\n        'queries',\n        sa.Column('id', sa.Integer, primary_key=True),\n        sa.Column('query', sa.Text),\n        sa.Column('query_hash', sa.String(length=10)))\n\n    conn = op.get_bind()\n    change_query_hash(conn, queries, query_text_to=str.lower)\n"
  },
  {
    "path": "migrations/versions/1655999df5e3_default_alert_selector.py",
    "content": "\"\"\"set default alert selector\n\nRevision ID: 1655999df5e3\nRevises: 9e8c841d1a30\nCreate Date: 2025-07-09 14:44:00\n\n\"\"\"\n\nfrom alembic import op\n\n# revision identifiers, used by Alembic.\nrevision = '1655999df5e3'\ndown_revision = '9e8c841d1a30'\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.execute(\"\"\"\n    UPDATE alerts\n    SET options = jsonb_set(options, '{selector}', '\"first\"')\n    WHERE options->>'selector' IS NULL;\n    \"\"\")\n\ndef downgrade():\n    pass\n"
  },
  {
    "path": "migrations/versions/1daa601d3ae5_add_columns_for_disabled_users.py",
    "content": "\"\"\"add columns for disabled users\n\nRevision ID: 1daa601d3ae5\nRevises: 969126bd800f\nCreate Date: 2018-03-07 10:20:10.410159\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\n\n\n# revision identifiers, used by Alembic.\nrevision = \"1daa601d3ae5\"\ndown_revision = \"969126bd800f\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.add_column(\"users\", sa.Column(\"disabled_at\", sa.DateTime(True), nullable=True))\n\n\ndef downgrade():\n    op.drop_column(\"users\", \"disabled_at\")\n"
  },
  {
    "path": "migrations/versions/5ec5c84ba61e_.py",
    "content": "\"\"\"Add Query.search_vector field for full text search.\n\nRevision ID: 5ec5c84ba61e\nRevises: 7671dca4e604\nCreate Date: 2017-10-17 18:21:00.174015\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nimport sqlalchemy_utils as su\nimport sqlalchemy_searchable as ss\n\n\n# revision identifiers, used by Alembic.\nrevision = \"5ec5c84ba61e\"\ndown_revision = \"7671dca4e604\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    conn = op.get_bind()\n    op.add_column(\"queries\", sa.Column(\"search_vector\", su.TSVectorType()))\n    op.create_index(\n        \"ix_queries_search_vector\",\n        \"queries\",\n        [\"search_vector\"],\n        unique=False,\n        postgresql_using=\"gin\",\n    )\n    ss.sync_trigger(conn, \"queries\", \"search_vector\", [\"name\", \"description\", \"query\"])\n\n\ndef downgrade():\n    conn = op.get_bind()\n\n    ss.drop_trigger(conn, \"queries\", \"search_vector\")\n    op.drop_index(\"ix_queries_search_vector\", table_name=\"queries\")\n    op.drop_column(\"queries\", \"search_vector\")\n"
  },
  {
    "path": "migrations/versions/640888ce445d_.py",
    "content": "\"\"\"\nAdd new scheduling data.\n\nRevision ID: 640888ce445d\nRevises: 71477dadd6ef\nCreate Date: 2018-09-21 19:35:58.578796\n\"\"\"\n\nimport json\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.sql import table\nfrom redash.models import MutableDict\n\n\n# revision identifiers, used by Alembic.\nrevision = \"640888ce445d\"\ndown_revision = \"71477dadd6ef\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    # Copy \"schedule\" column into \"old_schedule\" column\n    op.add_column(\n        \"queries\", sa.Column(\"old_schedule\", sa.String(length=10), nullable=True)\n    )\n\n    queries = table(\n        \"queries\",\n        sa.Column(\"schedule\", sa.String(length=10)),\n        sa.Column(\"old_schedule\", sa.String(length=10)),\n    )\n\n    op.execute(queries.update().values({\"old_schedule\": queries.c.schedule}))\n\n    # Recreate \"schedule\" column as a dict type\n    op.drop_column(\"queries\", \"schedule\")\n    op.add_column(\n        \"queries\",\n        sa.Column(\n            \"schedule\",\n            sa.Text(),\n            nullable=False,\n            server_default=json.dumps({}),\n        ),\n    )\n\n    # Move over values from old_schedule\n    queries = table(\n        \"queries\",\n        sa.Column(\"id\", sa.Integer, primary_key=True),\n        sa.Column(\"schedule\", sa.Text()),\n        sa.Column(\"old_schedule\", sa.String(length=10)),\n    )\n\n    conn = op.get_bind()\n    for query in conn.execute(queries.select()):\n        schedule_json = {\n            \"interval\": None,\n            \"until\": None,\n            \"day_of_week\": None,\n            \"time\": None,\n        }\n\n        if query.old_schedule is not None:\n            if \":\" in query.old_schedule:\n                schedule_json[\"interval\"] = 86400\n                schedule_json[\"time\"] = query.old_schedule\n            else:\n                schedule_json[\"interval\"] = int(query.old_schedule)\n\n        conn.execute(\n            queries.update()\n            .where(queries.c.id == query.id)\n            .values(schedule=MutableDict(schedule_json))\n        )\n\n    op.drop_column(\"queries\", \"old_schedule\")\n\n\ndef downgrade():\n    op.add_column(\n        \"queries\",\n        sa.Column(\n            \"old_schedule\",\n            sa.Text(),\n            nullable=False,\n            server_default=json.dumps({}),\n        ),\n    )\n\n    queries = table(\n        \"queries\",\n        sa.Column(\"schedule\", sa.Text()),\n        sa.Column(\"old_schedule\", sa.Text()),\n    )\n\n    op.execute(queries.update().values({\"old_schedule\": queries.c.schedule}))\n\n    op.drop_column(\"queries\", \"schedule\")\n    op.add_column(\"queries\", sa.Column(\"schedule\", sa.String(length=10), nullable=True))\n\n    queries = table(\n        \"queries\",\n        sa.Column(\"id\", sa.Integer, primary_key=True),\n        sa.Column(\"schedule\", sa.String(length=10)),\n        sa.Column(\"old_schedule\", sa.Text()),\n    )\n\n    conn = op.get_bind()\n    for query in conn.execute(queries.select()):\n        scheduleValue = query.old_schedule[\"interval\"]\n        if scheduleValue <= 86400:\n            scheduleValue = query.old_schedule[\"time\"]\n\n        conn.execute(\n            queries.update()\n            .where(queries.c.id == query.id)\n            .values(schedule=scheduleValue)\n        )\n\n    op.drop_column(\"queries\", \"old_schedule\")\n"
  },
  {
    "path": "migrations/versions/65fc9ede4746_add_is_draft_status_to_queries_and_.py",
    "content": "\"\"\"Add is_draft status to queries and dashboards\n\nRevision ID: 65fc9ede4746\nRevises:\nCreate Date: 2016-12-07 18:08:13.395586\n\n\"\"\"\n\nfrom alembic import op\nimport sqlalchemy as sa\n\n# revision identifiers, used by Alembic.\nfrom sqlalchemy.exc import ProgrammingError\n\nrevision = \"65fc9ede4746\"\ndown_revision = None\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    try:\n        op.add_column(\n            \"queries\", sa.Column(\"is_draft\", sa.Boolean, default=True, index=True)\n        )\n        op.add_column(\n            \"dashboards\", sa.Column(\"is_draft\", sa.Boolean, default=True, index=True)\n        )\n        op.execute(\"UPDATE queries SET is_draft = (name = 'New Query')\")\n        op.execute(\"UPDATE dashboards SET is_draft = false\")\n    except ProgrammingError as e:\n        # The columns might exist if you ran the old migrations.\n        if 'column \"is_draft\" of relation \"queries\" already exists' in str(e):\n            print(\n                \"Can't run this migration as you already have is_draft columns, please run:\"\n            )\n            print(\n                \"./manage.py db stamp {} # you might need to alter the command to match your environment.\".format(\n                    revision\n                )\n            )\n            exit()\n\n\ndef downgrade():\n    op.drop_column(\"queries\", \"is_draft\")\n    op.drop_column(\"dashboards\", \"is_draft\")\n"
  },
  {
    "path": "migrations/versions/6b5be7e0a0ef_.py",
    "content": "\"\"\"Re-index Query.search_vector with existing queries.\n\nRevision ID: 6b5be7e0a0ef\nRevises: 5ec5c84ba61e\nCreate Date: 2017-11-02 20:42:13.356360\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nimport sqlalchemy_searchable as ss\n\n\n# revision identifiers, used by Alembic.\nrevision = \"6b5be7e0a0ef\"\ndown_revision = \"5ec5c84ba61e\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    ss.vectorizer.clear()\n\n    conn = op.get_bind()\n\n    metadata = sa.MetaData(bind=conn)\n    queries = sa.Table(\"queries\", metadata, autoload=True)\n\n    @ss.vectorizer(queries.c.id)\n    def integer_vectorizer(column):\n        return sa.func.cast(column, sa.Text)\n\n    ss.sync_trigger(\n        conn,\n        \"queries\",\n        \"search_vector\",\n        [\"id\", \"name\", \"description\", \"query\"],\n        metadata=metadata,\n    )\n\n\ndef downgrade():\n    conn = op.get_bind()\n    ss.drop_trigger(conn, \"queries\", \"search_vector\")\n    op.drop_index(\"ix_queries_search_vector\", table_name=\"queries\")\n    op.create_index(\n        \"ix_queries_search_vector\",\n        \"queries\",\n        [\"search_vector\"],\n        unique=False,\n        postgresql_using=\"gin\",\n    )\n    ss.sync_trigger(conn, \"queries\", \"search_vector\", [\"name\", \"description\", \"query\"])\n"
  },
  {
    "path": "migrations/versions/71477dadd6ef_favorites_unique_constraint.py",
    "content": "\"\"\"favorites_unique_constraint\n\nRevision ID: 71477dadd6ef\nRevises: 0f740a081d20\nCreate Date: 2018-07-11 12:49:52.792123\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\n\n\n# revision identifiers, used by Alembic.\nrevision = \"71477dadd6ef\"\ndown_revision = \"0f740a081d20\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.create_unique_constraint(\n        \"unique_favorite\", \"favorites\", [\"object_type\", \"object_id\", \"user_id\"]\n    )\n\n\ndef downgrade():\n    op.drop_constraint(\"unique_favorite\", \"favorites\", type_=\"unique\")\n"
  },
  {
    "path": "migrations/versions/7205816877ec_change_type_of_json_fields_from_varchar_.py",
    "content": "\"\"\"change type of json fields from varchar to json\n\nRevision ID: 7205816877ec\nRevises: 7ce5925f832b\nCreate Date: 2024-01-03 13:55:18.885021\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.dialects.postgresql import JSONB, JSON\n\n\n# revision identifiers, used by Alembic.\nrevision = '7205816877ec'\ndown_revision = '7ce5925f832b'\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    connection = op.get_bind()\n    op.alter_column('queries', 'options',\n        existing_type=sa.Text(),\n        type_=JSONB(astext_type=sa.Text()),\n        nullable=True,\n        postgresql_using='options::jsonb',\n        )\n    op.alter_column('queries', 'schedule',\n        existing_type=sa.Text(),\n        type_=JSONB(astext_type=sa.Text()),\n        nullable=True,\n        postgresql_using='schedule::jsonb',\n        )\n    op.alter_column('events', 'additional_properties',\n        existing_type=sa.Text(),\n        type_=JSONB(astext_type=sa.Text()),\n        nullable=True,\n        postgresql_using='additional_properties::jsonb',\n        )\n    op.alter_column('organizations', 'settings',\n        existing_type=sa.Text(),\n        type_=JSONB(astext_type=sa.Text()),\n        nullable=True,\n        postgresql_using='settings::jsonb',\n        )\n    op.alter_column('alerts', 'options',\n        existing_type=JSON(astext_type=sa.Text()),\n        type_=JSONB(astext_type=sa.Text()),\n        nullable=True,\n        postgresql_using='options::jsonb',\n        )\n    op.alter_column('dashboards', 'options',\n        existing_type=JSON(astext_type=sa.Text()),\n        type_=JSONB(astext_type=sa.Text()),\n        postgresql_using='options::jsonb',\n        )\n    op.alter_column('dashboards', 'layout',\n        existing_type=sa.Text(),\n        type_=JSONB(astext_type=sa.Text()),\n        postgresql_using='layout::jsonb',\n        )\n    op.alter_column('changes', 'change',\n        existing_type=JSON(astext_type=sa.Text()),\n        type_=JSONB(astext_type=sa.Text()),\n        postgresql_using='change::jsonb',\n        )\n    op.alter_column('visualizations', 'options',\n        existing_type=sa.Text(),\n        type_=JSONB(astext_type=sa.Text()),\n        postgresql_using='options::jsonb',\n        )\n    op.alter_column('widgets', 'options',\n        existing_type=sa.Text(),\n        type_=JSONB(astext_type=sa.Text()),\n        postgresql_using='options::jsonb',\n        )\n\n\ndef downgrade():\n    connection = op.get_bind()\n    op.alter_column('queries', 'options',\n        existing_type=JSONB(astext_type=sa.Text()),\n        type_=sa.Text(),\n        postgresql_using='options::text',\n        existing_nullable=True,\n        )\n    op.alter_column('queries', 'schedule',\n        existing_type=JSONB(astext_type=sa.Text()),\n        type_=sa.Text(),\n        postgresql_using='schedule::text',\n        existing_nullable=True,\n        )\n    op.alter_column('events', 'additional_properties',\n        existing_type=JSONB(astext_type=sa.Text()),\n        type_=sa.Text(),\n        postgresql_using='additional_properties::text',\n        existing_nullable=True,\n        )\n    op.alter_column('organizations', 'settings',\n        existing_type=JSONB(astext_type=sa.Text()),\n        type_=sa.Text(),\n        postgresql_using='settings::text',\n        existing_nullable=True,\n        )\n    op.alter_column('alerts', 'options',\n        existing_type=JSONB(astext_type=sa.Text()),\n        type_=JSON(astext_type=sa.Text()),\n        postgresql_using='options::json',\n        existing_nullable=True,\n        )\n    op.alter_column('dashboards', 'options',\n        existing_type=JSONB(astext_type=sa.Text()),\n        type_=JSON(astext_type=sa.Text()),\n        postgresql_using='options::json',\n        )\n    op.alter_column('dashboards', 'layout',\n        existing_type=JSONB(astext_type=sa.Text()),\n        type_=sa.Text(),\n        postgresql_using='layout::text',\n        )\n    op.alter_column('changes', 'change',\n        existing_type=JSONB(astext_type=sa.Text()),\n        type_=JSON(astext_type=sa.Text()),\n        postgresql_using='change::json',\n        )\n    op.alter_column('visualizations', 'options',\n        type_=sa.Text(),\n        existing_type=JSONB(astext_type=sa.Text()),\n        postgresql_using='options::text',\n        )\n    op.alter_column('widgets', 'options',\n        type_=sa.Text(),\n        existing_type=JSONB(astext_type=sa.Text()),\n        postgresql_using='options::text',\n        )\n"
  },
  {
    "path": "migrations/versions/73beceabb948_bring_back_null_schedule.py",
    "content": "\"\"\"bring_back_null_schedule\n\nRevision ID: 73beceabb948\nRevises: e7f8a917aa8e\nCreate Date: 2019-01-17 13:22:21.729334\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.sql import table\n\nfrom redash.models import MutableDict\n\n# revision identifiers, used by Alembic.\nrevision = \"73beceabb948\"\ndown_revision = \"e7f8a917aa8e\"\nbranch_labels = None\ndepends_on = None\n\n\ndef is_empty_schedule(schedule):\n    if schedule is None:\n        return False\n\n    if schedule == {}:\n        return True\n\n    if (\n        schedule.get(\"interval\") is None\n        and schedule.get(\"until\") is None\n        and schedule.get(\"day_of_week\") is None\n        and schedule.get(\"time\") is None\n    ):\n        return True\n\n    return False\n\n\ndef upgrade():\n    op.alter_column(\"queries\", \"schedule\", nullable=True, server_default=None)\n\n    queries = table(\n        \"queries\",\n        sa.Column(\"id\", sa.Integer, primary_key=True),\n        sa.Column(\"schedule\", sa.Text()),\n    )\n\n    conn = op.get_bind()\n    for query in conn.execute(queries.select()):\n        if is_empty_schedule(query.schedule):\n            conn.execute(\n                queries.update().where(queries.c.id == query.id).values(schedule=None)\n            )\n\n\ndef downgrade():\n    pass\n"
  },
  {
    "path": "migrations/versions/7671dca4e604_.py",
    "content": "\"\"\"empty message\n\nRevision ID: 7671dca4e604\nRevises: d1eae8b9893e\nCreate Date: 2017-11-22 22:20:25.166045\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\n\n\n# revision identifiers, used by Alembic.\nrevision = \"7671dca4e604\"\ndown_revision = \"d1eae8b9893e\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.add_column(\n        \"users\",\n        sa.Column(\"profile_image_url\", sa.String(), nullable=True, server_default=None),\n    )\n\n\ndef downgrade():\n    op.drop_column(\"users\", \"profile_image_url\")\n"
  },
  {
    "path": "migrations/versions/7ce5925f832b_create_sqlalchemy_searchable_expressions.py",
    "content": "\"\"\"create sqlalchemy_searchable expressions\n\nRevision ID: 7ce5925f832b\nRevises: 1038c2174f5d\nCreate Date: 2023-09-29 16:48:29.517762\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy_searchable import sql_expressions\n\n\n# revision identifiers, used by Alembic.\nrevision = '7ce5925f832b'\ndown_revision = '1038c2174f5d'\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.execute(sql_expressions)\n\n\ndef downgrade():\n    pass\n"
  },
  {
    "path": "migrations/versions/89bc7873a3e0_fix_multiple_heads.py",
    "content": "\"\"\"fix_multiple_heads\n\nRevision ID: 89bc7873a3e0\nRevises: 0ec979123ba4, d7d747033183\nCreate Date: 2021-01-21 18:11:04.312259\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\n\n\n# revision identifiers, used by Alembic.\nrevision = '89bc7873a3e0'\ndown_revision = ('0ec979123ba4', 'd7d747033183')\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    pass\n\n\ndef downgrade():\n    pass\n"
  },
  {
    "path": "migrations/versions/969126bd800f_.py",
    "content": "\"\"\"Update widget's position data based on dashboard layout.\n\nRevision ID: 969126bd800f\nRevises: 6b5be7e0a0ef\nCreate Date: 2018-01-31 15:20:30.396533\n\n\"\"\"\n\nimport json\nfrom alembic import op\nimport sqlalchemy as sa\n\nfrom redash.models import Dashboard, Widget, db\n\n\n# revision identifiers, used by Alembic.\nrevision = \"969126bd800f\"\ndown_revision = \"6b5be7e0a0ef\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    # Update widgets position data:\n    column_size = 3\n    print(\"Updating dashboards position data:\")\n    dashboard_result = db.session.execute(\"SELECT id, layout FROM dashboards\")\n    for dashboard in dashboard_result:\n        print(\"  Updating dashboard: {}\".format(dashboard[\"id\"]))\n        layout = json.loads(dashboard[\"layout\"])\n\n        print(\"    Building widgets map:\")\n        widgets = {}\n        widget_result = db.session.execute(\n            \"SELECT id, options, width FROM widgets WHERE dashboard_id=:dashboard_id\",\n            {\"dashboard_id\": dashboard[\"id\"]},\n        )\n        for w in widget_result:\n            print(\"    Widget: {}\".format(w[\"id\"]))\n            widgets[w[\"id\"]] = w\n        widget_result.close()\n\n        print(\"    Iterating over layout:\")\n        for row_index, row in enumerate(layout):\n            print(\"      Row: {} - {}\".format(row_index, row))\n            if row is None:\n                continue\n\n            for column_index, widget_id in enumerate(row):\n                print(\"      Column: {} - {}\".format(column_index, widget_id))\n                widget = widgets.get(widget_id)\n\n                if widget is None:\n                    continue\n\n                options = json.loads(widget[\"options\"]) or {}\n                options[\"position\"] = {\n                    \"row\": row_index,\n                    \"col\": column_index * column_size,\n                    \"sizeX\": column_size * widget.width,\n                }\n\n                db.session.execute(\n                    \"UPDATE widgets SET options=:options WHERE id=:id\",\n                    {\"options\": json.dumps(options), \"id\": widget_id},\n                )\n\n    dashboard_result.close()\n    db.session.commit()\n\n    # Remove legacy columns no longer in use.\n    op.drop_column(\"widgets\", \"type\")\n    op.drop_column(\"widgets\", \"query_id\")\n\n\ndef downgrade():\n    op.add_column(\n        \"widgets\",\n        sa.Column(\"query_id\", sa.INTEGER(), autoincrement=False, nullable=True),\n    )\n    op.add_column(\n        \"widgets\",\n        sa.Column(\"type\", sa.VARCHAR(length=100), autoincrement=False, nullable=True),\n    )\n"
  },
  {
    "path": "migrations/versions/98af61feea92_add_encrypted_options_to_data_sources.py",
    "content": "\"\"\"add_encrypted_options_to_data_sources\n\nRevision ID: 98af61feea92\nRevises: 73beceabb948\nCreate Date: 2019-01-31 09:21:31.517265\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.dialects.postgresql import BYTEA\nfrom sqlalchemy.sql import table\nfrom sqlalchemy_utils.types.encrypted.encrypted_type import FernetEngine\n\nfrom redash import settings\nfrom redash.utils.configuration import ConfigurationContainer\nfrom redash.models.types import (\n    EncryptedConfiguration,\n    Configuration,\n    MutableDict,\n    MutableList,\n)\n\n# revision identifiers, used by Alembic.\nrevision = \"98af61feea92\"\ndown_revision = \"73beceabb948\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.add_column(\n        \"data_sources\",\n        sa.Column(\"encrypted_options\", BYTEA(), nullable=True),\n    )\n\n    # copy values\n    data_sources = table(\n        \"data_sources\",\n        sa.Column(\"id\", sa.Integer, primary_key=True),\n        sa.Column(\n            \"encrypted_options\",\n            ConfigurationContainer.as_mutable(\n                EncryptedConfiguration(\n                    sa.Text, settings.DATASOURCE_SECRET_KEY, FernetEngine\n                )\n            ),\n        ),\n        sa.Column(\"options\", ConfigurationContainer.as_mutable(Configuration)),\n    )\n\n    conn = op.get_bind()\n    for ds in conn.execute(data_sources.select()):\n        conn.execute(\n            data_sources.update()\n            .where(data_sources.c.id == ds.id)\n            .values(encrypted_options=ds.options)\n        )\n\n    op.drop_column(\"data_sources\", \"options\")\n    op.alter_column(\"data_sources\", \"encrypted_options\", nullable=False)\n\n\ndef downgrade():\n    pass\n"
  },
  {
    "path": "migrations/versions/9e8c841d1a30_fix_hash.py",
    "content": "\"\"\"fix_hash\n\nRevision ID: 9e8c841d1a30\nRevises: 7205816877ec\nCreate Date: 2024-10-05 18:55:35.730573\n\n\"\"\"\nimport logging\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.sql import table\nfrom sqlalchemy import select\n\nfrom redash.query_runner import BaseQueryRunner, get_query_runner\n\n\n# revision identifiers, used by Alembic.\nrevision = '9e8c841d1a30'\ndown_revision = '7205816877ec'\nbranch_labels = None\ndepends_on = None\n\n\ndef update_query_hash(record):\n    should_apply_auto_limit = record['options'].get(\"apply_auto_limit\", False) if record['options'] else False\n    query_runner = get_query_runner(record['type'], {}) if record['type'] else BaseQueryRunner({})\n    query_text = record['query']\n\n    parameters_dict = {p[\"name\"]: p.get(\"value\") for p in record['options'].get('parameters', [])} if record.options else {}\n    if any(parameters_dict):\n        print(f\"Query {record['query_id']} has parameters. Hash might be incorrect.\")\n\n    return query_runner.gen_query_hash(query_text, should_apply_auto_limit)\n\n\ndef upgrade():\n    conn = op.get_bind()\n\n    metadata = sa.MetaData(bind=conn)\n    queries = sa.Table(\"queries\", metadata, autoload=True)\n    data_sources = sa.Table(\"data_sources\", metadata, autoload=True)\n\n    joined_table = queries.outerjoin(data_sources, queries.c.data_source_id == data_sources.c.id)\n\n    query = select([\n        queries.c.id.label(\"query_id\"),\n        queries.c.query,\n        queries.c.query_hash,\n        queries.c.options,\n        data_sources.c.id.label(\"data_source_id\"),\n        data_sources.c.type\n    ]).select_from(joined_table)\n\n    for record in conn.execute(query):\n        new_hash = update_query_hash(record)\n        print(f\"Updating hash for query {record['query_id']} from {record['query_hash']} to {new_hash}\")\n        conn.execute(\n            queries.update()\n            .where(queries.c.id == record['query_id'])\n            .values(query_hash=new_hash))\n\n\ndef downgrade():\n    pass"
  },
  {
    "path": "migrations/versions/a92d92aa678e_inline_tags.py",
    "content": "\"\"\"inline_tags\n\nRevision ID: a92d92aa678e\nRevises: e7004224f284\nCreate Date: 2018-05-10 15:41:28.053237\n\n\"\"\"\nimport re\nfrom funcy import flatten, compact\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.dialects.postgresql import ARRAY\nfrom redash import models\n\n# revision identifiers, used by Alembic.\nrevision = \"a92d92aa678e\"\ndown_revision = \"e7004224f284\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.add_column(\n        \"dashboards\", sa.Column(\"tags\", ARRAY(sa.Unicode()), nullable=True)\n    )\n    op.add_column(\n        \"queries\", sa.Column(\"tags\", ARRAY(sa.Unicode()), nullable=True)\n    )\n\n\ndef downgrade():\n    op.drop_column(\"queries\", \"tags\")\n    op.drop_column(\"dashboards\", \"tags\")\n"
  },
  {
    "path": "migrations/versions/d1eae8b9893e_.py",
    "content": "\"\"\"add Query.schedule_failures\n\nRevision ID: d1eae8b9893e\nRevises: 65fc9ede4746\nCreate Date: 2017-02-03 01:45:02.954923\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\n\n\n# revision identifiers, used by Alembic.\nrevision = \"d1eae8b9893e\"\ndown_revision = \"65fc9ede4746\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.add_column(\n        \"queries\",\n        sa.Column(\n            \"schedule_failures\", sa.Integer(), nullable=False, server_default=\"0\"\n        ),\n    )\n\n\ndef downgrade():\n    op.drop_column(\"queries\", \"schedule_failures\")\n"
  },
  {
    "path": "migrations/versions/d4c798575877_create_favorites.py",
    "content": "\"\"\"empty message\n\nRevision ID: d4c798575877\nRevises: 1daa601d3ae5\nCreate Date: 2018-05-09 10:28:22.931442\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\n\n\n# revision identifiers, used by Alembic.\nrevision = \"d4c798575877\"\ndown_revision = \"1daa601d3ae5\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.create_table(\n        \"favorites\",\n        sa.Column(\"updated_at\", sa.DateTime(timezone=True), nullable=False),\n        sa.Column(\"created_at\", sa.DateTime(timezone=True), nullable=False),\n        sa.Column(\"id\", sa.Integer(), nullable=False),\n        sa.Column(\"object_type\", sa.Unicode(length=255), nullable=False),\n        sa.Column(\"object_id\", sa.Integer(), nullable=False),\n        sa.Column(\"user_id\", sa.Integer(), nullable=False),\n        sa.ForeignKeyConstraint([\"user_id\"], [\"users.id\"]),\n        sa.PrimaryKeyConstraint(\"id\"),\n    )\n\n\ndef downgrade():\n    op.drop_table(\"favorites\")\n"
  },
  {
    "path": "migrations/versions/d7d747033183_encrypt_alert_destinations.py",
    "content": "\"\"\"encrypt alert destinations\n\nRevision ID: d7d747033183\nRevises: e5c7a4e2df4d\nCreate Date: 2020-12-14 21:42:48.661684\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.dialects.postgresql import BYTEA\nfrom sqlalchemy.sql import table\nfrom sqlalchemy_utils.types.encrypted.encrypted_type import FernetEngine\n\nfrom redash import settings\nfrom redash.utils.configuration import ConfigurationContainer\nfrom redash.models.base import key_type\nfrom redash.models.types import (\n    EncryptedConfiguration,\n    Configuration,\n)\n\n\n# revision identifiers, used by Alembic.\nrevision = 'd7d747033183'\ndown_revision = 'e5c7a4e2df4d'\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.add_column(\n        \"notification_destinations\",\n        sa.Column(\"encrypted_options\", BYTEA(), nullable=True)\n    )\n\n    # copy values\n    notification_destinations = table(\n        \"notification_destinations\",\n        sa.Column(\"id\", key_type(\"NotificationDestination\"), primary_key=True),\n        sa.Column(\n            \"encrypted_options\",\n            ConfigurationContainer.as_mutable(\n                EncryptedConfiguration(\n                    sa.Text, settings.DATASOURCE_SECRET_KEY, FernetEngine\n                )\n            ),\n        ),\n        sa.Column(\"options\", ConfigurationContainer.as_mutable(Configuration)),\n    )\n\n    conn = op.get_bind()\n    for dest in conn.execute(notification_destinations.select()):\n        conn.execute(\n            notification_destinations.update()\n                .where(notification_destinations.c.id == dest.id)\n                .values(encrypted_options=dest.options)\n        )\n\n    op.drop_column(\"notification_destinations\", \"options\")\n    op.alter_column(\"notification_destinations\", \"encrypted_options\", nullable=False)\n\n\ndef downgrade():\n    pass\n"
  },
  {
    "path": "migrations/versions/db0aca1ebd32_12_column_dashboard_layout.py",
    "content": "\"\"\"12-column dashboard layout\n\nRevision ID: db0aca1ebd32\nRevises: 1655999df5e3\nCreate Date: 2025-03-31 13:45:43.160893\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\n\n\n# revision identifiers, used by Alembic.\nrevision = 'db0aca1ebd32'\ndown_revision = '1655999df5e3'\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.execute(\"\"\"\n    UPDATE widgets\n    SET options = jsonb_set(options, '{position,col}', to_json((options->'position'->>'col')::int * 2)::jsonb);\n    UPDATE widgets\n    SET options = jsonb_set(options, '{position,sizeX}', to_json((options->'position'->>'sizeX')::int * 2)::jsonb);\n    \"\"\")\n\n\ndef downgrade():\n    op.execute(\"\"\"\n    UPDATE widgets\n    SET options = jsonb_set(options, '{position,col}', to_json((options->'position'->>'col')::int / 2)::jsonb);\n    UPDATE widgets\n    SET options = jsonb_set(options, '{position,sizeX}', to_json((options->'position'->>'sizeX')::int / 2)::jsonb);\n    \"\"\")\n"
  },
  {
    "path": "migrations/versions/e5c7a4e2df4d_remove_query_tracker_keys.py",
    "content": "\"\"\"remove_query_tracker_keys\n\nRevision ID: e5c7a4e2df4d\nRevises: 98af61feea92\nCreate Date: 2019-02-27 11:30:15.375318\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\n\nfrom redash import redis_connection\n\n\n# revision identifiers, used by Alembic.\nrevision = \"e5c7a4e2df4d\"\ndown_revision = \"98af61feea92\"\nbranch_labels = None\ndepends_on = None\n\n\nDONE_LIST = \"query_task_trackers:done\"\nWAITING_LIST = \"query_task_trackers:waiting\"\nIN_PROGRESS_LIST = \"query_task_trackers:in_progress\"\n\n\ndef prune(list_name, keep_count, max_keys=100):\n    count = redis_connection.zcard(list_name)\n    if count <= keep_count:\n        return 0\n\n    remove_count = min(max_keys, count - keep_count)\n    keys = redis_connection.zrange(list_name, 0, remove_count - 1)\n    redis_connection.delete(*keys)\n    redis_connection.zremrangebyrank(list_name, 0, remove_count - 1)\n    return remove_count\n\n\ndef prune_all(list_name):\n    removed = 1000\n    while removed > 0:\n        removed = prune(list_name, 0)\n\n\ndef upgrade():\n    prune_all(DONE_LIST)\n    prune_all(WAITING_LIST)\n    prune_all(IN_PROGRESS_LIST)\n\n\ndef downgrade():\n    pass\n"
  },
  {
    "path": "migrations/versions/e7004224f284_add_org_id_to_favorites.py",
    "content": "\"\"\"add_org_id_to_favorites\n\nRevision ID: e7004224f284\nRevises: d4c798575877\nCreate Date: 2018-05-10 09:46:31.169938\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\n\n\n# revision identifiers, used by Alembic.\nrevision = \"e7004224f284\"\ndown_revision = \"d4c798575877\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.add_column(\"favorites\", sa.Column(\"org_id\", sa.Integer(), nullable=False))\n    op.create_foreign_key(None, \"favorites\", \"organizations\", [\"org_id\"], [\"id\"])\n\n\ndef downgrade():\n    op.drop_constraint(None, \"favorites\", type_=\"foreignkey\")\n    op.drop_column(\"favorites\", \"org_id\")\n"
  },
  {
    "path": "migrations/versions/e7f8a917aa8e_add_user_details_json_column.py",
    "content": "\"\"\"Add user details JSON column.\n\nRevision ID: e7f8a917aa8e\nRevises: 71477dadd6ef\nCreate Date: 2018-11-08 16:12:17.023569\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.dialects.postgresql import JSON\n\n# revision identifiers, used by Alembic.\nrevision = \"e7f8a917aa8e\"\ndown_revision = \"640888ce445d\"\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    op.add_column(\n        \"users\",\n        sa.Column(\n            \"details\",\n            JSON(astext_type=sa.Text()),\n            server_default=\"{}\",\n            nullable=True,\n        ),\n    )\n\n\ndef downgrade():\n    op.drop_column(\"users\", \"details\")\n"
  },
  {
    "path": "migrations/versions/fd4fc850d7ea_.py",
    "content": "\"\"\"Convert user details to jsonb and move user profile image url into details column\n\nRevision ID: fd4fc850d7ea\nRevises: 89bc7873a3e0\nCreate Date: 2022-01-31 15:24:16.507888\n\n\"\"\"\nfrom alembic import op\nimport sqlalchemy as sa\nfrom sqlalchemy.dialects.postgresql import JSON, JSONB\n\nfrom redash.models import db\n\n# revision identifiers, used by Alembic.\nrevision = 'fd4fc850d7ea'\ndown_revision = '89bc7873a3e0'\nbranch_labels = None\ndepends_on = None\n\n\ndef upgrade():\n    connection = op.get_bind()\n\n    ### commands auto generated by Alembic - please adjust! ###\n    op.alter_column('users', 'details',\n               existing_type=JSON(astext_type=sa.Text()),\n               type_=JSONB(astext_type=sa.Text()),\n               existing_nullable=True,\n               existing_server_default=sa.text(\"'{}'::jsonb\"))\n    ### end Alembic commands ###\n\n    update_query = \"\"\"\n    update users\n    set details = details::jsonb || ('{\"profile_image_url\": \"' || profile_image_url || '\"}')::jsonb\n    where 1=1\n    \"\"\"\n    connection.execute(update_query)\n    op.drop_column(\"users\", \"profile_image_url\")\n\n\ndef downgrade():\n    # ### commands auto generated by Alembic - please adjust! ###\n    connection = op.get_bind()\n    op.add_column(\"users\", sa.Column(\"profile_image_url\", db.String(320), nullable=True))\n\n    update_query = \"\"\"\n    update users set\n    profile_image_url = details->>'profile_image_url',\n    details = details - 'profile_image_url' ;\n    \"\"\"\n\n    connection.execute(update_query)\n    db.session.commit()\n    op.alter_column('users', 'details',\n               existing_type=JSONB(astext_type=sa.Text()),\n               type_=JSON(astext_type=sa.Text()),\n               existing_nullable=True,\n               existing_server_default=sa.text(\"'{}'::json\"))\n\n    # ### end Alembic commands ###\n"
  },
  {
    "path": "netlify.toml",
    "content": "[build]\n  base    = \"client\"\n  publish = \"client/dist\"\n  command = \"cd ../ && pnpm install --frozen-lockfile && pnpm run build && cd ./client\"\n\n[build.environment]\n  NODE_VERSION = \"24\"\n  CYPRESS_INSTALL_BINARY = \"0\"\n  PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = \"1\"\n\n[[redirects]]\n  from = \"/api/*\"\n  to = \"http://preview-backend.redashapp.com/api/:splat\"\n  status = 200\n\n[[redirects]]\n  from = \"/login\"\n  to = \"http://preview-login.redashapp.com/login\"\n  status = 200\n\n[[redirects]]\n  from = \"/logout\"\n  to = \"http://preview-backend.redashapp.com/logout\"\n  status = 200\n\n[[redirects]]\n  from = \"/status.json\"\n  to = \"http://preview-backend.redashapp.com/status.json\"\n  status = 200\n\n[[redirects]]\n  from = \"/static/server*\"\n  to = \"http://preview-backend.redashapp.com/static/server:splat\"\n  status = 200\n\n[[redirects]]\n  from = \"/static/*\"\n  to = \"/:splat\"\n  status = 200\n\n[[redirects]]\n  from = \"/*\"\n  to = \"/index.html\"\n  status = 200\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"redash-client\",\n  \"version\": \"26.03.0-dev\",\n  \"description\": \"The frontend part of Redash.\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"npm-run-all --parallel watch:viz webpack-dev-server\",\n    \"clean\": \"rm -rf ./client/dist/\",\n    \"build:viz\": \"pnpm --filter @redash/viz build:babel\",\n    \"build\": \"pnpm run clean && pnpm run build:viz && NODE_ENV=production webpack\",\n    \"watch:app\": \"webpack watch --progress\",\n    \"watch:viz\": \"pnpm --filter @redash/viz watch:babel\",\n    \"watch\": \"npm-run-all --parallel watch:*\",\n    \"webpack-dev-server\": \"webpack-dev-server\",\n    \"analyze\": \"pnpm run clean && BUNDLE_ANALYZER=on webpack\",\n    \"analyze:build\": \"pnpm run clean && NODE_ENV=production BUNDLE_ANALYZER=on webpack\",\n    \"lint\": \"pnpm run lint:base --ext .js --ext .jsx --ext .ts --ext .tsx ./client\",\n    \"lint:fix\": \"pnpm run lint:base  --fix --ext .js --ext .jsx --ext .ts --ext .tsx ./client\",\n    \"lint:base\": \"eslint --config ./client/.eslintrc.js --ignore-path ./client/.eslintignore\",\n    \"lint:ci\": \"pnpm run lint --max-warnings 0 --format junit --output-file /tmp/test-results/eslint/results.xml\",\n    \"prettier\": \"prettier --write 'client/app/**/*.{js,jsx,ts,tsx}' 'client/cypress/**/*.{js,jsx,ts,tsx}'\",\n    \"type-check\": \"tsc --noEmit --project client/tsconfig.json\",\n    \"type-check:watch\": \"pnpm run type-check --watch\",\n    \"jest\": \"TZ=Africa/Khartoum jest\",\n    \"test\": \"run-s type-check jest\",\n    \"test:watch\": \"jest --watch\",\n    \"cypress\": \"node client/cypress/cypress.js\",\n    \"postinstall\": \"pnpm run build:viz\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/getredash/redash.git\"\n  },\n  \"packageManager\": \"pnpm@10.30.3\",\n  \"engines\": {\n    \"node\": \">=18.0.0 <26.0.0\"\n  },\n  \"author\": \"Redash Contributors\",\n  \"license\": \"BSD-2-Clause\",\n  \"bugs\": {\n    \"url\": \"https://github.com/getredash/redash/issues\"\n  },\n  \"homepage\": \"https://redash.io/\",\n  \"dependencies\": {\n    \"@ant-design/icons\": \"^4.2.1\",\n    \"@redash/viz\": \"workspace:*\",\n    \"ace-builds\": \"^1.43.3\",\n    \"antd\": \"4.4.3\",\n    \"axios\": \"0.27.2\",\n    \"axios-auth-refresh\": \"3.3.6\",\n    \"bootstrap\": \"^3.4.1\",\n    \"classnames\": \"^2.2.6\",\n    \"d3\": \"^3.5.17\",\n    \"debug\": \"^3.2.7\",\n    \"dompurify\": \"^2.0.17\",\n    \"elliptic\": \"^6.6.0\",\n    \"font-awesome\": \"^4.7.0\",\n    \"history\": \"^4.10.1\",\n    \"hoist-non-react-statics\": \"^3.3.0\",\n    \"markdown\": \"0.5.0\",\n    \"material-design-iconic-font\": \"^2.2.0\",\n    \"mousetrap\": \"^1.6.1\",\n    \"mustache\": \"^2.3.0\",\n    \"numeral\": \"^2.0.6\",\n    \"path-to-regexp\": \"^3.3.0\",\n    \"prop-types\": \"^15.6.1\",\n    \"query-string\": \"^6.9.0\",\n    \"react\": \"16.14.0\",\n    \"react-ace\": \"^14.0.1\",\n    \"react-dom\": \"^16.14.0\",\n    \"react-grid-layout\": \"^0.18.2\",\n    \"react-resizable\": \"^1.10.1\",\n    \"react-virtualized\": \"^9.21.2\",\n    \"sql-formatter\": \"git+https://github.com/getredash/sql-formatter.git\",\n    \"universal-router\": \"^8.3.0\",\n    \"use-debounce\": \"^3.1.0\",\n    \"use-media\": \"^1.4.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.28.6\",\n    \"@babel/core\": \"^7.29.0\",\n    \"@babel/plugin-transform-class-properties\": \"^7.28.5\",\n    \"@babel/plugin-transform-object-assign\": \"^7.27.1\",\n    \"@babel/preset-env\": \"^7.29.0\",\n    \"@babel/preset-react\": \"^7.28.5\",\n    \"@babel/preset-typescript\": \"^7.28.5\",\n    \"@cypress/code-coverage\": \"^3.11.0\",\n    \"@percy/agent\": \"^0.28.7\",\n    \"@percy/cypress\": \"^3.1.2\",\n    \"@pmmmwh/react-refresh-webpack-plugin\": \"^0.6.2\",\n    \"@testing-library/cypress\": \"^8.0.7\",\n    \"@types/classnames\": \"^2.2.10\",\n    \"@types/hoist-non-react-statics\": \"^3.3.1\",\n    \"@types/lodash\": \"^4.14.157\",\n    \"@types/prop-types\": \"^15.7.3\",\n    \"@types/react\": \"^17.0.0\",\n    \"@types/react-dom\": \"^17.0.0\",\n    \"@types/sql-formatter\": \"^2.3.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.62.0\",\n    \"@typescript-eslint/parser\": \"^5.62.0\",\n    \"assert\": \"^2.1.0\",\n    \"atob\": \"^2.1.2\",\n    \"babel-jest\": \"^30.2.0\",\n    \"babel-loader\": \"^10.0.0\",\n    \"babel-plugin-istanbul\": \"^6.1.1\",\n    \"babel-plugin-transform-builtin-extend\": \"^1.1.2\",\n    \"copy-webpack-plugin\": \"^13.0.1\",\n    \"css-loader\": \"^7.1.4\",\n    \"cypress\": \"^11.2.0\",\n    \"dayjs\": \"^1.11.9\",\n    \"enzyme\": \"^3.8.0\",\n    \"enzyme-adapter-react-16\": \"^1.7.1\",\n    \"enzyme-to-json\": \"^3.3.5\",\n    \"eslint\": \"^8.57.1\",\n    \"eslint-config-prettier\": \"^8.10.0\",\n    \"eslint-config-react-app\": \"^7.0.1\",\n    \"eslint-plugin-chai-friendly\": \"^1.1.0\",\n    \"eslint-plugin-compat\": \"^4.2.0\",\n    \"eslint-plugin-cypress\": \"^2.15.2\",\n    \"eslint-plugin-flowtype\": \"^8.0.3\",\n    \"eslint-plugin-import\": \"^2.32.0\",\n    \"eslint-plugin-jest\": \"^27.9.0\",\n    \"eslint-plugin-jsx-a11y\": \"^6.10.2\",\n    \"eslint-plugin-no-only-tests\": \"^3.3.0\",\n    \"eslint-plugin-react\": \"^7.37.5\",\n    \"eslint-plugin-react-hooks\": \"^4.6.2\",\n    \"eslint-webpack-plugin\": \"^4.2.0\",\n    \"html-webpack-plugin\": \"^5.6.6\",\n    \"identity-obj-proxy\": \"^3.0.0\",\n    \"jest\": \"^30.2.0\",\n    \"jest-environment-jsdom\": \"^30.2.0\",\n    \"less\": \"^3.13.1\",\n    \"less-loader\": \"^11.1.4\",\n    \"less-plugin-autoprefix\": \"^2.0.0\",\n    \"lodash\": \"^4.17.21\",\n    \"mini-css-extract-plugin\": \"^2.10.0\",\n    \"mockdate\": \"^2.0.2\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prettier\": \"3.3.2\",\n    \"process\": \"^0.11.10\",\n    \"react-refresh\": \"^0.14.0\",\n    \"react-test-renderer\": \"^16.14.0\",\n    \"request-cookies\": \"^1.1.0\",\n    \"source-map-loader\": \"^5.0.0\",\n    \"stream-browserify\": \"^3.0.0\",\n    \"style-loader\": \"^4.0.0\",\n    \"typescript\": \"^5.9.3\",\n    \"url\": \"^0.11.4\",\n    \"webpack\": \"^5.105.3\",\n    \"webpack-build-notifier\": \"^3.1.0\",\n    \"webpack-bundle-analyzer\": \"^5.2.0\",\n    \"webpack-cli\": \"^6.0.1\",\n    \"webpack-dev-server\": \"^5.2.3\",\n    \"webpack-manifest-plugin\": \"^6.0.1\"\n  },\n  \"optionalDependencies\": {\n    \"fsevents\": \"^2.3.2\"\n  },\n  \"jest\": {\n    \"testEnvironment\": \"jsdom\",\n    \"rootDir\": \"./client\",\n    \"setupFiles\": [\n      \"./app/__tests__/enzyme_setup.js\",\n      \"./app/__tests__/mocks.js\"\n    ],\n    \"snapshotSerializers\": [\n      \"enzyme-to-json/serializer\"\n    ],\n    \"moduleNameMapper\": {\n      \"^@/(.*)\": \"<rootDir>/app/$1\",\n      \"\\\\.(css|less)$\": \"identity-obj-proxy\"\n    },\n    \"testPathIgnorePatterns\": [\n      \"<rootDir>/app/__tests__/\"\n    ]\n  },\n  \"nyc\": {\n    \"include\": [\n      \"client/app/**\",\n      \"viz-lib/**\"\n    ]\n  },\n  \"browser\": {\n    \"fs\": false,\n    \"path\": false\n  },\n  \"//\": \"browserslist set to 'Async functions' compatibility\",\n  \"browserslist\": [\n    \"Edge >= 15\",\n    \"Firefox >= 52\",\n    \"Chrome >= 55\",\n    \"Safari >= 10.1\",\n    \"iOS >= 10.3\",\n    \"Opera >= 42\",\n    \"op_mob >= 46\",\n    \"android >= 67\",\n    \"and_chr >= 71\",\n    \"and_ff >= 64\",\n    \"and_uc >= 11.8\",\n    \"samsung >= 6.2\"\n  ],\n  \"pnpm\": {\n    \"overrides\": {\n      \"@types/react\": \"^17.0.0\",\n      \"@types/react-dom\": \"^17.0.0\",\n      \"cheerio\": \"1.0.0-rc.12\"\n    }\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - \"viz-lib\"\n\nonlyBuiltDependencies:\n  - \"sql-formatter\"\n  - \"core-js\"\n  - \"core-js-pure\"\n  - \"cypress\"\n  - \"es5-ext\"\n  - \"less\"\n  - \"puppeteer\"\n  - \"unrs-resolver\"\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"redash\"\nversion = \"26.03.0-dev\"\nrequires-python = \">=3.13\"\ndescription = \"Make Your Company Data Driven. Connect to any data source, easily visualize, dashboard and share your data.\"\nauthors = [\n    { name = \"Arik Fraimovich\", email = \"<arik@redash.io>\" }\n]\n# to be added to/removed from the mailing list, please reach out to Arik via the above email or Discord\nmaintainers = [\n    { name = \"Redash maintainers and contributors\", email = \"<maintainers@redash.io>\" }\n]\nreadme = \"README.md\"\ndependencies = []\n\n[tool.black]\ntarget-version = ['py38']\nline-length = 119\nforce-exclude = '''\n/(\n  migrations\n)/\n'''\n\n[tool.poetry.dependencies]\npython = \">=3.13,<3.14\"\nadvocate = \"1.0.0\"\naniso8601 = \"8.0.0\"\nauthlib = \"0.15.5\"\nbackoff = \"2.2.1\"\nblinker = \"1.6.2\"\nclick = \"8.1.3\"\ncryptography = \"43.0.1\"\ndisposable-email-domains = \">=0.0.52\"\nflask = \"2.3.2\"\nflask-limiter = \"3.3.1\"\nflask-login = \"0.6.0\"\nflask-mail = \"0.9.1\"\nflask-migrate = \"2.5.2\"\nflask-restful = \"0.3.10\"\nflask-sqlalchemy = \"2.5.1\"\nflask-talisman = \"0.7.0\"\nflask-wtf = \"1.1.1\"\nfuncy = \"1.13\"\ngevent = \"25.9.1\"\ngreenlet = \"3.3.2\"\ngunicorn = \"22.0.0\"\nhttplib2 = \"0.19.0\"\nitsdangerous = \"2.1.2\"\njinja2 = \"3.1.5\"\njsonschema = \"3.1.1\"\nmarkupsafe = \"2.1.1\"\nmaxminddb-geolite2 = \"2018.703\"\nparsedatetime = \"2.6\"\npasslib = \"1.7.3\"\npsycopg2-binary = \"2.9.11\"\npyjwt = \"2.4.0\"\npyopenssl = \"24.2.1\"\npypd = \"1.1.0\"\npysaml2 = \"7.3.1\"\npystache = \"0.6.0\"\npython-dateutil = \"2.9.0.post0\"\npython-dotenv = \"0.19.2\"\npytz = \">=2019.3\"\npyyaml = \"6.0.1\"\nredis = \"4.6.0\"\nregex = \"2023.8.8\"\nrequests = \"2.32.3\"\nrestrictedpython = \"8.1\"\nrq = \"1.16.1\"\npyasynchat = \"1.0.5\"\nrq-scheduler = \"0.13.1\"\nsemver = \"2.8.1\"\nsentry-sdk = \"1.45.1\"\nsqlalchemy = \"1.3.24\"\nsqlalchemy-searchable = \"1.2.0\"\nsqlalchemy-utils = \"0.38.3\"\nsqlparse = \"0.5.0\"\nsshtunnel = \"0.1.5\"\nstatsd = \"3.3.0\"\nsupervisor = \"4.1.0\"\nsupervisor-checks = \"0.8.1\"\nua-parser = \"0.18.0\"\nurllib3 = \"1.26.19\"\nuser-agents = \"2.0\"\nwerkzeug = \"2.3.8\"\nwtforms = \"2.2.1\"\nxlsxwriter = \"3.2.9\"\ntzlocal = \"4.3.1\"\npyodbc = \"5.3.0\"\ndebugpy = \"^1.8.9\"\nparamiko = \"3.4.1\"\noracledb = \"2.5.1\"\nibm-db = { version = \"^3.2.7\", markers = \"platform_machine == 'x86_64' or platform_machine == 'AMD64'\" }\n\n[tool.poetry.group.all_ds]\noptional = true\n\n[tool.poetry.group.all_ds.dependencies]\natsd-client = \"3.0.5\"\nazure-kusto-data = \"5.0.1\"\nboto3 = \"1.28.8\"\nbotocore = \"1.31.8\"\ncassandra-driver = \"3.29.3\"\ncertifi = \">=2019.9.11\"\ncmem-cmempy = \"21.2.3\"\ndatabend-py = \"0.4.6\"\ndatabend-sqlalchemy = \"0.2.4\"\nduckdb = \"1.3.2\"\ngoogle-api-python-client = \"2.190.0\"\ngspread = \"5.11.2\"\nimpyla = \"0.22.0\"\ninfluxdb = \"5.2.3\"\ninfluxdb-client = \"1.38.0\"\nmemsql = \"3.2.0\"\nmysqlclient = \"2.1.1\"\nnumpy = \"2.4.2\"\nnzalchemy = \"^11.0.2\"\nnzpy = \">=1.15\"\noauth2client = \"4.1.3\"\nopenpyxl = \"3.1.5\"\npandas = \"2.3.3\"\nphoenixdb = \"1.2.2\"\npinotdb = \">=0.4.5\"\nprotobuf = \"6.33.5\"\npyathena = \"2.25.2\"\npydgraph = \"25.1.0\"\npydruid = \"0.5.7\"\npyexasol = \"0.12.0\"\npyhive = \"0.6.1\"\npyignite = \"0.6.1\"\npymongo = { version = \"4.6.3\", extras = [\"srv\", \"tls\"] }\npymssql = \"^2.3.1\"\npyodbc = \"5.3.0\"\npython-arango = \"6.1.0\"\npython-rapidjson = \"1.20\"\nrequests-aws-sign = \"0.1.5\"\nsasl = \">=0.4a1\"\nsimple-salesforce = \"0.74.3\"\nsnowflake-connector-python = \"3.12.3\"\ntd-client = \"1.5.0\"\nthrift = \">=0.8.0\"\nthrift-sasl = \">=0.1.0\"\ntrino = \">=0.305,<1.0\"\nvertica-python = \"1.1.1\"\nxlrd = \"2.0.1\"\ne6data-python-connector = \"2.2.0\"\n\n[tool.poetry.group.ldap3]\noptional = true\n\n[tool.poetry.group.ldap3.dependencies]\nldap3 = \"2.9.1\"\n\n[tool.poetry.group.dev]\noptional = true\n\n[tool.poetry.group.dev.dependencies]\npytest = \"7.4.0\"\ncoverage = \"7.2.7\"\nfreezegun = \"1.5.5\"\njwcrypto = \"1.5.6\"\nmock = \"5.0.2\"\npre-commit = \"3.3.3\"\nptpython = \"3.0.23\"\npytest-cov = \"4.1.0\"\nwatchdog = \"3.0.0\"\nruff = \"0.0.289\"\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\n[tool.ruff]\nexclude = [\".git\", \"viz-lib\", \"node_modules\", \"migrations\"]\nignore = [\"E501\"]\nselect = [\"C9\", \"E\", \"F\", \"W\", \"I001\", \"UP004\"]\n\n[tool.ruff.mccabe]\nmax-complexity = 15\n\n[tool.ruff.per-file-ignores]\n\"__init__.py\" = [\"F401\"]\n"
  },
  {
    "path": "pytest.ini",
    "content": "[pytest]\nnorecursedirs = *.egg .eggs dist build docs .tox\nfilterwarnings =\n    once::DeprecationWarning\n    once::PendingDeprecationWarning\n"
  },
  {
    "path": "redash/__init__.py",
    "content": "import logging\nimport os\nimport sys\n\nimport redis\nfrom flask_limiter import Limiter\nfrom flask_limiter.util import get_remote_address\nfrom flask_mail import Mail\nfrom flask_migrate import Migrate\nfrom statsd import StatsClient\n\nfrom redash import settings\nfrom redash.app import create_app  # noqa\nfrom redash.destinations import import_destinations\nfrom redash.query_runner import import_query_runners\n\n__version__ = \"26.03.0-dev\"\n\n\nif os.environ.get(\"REMOTE_DEBUG\"):\n    import debugpy\n\n    debugpy.listen((\"0.0.0.0\", 5678))\n    debugpy.wait_for_client()\n\n\ndef setup_logging():\n    handler = logging.StreamHandler(sys.stdout if settings.LOG_STDOUT else sys.stderr)\n    formatter = logging.Formatter(settings.LOG_FORMAT)\n    handler.setFormatter(formatter)\n    logging.getLogger().addHandler(handler)\n    logging.getLogger().setLevel(settings.LOG_LEVEL)\n\n    # Make noisy libraries less noisy\n    if settings.LOG_LEVEL != \"DEBUG\":\n        for name in [\n            \"passlib\",\n            \"requests.packages.urllib3\",\n            \"snowflake.connector\",\n            \"apiclient\",\n        ]:\n            logging.getLogger(name).setLevel(\"ERROR\")\n\n\nsetup_logging()\n\nredis_connection = redis.from_url(settings.REDIS_URL)\nrq_redis_connection = redis.from_url(settings.RQ_REDIS_URL)\nmail = Mail()\nmigrate = Migrate(compare_type=True)\nstatsd_client = StatsClient(host=settings.STATSD_HOST, port=settings.STATSD_PORT, prefix=settings.STATSD_PREFIX)\nlimiter = Limiter(key_func=get_remote_address, storage_uri=settings.LIMITER_STORAGE)\n\nimport_query_runners(settings.QUERY_RUNNERS)\nimport_destinations(settings.DESTINATIONS)\n"
  },
  {
    "path": "redash/app.py",
    "content": "from flask import Flask\nfrom werkzeug.middleware.proxy_fix import ProxyFix\n\nfrom redash import settings\n\n\nclass Redash(Flask):\n    \"\"\"A custom Flask app for Redash\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        kwargs.update(\n            {\n                \"template_folder\": settings.FLASK_TEMPLATE_PATH,\n                \"static_folder\": settings.STATIC_ASSETS_PATH,\n                \"static_url_path\": \"/static\",\n            }\n        )\n        super(Redash, self).__init__(__name__, *args, **kwargs)\n        # Make sure we get the right referral address even behind proxies like nginx.\n        self.wsgi_app = ProxyFix(self.wsgi_app, x_for=settings.PROXIES_COUNT, x_host=1)\n        # Configure Redash using our settings\n        self.config.from_object(\"redash.settings\")\n\n\ndef create_app():\n    from . import (\n        authentication,\n        handlers,\n        limiter,\n        mail,\n        migrate,\n        security,\n        tasks,\n    )\n    from .handlers.webpack import configure_webpack\n    from .metrics import request as request_metrics\n    from .models import db, users\n    from .utils import sentry\n    from .version_check import reset_new_version_status\n\n    sentry.init()\n    app = Redash()\n\n    # Check and update the cached version for use by the client\n    reset_new_version_status()\n\n    security.init_app(app)\n    request_metrics.init_app(app)\n    db.init_app(app)\n    migrate.init_app(app, db)\n    mail.init_app(app)\n    authentication.init_app(app)\n    limiter.init_app(app)\n    handlers.init_app(app)\n    configure_webpack(app)\n    users.init_app(app)\n    tasks.init_app(app)\n\n    return app\n"
  },
  {
    "path": "redash/authentication/__init__.py",
    "content": "import hashlib\nimport hmac\nimport logging\nimport time\nfrom datetime import timedelta\nfrom urllib.parse import urlsplit, urlunsplit\n\nfrom flask import jsonify, redirect, request, session, url_for\nfrom flask_login import LoginManager, login_user, logout_user, user_logged_in\nfrom sqlalchemy.orm.exc import NoResultFound\nfrom werkzeug.exceptions import Unauthorized\n\nfrom redash import models, settings\nfrom redash.authentication import jwt_auth\nfrom redash.authentication.org_resolving import current_org\nfrom redash.settings.organization import settings as org_settings\nfrom redash.tasks import record_event\n\nlogin_manager = LoginManager()\nlogger = logging.getLogger(\"authentication\")\n\n\ndef get_login_url(external=False, next=\"/\"):\n    if settings.MULTI_ORG and current_org == None:  # noqa: E711\n        login_url = \"/\"\n    elif settings.MULTI_ORG:\n        login_url = url_for(\"redash.login\", org_slug=current_org.slug, next=next, _external=external)\n    else:\n        login_url = url_for(\"redash.login\", next=next, _external=external)\n\n    return login_url\n\n\ndef sign(key, path, expires):\n    if not key:\n        return None\n\n    h = hmac.new(key.encode(), msg=path.encode(), digestmod=hashlib.sha1)\n    h.update(str(expires).encode())\n\n    return h.hexdigest()\n\n\n@login_manager.user_loader\ndef load_user(user_id_with_identity):\n    user = api_key_load_user_from_request(request)\n    if user:\n        return user\n\n    org = current_org._get_current_object()\n\n    try:\n        user_id, _ = user_id_with_identity.split(\"-\")\n        user = models.User.get_by_id_and_org(user_id, org)\n        if user.is_disabled or user.get_id() != user_id_with_identity:\n            return None\n\n        return user\n    except (models.NoResultFound, ValueError, AttributeError):\n        return None\n\n\ndef request_loader(request):\n    user = None\n    if settings.AUTH_TYPE == \"hmac\":\n        user = hmac_load_user_from_request(request)\n    elif settings.AUTH_TYPE == \"api_key\":\n        user = api_key_load_user_from_request(request)\n    else:\n        logger.warning(\"Unknown authentication type ({}). Using default (HMAC).\".format(settings.AUTH_TYPE))\n        user = hmac_load_user_from_request(request)\n\n    if org_settings[\"auth_jwt_login_enabled\"] and user is None:\n        user = jwt_token_load_user_from_request(request)\n    return user\n\n\ndef hmac_load_user_from_request(request):\n    signature = request.args.get(\"signature\")\n    expires = float(request.args.get(\"expires\") or 0)\n    query_id = request.view_args.get(\"query_id\", None)\n    user_id = request.args.get(\"user_id\", None)\n\n    # TODO: 3600 should be a setting\n    if signature and time.time() < expires <= time.time() + 3600:\n        if user_id:\n            user = models.User.query.get(user_id)\n            calculated_signature = sign(user.api_key, request.path, expires)\n\n            if user.api_key and signature == calculated_signature:\n                return user\n\n        if query_id:\n            query = models.Query.query.filter(models.Query.id == query_id).one()\n            calculated_signature = sign(query.api_key, request.path, expires)\n\n            if query.api_key and signature == calculated_signature:\n                return models.ApiUser(\n                    query.api_key,\n                    query.org,\n                    list(query.groups.keys()),\n                    name=\"ApiKey: Query {}\".format(query.id),\n                )\n\n    return None\n\n\ndef get_user_from_api_key(api_key, query_id):\n    if not api_key:\n        return None\n\n    user = None\n\n    # TODO: once we switch all api key storage into the ApiKey model, this code will be much simplified\n    org = current_org._get_current_object()\n    try:\n        user = models.User.get_by_api_key_and_org(api_key, org)\n        if user.is_disabled:\n            user = None\n    except models.NoResultFound:\n        try:\n            api_key = models.ApiKey.get_by_api_key(api_key)\n            user = models.ApiUser(api_key, api_key.org, [])\n        except models.NoResultFound:\n            if query_id:\n                query = models.Query.get_by_id_and_org(query_id, org)\n                if query and query.api_key == api_key:\n                    user = models.ApiUser(\n                        api_key,\n                        query.org,\n                        list(query.groups.keys()),\n                        name=\"ApiKey: Query {}\".format(query.id),\n                    )\n\n    return user\n\n\ndef get_api_key_from_request(request):\n    api_key = request.args.get(\"api_key\", None)\n\n    if api_key is not None:\n        return api_key\n\n    if request.headers.get(\"Authorization\"):\n        auth_header = request.headers.get(\"Authorization\")\n        api_key = auth_header.replace(\"Key \", \"\", 1)\n    elif request.view_args is not None and request.view_args.get(\"token\"):\n        api_key = request.view_args[\"token\"]\n\n    return api_key\n\n\ndef api_key_load_user_from_request(request):\n    api_key = get_api_key_from_request(request)\n    if request.view_args is not None:\n        query_id = request.view_args.get(\"query_id\", None)\n        user = get_user_from_api_key(api_key, query_id)\n    else:\n        user = None\n\n    return user\n\n\ndef jwt_token_load_user_from_request(request):\n    org = current_org._get_current_object()\n\n    payload = None\n\n    if org_settings[\"auth_jwt_auth_cookie_name\"]:\n        jwt_token = request.cookies.get(org_settings[\"auth_jwt_auth_cookie_name\"], None)\n    elif org_settings[\"auth_jwt_auth_header_name\"]:\n        jwt_token = request.headers.get(org_settings[\"auth_jwt_auth_header_name\"], None)\n    else:\n        return None\n\n    if jwt_token:\n        payload, token_is_valid = jwt_auth.verify_jwt_token(\n            jwt_token,\n            expected_issuer=org_settings[\"auth_jwt_auth_issuer\"],\n            expected_audience=org_settings[\"auth_jwt_auth_audience\"],\n            algorithms=org_settings[\"auth_jwt_auth_algorithms\"],\n            public_certs_url=org_settings[\"auth_jwt_auth_public_certs_url\"],\n        )\n        if not token_is_valid:\n            raise Unauthorized(\"Invalid JWT token\")\n\n    if not payload:\n        return\n\n    if \"email\" not in payload:\n        logger.info(\"No email field in token, refusing to login\")\n        return\n\n    try:\n        user = models.User.get_by_email_and_org(payload[\"email\"], org)\n    except models.NoResultFound:\n        user = create_and_login_user(current_org, payload[\"email\"], payload[\"email\"])\n\n    return user\n\n\ndef log_user_logged_in(app, user):\n    event = {\n        \"org_id\": user.org_id,\n        \"user_id\": user.id,\n        \"action\": \"login\",\n        \"object_type\": \"redash\",\n        \"timestamp\": int(time.time()),\n        \"user_agent\": request.user_agent.string,\n        \"ip\": request.remote_addr,\n    }\n\n    record_event.delay(event)\n\n\n@login_manager.unauthorized_handler\ndef redirect_to_login():\n    is_xhr = request.headers.get(\"X-Requested-With\") == \"XMLHttpRequest\"\n    if is_xhr or \"/api/\" in request.path:\n        return {\"message\": \"Couldn't find resource. Please login and try again.\"}, 404\n\n    login_url = get_login_url(next=request.url, external=False)\n\n    return redirect(login_url)\n\n\ndef logout_and_redirect_to_index():\n    logout_user()\n\n    if settings.MULTI_ORG and current_org == None:  # noqa: E711\n        index_url = \"/\"\n    elif settings.MULTI_ORG:\n        index_url = url_for(\"redash.index\", org_slug=current_org.slug, _external=False)\n    else:\n        index_url = url_for(\"redash.index\", _external=False)\n\n    return redirect(index_url)\n\n\ndef init_app(app):\n    from redash.authentication import ldap_auth, remote_user_auth, saml_auth\n    from redash.authentication.google_oauth import (\n        create_google_oauth_blueprint,\n    )\n\n    login_manager.init_app(app)\n    login_manager.anonymous_user = models.AnonymousUser\n    login_manager.REMEMBER_COOKIE_DURATION = settings.REMEMBER_COOKIE_DURATION\n\n    @app.before_request\n    def extend_session():\n        session.permanent = True\n        app.permanent_session_lifetime = timedelta(seconds=settings.SESSION_EXPIRY_TIME)\n\n    from redash.security import csrf\n\n    # Authlib's flask oauth client requires a Flask app to initialize\n    for blueprint in [\n        create_google_oauth_blueprint(app),\n        saml_auth.blueprint,\n        remote_user_auth.blueprint,\n        ldap_auth.blueprint,\n    ]:\n        csrf.exempt(blueprint)\n        app.register_blueprint(blueprint)\n\n    user_logged_in.connect(log_user_logged_in)\n    login_manager.request_loader(request_loader)\n\n\ndef create_and_login_user(org, name, email, picture=None):\n    try:\n        user_object = models.User.get_by_email_and_org(email, org)\n        if user_object.is_disabled:\n            return None\n        if user_object.is_invitation_pending:\n            user_object.is_invitation_pending = False\n            models.db.session.commit()\n        if user_object.name != name:\n            logger.debug(\"Updating user name (%r -> %r)\", user_object.name, name)\n            user_object.name = name\n            models.db.session.commit()\n    except NoResultFound:\n        logger.debug(\"Creating user object (%r)\", name)\n        user_object = models.User(\n            org=org,\n            name=name,\n            email=email,\n            is_invitation_pending=False,\n            _profile_image_url=picture,\n            group_ids=[org.default_group.id],\n        )\n        models.db.session.add(user_object)\n        models.db.session.commit()\n\n    login_user(user_object, remember=True)\n\n    return user_object\n\n\ndef get_next_path(unsafe_next_path):\n    if not unsafe_next_path:\n        return \"\"\n\n    # Preventing open redirection attacks\n    parts = list(urlsplit(unsafe_next_path))\n    parts[0] = \"\"  # clear scheme\n    parts[1] = \"\"  # clear netloc\n    safe_next_path = urlunsplit(parts)\n\n    # If the original path was a URL, we might end up with an empty\n    # safe url, which will redirect to the login page. Changing to\n    # relative root to redirect to the app root after login.\n    if not safe_next_path:\n        safe_next_path = \"./\"\n\n    return safe_next_path\n"
  },
  {
    "path": "redash/authentication/account.py",
    "content": "import logging\n\nfrom flask import render_template\nfrom itsdangerous import URLSafeTimedSerializer\n\nfrom redash import settings\nfrom redash.tasks import send_mail\nfrom redash.utils import base_url\n\nlogger = logging.getLogger(__name__)\nserializer = URLSafeTimedSerializer(settings.SECRET_KEY)\n\n\ndef invite_token(user):\n    return serializer.dumps(str(user.id))\n\n\ndef verify_link_for_user(user):\n    token = invite_token(user)\n    verify_url = \"{}/verify/{}\".format(base_url(user.org), token)\n\n    return verify_url\n\n\ndef invite_link_for_user(user):\n    token = invite_token(user)\n    invite_url = \"{}/invite/{}\".format(base_url(user.org), token)\n\n    return invite_url\n\n\ndef reset_link_for_user(user):\n    token = invite_token(user)\n    invite_url = \"{}/reset/{}\".format(base_url(user.org), token)\n\n    return invite_url\n\n\ndef validate_token(token):\n    max_token_age = settings.INVITATION_TOKEN_MAX_AGE\n    return serializer.loads(token, max_age=max_token_age)\n\n\ndef send_verify_email(user, org):\n    context = {\"user\": user, \"verify_url\": verify_link_for_user(user)}\n    html_content = render_template(\"emails/verify.html\", **context)\n    text_content = render_template(\"emails/verify.txt\", **context)\n    subject = \"{}, please verify your email address\".format(user.name)\n\n    send_mail.delay([user.email], subject, html_content, text_content)\n\n\ndef send_invite_email(inviter, invited, invite_url, org):\n    context = dict(inviter=inviter, invited=invited, org=org, invite_url=invite_url)\n    html_content = render_template(\"emails/invite.html\", **context)\n    text_content = render_template(\"emails/invite.txt\", **context)\n    subject = \"{} invited you to join Redash\".format(inviter.name)\n\n    send_mail.delay([invited.email], subject, html_content, text_content)\n\n\ndef send_password_reset_email(user):\n    reset_link = reset_link_for_user(user)\n    context = dict(user=user, reset_link=reset_link)\n    html_content = render_template(\"emails/reset.html\", **context)\n    text_content = render_template(\"emails/reset.txt\", **context)\n    subject = \"Reset your password\"\n\n    send_mail.delay([user.email], subject, html_content, text_content)\n    return reset_link\n\n\ndef send_user_disabled_email(user):\n    html_content = render_template(\"emails/reset_disabled.html\", user=user)\n    text_content = render_template(\"emails/reset_disabled.txt\", user=user)\n    subject = \"Your Redash account is disabled\"\n\n    send_mail.delay([user.email], subject, html_content, text_content)\n"
  },
  {
    "path": "redash/authentication/google_oauth.py",
    "content": "import logging\n\nimport requests\nfrom authlib.integrations.flask_client import OAuth\nfrom flask import Blueprint, flash, redirect, request, session, url_for\n\nfrom redash import models, settings\nfrom redash.authentication import (\n    create_and_login_user,\n    get_next_path,\n    logout_and_redirect_to_index,\n)\nfrom redash.authentication.org_resolving import current_org\n\n\ndef verify_profile(org, profile):\n    if org.is_public:\n        return True\n\n    email = profile[\"email\"]\n    domain = email.split(\"@\")[-1]\n\n    if domain in org.google_apps_domains:\n        return True\n\n    if org.has_user(email) == 1:\n        return True\n\n    return False\n\n\ndef get_user_profile(access_token, logger):\n    headers = {\"Authorization\": f\"OAuth {access_token}\"}\n    response = requests.get(\"https://www.googleapis.com/oauth2/v1/userinfo\", headers=headers)\n\n    if response.status_code == 401:\n        logger.warning(\"Failed getting user profile (response code 401).\")\n        return None\n\n    return response.json()\n\n\ndef build_redirect_uri():\n    scheme = settings.GOOGLE_OAUTH_SCHEME_OVERRIDE or None\n    return url_for(\".callback\", _external=True, _scheme=scheme)\n\n\ndef build_next_path(org_slug=None):\n    next_path = request.args.get(\"next\")\n    if not next_path:\n        if org_slug is None:\n            org_slug = session.get(\"org_slug\")\n\n        scheme = None\n        if settings.GOOGLE_OAUTH_SCHEME_OVERRIDE:\n            scheme = settings.GOOGLE_OAUTH_SCHEME_OVERRIDE\n\n        next_path = url_for(\n            \"redash.index\",\n            org_slug=org_slug,\n            _external=True,\n            _scheme=scheme,\n        )\n    return next_path\n\n\ndef create_google_oauth_blueprint(app):\n    oauth = OAuth(app)\n\n    logger = logging.getLogger(\"google_oauth\")\n    blueprint = Blueprint(\"google_oauth\", __name__)\n\n    CONF_URL = \"https://accounts.google.com/.well-known/openid-configuration\"\n    oauth.register(\n        name=\"google\",\n        server_metadata_url=CONF_URL,\n        client_kwargs={\"scope\": \"openid email profile\"},\n    )\n\n    @blueprint.route(\"/<org_slug>/oauth/google\", endpoint=\"authorize_org\")\n    def org_login(org_slug):\n        session[\"org_slug\"] = current_org.slug\n        return redirect(url_for(\".authorize\", next=request.args.get(\"next\", None)))\n\n    @blueprint.route(\"/oauth/google\", endpoint=\"authorize\")\n    def login():\n        redirect_uri = build_redirect_uri()\n\n        next_path = build_next_path()\n        logger.debug(\"Callback url: %s\", redirect_uri)\n        logger.debug(\"Next is: %s\", next_path)\n\n        session[\"next_url\"] = next_path\n\n        return oauth.google.authorize_redirect(redirect_uri)\n\n    @blueprint.route(\"/oauth/google_callback\", endpoint=\"callback\")\n    def authorized():\n        logger.debug(\"Authorized user inbound\")\n\n        resp = oauth.google.authorize_access_token()\n        user = resp.get(\"userinfo\")\n        if user:\n            session[\"user\"] = user\n\n        access_token = resp[\"access_token\"]\n\n        if access_token is None:\n            logger.warning(\"Access token missing in call back request.\")\n            flash(\"Validation error. Please retry.\")\n            return redirect(url_for(\"redash.login\"))\n\n        profile = get_user_profile(access_token, logger)\n        if profile is None:\n            flash(\"Validation error. Please retry.\")\n            return redirect(url_for(\"redash.login\"))\n\n        if \"org_slug\" in session:\n            org = models.Organization.get_by_slug(session.pop(\"org_slug\"))\n        else:\n            org = current_org\n\n        if not verify_profile(org, profile):\n            logger.warning(\n                \"User tried to login with unauthorized domain name: %s (org: %s)\",\n                profile[\"email\"],\n                org,\n            )\n            flash(\"Your Google Apps account ({}) isn't allowed.\".format(profile[\"email\"]))\n            return redirect(url_for(\"redash.login\", org_slug=org.slug))\n\n        picture_url = \"%s?sz=40\" % profile[\"picture\"]\n        user = create_and_login_user(org, profile[\"name\"], profile[\"email\"], picture_url)\n        if user is None:\n            return logout_and_redirect_to_index()\n\n        unsafe_next_path = session.get(\"next_url\")\n        if not unsafe_next_path:\n            unsafe_next_path = build_next_path(org.slug)\n        next_path = get_next_path(unsafe_next_path)\n\n        return redirect(next_path)\n\n    return blueprint\n"
  },
  {
    "path": "redash/authentication/jwt_auth.py",
    "content": "import json\nimport logging\n\nimport jwt\nimport requests\n\nlogger = logging.getLogger(\"jwt_auth\")\n\nFILE_SCHEME_PREFIX = \"file://\"\n\n\ndef get_public_key_from_file(url):\n    file_path = url[len(FILE_SCHEME_PREFIX) :]\n    with open(file_path) as key_file:\n        key_str = key_file.read()\n\n    get_public_keys.key_cache[url] = [key_str]\n    return key_str\n\n\ndef get_public_key_from_net(url):\n    r = requests.get(url)\n    r.raise_for_status()\n    data = r.json()\n    if \"keys\" in data:\n        public_keys = []\n        for key_dict in data[\"keys\"]:\n            public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key_dict))\n            public_keys.append(public_key)\n\n        get_public_keys.key_cache[url] = public_keys\n        return public_keys\n    else:\n        get_public_keys.key_cache[url] = data\n        return data\n\n\ndef get_public_keys(url):\n    \"\"\"\n    Returns:\n        List of RSA public keys usable by PyJWT.\n    \"\"\"\n    key_cache = get_public_keys.key_cache\n    keys = {}\n    if url in key_cache:\n        keys = key_cache[url]\n    else:\n        if url.startswith(FILE_SCHEME_PREFIX):\n            keys = [get_public_key_from_file(url)]\n        else:\n            keys = get_public_key_from_net(url)\n    return keys\n\n\nget_public_keys.key_cache = {}\n\n\ndef verify_jwt_token(jwt_token, expected_issuer, expected_audience, algorithms, public_certs_url):\n    # https://developers.cloudflare.com/access/setting-up-access/validate-jwt-tokens/\n    # https://cloud.google.com/iap/docs/signed-headers-howto\n    # Loop through the keys since we can't pass the key set to the decoder\n    keys = get_public_keys(public_certs_url)\n\n    key_id = jwt.get_unverified_header(jwt_token).get(\"kid\", \"\")\n    if key_id and isinstance(keys, dict):\n        keys = [keys.get(key_id)]\n\n    valid_token = False\n    payload = None\n    for key in keys:\n        try:\n            # decode returns the claims which has the email if you need it\n            payload = jwt.decode(jwt_token, key=key, audience=expected_audience, algorithms=algorithms)\n            issuer = payload[\"iss\"]\n            if issuer != expected_issuer:\n                raise Exception(\"Wrong issuer: {}\".format(issuer))\n            valid_token = True\n            break\n        except Exception as e:\n            logging.exception(e)\n\n    return payload, valid_token\n"
  },
  {
    "path": "redash/authentication/ldap_auth.py",
    "content": "import logging\nimport sys\n\nfrom flask import Blueprint, flash, redirect, render_template, request, url_for\nfrom flask_login import current_user\n\nfrom redash import settings\n\ntry:\n    from ldap3 import Connection, Server\n    from ldap3.utils.conv import escape_filter_chars\nexcept ImportError:\n    if settings.LDAP_LOGIN_ENABLED:\n        sys.exit(\n            \"The ldap3 library was not found. This is required to use LDAP authentication. Rebuild the Docker image installing the `ldap3` poetry dependency group.\"\n        )\n\nfrom redash.authentication import (\n    create_and_login_user,\n    get_next_path,\n    logout_and_redirect_to_index,\n)\nfrom redash.authentication.org_resolving import current_org\nfrom redash.handlers.base import org_scoped_rule\n\nlogger = logging.getLogger(\"ldap_auth\")\n\n\nblueprint = Blueprint(\"ldap_auth\", __name__)\n\n\n@blueprint.route(org_scoped_rule(\"/ldap/login\"), methods=[\"GET\", \"POST\"])\ndef login(org_slug=None):\n    index_url = url_for(\"redash.index\", org_slug=org_slug)\n    unsafe_next_path = request.args.get(\"next\", index_url)\n    next_path = get_next_path(unsafe_next_path)\n\n    if not settings.LDAP_LOGIN_ENABLED:\n        logger.error(\"Cannot use LDAP for login without being enabled in settings\")\n        return redirect(url_for(\"redash.index\", next=next_path))\n\n    if current_user.is_authenticated:\n        return redirect(next_path)\n\n    if request.method == \"POST\":\n        ldap_user = auth_ldap_user(request.form[\"email\"], request.form[\"password\"])\n\n        if ldap_user is not None:\n            user = create_and_login_user(\n                current_org,\n                ldap_user[settings.LDAP_DISPLAY_NAME_KEY][0],\n                ldap_user[settings.LDAP_EMAIL_KEY][0],\n            )\n            if user is None:\n                return logout_and_redirect_to_index()\n\n            return redirect(next_path or url_for(\"redash.index\"))\n        else:\n            flash(\"Incorrect credentials.\")\n\n    return render_template(\n        \"login.html\",\n        org_slug=org_slug,\n        next=next_path,\n        email=request.form.get(\"email\", \"\"),\n        show_password_login=True,\n        username_prompt=settings.LDAP_CUSTOM_USERNAME_PROMPT,\n        hide_forgot_password=True,\n    )\n\n\ndef auth_ldap_user(username, password):\n    clean_username = escape_filter_chars(username)\n    server = Server(settings.LDAP_HOST_URL, use_ssl=settings.LDAP_SSL)\n    if settings.LDAP_BIND_DN is not None:\n        conn = Connection(\n            server,\n            settings.LDAP_BIND_DN,\n            password=settings.LDAP_BIND_DN_PASSWORD,\n            authentication=settings.LDAP_AUTH_METHOD,\n            auto_bind=True,\n        )\n    else:\n        conn = Connection(server, auto_bind=True)\n\n    conn.search(\n        settings.LDAP_SEARCH_DN,\n        settings.LDAP_SEARCH_TEMPLATE % {\"username\": clean_username},\n        attributes=[settings.LDAP_DISPLAY_NAME_KEY, settings.LDAP_EMAIL_KEY],\n    )\n\n    if len(conn.entries) == 0:\n        return None\n\n    user = conn.entries[0]\n\n    if not conn.rebind(user=user.entry_dn, password=password):\n        return None\n\n    return user\n"
  },
  {
    "path": "redash/authentication/org_resolving.py",
    "content": "import logging\n\nfrom flask import g, request\nfrom werkzeug.local import LocalProxy\n\nfrom redash.models import Organization\n\n\ndef _get_current_org():\n    if \"org\" in g:\n        return g.org\n\n    if request.view_args is None:\n        slug = g.get(\"org_slug\", \"default\")\n    else:\n        slug = request.view_args.get(\"org_slug\", g.get(\"org_slug\", \"default\"))\n\n    g.org = Organization.get_by_slug(slug)\n    logging.debug(\"Current organization: %s (slug: %s)\", g.org, slug)\n    return g.org\n\n\n# TODO: move to authentication\ncurrent_org = LocalProxy(_get_current_org)\n"
  },
  {
    "path": "redash/authentication/remote_user_auth.py",
    "content": "import logging\n\nfrom flask import Blueprint, redirect, request, url_for\n\nfrom redash import settings\nfrom redash.authentication import (\n    create_and_login_user,\n    get_next_path,\n    logout_and_redirect_to_index,\n)\nfrom redash.authentication.org_resolving import current_org\nfrom redash.handlers.base import org_scoped_rule\n\nlogger = logging.getLogger(\"remote_user_auth\")\n\nblueprint = Blueprint(\"remote_user_auth\", __name__)\n\n\n@blueprint.route(org_scoped_rule(\"/remote_user/login\"))\ndef login(org_slug=None):\n    unsafe_next_path = request.args.get(\"next\")\n    next_path = get_next_path(unsafe_next_path)\n\n    if not settings.REMOTE_USER_LOGIN_ENABLED:\n        logger.error(\"Cannot use remote user for login without being enabled in settings\")\n        return redirect(url_for(\"redash.index\", next=next_path, org_slug=org_slug))\n\n    email = request.headers.get(settings.REMOTE_USER_HEADER)\n\n    # Some Apache auth configurations will, stupidly, set (null) instead of a\n    # falsey value.  Special case that here so it Just Works for more installs.\n    # '(null)' should never really be a value that anyone wants to legitimately\n    # use as a redash user email.\n    if email == \"(null)\":\n        email = None\n\n    if not email:\n        logger.error(\n            \"Cannot use remote user for login when it's not provided in the request (looked in headers['\"\n            + settings.REMOTE_USER_HEADER\n            + \"'])\"\n        )\n        return redirect(url_for(\"redash.index\", next=next_path, org_slug=org_slug))\n\n    logger.info(\"Logging in \" + email + \" via remote user\")\n\n    user = create_and_login_user(current_org, email, email)\n    if user is None:\n        return logout_and_redirect_to_index()\n\n    return redirect(next_path or url_for(\"redash.index\", org_slug=org_slug), code=302)\n"
  },
  {
    "path": "redash/authentication/saml_auth.py",
    "content": "import logging\n\nfrom flask import Blueprint, flash, redirect, request, url_for\nfrom saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT, entity\nfrom saml2.client import Saml2Client\nfrom saml2.config import Config as Saml2Config\nfrom saml2.saml import NAMEID_FORMAT_TRANSIENT\nfrom saml2.sigver import get_xmlsec_binary\n\nfrom redash import settings\nfrom redash.authentication import (\n    create_and_login_user,\n    logout_and_redirect_to_index,\n)\nfrom redash.authentication.org_resolving import current_org\nfrom redash.handlers.base import org_scoped_rule\nfrom redash.utils import mustache_render\n\nlogger = logging.getLogger(\"saml_auth\")\nblueprint = Blueprint(\"saml_auth\", __name__)\ninline_metadata_template = \"\"\"<?xml version=\"1.0\" encoding=\"UTF-8\"?><md:EntityDescriptor entityID=\"{{entity_id}}\" xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"><md:IDPSSODescriptor WantAuthnRequestsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\"><md:KeyDescriptor use=\"signing\"><ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:X509Data><ds:X509Certificate>{{x509_cert}}</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"{{sso_url}}\"/><md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"{{sso_url}}\"/></md:IDPSSODescriptor></md:EntityDescriptor>\"\"\"\n\n\ndef get_saml_client(org):\n    \"\"\"\n    Return SAML configuration.\n\n    The configuration is a hash for use by saml2.config.Config\n    \"\"\"\n\n    saml_type = org.get_setting(\"auth_saml_type\")\n    entity_id = org.get_setting(\"auth_saml_entity_id\")\n    sso_url = org.get_setting(\"auth_saml_sso_url\")\n    x509_cert = org.get_setting(\"auth_saml_x509_cert\")\n    metadata_url = org.get_setting(\"auth_saml_metadata_url\")\n    sp_settings = org.get_setting(\"auth_saml_sp_settings\")\n\n    if settings.SAML_SCHEME_OVERRIDE:\n        acs_url = url_for(\n            \"saml_auth.idp_initiated\",\n            org_slug=org.slug,\n            _external=True,\n            _scheme=settings.SAML_SCHEME_OVERRIDE,\n        )\n    else:\n        acs_url = url_for(\"saml_auth.idp_initiated\", org_slug=org.slug, _external=True)\n\n    saml_settings = {\n        \"metadata\": {\"remote\": [{\"url\": metadata_url}]},\n        \"service\": {\n            \"sp\": {\n                \"endpoints\": {\n                    \"assertion_consumer_service\": [\n                        (acs_url, BINDING_HTTP_REDIRECT),\n                        (acs_url, BINDING_HTTP_POST),\n                    ]\n                },\n                # Don't verify that the incoming requests originate from us via\n                # the built-in cache for authn request ids in pysaml2\n                \"allow_unsolicited\": True,\n                # Don't sign authn requests, since signed requests only make\n                # sense in a situation where you control both the SP and IdP\n                \"authn_requests_signed\": False,\n                \"logout_requests_signed\": True,\n                \"want_assertions_signed\": True,\n                \"want_response_signed\": False,\n            }\n        },\n    }\n\n    if settings.SAML_ENCRYPTION_ENABLED:\n        encryption_dict = {\n            \"xmlsec_binary\": get_xmlsec_binary(),\n            \"encryption_keypairs\": [\n                {\n                    \"key_file\": settings.SAML_ENCRYPTION_PEM_PATH,\n                    \"cert_file\": settings.SAML_ENCRYPTION_CERT_PATH,\n                }\n            ],\n        }\n        saml_settings.update(encryption_dict)\n\n    if saml_type is not None and saml_type == \"static\":\n        metadata_inline = mustache_render(\n            inline_metadata_template,\n            entity_id=entity_id,\n            x509_cert=x509_cert,\n            sso_url=sso_url,\n        )\n\n        saml_settings[\"metadata\"] = {\"inline\": [metadata_inline]}\n\n    if entity_id is not None and entity_id != \"\":\n        saml_settings[\"entityid\"] = entity_id\n\n    if sp_settings:\n        import json\n\n        saml_settings[\"service\"][\"sp\"].update(json.loads(sp_settings))\n\n    sp_config = Saml2Config()\n    sp_config.load(saml_settings)\n    sp_config.allow_unknown_attributes = True\n    saml_client = Saml2Client(config=sp_config)\n\n    return saml_client\n\n\n@blueprint.route(org_scoped_rule(\"/saml/callback\"), methods=[\"POST\"])\ndef idp_initiated(org_slug=None):\n    if not current_org.get_setting(\"auth_saml_enabled\"):\n        logger.error(\"SAML Login is not enabled\")\n        return redirect(url_for(\"redash.index\", org_slug=org_slug))\n\n    saml_client = get_saml_client(current_org)\n    try:\n        authn_response = saml_client.parse_authn_request_response(\n            request.form[\"SAMLResponse\"], entity.BINDING_HTTP_POST\n        )\n    except Exception:\n        logger.error(\"Failed to parse SAML response\", exc_info=True)\n        flash(\"SAML login failed. Please try again later.\")\n        return redirect(url_for(\"redash.login\", org_slug=org_slug))\n\n    authn_response.get_identity()\n    user_info = authn_response.get_subject()\n    email = user_info.text\n    name = \"%s %s\" % (\n        authn_response.ava[\"FirstName\"][0],\n        authn_response.ava[\"LastName\"][0],\n    )\n\n    # This is what as known as \"Just In Time (JIT) provisioning\".\n    # What that means is that, if a user in a SAML assertion\n    # isn't in the user store, we create that user first, then log them in\n    user = create_and_login_user(current_org, name, email)\n    if user is None:\n        return logout_and_redirect_to_index()\n\n    if \"RedashGroups\" in authn_response.ava:\n        group_names = authn_response.ava.get(\"RedashGroups\")\n        user.update_group_assignments(group_names)\n\n    url = url_for(\"redash.index\", org_slug=org_slug)\n\n    return redirect(url)\n\n\n@blueprint.route(org_scoped_rule(\"/saml/login\"))\ndef sp_initiated(org_slug=None):\n    if not current_org.get_setting(\"auth_saml_enabled\"):\n        logger.error(\"SAML Login is not enabled\")\n        return redirect(url_for(\"redash.index\", org_slug=org_slug))\n\n    saml_client = get_saml_client(current_org)\n    nameid_format = current_org.get_setting(\"auth_saml_nameid_format\")\n    if nameid_format is None or nameid_format == \"\":\n        nameid_format = NAMEID_FORMAT_TRANSIENT\n\n    _, info = saml_client.prepare_for_authenticate(nameid_format=nameid_format)\n\n    redirect_url = None\n    # Select the IdP URL to send the AuthN request to\n    for key, value in info[\"headers\"]:\n        if key == \"Location\":\n            redirect_url = value\n    response = redirect(redirect_url, code=302)\n\n    # NOTE:\n    #   I realize I _technically_ don't need to set Cache-Control or Pragma:\n    #     https://stackoverflow.com/a/5494469\n    #   However, Section 3.2.3.2 of the SAML spec suggests they are set:\n    #     http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf\n    #   We set those headers here as a \"belt and suspenders\" approach,\n    #   since enterprise environments don't always conform to RFCs\n    response.headers[\"Cache-Control\"] = \"no-cache, no-store\"\n    response.headers[\"Pragma\"] = \"no-cache\"\n    return response\n"
  },
  {
    "path": "redash/cli/__init__.py",
    "content": "import json\n\nimport click\nfrom flask import current_app\nfrom flask.cli import FlaskGroup, run_command, with_appcontext\nfrom rq import Connection\n\nfrom redash import __version__, create_app, rq_redis_connection, settings\nfrom redash.cli import (\n    data_sources,\n    database,\n    groups,\n    organization,\n    queries,\n    rq,\n    users,\n)\nfrom redash.monitor import get_status\n\n\ndef create():\n    app = current_app or create_app()\n\n    @app.shell_context_processor\n    def shell_context():\n        from redash import models, settings\n\n        return {\"models\": models, \"settings\": settings}\n\n    return app\n\n\n@click.group(cls=FlaskGroup, create_app=create)\ndef manager():\n    \"\"\"Management script for Redash\"\"\"\n\n\nmanager.add_command(database.manager, \"database\")\nmanager.add_command(users.manager, \"users\")\nmanager.add_command(groups.manager, \"groups\")\nmanager.add_command(data_sources.manager, \"ds\")\nmanager.add_command(organization.manager, \"org\")\nmanager.add_command(queries.manager, \"queries\")\nmanager.add_command(rq.manager, \"rq\")\nmanager.add_command(run_command, \"runserver\")\n\n\n@manager.command()\ndef version():\n    \"\"\"Displays Redash version.\"\"\"\n    print(__version__)\n\n\n@manager.command()\ndef status():\n    with Connection(rq_redis_connection):\n        print(json.dumps(get_status(), indent=2))\n\n\n@manager.command()\ndef check_settings():\n    \"\"\"Show the settings as Redash sees them (useful for debugging).\"\"\"\n    for name, item in current_app.config.items():\n        print(\"{} = {}\".format(name, item))\n\n\n@manager.command()\n@click.argument(\"email\", default=settings.MAIL_DEFAULT_SENDER, required=False)\ndef send_test_mail(email=None):\n    \"\"\"\n    Send test message to EMAIL (default: the address you defined in MAIL_DEFAULT_SENDER)\n    \"\"\"\n    from flask_mail import Message\n\n    from redash import mail\n\n    if email is None:\n        email = settings.MAIL_DEFAULT_SENDER\n\n    mail.send(Message(subject=\"Test Message from Redash\", recipients=[email], body=\"Test message.\"))\n\n\n@manager.command(\"shell\")\n@with_appcontext\ndef shell():\n    import sys\n\n    from flask.globals import _app_ctx_stack\n    from ptpython import repl\n\n    app = _app_ctx_stack.top.app\n\n    repl.embed(globals=app.make_shell_context())\n"
  },
  {
    "path": "redash/cli/data_sources.py",
    "content": "from sys import exit\n\nimport click\nfrom click.types import convert_type\nfrom flask.cli import AppGroup\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom redash import models\nfrom redash.query_runner import (\n    get_configuration_schema_for_query_runner_type,\n    query_runners,\n)\nfrom redash.utils import json_loads\nfrom redash.utils.configuration import ConfigurationContainer\n\nmanager = AppGroup(help=\"Data sources management commands.\")\n\n\n@manager.command(name=\"list\")\n@click.option(\n    \"--org\",\n    \"organization\",\n    default=None,\n    help=\"The organization the user belongs to (leave blank for \" \"all organizations).\",\n)\ndef list_command(organization=None):\n    \"\"\"List currently configured data sources.\"\"\"\n    if organization:\n        org = models.Organization.get_by_slug(organization)\n        data_sources = models.DataSource.query.filter(models.DataSource.org == org)\n    else:\n        data_sources = models.DataSource.query\n    for i, ds in enumerate(data_sources.order_by(models.DataSource.name)):\n        if i > 0:\n            print(\"-\" * 20)\n\n        print(\"Id: {}\\nName: {}\\nType: {}\\nOptions: {}\".format(ds.id, ds.name, ds.type, ds.options.to_json()))\n\n\n@manager.command(name=\"list_types\")\ndef list_types():\n    print(\"Enabled Query Runners:\")\n    types = sorted(query_runners.keys())\n    for query_runner_type in types:\n        print(query_runner_type)\n    print(\"Total of {}.\".format(len(types)))\n\n\ndef validate_data_source_type(type):\n    if type not in query_runners.keys():\n        print(\n            'Error: the type \"{}\" is not supported (supported types: {}).'.format(\n                type, \", \".join(query_runners.keys())\n            )\n        )\n        print(\"OJNK\")\n        exit(1)\n\n\n@manager.command()\n@click.argument(\"name\")\n@click.option(\n    \"--org\",\n    \"organization\",\n    default=\"default\",\n    help=\"The organization the user belongs to \" \"(leave blank for 'default').\",\n)\ndef test(name, organization=\"default\"):\n    \"\"\"Test connection to data source by issuing a trivial query.\"\"\"\n    try:\n        org = models.Organization.get_by_slug(organization)\n        data_source = models.DataSource.query.filter(\n            models.DataSource.name == name, models.DataSource.org == org\n        ).one()\n        print(\"Testing connection to data source: {} (id={})\".format(name, data_source.id))\n        try:\n            data_source.query_runner.test_connection()\n        except Exception as e:\n            print(\"Failure: {}\".format(e))\n            exit(1)\n        else:\n            print(\"Success\")\n    except NoResultFound:\n        print(\"Couldn't find data source named: {}\".format(name))\n        exit(1)\n\n\n@manager.command()\n@click.argument(\"name\", default=None, required=False)\n@click.option(\"--type\", default=None, help=\"new type for the data source\")\n@click.option(\"--options\", default=None, help=\"updated options for the data source\")\n@click.option(\n    \"--org\",\n    \"organization\",\n    default=\"default\",\n    help=\"The organization the user belongs to (leave blank for \" \"'default').\",\n)\ndef new(name=None, type=None, options=None, organization=\"default\"):\n    \"\"\"Create new data source.\"\"\"\n\n    if name is None:\n        name = click.prompt(\"Name\")\n\n    if type is None:\n        print(\"Select type:\")\n        for i, query_runner_name in enumerate(query_runners.keys()):\n            print(\"{}. {}\".format(i + 1, query_runner_name))\n\n        idx = 0\n        while idx < 1 or idx > len(list(query_runners.keys())):\n            idx = click.prompt(\"[{}-{}]\".format(1, len(query_runners.keys())), type=int)\n\n        type = list(query_runners.keys())[idx - 1]\n    else:\n        validate_data_source_type(type)\n\n    query_runner = query_runners[type]\n    schema = query_runner.configuration_schema()\n\n    if options is None:\n        types = {\"string\": str, \"number\": int, \"boolean\": bool}\n\n        options_obj = {}\n\n        for k, prop in schema[\"properties\"].items():\n            required = k in schema.get(\"required\", [])\n            default_value = \"<<DEFAULT_VALUE>>\"\n            if required:\n                default_value = None\n\n            prompt = prop.get(\"title\", k.capitalize())\n            if required:\n                prompt = \"{} (required)\".format(prompt)\n            else:\n                prompt = \"{} (optional)\".format(prompt)\n\n            _type = types[prop[\"type\"]]\n\n            def value_proc(value):\n                if value == default_value:\n                    return default_value\n                return convert_type(_type, default_value)(value)\n\n            value = click.prompt(\n                prompt,\n                default=default_value,\n                type=_type,\n                show_default=False,\n                value_proc=value_proc,\n            )\n            if value != default_value:\n                options_obj[k] = value\n\n        options = ConfigurationContainer(options_obj, schema)\n    else:\n        options = ConfigurationContainer(json_loads(options), schema)\n\n    if not options.is_valid():\n        print(\"Error: invalid configuration.\")\n        exit(1)\n\n    print(\"Creating {} data source ({}) with options:\\n{}\".format(type, name, options.to_json()))\n\n    data_source = models.DataSource.create_with_group(\n        name=name,\n        type=type,\n        options=options,\n        org=models.Organization.get_by_slug(organization),\n    )\n    models.db.session.commit()\n    print(\"Id: {}\".format(data_source.id))\n\n\n@manager.command()\n@click.argument(\"name\")\n@click.option(\n    \"--org\",\n    \"organization\",\n    default=\"default\",\n    help=\"The organization the user belongs to (leave blank for \" \"'default').\",\n)\ndef delete(name, organization=\"default\"):\n    \"\"\"Delete data source by name.\"\"\"\n    try:\n        org = models.Organization.get_by_slug(organization)\n        data_source = models.DataSource.query.filter(\n            models.DataSource.name == name, models.DataSource.org == org\n        ).one()\n        print(\"Deleting data source: {} (id={})\".format(name, data_source.id))\n        models.db.session.delete(data_source)\n        models.db.session.commit()\n    except NoResultFound:\n        print(\"Couldn't find data source named: {}\".format(name))\n        exit(1)\n\n\ndef update_attr(obj, attr, new_value):\n    if new_value is not None:\n        old_value = getattr(obj, attr)\n        print(\"Updating {}: {} -> {}\".format(attr, old_value, new_value))\n        setattr(obj, attr, new_value)\n\n\n@manager.command()\n@click.argument(\"name\")\n@click.option(\"--name\", \"new_name\", default=None, help=\"new name for the data source\")\n@click.option(\"--options\", default=None, help=\"updated options for the data source\")\n@click.option(\"--type\", default=None, help=\"new type for the data source\")\n@click.option(\n    \"--org\",\n    \"organization\",\n    default=\"default\",\n    help=\"The organization the user belongs to (leave blank for \" \"'default').\",\n)\ndef edit(name, new_name=None, options=None, type=None, organization=\"default\"):\n    \"\"\"Edit data source settings (name, options, type).\"\"\"\n    try:\n        if type is not None:\n            validate_data_source_type(type)\n        org = models.Organization.get_by_slug(organization)\n        data_source = models.DataSource.query.filter(\n            models.DataSource.name == name, models.DataSource.org == org\n        ).one()\n        update_attr(data_source, \"name\", new_name)\n        update_attr(data_source, \"type\", type)\n\n        if options is not None:\n            schema = get_configuration_schema_for_query_runner_type(data_source.type)\n            options = json_loads(options)\n            data_source.options.set_schema(schema)\n            data_source.options.update(options)\n\n        models.db.session.add(data_source)\n        models.db.session.commit()\n\n    except NoResultFound:\n        print(\"Couldn't find data source named: {}\".format(name))\n"
  },
  {
    "path": "redash/cli/database.py",
    "content": "import logging\nimport time\n\nimport sqlalchemy\nfrom click import argument, option\nfrom cryptography.fernet import InvalidToken\nfrom flask.cli import AppGroup\nfrom flask_migrate import stamp\nfrom sqlalchemy.exc import DatabaseError\nfrom sqlalchemy.sql import select\nfrom sqlalchemy_utils.types.encrypted.encrypted_type import FernetEngine\n\nfrom redash import settings\nfrom redash.models.base import Column, key_type\nfrom redash.models.types import EncryptedConfiguration\nfrom redash.utils.configuration import ConfigurationContainer\n\nmanager = AppGroup(help=\"Manage the database (create/drop tables. reencrypt data.).\")\n\n\ndef _wait_for_db_connection(db):\n    retried = False\n    while not retried:\n        try:\n            db.engine.execute(\"SELECT 1;\")\n            return\n        except DatabaseError:\n            time.sleep(30)\n\n        retried = True\n\n\ndef is_db_empty():\n    from redash.models import db\n\n    table_names = sqlalchemy.inspect(db.get_engine()).get_table_names()\n    return len(table_names) == 0\n\n\ndef load_extensions(db):\n    with db.engine.connect() as connection:\n        for extension in settings.dynamic_settings.database_extensions:\n            connection.execute(f'CREATE EXTENSION IF NOT EXISTS \"{extension}\";')\n\n\n@manager.command(name=\"create_tables\")\ndef create_tables():\n    \"\"\"Create the database tables.\"\"\"\n    from redash.models import db\n\n    _wait_for_db_connection(db)\n\n    # We need to make sure we run this only if the DB is empty, because otherwise calling\n    # stamp() will stamp it with the latest migration value and migrations won't run.\n    if is_db_empty():\n        load_extensions(db)\n\n        # To create triggers for searchable models, we need to call configure_mappers().\n        sqlalchemy.orm.configure_mappers()\n        db.create_all()\n\n        # Need to mark current DB as up to date\n        stamp()\n\n\n@manager.command(name=\"drop_tables\")\ndef drop_tables():\n    \"\"\"Drop the database tables.\"\"\"\n    from redash.models import db\n\n    _wait_for_db_connection(db)\n    db.drop_all()\n\n\n@manager.command()\n@argument(\"old_secret\")\n@argument(\"new_secret\")\n@option(\"--show-sql/--no-show-sql\", default=False, help=\"show sql for debug\")\ndef reencrypt(old_secret, new_secret, show_sql):\n    \"\"\"Reencrypt data encrypted by OLD_SECRET with NEW_SECRET.\"\"\"\n    from redash.models import db\n\n    _wait_for_db_connection(db)\n\n    if show_sql:\n        logging.basicConfig()\n        logging.getLogger(\"sqlalchemy.engine\").setLevel(logging.INFO)\n\n    def _reencrypt_for_table(table_name, orm_name):\n        table_for_select = sqlalchemy.Table(\n            table_name,\n            sqlalchemy.MetaData(),\n            Column(\"id\", key_type(orm_name), primary_key=True),\n            Column(\n                \"encrypted_options\",\n                ConfigurationContainer.as_mutable(EncryptedConfiguration(db.Text, old_secret, FernetEngine)),\n            ),\n        )\n        table_for_update = sqlalchemy.Table(\n            table_name,\n            sqlalchemy.MetaData(),\n            Column(\"id\", key_type(orm_name), primary_key=True),\n            Column(\n                \"encrypted_options\",\n                ConfigurationContainer.as_mutable(EncryptedConfiguration(db.Text, new_secret, FernetEngine)),\n            ),\n        )\n\n        update = table_for_update.update()\n        selected_items = db.session.execute(select([table_for_select]))\n        for item in selected_items:\n            try:\n                stmt = update.where(table_for_update.c.id == item[\"id\"]).values(\n                    encrypted_options=item[\"encrypted_options\"]\n                )\n            except InvalidToken:\n                logging.error(f'Invalid Decryption Key for id {item[\"id\"]} in table {table_for_select}')\n            else:\n                db.session.execute(stmt)\n\n        selected_items.close()\n        db.session.commit()\n\n    _reencrypt_for_table(\"data_sources\", \"DataSource\")\n    _reencrypt_for_table(\"notification_destinations\", \"NotificationDestination\")\n"
  },
  {
    "path": "redash/cli/groups.py",
    "content": "from sys import exit\n\nfrom click import argument, option\nfrom flask.cli import AppGroup\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom redash import models\n\nmanager = AppGroup(help=\"Groups management commands.\")\n\n\n@manager.command()\n@argument(\"name\")\n@option(\n    \"--org\",\n    \"organization\",\n    default=\"default\",\n    help=\"The organization the user belongs to (leave blank for \" \"'default').\",\n)\n@option(\n    \"--permissions\",\n    default=None,\n    help=\"Comma separated list of permissions ('create_dashboard',\"\n    \" 'create_query', 'edit_dashboard', 'edit_query', \"\n    \"'view_query', 'view_source', 'execute_query', 'list_users',\"\n    \" 'schedule_query', 'list_dashboards', 'list_alerts',\"\n    \" 'list_data_sources') (leave blank for default).\",\n)\ndef create(name, permissions=None, organization=\"default\"):\n    print(\"Creating group (%s)...\" % (name))\n\n    org = models.Organization.get_by_slug(organization)\n\n    permissions = extract_permissions_string(permissions)\n\n    print(\"permissions: [%s]\" % \",\".join(permissions))\n\n    try:\n        models.db.session.add(models.Group(name=name, org=org, permissions=permissions))\n        models.db.session.commit()\n    except Exception as e:\n        print(\"Failed create group: %s\" % e)\n        exit(1)\n\n\n@manager.command(name=\"change_permissions\")\n@argument(\"group_id\")\n@option(\n    \"--permissions\",\n    default=None,\n    help=\"Comma separated list of permissions ('create_dashboard',\"\n    \" 'create_query', 'edit_dashboard', 'edit_query',\"\n    \" 'view_query', 'view_source', 'execute_query', 'list_users',\"\n    \" 'schedule_query', 'list_dashboards', 'list_alerts',\"\n    \" 'list_data_sources') (leave blank for default).\",\n)\ndef change_permissions(group_id, permissions=None):\n    print(\"Change permissions of group %s ...\" % group_id)\n\n    try:\n        group = models.Group.query.get(group_id)\n    except NoResultFound:\n        print(\"Group [%s] not found.\" % group_id)\n        exit(1)\n\n    permissions = extract_permissions_string(permissions)\n    print(\"current permissions [%s] will be modify to [%s]\" % (\",\".join(group.permissions), \",\".join(permissions)))\n\n    group.permissions = permissions\n\n    try:\n        models.db.session.add(group)\n        models.db.session.commit()\n    except Exception as e:\n        print(\"Failed change permission: %s\" % e)\n        exit(1)\n\n\ndef extract_permissions_string(permissions):\n    if permissions is None:\n        permissions = models.Group.DEFAULT_PERMISSIONS\n    else:\n        permissions = permissions.split(\",\")\n        permissions = [p.strip() for p in permissions]\n    return permissions\n\n\n@manager.command(name=\"list\")\n@option(\n    \"--org\",\n    \"organization\",\n    default=None,\n    help=\"The organization to limit to (leave blank for all).\",\n)\ndef list_command(organization=None):\n    \"\"\"List all groups\"\"\"\n    if organization:\n        org = models.Organization.get_by_slug(organization)\n        groups = models.Group.query.filter(models.Group.org == org)\n    else:\n        groups = models.Group.query\n\n    for i, group in enumerate(groups.order_by(models.Group.name)):\n        if i > 0:\n            print(\"-\" * 20)\n\n        print(\n            \"Id: {}\\nName: {}\\nType: {}\\nOrganization: {}\\nPermissions: [{}]\".format(\n                group.id,\n                group.name,\n                group.type,\n                group.org.slug,\n                \",\".join(group.permissions),\n            )\n        )\n\n        members = models.Group.members(group.id)\n        user_names = [m.name for m in members]\n        if user_names:\n            print(\"Users: {}\".format(\", \".join(user_names)))\n        else:\n            print(\"Users:\")\n"
  },
  {
    "path": "redash/cli/organization.py",
    "content": "from click import argument, option\nfrom flask.cli import AppGroup\n\nfrom redash import models\n\nmanager = AppGroup(help=\"Organization management commands.\")\n\n\n@manager.command(name=\"set_google_apps_domains\")\n@argument(\"domains\")\ndef set_google_apps_domains(domains):\n    \"\"\"\n    Sets the allowable domains to the comma separated list DOMAINS.\n    \"\"\"\n    organization = models.Organization.query.first()\n    k = models.Organization.SETTING_GOOGLE_APPS_DOMAINS\n    organization.settings[k] = domains.split(\",\")\n    models.db.session.add(organization)\n    models.db.session.commit()\n    print(\"Updated list of allowed domains to: {}\".format(organization.google_apps_domains))\n\n\n@manager.command(name=\"show_google_apps_domains\")\ndef show_google_apps_domains():\n    organization = models.Organization.query.first()\n    print(\"Current list of Google Apps domains: {}\".format(\", \".join(organization.google_apps_domains)))\n\n\n@manager.command(name=\"create\")\n@argument(\"name\")\n@option(\n    \"--slug\",\n    \"slug\",\n    default=\"default\",\n    help=\"The slug the organization belongs to (leave blank for \" \"'default').\",\n)\ndef create(name, slug=\"default\"):\n    print(\"Creating organization (%s)...\" % (name))\n\n    try:\n        models.db.session.add(models.Organization(name=name, slug=slug, settings={}))\n        models.db.session.commit()\n    except Exception as e:\n        print(\"Failed create organization: %s\" % e)\n        exit(1)\n\n\n@manager.command(name=\"list\")\ndef list_command():\n    \"\"\"List all organizations\"\"\"\n    orgs = models.Organization.query\n    for i, org in enumerate(orgs.order_by(models.Organization.name)):\n        if i > 0:\n            print(\"-\" * 20)\n\n        print(\"Id: {}\\nName: {}\\nSlug: {}\".format(org.id, org.name, org.slug))\n"
  },
  {
    "path": "redash/cli/queries.py",
    "content": "from click import argument\nfrom flask.cli import AppGroup\nfrom sqlalchemy.orm.exc import NoResultFound\n\nmanager = AppGroup(help=\"Queries management commands.\")\n\n\n@manager.command(name=\"rehash\")\ndef rehash():\n    from redash import models\n\n    for q in models.Query.query.all():\n        old_hash = q.query_hash\n        q.update_query_hash()\n        new_hash = q.query_hash\n\n        if old_hash != new_hash:\n            print(f\"Query {q.id} has changed hash from {old_hash} to {new_hash}\")\n            models.db.session.add(q)\n\n    models.db.session.commit()\n\n\n@manager.command(name=\"add_tag\")\n@argument(\"query_id\")\n@argument(\"tag\")\ndef add_tag(query_id, tag):\n    from redash import models\n\n    query_id = int(query_id)\n\n    try:\n        q = models.Query.get_by_id(query_id)\n    except NoResultFound:\n        print(\"Query not found.\")\n        exit(1)\n\n    tags = q.tags\n    if tags is None:\n        tags = []\n    tags.append(tag)\n    q.tags = list(set(tags))\n\n    models.db.session.add(q)\n    models.db.session.commit()\n\n    print(\"Tag added.\")\n\n\n@manager.command(name=\"remove_tag\")\n@argument(\"query_id\")\n@argument(\"tag\")\ndef remove_tag(query_id, tag):\n    from redash import models\n\n    query_id = int(query_id)\n\n    try:\n        q = models.Query.get_by_id(query_id)\n    except NoResultFound:\n        print(\"Query not found.\")\n        exit(1)\n\n    tags = q.tags\n    if tags is None:\n        print(\"Tag is empty.\")\n        exit(1)\n\n    try:\n        tags.remove(tag)\n    except ValueError:\n        print(\"Tag not found.\")\n        exit(1)\n\n    q.tags = list(set(tags))\n\n    models.db.session.add(q)\n    models.db.session.commit()\n\n    print(\"Tag removed.\")\n"
  },
  {
    "path": "redash/cli/rq.py",
    "content": "import datetime\nimport socket\nfrom itertools import chain\n\nfrom click import argument\nfrom flask.cli import AppGroup\nfrom rq import Connection\nfrom rq.worker import WorkerStatus\nfrom sqlalchemy.orm import configure_mappers\nfrom supervisor_checks import check_runner\nfrom supervisor_checks.check_modules import base\n\nfrom redash import rq_redis_connection\nfrom redash.tasks import (\n    periodic_job_definitions,\n    rq_scheduler,\n    schedule_periodic_jobs,\n)\nfrom redash.tasks.worker import Worker\nfrom redash.worker import default_queues\n\nmanager = AppGroup(help=\"RQ management commands.\")\n\n\n@manager.command()\ndef scheduler():\n    jobs = periodic_job_definitions()\n    schedule_periodic_jobs(jobs)\n    rq_scheduler.run()\n\n\n@manager.command()\n@argument(\"queues\", nargs=-1)\ndef worker(queues):\n    # Configure any SQLAlchemy mappers loaded until now so that the mapping configuration\n    # will already be available to the forked work horses and they won't need\n    # to spend valuable time re-doing that on every fork.\n    configure_mappers()\n\n    if not queues:\n        queues = default_queues\n    else:\n        queues = chain(*[queue.split(\",\") for queue in queues])\n\n    with Connection(rq_redis_connection):\n        w = Worker(queues, log_job_description=False, job_monitoring_interval=5)\n        w.work()\n\n\nclass WorkerHealthcheck(base.BaseCheck):\n    NAME = \"RQ Worker Healthcheck\"\n\n    def __call__(self, process_spec):\n        pid = process_spec[\"pid\"]\n        all_workers = Worker.all(connection=rq_redis_connection)\n        workers = [w for w in all_workers if w.hostname == socket.gethostname() and w.pid == pid]\n\n        if not workers:\n            self._log(f\"Cannot find worker for hostname {socket.gethostname()} and pid {pid}. ==> Is healthy? False\")\n            return False\n\n        worker = workers.pop()\n\n        is_busy = worker.get_state() == WorkerStatus.BUSY\n\n        time_since_seen = datetime.datetime.utcnow() - worker.last_heartbeat\n        seen_lately = time_since_seen.seconds < 60\n\n        total_jobs_in_watched_queues = sum([len(q.jobs) for q in worker.queues])\n        has_nothing_to_do = total_jobs_in_watched_queues == 0\n\n        is_healthy = is_busy or seen_lately or has_nothing_to_do\n\n        self._log(\n            \"Worker %s healthcheck: Is busy? %s. \"\n            \"Seen lately? %s (%d seconds ago). \"\n            \"Has nothing to do? %s (%d jobs in watched queues). \"\n            \"==> Is healthy? %s\",\n            worker.key,\n            is_busy,\n            seen_lately,\n            time_since_seen.seconds,\n            has_nothing_to_do,\n            total_jobs_in_watched_queues,\n            is_healthy,\n        )\n\n        return is_healthy\n\n\n@manager.command()\ndef healthcheck():\n    return check_runner.CheckRunner(\"worker_healthcheck\", \"worker\", None, [(WorkerHealthcheck, {})]).run()\n"
  },
  {
    "path": "redash/cli/users.py",
    "content": "import json\nfrom sys import exit\n\nfrom click import BOOL, argument, option, prompt\nfrom flask.cli import AppGroup\nfrom sqlalchemy.exc import IntegrityError\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom redash import models\nfrom redash.handlers.users import invite_user\n\nmanager = AppGroup(help=\"Users management commands.\")\n\n\ndef build_groups(org, groups, is_admin):\n    if isinstance(groups, str):\n        groups = groups.split(\",\")\n        groups.remove(\"\")  # in case it was empty string\n        groups = [int(g) for g in groups]\n\n    if groups is None:\n        groups = [org.default_group.id]\n\n    if is_admin:\n        groups += [org.admin_group.id]\n\n    return groups\n\n\n@manager.command(name=\"grant_admin\")\n@argument(\"email\")\n@option(\n    \"--org\",\n    \"organization\",\n    default=\"default\",\n    help=\"the organization the user belongs to, (leave blank for \" \"'default').\",\n)\ndef grant_admin(email, organization=\"default\"):\n    \"\"\"\n    Grant admin access to user EMAIL.\n    \"\"\"\n    try:\n        org = models.Organization.get_by_slug(organization)\n        admin_group = org.admin_group\n        user = models.User.get_by_email_and_org(email, org)\n\n        if admin_group.id in user.group_ids:\n            print(\"User is already an admin.\")\n        else:\n            user.group_ids = user.group_ids + [org.admin_group.id]\n            models.db.session.add(user)\n            models.db.session.commit()\n            print(\"User updated.\")\n    except NoResultFound:\n        print(\"User [%s] not found.\" % email)\n\n\n@manager.command()\n@argument(\"email\")\n@argument(\"name\")\n@option(\n    \"--org\",\n    \"organization\",\n    default=\"default\",\n    help=\"The organization the user belongs to (leave blank for \" \"'default').\",\n)\n@option(\"--admin\", \"is_admin\", is_flag=True, default=False, help=\"set user as admin\")\n@option(\n    \"--google\",\n    \"google_auth\",\n    is_flag=True,\n    default=False,\n    help=\"user uses Google Auth to login\",\n)\n@option(\n    \"--password\",\n    \"password\",\n    default=None,\n    help=\"Password for users who don't use Google Auth \" \"(leave blank for prompt).\",\n)\n@option(\n    \"--groups\",\n    \"groups\",\n    default=None,\n    help=\"Comma separated list of groups (leave blank for \" \"default).\",\n)\ndef create(\n    email,\n    name,\n    groups,\n    is_admin=False,\n    google_auth=False,\n    password=None,\n    organization=\"default\",\n):\n    \"\"\"\n    Create user EMAIL with display name NAME.\n    \"\"\"\n    print(\"Creating user (%s, %s) in organization %s...\" % (email, name, organization))\n    print(\"Admin: %r\" % is_admin)\n    print(\"Login with Google Auth: %r\\n\" % google_auth)\n\n    org = models.Organization.get_by_slug(organization)\n    groups = build_groups(org, groups, is_admin)\n\n    user = models.User(org=org, email=email, name=name, group_ids=groups)\n    if not password and not google_auth:\n        password = prompt(\"Password\", hide_input=True, confirmation_prompt=True)\n    if not google_auth:\n        user.hash_password(password)\n\n    try:\n        models.db.session.add(user)\n        models.db.session.commit()\n    except Exception as e:\n        print(\"Failed creating user: %s\" % e)\n        exit(1)\n\n\n@manager.command(name=\"create_root\")\n@argument(\"email\")\n@argument(\"name\")\n@option(\n    \"--org\",\n    \"organization\",\n    default=\"default\",\n    help=\"The organization the root user belongs to (leave blank for 'default').\",\n)\n@option(\n    \"--google\",\n    \"google_auth\",\n    is_flag=True,\n    default=False,\n    help=\"user uses Google Auth to login\",\n)\n@option(\n    \"--password\",\n    \"password\",\n    default=None,\n    help=\"Password for root user who don't use Google Auth (leave blank for prompt).\",\n)\ndef create_root(email, name, google_auth=False, password=None, organization=\"default\"):\n    \"\"\"\n    Create root user.\n    \"\"\"\n    print(\"Creating root user (%s, %s) in organization %s...\" % (email, name, organization))\n    print(\"Login with Google Auth: %r\\n\" % google_auth)\n\n    user = models.User.query.filter(models.User.email == email).first()\n    if user is not None:\n        print(\"User [%s] is already exists.\" % email)\n        exit(1)\n\n    org_slug = organization\n    org = models.Organization.query.filter(models.Organization.slug == org_slug).first()\n    if org is None:\n        org = models.Organization(name=org_slug, slug=org_slug, settings={})\n\n    admin_group = models.Group(\n        name=\"admin\",\n        permissions=models.Group.ADMIN_PERMISSIONS,\n        org=org,\n        type=models.Group.BUILTIN_GROUP,\n    )\n    default_group = models.Group(\n        name=\"default\",\n        permissions=models.Group.DEFAULT_PERMISSIONS,\n        org=org,\n        type=models.Group.BUILTIN_GROUP,\n    )\n\n    models.db.session.add_all([org, admin_group, default_group])\n    models.db.session.commit()\n\n    user = models.User(\n        org=org,\n        email=email,\n        name=name,\n        group_ids=[admin_group.id, default_group.id],\n    )\n    if not google_auth:\n        user.hash_password(password)\n\n    try:\n        models.db.session.add(user)\n        models.db.session.commit()\n    except Exception as e:\n        print(\"Failed creating root user: %s\" % e)\n        exit(1)\n\n\n@manager.command()\n@argument(\"email\")\n@option(\n    \"--org\",\n    \"organization\",\n    default=None,\n    help=\"The organization the user belongs to (leave blank for all\" \" organizations).\",\n)\ndef delete(email, organization=None):\n    \"\"\"\n    Delete user EMAIL.\n    \"\"\"\n    if organization:\n        org = models.Organization.get_by_slug(organization)\n        deleted_count = models.User.query.filter(models.User.email == email, models.User.org == org.id).delete()\n    else:\n        deleted_count = models.User.query.filter(models.User.email == email).delete(synchronize_session=False)\n    models.db.session.commit()\n    print(\"Deleted %d users.\" % deleted_count)\n\n\n@manager.command()\n@argument(\"email\")\n@argument(\"password\")\n@option(\n    \"--org\",\n    \"organization\",\n    default=None,\n    help=\"The organization the user belongs to (leave blank for all \" \"organizations).\",\n)\ndef password(email, password, organization=None):\n    \"\"\"\n    Resets password for EMAIL to PASSWORD.\n    \"\"\"\n    if organization:\n        org = models.Organization.get_by_slug(organization)\n        user = models.User.query.filter(models.User.email == email, models.User.org == org).first()\n    else:\n        user = models.User.query.filter(models.User.email == email).first()\n\n    if user is not None:\n        user.hash_password(password)\n        models.db.session.add(user)\n        models.db.session.commit()\n        print(\"User updated.\")\n    else:\n        print(\"User [%s] not found.\" % email)\n        exit(1)\n\n\n@manager.command()\n@argument(\"email\")\n@argument(\"name\")\n@argument(\"inviter_email\")\n@option(\n    \"--org\",\n    \"organization\",\n    default=\"default\",\n    help=\"The organization the user belongs to (leave blank for 'default')\",\n)\n@option(\"--admin\", \"is_admin\", type=BOOL, default=False, help=\"set user as admin\")\n@option(\n    \"--groups\",\n    \"groups\",\n    default=None,\n    help=\"Comma separated list of groups (leave blank for default).\",\n)\ndef invite(email, name, inviter_email, groups, is_admin=False, organization=\"default\"):\n    \"\"\"\n    Sends an invitation to the given NAME and EMAIL from INVITER_EMAIL.\n    \"\"\"\n    org = models.Organization.get_by_slug(organization)\n    groups = build_groups(org, groups, is_admin)\n    try:\n        user_from = models.User.get_by_email_and_org(inviter_email, org)\n        user = models.User(org=org, name=name, email=email, group_ids=groups)\n        models.db.session.add(user)\n        try:\n            models.db.session.commit()\n            invite_user(org, user_from, user)\n            print(\"An invitation was sent to [%s] at [%s].\" % (name, email))\n        except IntegrityError as e:\n            if \"email\" in str(e):\n                print(\"Cannot invite. User already exists [%s]\" % email)\n            else:\n                print(e)\n    except NoResultFound:\n        print(\"The inviter [%s] was not found.\" % inviter_email)\n\n\n@manager.command(name=\"list\")\n@option(\n    \"--org\",\n    \"organization\",\n    default=None,\n    help=\"The organization the user belongs to (leave blank for all\" \" organizations)\",\n)\n@option(\"--json\", \"as_json\", is_flag=True, default=False, help=\"Output as JSON\")\ndef list_command(organization=None, as_json=False):\n    \"\"\"List all users\"\"\"\n    if organization:\n        org = models.Organization.get_by_slug(organization)\n        users = models.User.query.filter(models.User.org == org)\n    else:\n        users = models.User.query\n    if as_json:\n        result = []\n        for user in users.order_by(models.User.name):\n            result.append(\n                {\n                    \"id\": user.id,\n                    \"name\": user.name,\n                    \"email\": user.email,\n                    \"org\": {\n                        \"slug\": user.org.slug,\n                        \"name\": user.org.name,\n                    },\n                    \"active\": not user.is_disabled,\n                }\n            )\n\n        print(json.dumps(result, indent=2))\n        return\n\n    for i, user in enumerate(users.order_by(models.User.name)):\n        if i > 0:\n            print(\"-\" * 20)\n\n        print(\n            \"Id: {}\\nName: {}\\nEmail: {}\\nOrganization: {}\\nActive: {}\".format(\n                user.id, user.name, user.email, user.org.name, not (user.is_disabled)\n            )\n        )\n\n        groups = models.Group.query.filter(models.Group.id.in_(user.group_ids)).all()\n        group_names = [group.name for group in groups]\n        print(\"Groups: {}\".format(\", \".join(group_names)))\n"
  },
  {
    "path": "redash/destinations/__init__.py",
    "content": "import logging\n\nlogger = logging.getLogger(__name__)\n\n__all__ = [\"BaseDestination\", \"register\", \"get_destination\", \"import_destinations\"]\n\n\nclass BaseDestination:\n    deprecated = False\n\n    def __init__(self, configuration):\n        self.configuration = configuration\n\n    @classmethod\n    def name(cls):\n        return cls.__name__\n\n    @classmethod\n    def type(cls):\n        return cls.__name__.lower()\n\n    @classmethod\n    def icon(cls):\n        return \"fa-bullseye\"\n\n    @classmethod\n    def enabled(cls):\n        return True\n\n    @classmethod\n    def configuration_schema(cls):\n        return {}\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        raise NotImplementedError()\n\n    @classmethod\n    def to_dict(cls):\n        return {\n            \"name\": cls.name(),\n            \"type\": cls.type(),\n            \"icon\": cls.icon(),\n            \"configuration_schema\": cls.configuration_schema(),\n            **({\"deprecated\": True} if cls.deprecated else {}),\n        }\n\n\ndestinations = {}\n\n\ndef register(destination_class):\n    global destinations\n    if destination_class.enabled():\n        logger.debug(\n            \"Registering %s (%s) destinations.\",\n            destination_class.name(),\n            destination_class.type(),\n        )\n        destinations[destination_class.type()] = destination_class\n    else:\n        logger.warning(\n            \"%s destination enabled but not supported, not registering. Either disable or install missing dependencies.\",\n            destination_class.name(),\n        )\n\n\ndef get_destination(destination_type, configuration):\n    destination_class = destinations.get(destination_type, None)\n    if destination_class is None:\n        return None\n    return destination_class(configuration)\n\n\ndef get_configuration_schema_for_destination_type(destination_type):\n    destination_class = destinations.get(destination_type, None)\n    if destination_class is None:\n        return None\n\n    return destination_class.configuration_schema()\n\n\ndef import_destinations(destination_imports):\n    for destination_import in destination_imports:\n        __import__(destination_import)\n"
  },
  {
    "path": "redash/destinations/asana.py",
    "content": "import logging\nimport textwrap\n\nimport requests\n\nfrom redash.destinations import BaseDestination, register\nfrom redash.models import Alert\n\n\nclass Asana(BaseDestination):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"pat\": {\"type\": \"string\", \"title\": \"Asana Personal Access Token\"},\n                \"project_id\": {\"type\": \"string\", \"title\": \"Asana Project ID\"},\n            },\n            \"secret\": [\"pat\"],\n            \"required\": [\"pat\", \"project_id\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-asana\"\n\n    @property\n    def api_base_url(self):\n        return \"https://app.asana.com/api/1.0/tasks\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        # Documentation: https://developers.asana.com/docs/tasks\n        state = \"TRIGGERED\" if new_state == Alert.TRIGGERED_STATE else \"RECOVERED\"\n\n        notes = textwrap.dedent(\n            f\"\"\"\n        {alert.name} has {state}.\n\n        Query: {host}/queries/{query.id}\n        Alert: {host}/alerts/{alert.id}\n        \"\"\"\n        ).strip()\n\n        data = {\n            \"name\": f\"[Redash Alert] {state}: {alert.name}\",\n            \"notes\": notes,\n            \"projects\": [options[\"project_id\"]],\n        }\n\n        try:\n            resp = requests.post(\n                self.api_base_url,\n                data=data,\n                timeout=5.0,\n                headers={\"Authorization\": f\"Bearer {options['pat']}\"},\n            )\n            logging.warning(resp.text)\n            if resp.status_code != 201:\n                logging.error(\"Asana send ERROR. status_code => {status}\".format(status=resp.status_code))\n        except Exception as e:\n            logging.exception(\"Asana send ERROR. {exception}\".format(exception=e))\n\n\nregister(Asana)\n"
  },
  {
    "path": "redash/destinations/chatwork.py",
    "content": "import logging\n\nimport requests\n\nfrom redash.destinations import BaseDestination, register\n\n\nclass ChatWork(BaseDestination):\n    ALERTS_DEFAULT_MESSAGE_TEMPLATE = \"{alert_name} changed state to {new_state}.\\\\n{alert_url}\\\\n{query_url}\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"api_token\": {\"type\": \"string\", \"title\": \"API Token\"},\n                \"room_id\": {\"type\": \"string\", \"title\": \"Room ID\"},\n                \"message_template\": {\n                    \"type\": \"string\",\n                    \"default\": ChatWork.ALERTS_DEFAULT_MESSAGE_TEMPLATE,\n                    \"title\": \"Message Template\",\n                },\n            },\n            \"secret\": [\"api_token\"],\n            \"required\": [\"message_template\", \"api_token\", \"room_id\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-comment\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        try:\n            # Documentation: http://developer.chatwork.com/ja/endpoint_rooms.html#POST-rooms-room_id-messages\n            url = \"https://api.chatwork.com/v2/rooms/{room_id}/messages\".format(room_id=options.get(\"room_id\"))\n\n            message = \"\"\n            if alert.custom_subject:\n                message = alert.custom_subject + \"\\n\"\n            if alert.custom_body:\n                message += alert.custom_body\n            else:\n                alert_url = \"{host}/alerts/{alert_id}\".format(host=host, alert_id=alert.id)\n                query_url = \"{host}/queries/{query_id}\".format(host=host, query_id=query.id)\n                message_template = options.get(\"message_template\", ChatWork.ALERTS_DEFAULT_MESSAGE_TEMPLATE)\n                message += message_template.replace(\"\\\\n\", \"\\n\").format(\n                    alert_name=alert.name,\n                    new_state=new_state.upper(),\n                    alert_url=alert_url,\n                    query_url=query_url,\n                )\n\n            headers = {\"X-ChatWorkToken\": options.get(\"api_token\")}\n            payload = {\"body\": message}\n\n            resp = requests.post(url, headers=headers, data=payload, timeout=5.0)\n            logging.warning(resp.text)\n            if resp.status_code != 200:\n                logging.error(\"ChatWork send ERROR. status_code => {status}\".format(status=resp.status_code))\n        except Exception:\n            logging.exception(\"ChatWork send ERROR.\")\n\n\nregister(ChatWork)\n"
  },
  {
    "path": "redash/destinations/datadog.py",
    "content": "import logging\nimport os\n\nimport requests\n\nfrom redash.destinations import BaseDestination, register\nfrom redash.utils import json_dumps\n\n\nclass Datadog(BaseDestination):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"api_key\": {\"type\": \"string\", \"title\": \"API Key\"},\n                \"tags\": {\"type\": \"string\", \"title\": \"Tags\"},\n                \"priority\": {\"type\": \"string\", \"default\": \"normal\", \"title\": \"Priority\"},\n                # https://docs.datadoghq.com/integrations/faq/list-of-api-source-attribute-value/\n                \"source_type_name\": {\"type\": \"string\", \"default\": \"my_apps\", \"title\": \"Source Type Name\"},\n            },\n            \"secret\": [\"api_key\"],\n            \"required\": [\"api_key\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-datadog\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        # Documentation: https://docs.datadoghq.com/api/latest/events/#post-an-event\n        if new_state == \"triggered\":\n            alert_type = \"error\"\n            if alert.custom_subject:\n                title = alert.custom_subject\n            else:\n                title = f\"{alert.name} just triggered\"\n        else:\n            alert_type = \"success\"\n            if alert.custom_subject:\n                title = alert.custom_subject\n            else:\n                title = f\"{alert.name} went back to normal\"\n\n        if alert.custom_body:\n            text = alert.custom_body\n        else:\n            text = f\"{alert.name} changed state to {new_state}.\"\n\n        query_url = f\"{host}/queries/{query.id}\"\n        alert_url = f\"{host}/alerts/{alert.id}\"\n        text += f\"\\nQuery: {query_url}\\nAlert: {alert_url}\"\n\n        headers = {\n            \"Accept\": \"application/json\",\n            \"Content-Type\": \"application/json\",\n            \"DD-API-KEY\": options.get(\"api_key\"),\n        }\n\n        body = {\n            \"title\": title,\n            \"text\": text,\n            \"alert_type\": alert_type,\n            \"priority\": options.get(\"priority\"),\n            \"source_type_name\": options.get(\"source_type_name\"),\n            \"aggregation_key\": f\"redash:{alert_url}\",\n            \"tags\": [],\n        }\n\n        tags = options.get(\"tags\")\n        if tags:\n            body[\"tags\"] = tags.split(\",\")\n        body[\"tags\"].extend(\n            [\n                \"redash\",\n                f\"query_id:{query.id}\",\n                f\"alert_id:{alert.id}\",\n            ]\n        )\n\n        dd_host = os.getenv(\"DATADOG_HOST\", \"api.datadoghq.com\")\n        url = f\"https://{dd_host}/api/v1/events\"\n\n        try:\n            resp = requests.post(url, headers=headers, data=json_dumps(body), timeout=5.0)\n            logging.warning(resp.text)\n            if resp.status_code != 202:\n                logging.error(f\"Datadog send ERROR. status_code => {resp.status_code}\")\n        except Exception as e:\n            logging.exception(\"Datadog send ERROR: %s\", e)\n\n\nregister(Datadog)\n"
  },
  {
    "path": "redash/destinations/discord.py",
    "content": "import logging\n\nimport requests\n\nfrom redash.destinations import BaseDestination, register\nfrom redash.models import Alert\nfrom redash.utils import json_dumps\n\ncolors = {\n    # Colors are in a Decimal format as Discord requires them to be Decimals for embeds\n    Alert.OK_STATE: \"2600544\",  # Green Decimal Code\n    Alert.TRIGGERED_STATE: \"12597547\",  # Red Decimal Code\n    Alert.UNKNOWN_STATE: \"16776960\",  # Yellow Decimal Code\n}\n\n\nclass Discord(BaseDestination):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\"url\": {\"type\": \"string\", \"title\": \"Discord Webhook URL\"}},\n            \"secret\": [\"url\"],\n            \"required\": [\"url\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-discord\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        # Documentation: https://birdie0.github.io/discord-webhooks-guide/discord_webhook.html\n        fields = [\n            {\n                \"name\": \"Query\",\n                \"value\": f\"{host}/queries/{query.id}\",\n                \"inline\": True,\n            },\n            {\n                \"name\": \"Alert\",\n                \"value\": f\"{host}/alerts/{alert.id}\",\n                \"inline\": True,\n            },\n        ]\n        if alert.custom_body:\n            fields.append({\"name\": \"Description\", \"value\": alert.custom_body})\n        if new_state == Alert.TRIGGERED_STATE:\n            if alert.options.get(\"custom_subject\"):\n                text = alert.options[\"custom_subject\"]\n            else:\n                text = f\"{alert.name} just triggered\"\n        else:\n            text = f\"{alert.name} went back to normal\"\n        color = colors.get(new_state)\n        payload = {\"content\": text, \"embeds\": [{\"color\": color, \"fields\": fields}]}\n        headers = {\"Content-Type\": \"application/json\"}\n        try:\n            resp = requests.post(\n                options.get(\"url\"),\n                data=json_dumps(payload),\n                headers=headers,\n                timeout=5.0,\n            )\n            if resp.status_code != 200 and resp.status_code != 204:\n                logging.error(f\"Discord send ERROR. status_code => {resp.status_code}\")\n        except Exception as e:\n            logging.exception(\"Discord send ERROR: %s\", e)\n\n\nregister(Discord)\n"
  },
  {
    "path": "redash/destinations/email.py",
    "content": "import logging\n\nfrom flask_mail import Message\n\nfrom redash import mail, settings\nfrom redash.destinations import BaseDestination, register\n\n\nclass Email(BaseDestination):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"addresses\": {\"type\": \"string\"},\n                \"subject_template\": {\n                    \"type\": \"string\",\n                    \"default\": settings.ALERTS_DEFAULT_MAIL_SUBJECT_TEMPLATE,\n                    \"title\": \"Subject Template\",\n                },\n            },\n            \"required\": [\"addresses\"],\n            \"extra_options\": [\"subject_template\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-envelope\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        recipients = [email for email in options.get(\"addresses\", \"\").split(\",\") if email]\n\n        if not recipients:\n            logging.warning(\"No emails given. Skipping send.\")\n\n        if alert.custom_body:\n            html = alert.custom_body\n        else:\n            with open(settings.REDASH_ALERTS_DEFAULT_MAIL_BODY_TEMPLATE_FILE, \"r\") as f:\n                html = alert.render_template(f.read())\n        logging.debug(\"Notifying: %s\", recipients)\n\n        try:\n            state = new_state.upper()\n            if alert.custom_subject:\n                subject = alert.custom_subject\n            else:\n                subject_template = options.get(\"subject_template\", settings.ALERTS_DEFAULT_MAIL_SUBJECT_TEMPLATE)\n                subject = subject_template.format(alert_name=alert.name, state=state)\n\n            message = Message(recipients=recipients, subject=subject, html=html)\n            mail.send(message)\n        except Exception:\n            logging.exception(\"Mail send error.\")\n\n\nregister(Email)\n"
  },
  {
    "path": "redash/destinations/hangoutschat.py",
    "content": "import logging\n\nimport requests\n\nfrom redash.destinations import BaseDestination, register\nfrom redash.utils import json_dumps\n\n\nclass HangoutsChat(BaseDestination):\n    @classmethod\n    def name(cls):\n        return \"Google Hangouts Chat\"\n\n    @classmethod\n    def type(cls):\n        return \"hangouts_chat\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\n                    \"type\": \"string\",\n                    \"title\": \"Webhook URL (get it from the room settings)\",\n                },\n                \"icon_url\": {\n                    \"type\": \"string\",\n                    \"title\": \"Icon URL (32x32 or multiple, png format)\",\n                },\n            },\n            \"secret\": [\"url\"],\n            \"required\": [\"url\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-bolt\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        try:\n            if new_state == \"triggered\":\n                message = '<b><font color=\"#c0392b\">Triggered</font></b>'\n            elif new_state == \"ok\":\n                message = '<font color=\"#27ae60\">Went back to normal</font>'\n            else:\n                message = \"Unable to determine status. Check Query and Alert configuration.\"\n\n            if alert.custom_subject:\n                title = alert.custom_subject\n            else:\n                title = alert.name\n\n            data = {\n                \"cards\": [\n                    {\n                        \"header\": {\"title\": title},\n                        \"sections\": [{\"widgets\": [{\"textParagraph\": {\"text\": message}}]}],\n                    }\n                ]\n            }\n\n            if alert.custom_body:\n                data[\"cards\"][0][\"sections\"].append({\"widgets\": [{\"textParagraph\": {\"text\": alert.custom_body}}]})\n\n            if options.get(\"icon_url\"):\n                data[\"cards\"][0][\"header\"][\"imageUrl\"] = options.get(\"icon_url\")\n\n            # Hangouts Chat will create a blank card if an invalid URL (no hostname) is posted.\n            if host:\n                data[\"cards\"][0][\"sections\"][0][\"widgets\"].append(\n                    {\n                        \"buttons\": [\n                            {\n                                \"textButton\": {\n                                    \"text\": \"OPEN QUERY\",\n                                    \"onClick\": {\n                                        \"openLink\": {\n                                            \"url\": \"{host}/queries/{query_id}\".format(host=host, query_id=query.id)\n                                        }\n                                    },\n                                }\n                            }\n                        ]\n                    }\n                )\n\n            headers = {\"Content-Type\": \"application/json; charset=UTF-8\"}\n            resp = requests.post(options.get(\"url\"), data=json_dumps(data), headers=headers, timeout=5.0)\n            if resp.status_code != 200:\n                logging.error(\"webhook send ERROR. status_code => {status}\".format(status=resp.status_code))\n        except Exception:\n            logging.exception(\"webhook send ERROR.\")\n\n\nregister(HangoutsChat)\n"
  },
  {
    "path": "redash/destinations/mattermost.py",
    "content": "import logging\n\nimport requests\n\nfrom redash.destinations import BaseDestination, register\nfrom redash.utils import json_dumps\n\n\nclass Mattermost(BaseDestination):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\", \"title\": \"Mattermost Webhook URL\"},\n                \"username\": {\"type\": \"string\", \"title\": \"Username\"},\n                \"icon_url\": {\"type\": \"string\", \"title\": \"Icon (URL)\"},\n                \"channel\": {\"type\": \"string\", \"title\": \"Channel\"},\n            },\n            \"secret\": \"url\",\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-bolt\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        if alert.custom_subject:\n            text = alert.custom_subject\n        elif new_state == \"triggered\":\n            text = \"#### \" + alert.name + \" just triggered\"\n        else:\n            text = \"#### \" + alert.name + \" went back to normal\"\n        payload = {\"text\": text}\n\n        if alert.custom_body:\n            payload[\"attachments\"] = [{\"fields\": [{\"title\": \"Description\", \"value\": alert.custom_body}]}]\n\n        if options.get(\"username\"):\n            payload[\"username\"] = options.get(\"username\")\n        if options.get(\"icon_url\"):\n            payload[\"icon_url\"] = options.get(\"icon_url\")\n        if options.get(\"channel\"):\n            payload[\"channel\"] = options.get(\"channel\")\n\n        try:\n            resp = requests.post(options.get(\"url\"), data=json_dumps(payload), timeout=5.0)\n            logging.warning(resp.text)\n\n            if resp.status_code != 200:\n                logging.error(\"Mattermost webhook send ERROR. status_code => {status}\".format(status=resp.status_code))\n        except Exception:\n            logging.exception(\"Mattermost webhook send ERROR.\")\n\n\nregister(Mattermost)\n"
  },
  {
    "path": "redash/destinations/microsoft_teams_webhook.py",
    "content": "import logging\nfrom string import Template\n\nimport requests\n\nfrom redash.destinations import BaseDestination, register\nfrom redash.utils import json_dumps\n\n\ndef json_string_substitute(j, substitutions):\n    \"\"\"\n    Alternative to string.format when the string has braces.\n    :param j: json string that will have substitutions\n    :type j: str\n    :param substitutions: dictionary of values to be replaced\n    :type substitutions: dict\n    \"\"\"\n    if substitutions:\n        substitution_candidate = j.replace(\"{\", \"${\")\n        string_template = Template(substitution_candidate)\n        substituted = string_template.safe_substitute(substitutions)\n        out_str = substituted.replace(\"${\", \"{\")\n        return out_str\n    else:\n        return j\n\n\nclass MicrosoftTeamsWebhook(BaseDestination):\n    ALERTS_DEFAULT_MESSAGE_TEMPLATE = json_dumps(\n        {\n            \"@type\": \"MessageCard\",\n            \"@context\": \"http://schema.org/extensions\",\n            \"themeColor\": \"0076D7\",\n            \"summary\": \"A Redash Alert was Triggered\",\n            \"sections\": [\n                {\n                    \"activityTitle\": \"A Redash Alert was Triggered\",\n                    \"facts\": [\n                        {\"name\": \"Alert Name\", \"value\": \"{alert_name}\"},\n                        {\"name\": \"Alert URL\", \"value\": \"{alert_url}\"},\n                        {\"name\": \"Query\", \"value\": \"{query_text}\"},\n                        {\"name\": \"Query URL\", \"value\": \"{query_url}\"},\n                    ],\n                    \"markdown\": True,\n                }\n            ],\n        }\n    )\n\n    @classmethod\n    def name(cls):\n        return \"Microsoft Teams Webhook\"\n\n    @classmethod\n    def type(cls):\n        return \"microsoft_teams_webhook\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\", \"title\": \"Microsoft Teams Webhook URL\"},\n                \"message_template\": {\n                    \"type\": \"string\",\n                    \"default\": MicrosoftTeamsWebhook.ALERTS_DEFAULT_MESSAGE_TEMPLATE,\n                    \"title\": \"Message Template\",\n                },\n            },\n            \"required\": [\"url\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-bolt\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        \"\"\"\n        :type app: redash.Redash\n        \"\"\"\n        try:\n            alert_url = \"{host}/alerts/{alert_id}\".format(host=host, alert_id=alert.id)\n\n            query_url = \"{host}/queries/{query_id}\".format(host=host, query_id=query.id)\n\n            message_template = options.get(\"message_template\", MicrosoftTeamsWebhook.ALERTS_DEFAULT_MESSAGE_TEMPLATE)\n\n            # Doing a string Template substitution here because the template contains braces, which\n            # result in keyerrors when attempting string.format\n            payload = json_string_substitute(\n                message_template,\n                {\n                    \"alert_name\": alert.name,\n                    \"alert_url\": alert_url,\n                    \"query_text\": query.query_text,\n                    \"query_url\": query_url,\n                },\n            )\n\n            headers = {\"Content-Type\": \"application/json\"}\n\n            resp = requests.post(\n                options.get(\"url\"),\n                data=payload,\n                headers=headers,\n                timeout=5.0,\n            )\n            if resp.status_code != 200:\n                logging.error(\"MS Teams Webhook send ERROR. status_code => {status}\".format(status=resp.status_code))\n        except Exception:\n            logging.exception(\"MS Teams Webhook send ERROR.\")\n\n\nregister(MicrosoftTeamsWebhook)\n"
  },
  {
    "path": "redash/destinations/pagerduty.py",
    "content": "import logging\n\nfrom redash.destinations import BaseDestination, register\n\nenabled = True\n\ntry:\n    import pypd\nexcept ImportError:\n    enabled = False\n\n\nclass PagerDuty(BaseDestination):\n    KEY_STRING = \"{alert_id}_{query_id}\"\n    DESCRIPTION_STR = \"Alert: {alert_name}\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"integration_key\": {\n                    \"type\": \"string\",\n                    \"title\": \"PagerDuty Service Integration Key\",\n                },\n                \"description\": {\n                    \"type\": \"string\",\n                    \"title\": \"Description for the event, defaults to alert name\",\n                },\n            },\n            \"secret\": [\"integration_key\"],\n            \"required\": [\"integration_key\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"creative-commons-pd-alt\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        if alert.custom_subject:\n            default_desc = alert.custom_subject\n        elif options.get(\"description\"):\n            default_desc = options.get(\"description\")\n        else:\n            default_desc = self.DESCRIPTION_STR.format(alert_name=alert.name)\n\n        incident_key = self.KEY_STRING.format(alert_id=alert.id, query_id=query.id)\n        data = {\n            \"routing_key\": options.get(\"integration_key\"),\n            \"incident_key\": incident_key,\n            \"dedup_key\": incident_key,\n            \"payload\": {\n                \"summary\": default_desc,\n                \"severity\": \"error\",\n                \"source\": \"redash\",\n            },\n        }\n\n        if alert.custom_body:\n            data[\"payload\"][\"custom_details\"] = alert.custom_body\n\n        if new_state == \"triggered\":\n            data[\"event_action\"] = \"trigger\"\n        elif new_state == \"unknown\":\n            logging.info(\"Unknown state, doing nothing\")\n            return\n        else:\n            data[\"event_action\"] = \"resolve\"\n\n        try:\n            ev = pypd.EventV2.create(data=data)\n            logging.warning(ev)\n\n        except Exception:\n            logging.exception(\"PagerDuty trigger failed!\")\n\n\nregister(PagerDuty)\n"
  },
  {
    "path": "redash/destinations/slack.py",
    "content": "import logging\n\nimport requests\n\nfrom redash.destinations import BaseDestination, register\nfrom redash.utils import json_dumps\n\n\nclass Slack(BaseDestination):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\", \"title\": \"Slack Webhook URL\"},\n            },\n            \"secret\": [\"url\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-slack\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        # Documentation: https://api.slack.com/docs/attachments\n        fields = [\n            {\n                \"title\": \"Query\",\n                \"type\": \"mrkdwn\",\n                \"value\": \"{host}/queries/{query_id}\".format(host=host, query_id=query.id),\n            },\n            {\n                \"title\": \"Alert\",\n                \"type\": \"mrkdwn\",\n                \"value\": \"{host}/alerts/{alert_id}\".format(host=host, alert_id=alert.id),\n            },\n        ]\n        if alert.custom_body:\n            fields.append({\"title\": \"Description\", \"value\": alert.custom_body})\n        if new_state == \"triggered\":\n            if alert.custom_subject:\n                text = alert.custom_subject\n            else:\n                text = alert.name + \" just triggered\"\n            color = \"#c0392b\"\n        else:\n            text = alert.name + \" went back to normal\"\n            color = \"#27ae60\"\n\n        payload = {\"attachments\": [{\"text\": text, \"color\": color, \"fields\": fields}]}\n\n        try:\n            resp = requests.post(options.get(\"url\"), data=json_dumps(payload).encode(\"utf-8\"), timeout=5.0)\n            logging.warning(resp.text)\n            if resp.status_code != 200:\n                logging.error(\"Slack send ERROR. status_code => {status}\".format(status=resp.status_code))\n        except Exception:\n            logging.exception(\"Slack send ERROR.\")\n\n\nregister(Slack)\n"
  },
  {
    "path": "redash/destinations/webex.py",
    "content": "import html\nimport json\nimport logging\nfrom copy import deepcopy\n\nimport requests\n\nfrom redash.destinations import BaseDestination, register\nfrom redash.models import Alert\n\n\nclass Webex(BaseDestination):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"webex_bot_token\": {\"type\": \"string\", \"title\": \"Webex Bot Token\"},\n                \"to_person_emails\": {\n                    \"type\": \"string\",\n                    \"title\": \"People (comma-separated)\",\n                },\n                \"to_room_ids\": {\n                    \"type\": \"string\",\n                    \"title\": \"Rooms (comma-separated)\",\n                },\n            },\n            \"secret\": [\"webex_bot_token\"],\n            \"required\": [\"webex_bot_token\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-webex\"\n\n    @property\n    def api_base_url(self):\n        return \"https://webexapis.com/v1/messages\"\n\n    @staticmethod\n    def formatted_attachments_template(subject, description, query_link, alert_link):\n        # Attempt to parse the description to find a 2D array\n        try:\n            # Extract the part of the description that looks like a JSON array\n            start_index = description.find(\"[\")\n            end_index = description.rfind(\"]\") + 1\n            json_array_str = description[start_index:end_index]\n\n            # Decode HTML entities\n            json_array_str = html.unescape(json_array_str)\n\n            # Replace single quotes with double quotes for valid JSON\n            json_array_str = json_array_str.replace(\"'\", '\"')\n\n            # Load the JSON array\n            data_array = json.loads(json_array_str)\n\n            # Check if it's a 2D array\n            if isinstance(data_array, list) and all(isinstance(i, list) for i in data_array):\n                # Create a table for the Adaptive Card\n                table_rows = []\n                for row in data_array:\n                    table_rows.append(\n                        {\n                            \"type\": \"ColumnSet\",\n                            \"columns\": [\n                                {\"type\": \"Column\", \"items\": [{\"type\": \"TextBlock\", \"text\": str(item), \"wrap\": True}]}\n                                for item in row\n                            ],\n                        }\n                    )\n\n                # Create the body of the card with the table\n                body = (\n                    [\n                        {\n                            \"type\": \"TextBlock\",\n                            \"text\": f\"{subject}\",\n                            \"weight\": \"bolder\",\n                            \"size\": \"medium\",\n                            \"wrap\": True,\n                        },\n                        {\n                            \"type\": \"TextBlock\",\n                            \"text\": f\"{description[:start_index]}\",\n                            \"isSubtle\": True,\n                            \"wrap\": True,\n                        },\n                    ]\n                    + table_rows\n                    + [\n                        {\n                            \"type\": \"TextBlock\",\n                            \"text\": f\"Click [here]({query_link}) to check your query!\",\n                            \"wrap\": True,\n                            \"isSubtle\": True,\n                        },\n                        {\n                            \"type\": \"TextBlock\",\n                            \"text\": f\"Click [here]({alert_link}) to check your alert!\",\n                            \"wrap\": True,\n                            \"isSubtle\": True,\n                        },\n                    ]\n                )\n            else:\n                # Fallback to the original description if no valid 2D array is found\n                body = [\n                    {\n                        \"type\": \"TextBlock\",\n                        \"text\": f\"{subject}\",\n                        \"weight\": \"bolder\",\n                        \"size\": \"medium\",\n                        \"wrap\": True,\n                    },\n                    {\n                        \"type\": \"TextBlock\",\n                        \"text\": f\"{description}\",\n                        \"isSubtle\": True,\n                        \"wrap\": True,\n                    },\n                    {\n                        \"type\": \"TextBlock\",\n                        \"text\": f\"Click [here]({query_link}) to check your query!\",\n                        \"wrap\": True,\n                        \"isSubtle\": True,\n                    },\n                    {\n                        \"type\": \"TextBlock\",\n                        \"text\": f\"Click [here]({alert_link}) to check your alert!\",\n                        \"wrap\": True,\n                        \"isSubtle\": True,\n                    },\n                ]\n        except json.JSONDecodeError:\n            # If parsing fails, fallback to the original description\n            body = [\n                {\n                    \"type\": \"TextBlock\",\n                    \"text\": f\"{subject}\",\n                    \"weight\": \"bolder\",\n                    \"size\": \"medium\",\n                    \"wrap\": True,\n                },\n                {\n                    \"type\": \"TextBlock\",\n                    \"text\": f\"{description}\",\n                    \"isSubtle\": True,\n                    \"wrap\": True,\n                },\n                {\n                    \"type\": \"TextBlock\",\n                    \"text\": f\"Click [here]({query_link}) to check your query!\",\n                    \"wrap\": True,\n                    \"isSubtle\": True,\n                },\n                {\n                    \"type\": \"TextBlock\",\n                    \"text\": f\"Click [here]({alert_link}) to check your alert!\",\n                    \"wrap\": True,\n                    \"isSubtle\": True,\n                },\n            ]\n\n        return [\n            {\n                \"contentType\": \"application/vnd.microsoft.card.adaptive\",\n                \"content\": {\n                    \"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n                    \"type\": \"AdaptiveCard\",\n                    \"version\": \"1.0\",\n                    \"body\": body,\n                },\n            }\n        ]\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        # Documentation: https://developer.webex.com/docs/api/guides/cards\n\n        query_link = f\"{host}/queries/{query.id}\"\n        alert_link = f\"{host}/alerts/{alert.id}\"\n\n        if new_state == Alert.TRIGGERED_STATE:\n            subject = alert.custom_subject or f\"{alert.name} just triggered\"\n        else:\n            subject = f\"{alert.name} went back to normal\"\n\n        attachments = self.formatted_attachments_template(\n            subject=subject, description=alert.custom_body, query_link=query_link, alert_link=alert_link\n        )\n\n        template_payload = {\"markdown\": subject + \"\\n\" + alert.custom_body, \"attachments\": attachments}\n\n        headers = {\"Authorization\": f\"Bearer {options['webex_bot_token']}\"}\n\n        api_destinations = {\n            \"toPersonEmail\": options.get(\"to_person_emails\"),\n            \"roomId\": options.get(\"to_room_ids\"),\n        }\n\n        for payload_tag, destinations in api_destinations.items():\n            if destinations is None:\n                continue\n\n            # destinations is guaranteed to be a comma-separated string\n            for destination_id in destinations.split(\",\"):\n                destination_id = destination_id.strip()  # Remove any leading or trailing whitespace\n                if not destination_id:  # Check if the destination_id is empty or blank\n                    continue  # Skip to the next iteration if it's empty or blank\n\n                payload = deepcopy(template_payload)\n                payload[payload_tag] = destination_id\n                self.post_message(payload, headers)\n\n    def post_message(self, payload, headers):\n        try:\n            resp = requests.post(\n                self.api_base_url,\n                json=payload,\n                headers=headers,\n                timeout=5.0,\n            )\n            logging.warning(resp.text)\n            if resp.status_code != 200:\n                logging.error(\"Webex send ERROR. status_code => {status}\".format(status=resp.status_code))\n        except Exception as e:\n            logging.exception(f\"Webex send ERROR: {e}\")\n\n\nregister(Webex)\n"
  },
  {
    "path": "redash/destinations/webhook.py",
    "content": "import logging\n\nimport requests\nfrom requests.auth import HTTPBasicAuth\n\nfrom redash.destinations import BaseDestination, register\nfrom redash.serializers import serialize_alert\nfrom redash.utils import json_dumps\n\n\nclass Webhook(BaseDestination):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\"},\n                \"username\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n            },\n            \"required\": [\"url\"],\n            \"secret\": [\"password\", \"url\"],\n        }\n\n    @classmethod\n    def icon(cls):\n        return \"fa-bolt\"\n\n    def notify(self, alert, query, user, new_state, app, host, metadata, options):\n        try:\n            data = {\n                \"event\": \"alert_state_change\",\n                \"alert\": serialize_alert(alert, full=False),\n                \"url_base\": host,\n                \"metadata\": metadata,\n            }\n\n            data[\"alert\"][\"description\"] = alert.custom_body\n            data[\"alert\"][\"title\"] = alert.custom_subject\n\n            headers = {\"Content-Type\": \"application/json\"}\n            auth = HTTPBasicAuth(options.get(\"username\"), options.get(\"password\")) if options.get(\"username\") else None\n            resp = requests.post(\n                options.get(\"url\"),\n                data=json_dumps(data).encode(\"utf-8\"),\n                auth=auth,\n                headers=headers,\n                timeout=5.0,\n            )\n            if resp.status_code != 200:\n                logging.error(\"webhook send ERROR. status_code => {status}\".format(status=resp.status_code))\n        except Exception:\n            logging.exception(\"webhook send ERROR.\")\n\n\nregister(Webhook)\n"
  },
  {
    "path": "redash/handlers/__init__.py",
    "content": "from flask import jsonify\nfrom flask_login import login_required\n\nfrom redash.handlers.api import api\nfrom redash.handlers.base import routes\nfrom redash.monitor import get_status\nfrom redash.permissions import require_super_admin\nfrom redash.security import talisman\n\n\n@routes.route(\"/ping\", methods=[\"GET\"])\n@talisman(force_https=False)\ndef ping():\n    return \"PONG.\"\n\n\n@routes.route(\"/status.json\")\n@login_required\n@require_super_admin\ndef status_api():\n    status = get_status()\n    return jsonify(status)\n\n\ndef init_app(app):\n    from redash.handlers import (\n        admin,\n        authentication,\n        embed,\n        organization,\n        queries,\n        setup,\n        static,\n    )\n\n    app.register_blueprint(routes)\n    api.init_app(app)\n"
  },
  {
    "path": "redash/handlers/admin.py",
    "content": "from flask_login import current_user, login_required\n\nfrom redash import models, redis_connection\nfrom redash.authentication import current_org\nfrom redash.handlers import routes\nfrom redash.handlers.base import json_response, record_event\nfrom redash.monitor import rq_status\nfrom redash.permissions import require_super_admin\nfrom redash.serializers import QuerySerializer\nfrom redash.utils import json_loads\n\n\n@routes.route(\"/api/admin/queries/outdated\", methods=[\"GET\"])\n@require_super_admin\n@login_required\ndef outdated_queries():\n    manager_status = redis_connection.hgetall(\"redash:status\")\n    query_ids = json_loads(manager_status.get(\"query_ids\", \"[]\"))\n    if query_ids:\n        outdated_queries = (\n            models.Query.query.outerjoin(models.QueryResult)\n            .filter(models.Query.id.in_(query_ids))\n            .order_by(models.Query.created_at.desc())\n        )\n    else:\n        outdated_queries = []\n\n    record_event(\n        current_org,\n        current_user._get_current_object(),\n        {\n            \"action\": \"list\",\n            \"object_type\": \"outdated_queries\",\n        },\n    )\n\n    response = {\n        \"queries\": QuerySerializer(outdated_queries, with_stats=True, with_last_modified_by=False).serialize(),\n        \"updated_at\": manager_status[\"last_refresh_at\"],\n    }\n    return json_response(response)\n\n\n@routes.route(\"/api/admin/queries/rq_status\", methods=[\"GET\"])\n@require_super_admin\n@login_required\ndef queries_rq_status():\n    record_event(\n        current_org,\n        current_user._get_current_object(),\n        {\"action\": \"list\", \"object_type\": \"rq_status\"},\n    )\n\n    return json_response(rq_status())\n"
  },
  {
    "path": "redash/handlers/alerts.py",
    "content": "from flask import request\nfrom funcy import project\n\nfrom redash import models, utils\nfrom redash.handlers.base import (\n    BaseResource,\n    get_object_or_404,\n    require_fields,\n)\nfrom redash.permissions import (\n    require_access,\n    require_admin_or_owner,\n    require_permission,\n    view_only,\n)\nfrom redash.serializers import serialize_alert\nfrom redash.tasks.alerts import (\n    notify_subscriptions,\n    should_notify,\n)\n\n\nclass AlertResource(BaseResource):\n    def get(self, alert_id):\n        alert = get_object_or_404(models.Alert.get_by_id_and_org, alert_id, self.current_org)\n        require_access(alert, self.current_user, view_only)\n        self.record_event({\"action\": \"view\", \"object_id\": alert.id, \"object_type\": \"alert\"})\n        return serialize_alert(alert)\n\n    def post(self, alert_id):\n        req = request.get_json(True)\n        params = project(req, (\"options\", \"name\", \"query_id\", \"rearm\"))\n        alert = get_object_or_404(models.Alert.get_by_id_and_org, alert_id, self.current_org)\n        require_admin_or_owner(alert.user.id)\n\n        self.update_model(alert, params)\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"edit\", \"object_id\": alert.id, \"object_type\": \"alert\"})\n\n        return serialize_alert(alert)\n\n    def delete(self, alert_id):\n        alert = get_object_or_404(models.Alert.get_by_id_and_org, alert_id, self.current_org)\n        require_admin_or_owner(alert.user_id)\n        models.db.session.delete(alert)\n        models.db.session.commit()\n\n\nclass AlertEvaluateResource(BaseResource):\n    def post(self, alert_id):\n        alert = get_object_or_404(models.Alert.get_by_id_and_org, alert_id, self.current_org)\n        require_admin_or_owner(alert.user.id)\n\n        new_state = alert.evaluate()\n        if should_notify(alert, new_state):\n            alert.state = new_state\n            alert.last_triggered_at = utils.utcnow()\n            models.db.session.commit()\n\n        notify_subscriptions(alert, new_state, {})\n        self.record_event({\"action\": \"evaluate\", \"object_id\": alert.id, \"object_type\": \"alert\"})\n\n\nclass AlertMuteResource(BaseResource):\n    def post(self, alert_id):\n        alert = get_object_or_404(models.Alert.get_by_id_and_org, alert_id, self.current_org)\n        require_admin_or_owner(alert.user.id)\n\n        alert.options[\"muted\"] = True\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"mute\", \"object_id\": alert.id, \"object_type\": \"alert\"})\n\n    def delete(self, alert_id):\n        alert = get_object_or_404(models.Alert.get_by_id_and_org, alert_id, self.current_org)\n        require_admin_or_owner(alert.user.id)\n\n        alert.options[\"muted\"] = False\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"unmute\", \"object_id\": alert.id, \"object_type\": \"alert\"})\n\n\nclass AlertListResource(BaseResource):\n    def post(self):\n        req = request.get_json(True)\n        require_fields(req, (\"options\", \"name\", \"query_id\"))\n\n        query = models.Query.get_by_id_and_org(req[\"query_id\"], self.current_org)\n        require_access(query, self.current_user, view_only)\n\n        alert = models.Alert(\n            name=req[\"name\"],\n            query_rel=query,\n            user=self.current_user,\n            rearm=req.get(\"rearm\"),\n            options=req[\"options\"],\n        )\n\n        models.db.session.add(alert)\n        models.db.session.flush()\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"create\", \"object_id\": alert.id, \"object_type\": \"alert\"})\n\n        return serialize_alert(alert)\n\n    @require_permission(\"list_alerts\")\n    def get(self):\n        self.record_event({\"action\": \"list\", \"object_type\": \"alert\"})\n        return [serialize_alert(alert) for alert in models.Alert.all(group_ids=self.current_user.group_ids)]\n\n\nclass AlertSubscriptionListResource(BaseResource):\n    def post(self, alert_id):\n        req = request.get_json(True)\n\n        alert = models.Alert.get_by_id_and_org(alert_id, self.current_org)\n        require_access(alert, self.current_user, view_only)\n        kwargs = {\"alert\": alert, \"user\": self.current_user}\n\n        if \"destination_id\" in req:\n            destination = models.NotificationDestination.get_by_id_and_org(req[\"destination_id\"], self.current_org)\n            kwargs[\"destination\"] = destination\n\n        subscription = models.AlertSubscription(**kwargs)\n        models.db.session.add(subscription)\n        models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"subscribe\",\n                \"object_id\": alert_id,\n                \"object_type\": \"alert\",\n                \"destination\": req.get(\"destination_id\"),\n            }\n        )\n\n        d = subscription.to_dict()\n        return d\n\n    def get(self, alert_id):\n        alert = models.Alert.get_by_id_and_org(alert_id, self.current_org)\n        require_access(alert, self.current_user, view_only)\n\n        subscriptions = models.AlertSubscription.all(alert_id)\n        return [s.to_dict() for s in subscriptions]\n\n\nclass AlertSubscriptionResource(BaseResource):\n    def delete(self, alert_id, subscriber_id):\n        subscription = models.AlertSubscription.query.get_or_404(subscriber_id)\n        require_admin_or_owner(subscription.user.id)\n        models.db.session.delete(subscription)\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"unsubscribe\", \"object_id\": alert_id, \"object_type\": \"alert\"})\n"
  },
  {
    "path": "redash/handlers/api.py",
    "content": "from flask import make_response\nfrom flask_restful import Api\nfrom werkzeug.wrappers import Response\n\nfrom redash.handlers.alerts import (\n    AlertEvaluateResource,\n    AlertListResource,\n    AlertMuteResource,\n    AlertResource,\n    AlertSubscriptionListResource,\n    AlertSubscriptionResource,\n)\nfrom redash.handlers.base import org_scoped_rule\nfrom redash.handlers.dashboards import (\n    DashboardFavoriteListResource,\n    DashboardForkResource,\n    DashboardListResource,\n    DashboardResource,\n    DashboardShareResource,\n    DashboardTagsResource,\n    MyDashboardsResource,\n    PublicDashboardResource,\n)\nfrom redash.handlers.data_sources import (\n    DataSourceListResource,\n    DataSourcePauseResource,\n    DataSourceResource,\n    DataSourceSchemaResource,\n    DataSourceTestResource,\n    DataSourceTypeListResource,\n)\nfrom redash.handlers.databricks import (\n    DatabricksDatabaseListResource,\n    DatabricksSchemaResource,\n    DatabricksTableColumnListResource,\n)\nfrom redash.handlers.destinations import (\n    DestinationListResource,\n    DestinationResource,\n    DestinationTypeListResource,\n)\nfrom redash.handlers.events import EventsResource\nfrom redash.handlers.favorites import (\n    DashboardFavoriteResource,\n    QueryFavoriteResource,\n)\nfrom redash.handlers.groups import (\n    GroupDataSourceListResource,\n    GroupDataSourceResource,\n    GroupListResource,\n    GroupMemberListResource,\n    GroupMemberResource,\n    GroupResource,\n)\nfrom redash.handlers.permissions import (\n    CheckPermissionResource,\n    ObjectPermissionsListResource,\n)\nfrom redash.handlers.queries import (\n    MyQueriesResource,\n    QueryArchiveResource,\n    QueryFavoriteListResource,\n    QueryForkResource,\n    QueryListResource,\n    QueryRecentResource,\n    QueryRefreshResource,\n    QueryRegenerateApiKeyResource,\n    QueryResource,\n    QuerySearchResource,\n    QueryTagsResource,\n)\nfrom redash.handlers.query_results import (\n    JobResource,\n    QueryDropdownsResource,\n    QueryResultDropdownResource,\n    QueryResultListResource,\n    QueryResultResource,\n)\nfrom redash.handlers.query_snippets import (\n    QuerySnippetListResource,\n    QuerySnippetResource,\n)\nfrom redash.handlers.settings import OrganizationSettings\nfrom redash.handlers.users import (\n    UserDisableResource,\n    UserInviteResource,\n    UserListResource,\n    UserRegenerateApiKeyResource,\n    UserResetPasswordResource,\n    UserResource,\n)\nfrom redash.handlers.visualizations import (\n    VisualizationListResource,\n    VisualizationResource,\n)\nfrom redash.handlers.widgets import WidgetListResource, WidgetResource\nfrom redash.utils import json_dumps\n\n\nclass ApiExt(Api):\n    def add_org_resource(self, resource, *urls, **kwargs):\n        urls = [org_scoped_rule(url) for url in urls]\n        return self.add_resource(resource, *urls, **kwargs)\n\n\napi = ApiExt()\n\n\n@api.representation(\"application/json\")\ndef json_representation(data, code, headers=None):\n    # Flask-Restful checks only for flask.Response but flask-login uses werkzeug.wrappers.Response\n    if isinstance(data, Response):\n        return data\n    resp = make_response(json_dumps(data), code)\n    resp.headers.extend(headers or {})\n    return resp\n\n\napi.add_org_resource(AlertResource, \"/api/alerts/<alert_id>\", endpoint=\"alert\")\napi.add_org_resource(AlertMuteResource, \"/api/alerts/<alert_id>/mute\", endpoint=\"alert_mute\")\napi.add_org_resource(AlertEvaluateResource, \"/api/alerts/<alert_id>/eval\", endpoint=\"alert_eval\")\napi.add_org_resource(\n    AlertSubscriptionListResource,\n    \"/api/alerts/<alert_id>/subscriptions\",\n    endpoint=\"alert_subscriptions\",\n)\napi.add_org_resource(\n    AlertSubscriptionResource,\n    \"/api/alerts/<alert_id>/subscriptions/<subscriber_id>\",\n    endpoint=\"alert_subscription\",\n)\napi.add_org_resource(AlertListResource, \"/api/alerts\", endpoint=\"alerts\")\n\napi.add_org_resource(DashboardListResource, \"/api/dashboards\", endpoint=\"dashboards\")\napi.add_org_resource(DashboardResource, \"/api/dashboards/<dashboard_id>\", endpoint=\"dashboard\")\napi.add_org_resource(\n    PublicDashboardResource,\n    \"/api/dashboards/public/<token>\",\n    endpoint=\"public_dashboard\",\n)\napi.add_org_resource(\n    DashboardShareResource,\n    \"/api/dashboards/<dashboard_id>/share\",\n    endpoint=\"dashboard_share\",\n)\n\napi.add_org_resource(DataSourceTypeListResource, \"/api/data_sources/types\", endpoint=\"data_source_types\")\napi.add_org_resource(DataSourceListResource, \"/api/data_sources\", endpoint=\"data_sources\")\napi.add_org_resource(DataSourceSchemaResource, \"/api/data_sources/<data_source_id>/schema\")\napi.add_org_resource(DatabricksDatabaseListResource, \"/api/databricks/databases/<data_source_id>\")\napi.add_org_resource(\n    DatabricksSchemaResource,\n    \"/api/databricks/databases/<data_source_id>/<database_name>/tables\",\n)\napi.add_org_resource(\n    DatabricksTableColumnListResource,\n    \"/api/databricks/databases/<data_source_id>/<database_name>/columns/<table_name>\",\n)\napi.add_org_resource(DataSourcePauseResource, \"/api/data_sources/<data_source_id>/pause\")\napi.add_org_resource(DataSourceTestResource, \"/api/data_sources/<data_source_id>/test\")\napi.add_org_resource(DataSourceResource, \"/api/data_sources/<data_source_id>\", endpoint=\"data_source\")\n\napi.add_org_resource(GroupListResource, \"/api/groups\", endpoint=\"groups\")\napi.add_org_resource(GroupResource, \"/api/groups/<group_id>\", endpoint=\"group\")\napi.add_org_resource(GroupMemberListResource, \"/api/groups/<group_id>/members\", endpoint=\"group_members\")\napi.add_org_resource(\n    GroupMemberResource,\n    \"/api/groups/<group_id>/members/<user_id>\",\n    endpoint=\"group_member\",\n)\napi.add_org_resource(\n    GroupDataSourceListResource,\n    \"/api/groups/<group_id>/data_sources\",\n    endpoint=\"group_data_sources\",\n)\napi.add_org_resource(\n    GroupDataSourceResource,\n    \"/api/groups/<group_id>/data_sources/<data_source_id>\",\n    endpoint=\"group_data_source\",\n)\n\napi.add_org_resource(EventsResource, \"/api/events\", endpoint=\"events\")\n\napi.add_org_resource(QueryFavoriteListResource, \"/api/queries/favorites\", endpoint=\"query_favorites\")\napi.add_org_resource(QueryFavoriteResource, \"/api/queries/<query_id>/favorite\", endpoint=\"query_favorite\")\napi.add_org_resource(\n    DashboardFavoriteListResource,\n    \"/api/dashboards/favorites\",\n    endpoint=\"dashboard_favorites\",\n)\napi.add_org_resource(\n    DashboardFavoriteResource,\n    \"/api/dashboards/<object_id>/favorite\",\n    endpoint=\"dashboard_favorite\",\n)\napi.add_org_resource(DashboardForkResource, \"/api/dashboards/<dashboard_id>/fork\", endpoint=\"dashboard_fork\")\n\napi.add_org_resource(MyDashboardsResource, \"/api/dashboards/my\", endpoint=\"my_dashboards\")\n\napi.add_org_resource(QueryTagsResource, \"/api/queries/tags\", endpoint=\"query_tags\")\napi.add_org_resource(DashboardTagsResource, \"/api/dashboards/tags\", endpoint=\"dashboard_tags\")\n\napi.add_org_resource(QuerySearchResource, \"/api/queries/search\", endpoint=\"queries_search\")\napi.add_org_resource(QueryRecentResource, \"/api/queries/recent\", endpoint=\"recent_queries\")\napi.add_org_resource(QueryArchiveResource, \"/api/queries/archive\", endpoint=\"queries_archive\")\napi.add_org_resource(QueryListResource, \"/api/queries\", endpoint=\"queries\")\napi.add_org_resource(MyQueriesResource, \"/api/queries/my\", endpoint=\"my_queries\")\napi.add_org_resource(QueryRefreshResource, \"/api/queries/<query_id>/refresh\", endpoint=\"query_refresh\")\napi.add_org_resource(QueryResource, \"/api/queries/<query_id>\", endpoint=\"query\")\napi.add_org_resource(QueryForkResource, \"/api/queries/<query_id>/fork\", endpoint=\"query_fork\")\napi.add_org_resource(\n    QueryRegenerateApiKeyResource,\n    \"/api/queries/<query_id>/regenerate_api_key\",\n    endpoint=\"query_regenerate_api_key\",\n)\n\napi.add_org_resource(\n    ObjectPermissionsListResource,\n    \"/api/<object_type>/<object_id>/acl\",\n    endpoint=\"object_permissions\",\n)\napi.add_org_resource(\n    CheckPermissionResource,\n    \"/api/<object_type>/<object_id>/acl/<access_type>\",\n    endpoint=\"check_permissions\",\n)\n\napi.add_org_resource(QueryResultListResource, \"/api/query_results\", endpoint=\"query_results\")\napi.add_org_resource(\n    QueryResultDropdownResource,\n    \"/api/queries/<query_id>/dropdown\",\n    endpoint=\"query_result_dropdown\",\n)\napi.add_org_resource(\n    QueryDropdownsResource,\n    \"/api/queries/<query_id>/dropdowns/<dropdown_query_id>\",\n    endpoint=\"query_result_dropdowns\",\n)\napi.add_org_resource(\n    QueryResultResource,\n    \"/api/query_results/<query_result_id>.<filetype>\",\n    \"/api/query_results/<query_result_id>\",\n    \"/api/queries/<query_id>/results\",\n    \"/api/queries/<query_id>/results.<filetype>\",\n    \"/api/queries/<query_id>/results/<query_result_id>.<filetype>\",\n    endpoint=\"query_result\",\n)\napi.add_org_resource(\n    JobResource,\n    \"/api/jobs/<job_id>\",\n    \"/api/queries/<query_id>/jobs/<job_id>\",\n    endpoint=\"job\",\n)\n\napi.add_org_resource(UserListResource, \"/api/users\", endpoint=\"users\")\napi.add_org_resource(UserResource, \"/api/users/<user_id>\", endpoint=\"user\")\napi.add_org_resource(UserInviteResource, \"/api/users/<user_id>/invite\", endpoint=\"user_invite\")\napi.add_org_resource(\n    UserResetPasswordResource,\n    \"/api/users/<user_id>/reset_password\",\n    endpoint=\"user_reset_password\",\n)\napi.add_org_resource(\n    UserRegenerateApiKeyResource,\n    \"/api/users/<user_id>/regenerate_api_key\",\n    endpoint=\"user_regenerate_api_key\",\n)\napi.add_org_resource(UserDisableResource, \"/api/users/<user_id>/disable\", endpoint=\"user_disable\")\n\napi.add_org_resource(VisualizationListResource, \"/api/visualizations\", endpoint=\"visualizations\")\napi.add_org_resource(\n    VisualizationResource,\n    \"/api/visualizations/<visualization_id>\",\n    endpoint=\"visualization\",\n)\n\napi.add_org_resource(WidgetListResource, \"/api/widgets\", endpoint=\"widgets\")\napi.add_org_resource(WidgetResource, \"/api/widgets/<int:widget_id>\", endpoint=\"widget\")\n\napi.add_org_resource(DestinationTypeListResource, \"/api/destinations/types\", endpoint=\"destination_types\")\napi.add_org_resource(DestinationResource, \"/api/destinations/<destination_id>\", endpoint=\"destination\")\napi.add_org_resource(DestinationListResource, \"/api/destinations\", endpoint=\"destinations\")\n\napi.add_org_resource(QuerySnippetResource, \"/api/query_snippets/<snippet_id>\", endpoint=\"query_snippet\")\napi.add_org_resource(QuerySnippetListResource, \"/api/query_snippets\", endpoint=\"query_snippets\")\n\napi.add_org_resource(OrganizationSettings, \"/api/settings/organization\", endpoint=\"organization_settings\")\n"
  },
  {
    "path": "redash/handlers/authentication.py",
    "content": "import logging\n\nfrom flask import abort, flash, redirect, render_template, request, url_for\nfrom flask_login import current_user, login_required, login_user, logout_user\nfrom itsdangerous import BadSignature, SignatureExpired\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom redash import __version__, limiter, models, settings\nfrom redash.authentication import current_org, get_login_url, get_next_path\nfrom redash.authentication.account import (\n    send_password_reset_email,\n    send_user_disabled_email,\n    send_verify_email,\n    validate_token,\n)\nfrom redash.handlers import routes\nfrom redash.handlers.base import json_response, org_scoped_rule\nfrom redash.version_check import get_latest_version\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_google_auth_url(next_path):\n    if settings.MULTI_ORG:\n        google_auth_url = url_for(\"google_oauth.authorize_org\", next=next_path, org_slug=current_org.slug)\n    else:\n        google_auth_url = url_for(\"google_oauth.authorize\", next=next_path)\n    return google_auth_url\n\n\ndef render_token_login_page(template, org_slug, token, invite):\n    error_message = None\n    try:\n        user_id = validate_token(token)\n        org = current_org._get_current_object()\n        user = models.User.get_by_id_and_org(user_id, org)\n    except NoResultFound:\n        logger.exception(\n            \"Bad user id in token. Token=%s , User id= %s, Org=%s\",\n            token,\n            user_id,\n            org_slug,\n        )\n        error_message = \"Your invite link is invalid. Bad user id in token. Please ask for a new one.\"\n    except SignatureExpired:\n        logger.exception(\"Token signature has expired. Token: %s, org=%s\", token, org_slug)\n        error_message = \"Your invite link has expired. Please ask for a new one.\"\n    except BadSignature:\n        logger.exception(\"Bad signature for the token: %s, org=%s\", token, org_slug)\n        error_message = \"Your invite link is invalid. Bad signature. Please double-check the token.\"\n\n    if error_message:\n        return (\n            render_template(\n                \"error.html\",\n                error_message=error_message,\n            ),\n            400,\n        )\n\n    if invite and user.details.get(\"is_invitation_pending\") is False:\n        return (\n            render_template(\n                \"error.html\",\n                error_message=(\n                    \"This invitation has already been accepted. Please try resetting your password instead.\"\n                ),\n            ),\n            400,\n        )\n\n    status_code = 200\n    if request.method == \"POST\":\n        if \"password\" not in request.form:\n            flash(\"Bad Request\")\n            status_code = 400\n        elif not request.form[\"password\"]:\n            flash(\"Cannot use empty password.\")\n            status_code = 400\n        elif len(request.form[\"password\"]) < 6:\n            flash(\"Password length is too short (<6).\")\n            status_code = 400\n        else:\n            if invite or user.is_invitation_pending:\n                user.is_invitation_pending = False\n            user.hash_password(request.form[\"password\"])\n            models.db.session.add(user)\n            login_user(user)\n            models.db.session.commit()\n            return redirect(url_for(\"redash.index\", org_slug=org_slug))\n\n    google_auth_url = get_google_auth_url(url_for(\"redash.index\", org_slug=org_slug))\n\n    return (\n        render_template(\n            template,\n            show_google_openid=settings.GOOGLE_OAUTH_ENABLED,\n            google_auth_url=google_auth_url,\n            show_saml_login=current_org.get_setting(\"auth_saml_enabled\"),\n            show_remote_user_login=settings.REMOTE_USER_LOGIN_ENABLED,\n            show_ldap_login=settings.LDAP_LOGIN_ENABLED,\n            org_slug=org_slug,\n            user=user,\n        ),\n        status_code,\n    )\n\n\n@routes.route(org_scoped_rule(\"/invite/<token>\"), methods=[\"GET\", \"POST\"])\ndef invite(token, org_slug=None):\n    return render_token_login_page(\"invite.html\", org_slug, token, True)\n\n\n@routes.route(org_scoped_rule(\"/reset/<token>\"), methods=[\"GET\", \"POST\"])\ndef reset(token, org_slug=None):\n    return render_token_login_page(\"reset.html\", org_slug, token, False)\n\n\n@routes.route(org_scoped_rule(\"/verify/<token>\"), methods=[\"GET\"])\ndef verify(token, org_slug=None):\n    try:\n        user_id = validate_token(token)\n        org = current_org._get_current_object()\n        user = models.User.get_by_id_and_org(user_id, org)\n    except (BadSignature, NoResultFound):\n        logger.exception(\"Failed to verify email verification token: %s, org=%s\", token, org_slug)\n        return (\n            render_template(\n                \"error.html\",\n                error_message=\"Your verification link is invalid. Please ask for a new one.\",\n            ),\n            400,\n        )\n\n    user.is_email_verified = True\n    models.db.session.add(user)\n    models.db.session.commit()\n\n    template_context = {\"org_slug\": org_slug} if settings.MULTI_ORG else {}\n    next_url = url_for(\"redash.index\", **template_context)\n\n    return render_template(\"verify.html\", next_url=next_url)\n\n\n@routes.route(org_scoped_rule(\"/forgot\"), methods=[\"GET\", \"POST\"])\n@limiter.limit(settings.THROTTLE_PASS_RESET_PATTERN)\ndef forgot_password(org_slug=None):\n    if not current_org.get_setting(\"auth_password_login_enabled\"):\n        abort(404)\n\n    submitted = False\n    if request.method == \"POST\" and request.form[\"email\"]:\n        submitted = True\n        email = request.form[\"email\"]\n        try:\n            org = current_org._get_current_object()\n            user = models.User.get_by_email_and_org(email, org)\n            if user.is_disabled:\n                send_user_disabled_email(user)\n            else:\n                send_password_reset_email(user)\n        except NoResultFound:\n            logging.error(\"No user found for forgot password: %s\", email)\n\n    return render_template(\"forgot.html\", submitted=submitted)\n\n\n@routes.route(org_scoped_rule(\"/verification_email/\"), methods=[\"POST\"])\ndef verification_email(org_slug=None):\n    if not current_user.is_email_verified:\n        send_verify_email(current_user, current_org)\n\n    return json_response({\"message\": \"Please check your email inbox in order to verify your email address.\"})\n\n\n@routes.route(org_scoped_rule(\"/login\"), methods=[\"GET\", \"POST\"])\n@limiter.limit(settings.THROTTLE_LOGIN_PATTERN)\ndef login(org_slug=None):\n    # We intentionally use == as otherwise it won't actually use the proxy. So weird :O\n    # noinspection PyComparisonWithNone\n    if current_org == None and not settings.MULTI_ORG:  # noqa: E711\n        return redirect(\"/setup\")\n    elif current_org == None:  # noqa: E711\n        return redirect(\"/\")\n\n    index_url = url_for(\"redash.index\", org_slug=org_slug)\n    unsafe_next_path = request.args.get(\"next\", index_url)\n    next_path = get_next_path(unsafe_next_path)\n    if current_user.is_authenticated:\n        return redirect(next_path)\n\n    if request.method == \"POST\" and current_org.get_setting(\"auth_password_login_enabled\"):\n        try:\n            org = current_org._get_current_object()\n            user = models.User.get_by_email_and_org(request.form[\"email\"], org)\n            if user and not user.is_disabled and user.verify_password(request.form[\"password\"]):\n                remember = \"remember\" in request.form\n                login_user(user, remember=remember)\n                return redirect(next_path)\n            else:\n                flash(\"Wrong email or password.\")\n        except NoResultFound:\n            flash(\"Wrong email or password.\")\n    elif request.method == \"POST\" and not current_org.get_setting(\"auth_password_login_enabled\"):\n        flash(\"Password login is not enabled for your organization.\")\n\n    google_auth_url = get_google_auth_url(next_path)\n\n    return render_template(\n        \"login.html\",\n        org_slug=org_slug,\n        next=next_path,\n        email=request.form.get(\"email\", \"\"),\n        show_google_openid=settings.GOOGLE_OAUTH_ENABLED,\n        google_auth_url=google_auth_url,\n        show_password_login=current_org.get_setting(\"auth_password_login_enabled\"),\n        show_saml_login=current_org.get_setting(\"auth_saml_enabled\"),\n        show_remote_user_login=settings.REMOTE_USER_LOGIN_ENABLED,\n        show_ldap_login=settings.LDAP_LOGIN_ENABLED,\n    )\n\n\n@routes.route(org_scoped_rule(\"/logout\"))\ndef logout(org_slug=None):\n    logout_user()\n    return redirect(get_login_url(next=None))\n\n\ndef base_href():\n    if settings.MULTI_ORG:\n        base_href = url_for(\"redash.index\", _external=True, org_slug=current_org.slug)\n    else:\n        base_href = url_for(\"redash.index\", _external=True)\n\n    return base_href\n\n\ndef date_time_format_config():\n    date_format = current_org.get_setting(\"date_format\")\n    date_format_list = set([\"DD/MM/YY\", \"MM/DD/YY\", \"YYYY-MM-DD\", settings.DATE_FORMAT])\n    time_format = current_org.get_setting(\"time_format\")\n    time_format_list = set([\"HH:mm\", \"HH:mm:ss\", \"HH:mm:ss.SSS\", settings.TIME_FORMAT])\n    return {\n        \"dateFormat\": date_format,\n        \"dateFormatList\": list(date_format_list),\n        \"timeFormatList\": list(time_format_list),\n        \"dateTimeFormat\": \"{0} {1}\".format(date_format, time_format),\n    }\n\n\ndef number_format_config():\n    return {\n        \"integerFormat\": current_org.get_setting(\"integer_format\"),\n        \"floatFormat\": current_org.get_setting(\"float_format\"),\n    }\n\n\ndef null_value_config():\n    return {\n        \"nullValue\": current_org.get_setting(\"null_value\"),\n    }\n\n\ndef client_config():\n    if not current_user.is_api_user() and current_user.is_authenticated:\n        client_config = {\n            \"newVersionAvailable\": bool(get_latest_version()),\n            \"version\": __version__,\n        }\n    else:\n        client_config = {}\n\n    if current_user.has_permission(\"admin\") and current_org.get_setting(\"beacon_consent\") is None:\n        client_config[\"showBeaconConsentMessage\"] = True\n\n    defaults = {\n        \"allowScriptsInUserInput\": settings.ALLOW_SCRIPTS_IN_USER_INPUT,\n        \"showPermissionsControl\": current_org.get_setting(\"feature_show_permissions_control\"),\n        \"hidePlotlyModeBar\": current_org.get_setting(\"hide_plotly_mode_bar\"),\n        \"disablePublicUrls\": current_org.get_setting(\"disable_public_urls\"),\n        \"multiByteSearchEnabled\": current_org.get_setting(\"multi_byte_search_enabled\"),\n        \"allowCustomJSVisualizations\": settings.FEATURE_ALLOW_CUSTOM_JS_VISUALIZATIONS,\n        \"autoPublishNamedQueries\": settings.FEATURE_AUTO_PUBLISH_NAMED_QUERIES,\n        \"extendedAlertOptions\": settings.FEATURE_EXTENDED_ALERT_OPTIONS,\n        \"mailSettingsMissing\": not settings.email_server_is_configured(),\n        \"dashboardRefreshIntervals\": settings.DASHBOARD_REFRESH_INTERVALS,\n        \"queryRefreshIntervals\": settings.QUERY_REFRESH_INTERVALS,\n        \"googleLoginEnabled\": settings.GOOGLE_OAUTH_ENABLED,\n        \"ldapLoginEnabled\": settings.LDAP_LOGIN_ENABLED,\n        \"pageSize\": settings.PAGE_SIZE,\n        \"pageSizeOptions\": settings.PAGE_SIZE_OPTIONS,\n        \"tableCellMaxJSONSize\": settings.TABLE_CELL_MAX_JSON_SIZE,\n    }\n\n    client_config.update(defaults)\n    client_config.update({\"basePath\": base_href()})\n    client_config.update(date_time_format_config())\n    client_config.update(number_format_config())\n    client_config.update(null_value_config())\n\n    return client_config\n\n\ndef messages():\n    messages = []\n\n    if not current_user.is_email_verified:\n        messages.append(\"email-not-verified\")\n\n    if settings.ALLOW_PARAMETERS_IN_EMBEDS:\n        messages.append(\"using-deprecated-embed-feature\")\n\n    return messages\n\n\n@routes.route(\"/api/config\", methods=[\"GET\"])\ndef config(org_slug=None):\n    return json_response({\"org_slug\": current_org.slug, \"client_config\": client_config()})\n\n\n@routes.route(org_scoped_rule(\"/api/session\"), methods=[\"GET\"])\n@login_required\ndef session(org_slug=None):\n    if current_user.is_api_user():\n        user = {\"permissions\": [], \"apiKey\": current_user.id}\n    else:\n        user = {\n            \"profile_image_url\": current_user.profile_image_url,\n            \"id\": current_user.id,\n            \"name\": current_user.name,\n            \"email\": current_user.email,\n            \"groups\": current_user.group_ids,\n            \"permissions\": current_user.permissions,\n        }\n\n    return json_response(\n        {\n            \"user\": user,\n            \"messages\": messages(),\n            \"org_slug\": current_org.slug,\n            \"client_config\": client_config(),\n        }\n    )\n"
  },
  {
    "path": "redash/handlers/base.py",
    "content": "import time\nfrom inspect import isclass\n\nfrom flask import Blueprint, current_app, request\nfrom flask_login import current_user, login_required\nfrom flask_restful import Resource, abort\nfrom sqlalchemy import cast\nfrom sqlalchemy.dialects.postgresql import ARRAY\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom redash import settings\nfrom redash.authentication import current_org\nfrom redash.models import db\nfrom redash.tasks import record_event as record_event_task\nfrom redash.utils import json_dumps\nfrom redash.utils.query_order import sort_query\n\nroutes = Blueprint(\"redash\", __name__, template_folder=settings.fix_assets_path(\"templates\"))\n\n\nclass BaseResource(Resource):\n    decorators = [login_required]\n\n    def __init__(self, *args, **kwargs):\n        super(BaseResource, self).__init__(*args, **kwargs)\n        self._user = None\n\n    def dispatch_request(self, *args, **kwargs):\n        kwargs.pop(\"org_slug\", None)\n\n        return super(BaseResource, self).dispatch_request(*args, **kwargs)\n\n    @property\n    def current_user(self):\n        return current_user._get_current_object()\n\n    @property\n    def current_org(self):\n        return current_org._get_current_object()\n\n    def record_event(self, options):\n        record_event(self.current_org, self.current_user, options)\n\n    # TODO: this should probably be somewhere else\n    def update_model(self, model, updates):\n        for k, v in updates.items():\n            setattr(model, k, v)\n\n\ndef record_event(org, user, options):\n    if user.is_api_user():\n        options.update({\"api_key\": user.name, \"org_id\": org.id})\n    else:\n        options.update({\"user_id\": user.id, \"user_name\": user.name, \"org_id\": org.id})\n\n    options.update({\"user_agent\": request.user_agent.string, \"ip\": request.remote_addr})\n\n    if \"timestamp\" not in options:\n        options[\"timestamp\"] = int(time.time())\n\n    record_event_task.delay(options)\n\n\ndef require_fields(req, fields):\n    for f in fields:\n        if f not in req:\n            abort(400)\n\n\ndef get_object_or_404(fn, *args, **kwargs):\n    try:\n        rv = fn(*args, **kwargs)\n        if rv is None:\n            abort(404)\n    except NoResultFound:\n        abort(404)\n    return rv\n\n\ndef paginate(query_set, page, page_size, serializer, **kwargs):\n    count = query_set.count()\n\n    if page < 1:\n        abort(400, message=\"Page must be positive integer.\")\n\n    if (page - 1) * page_size + 1 > count > 0:\n        abort(400, message=\"Page is out of range.\")\n\n    if page_size > 250 or page_size < 1:\n        abort(400, message=\"Page size is out of range (1-250).\")\n\n    results = query_set.paginate(page, page_size)\n\n    # support for old function based serializers\n    if isclass(serializer):\n        items = serializer(results.items, **kwargs).serialize()\n    else:\n        items = [serializer(result) for result in results.items]\n\n    return {\"count\": count, \"page\": page, \"page_size\": page_size, \"results\": items}\n\n\ndef org_scoped_rule(rule):\n    if settings.MULTI_ORG:\n        return \"/<org_slug>{}\".format(rule)\n\n    return rule\n\n\ndef json_response(response):\n    return current_app.response_class(json_dumps(response), mimetype=\"application/json\")\n\n\ndef filter_by_tags(result_set, column):\n    if request.args.getlist(\"tags\"):\n        tags = request.args.getlist(\"tags\")\n        result_set = result_set.filter(cast(column, ARRAY(db.Text)).contains(tags))\n    return result_set\n\n\ndef order_results(results, default_order, allowed_orders, fallback=True):\n    \"\"\"\n    Orders the given results with the sort order as requested in the\n    \"order\" request query parameter or the given default order.\n    \"\"\"\n    # See if a particular order has been requested\n    requested_order = request.args.get(\"order\", \"\").strip()\n\n    # and if not (and no fallback is wanted) return results as is\n    if not requested_order and not fallback:\n        return results\n\n    # and if it matches a long-form for related fields, falling\n    # back to the default order\n    selected_order = allowed_orders.get(requested_order, None)\n    if selected_order is None and fallback:\n        selected_order = default_order\n    # The query may already have an ORDER BY statement attached\n    # so we clear it here and apply the selected order\n    return sort_query(results.order_by(None), selected_order)\n"
  },
  {
    "path": "redash/handlers/dashboards.py",
    "content": "from flask import request, url_for\nfrom flask_restful import abort\nfrom funcy import partial, project\nfrom sqlalchemy.orm.exc import StaleDataError\n\nfrom redash import models\nfrom redash.handlers.base import (\n    BaseResource,\n    filter_by_tags,\n    get_object_or_404,\n    paginate,\n)\nfrom redash.handlers.base import order_results as _order_results\nfrom redash.permissions import (\n    can_modify,\n    require_admin_or_owner,\n    require_object_modify_permission,\n    require_permission,\n)\nfrom redash.security import csp_allows_embeding\nfrom redash.serializers import DashboardSerializer, public_dashboard\n\n# Ordering map for relationships\norder_map = {\n    \"name\": \"lowercase_name\",\n    \"-name\": \"-lowercase_name\",\n    \"created_at\": \"created_at\",\n    \"-created_at\": \"-created_at\",\n    \"starred_at\": \"favorites-created_at\",\n    \"-starred_at\": \"-favorites-created_at\",\n}\n\norder_results = partial(_order_results, default_order=\"-created_at\", allowed_orders=order_map)\n\n\nclass DashboardListResource(BaseResource):\n    @require_permission(\"list_dashboards\")\n    def get(self):\n        \"\"\"\n        Lists all accessible dashboards.\n\n        :qparam number page_size: Number of queries to return per page\n        :qparam number page: Page number to retrieve\n        :qparam number order: Name of column to order by\n        :qparam number q: Full text search term\n\n        Responds with an array of :ref:`dashboard <dashboard-response-label>`\n        objects.\n        \"\"\"\n        search_term = request.args.get(\"q\")\n\n        if search_term:\n            results = models.Dashboard.search(\n                self.current_org,\n                self.current_user.group_ids,\n                self.current_user.id,\n                search_term,\n            )\n        else:\n            results = models.Dashboard.all(self.current_org, self.current_user.group_ids, self.current_user.id)\n\n        results = filter_by_tags(results, models.Dashboard.tags)\n\n        # order results according to passed order parameter,\n        # special-casing search queries where the database\n        # provides an order by search rank\n        ordered_results = order_results(results, fallback=not bool(search_term))\n\n        page = request.args.get(\"page\", 1, type=int)\n        page_size = request.args.get(\"page_size\", 25, type=int)\n\n        response = paginate(\n            ordered_results,\n            page=page,\n            page_size=page_size,\n            serializer=DashboardSerializer,\n        )\n\n        if search_term:\n            self.record_event({\"action\": \"search\", \"object_type\": \"dashboard\", \"term\": search_term})\n        else:\n            self.record_event({\"action\": \"list\", \"object_type\": \"dashboard\"})\n\n        return response\n\n    @require_permission(\"create_dashboard\")\n    def post(self):\n        \"\"\"\n        Creates a new dashboard.\n\n        :<json string name: Dashboard name\n\n        Responds with a :ref:`dashboard <dashboard-response-label>`.\n        \"\"\"\n        dashboard_properties = request.get_json(force=True)\n        dashboard = models.Dashboard(\n            name=dashboard_properties[\"name\"],\n            org=self.current_org,\n            user=self.current_user,\n            is_draft=True,\n            layout=[],\n        )\n        models.db.session.add(dashboard)\n        models.db.session.commit()\n        return DashboardSerializer(dashboard).serialize()\n\n\nclass MyDashboardsResource(BaseResource):\n    @require_permission(\"list_dashboards\")\n    def get(self):\n        \"\"\"\n        Retrieve a list of dashboards created by the current user.\n\n        :qparam number page_size: Number of dashboards to return per page\n        :qparam number page: Page number to retrieve\n        :qparam number order: Name of column to order by\n        :qparam number search: Full text search term\n\n        Responds with an array of :ref:`dashboard <dashboard-response-label>`\n        objects.\n        \"\"\"\n        search_term = request.args.get(\"q\", \"\")\n        if search_term:\n            results = models.Dashboard.search_by_user(search_term, self.current_user)\n        else:\n            results = models.Dashboard.by_user(self.current_user)\n\n        results = filter_by_tags(results, models.Dashboard.tags)\n\n        # order results according to passed order parameter,\n        # special-casing search queries where the database\n        # provides an order by search rank\n        ordered_results = order_results(results, fallback=not bool(search_term))\n\n        page = request.args.get(\"page\", 1, type=int)\n        page_size = request.args.get(\"page_size\", 25, type=int)\n        return paginate(ordered_results, page, page_size, DashboardSerializer)\n\n\nclass DashboardResource(BaseResource):\n    @require_permission(\"list_dashboards\")\n    def get(self, dashboard_id=None):\n        \"\"\"\n        Retrieves a dashboard.\n\n        :qparam number id: Id of dashboard to retrieve.\n\n        .. _dashboard-response-label:\n\n        :>json number id: Dashboard ID\n        :>json string name:\n        :>json string slug:\n        :>json number user_id: ID of the dashboard creator\n        :>json string created_at: ISO format timestamp for dashboard creation\n        :>json string updated_at: ISO format timestamp for last dashboard modification\n        :>json number version: Revision number of dashboard\n        :>json boolean dashboard_filters_enabled: Whether filters are enabled or not\n        :>json boolean is_archived: Whether this dashboard has been removed from the index or not\n        :>json boolean is_draft: Whether this dashboard is a draft or not.\n        :>json array layout: Array of arrays containing widget IDs, corresponding to the rows and columns the widgets are displayed in\n        :>json array widgets: Array of arrays containing :ref:`widget <widget-response-label>` data\n        :>json object options: Dashboard options\n\n        .. _widget-response-label:\n\n        Widget structure:\n\n        :>json number widget.id: Widget ID\n        :>json number widget.width: Widget size\n        :>json object widget.options: Widget options\n        :>json number widget.dashboard_id: ID of dashboard containing this widget\n        :>json string widget.text: Widget contents, if this is a text-box widget\n        :>json object widget.visualization: Widget contents, if this is a visualization widget\n        :>json string widget.created_at: ISO format timestamp for widget creation\n        :>json string widget.updated_at: ISO format timestamp for last widget modification\n        \"\"\"\n        if request.args.get(\"legacy\") is not None:\n            fn = models.Dashboard.get_by_slug_and_org\n        else:\n            fn = models.Dashboard.get_by_id_and_org\n\n        dashboard = get_object_or_404(fn, dashboard_id, self.current_org)\n        response = DashboardSerializer(dashboard, with_widgets=True, user=self.current_user).serialize()\n\n        api_key = models.ApiKey.get_by_object(dashboard)\n        if api_key:\n            response[\"public_url\"] = url_for(\n                \"redash.public_dashboard\",\n                token=api_key.api_key,\n                org_slug=self.current_org.slug,\n                _external=True,\n            )\n            response[\"api_key\"] = api_key.api_key\n\n        response[\"can_edit\"] = can_modify(dashboard, self.current_user)\n\n        self.record_event({\"action\": \"view\", \"object_id\": dashboard.id, \"object_type\": \"dashboard\"})\n\n        return response\n\n    @require_permission(\"edit_dashboard\")\n    def post(self, dashboard_id):\n        \"\"\"\n        Modifies a dashboard.\n\n        :qparam number id: Id of dashboard to retrieve.\n\n        Responds with the updated :ref:`dashboard <dashboard-response-label>`.\n\n        :status 200: success\n        :status 409: Version conflict -- dashboard modified since last read\n        \"\"\"\n        dashboard_properties = request.get_json(force=True)\n        # TODO: either convert all requests to use slugs or ids\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)\n\n        require_object_modify_permission(dashboard, self.current_user)\n\n        updates = project(\n            dashboard_properties,\n            (\n                \"name\",\n                \"layout\",\n                \"version\",\n                \"tags\",\n                \"is_draft\",\n                \"is_archived\",\n                \"dashboard_filters_enabled\",\n                \"options\",\n            ),\n        )\n\n        # SQLAlchemy handles the case where a concurrent transaction beats us\n        # to the update. But we still have to make sure that we're not starting\n        # out behind.\n        if \"version\" in updates and updates[\"version\"] != dashboard.version:\n            abort(409)\n\n        updates[\"changed_by\"] = self.current_user\n\n        self.update_model(dashboard, updates)\n        models.db.session.add(dashboard)\n        try:\n            models.db.session.commit()\n        except StaleDataError:\n            abort(409)\n\n        result = DashboardSerializer(dashboard, with_widgets=True, user=self.current_user).serialize()\n\n        self.record_event({\"action\": \"edit\", \"object_id\": dashboard.id, \"object_type\": \"dashboard\"})\n\n        return result\n\n    @require_permission(\"edit_dashboard\")\n    def delete(self, dashboard_id):\n        \"\"\"\n        Archives a dashboard.\n\n        :qparam number id: Id of dashboard to retrieve.\n\n        Responds with the archived :ref:`dashboard <dashboard-response-label>`.\n        \"\"\"\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)\n        dashboard.is_archived = True\n        dashboard.record_changes(changed_by=self.current_user)\n        models.db.session.add(dashboard)\n        d = DashboardSerializer(dashboard, with_widgets=True, user=self.current_user).serialize()\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"archive\", \"object_id\": dashboard.id, \"object_type\": \"dashboard\"})\n\n        return d\n\n\nclass PublicDashboardResource(BaseResource):\n    decorators = BaseResource.decorators + [csp_allows_embeding]\n\n    def get(self, token):\n        \"\"\"\n        Retrieve a public dashboard.\n\n        :param token: An API key for a public dashboard.\n        :>json array widgets: An array of arrays of :ref:`public widgets <public-widget-label>`, corresponding to the rows and columns the widgets are displayed in\n        \"\"\"\n        if self.current_org.get_setting(\"disable_public_urls\"):\n            abort(400, message=\"Public URLs are disabled.\")\n\n        if not isinstance(self.current_user, models.ApiUser):\n            api_key = get_object_or_404(models.ApiKey.get_by_api_key, token)\n            dashboard = api_key.object\n        else:\n            dashboard = self.current_user.object\n\n        return public_dashboard(dashboard)\n\n\nclass DashboardShareResource(BaseResource):\n    def post(self, dashboard_id):\n        \"\"\"\n        Allow anonymous access to a dashboard.\n\n        :param dashboard_id: The numeric ID of the dashboard to share.\n        :>json string public_url: The URL for anonymous access to the dashboard.\n        :>json api_key: The API key to use when accessing it.\n        \"\"\"\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)\n        require_admin_or_owner(dashboard.user_id)\n        api_key = models.ApiKey.create_for_object(dashboard, self.current_user)\n        models.db.session.flush()\n        models.db.session.commit()\n\n        public_url = url_for(\n            \"redash.public_dashboard\",\n            token=api_key.api_key,\n            org_slug=self.current_org.slug,\n            _external=True,\n        )\n\n        self.record_event(\n            {\n                \"action\": \"activate_api_key\",\n                \"object_id\": dashboard.id,\n                \"object_type\": \"dashboard\",\n            }\n        )\n\n        return {\"public_url\": public_url, \"api_key\": api_key.api_key}\n\n    def delete(self, dashboard_id):\n        \"\"\"\n        Disable anonymous access to a dashboard.\n\n        :param dashboard_id: The numeric ID of the dashboard to unshare.\n        \"\"\"\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)\n        require_admin_or_owner(dashboard.user_id)\n        api_key = models.ApiKey.get_by_object(dashboard)\n\n        if api_key:\n            api_key.active = False\n            models.db.session.add(api_key)\n            models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"deactivate_api_key\",\n                \"object_id\": dashboard.id,\n                \"object_type\": \"dashboard\",\n            }\n        )\n\n\nclass DashboardTagsResource(BaseResource):\n    @require_permission(\"list_dashboards\")\n    def get(self):\n        \"\"\"\n        Lists all accessible dashboards.\n        \"\"\"\n        tags = models.Dashboard.all_tags(self.current_org, self.current_user)\n        return {\"tags\": [{\"name\": name, \"count\": count} for name, count in tags]}\n\n\nclass DashboardFavoriteListResource(BaseResource):\n    def get(self):\n        search_term = request.args.get(\"q\")\n\n        if search_term:\n            base_query = models.Dashboard.search(\n                self.current_org,\n                self.current_user.group_ids,\n                self.current_user.id,\n                search_term,\n            )\n            favorites = models.Dashboard.favorites(self.current_user, base_query=base_query)\n        else:\n            favorites = models.Dashboard.favorites(self.current_user)\n\n        favorites = filter_by_tags(favorites, models.Dashboard.tags)\n\n        # order results according to passed order parameter,\n        # special-casing search queries where the database\n        # provides an order by search rank\n        favorites = order_results(favorites, fallback=not bool(search_term))\n\n        page = request.args.get(\"page\", 1, type=int)\n        page_size = request.args.get(\"page_size\", 25, type=int)\n        # TODO: we don't need to check for favorite status here\n        response = paginate(favorites, page, page_size, DashboardSerializer)\n\n        self.record_event(\n            {\n                \"action\": \"load_favorites\",\n                \"object_type\": \"dashboard\",\n                \"params\": {\n                    \"q\": search_term,\n                    \"tags\": request.args.getlist(\"tags\"),\n                    \"page\": page,\n                },\n            }\n        )\n\n        return response\n\n\nclass DashboardForkResource(BaseResource):\n    @require_permission(\"edit_dashboard\")\n    def post(self, dashboard_id):\n        dashboard = models.Dashboard.get_by_id_and_org(dashboard_id, self.current_org)\n\n        fork_dashboard = dashboard.fork(self.current_user)\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"fork\", \"object_id\": dashboard_id, \"object_type\": \"dashboard\"})\n\n        return DashboardSerializer(fork_dashboard, with_widgets=True).serialize()\n"
  },
  {
    "path": "redash/handlers/data_sources.py",
    "content": "import logging\nimport time\n\nfrom flask import make_response, request\nfrom flask_restful import abort\nfrom funcy import project\nfrom sqlalchemy.exc import IntegrityError\n\nfrom redash import models\nfrom redash.handlers.base import (\n    BaseResource,\n    get_object_or_404,\n    require_fields,\n)\nfrom redash.permissions import (\n    require_access,\n    require_admin,\n    require_permission,\n    view_only,\n)\nfrom redash.query_runner import (\n    get_configuration_schema_for_query_runner_type,\n    query_runners,\n)\nfrom redash.serializers import serialize_job\nfrom redash.tasks.general import get_schema, test_connection\nfrom redash.utils import filter_none\nfrom redash.utils.configuration import ConfigurationContainer, ValidationError\n\n\nclass DataSourceTypeListResource(BaseResource):\n    @require_admin\n    def get(self):\n        return [q.to_dict() for q in sorted(query_runners.values(), key=lambda q: q.name().lower())]\n\n\nclass DataSourceResource(BaseResource):\n    def get(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n        require_access(data_source, self.current_user, view_only)\n\n        ds = {}\n        if self.current_user.has_permission(\"list_data_sources\"):\n            # if it's a non-admin, limit the information\n            ds = data_source.to_dict(all=self.current_user.has_permission(\"admin\"))\n\n        # add view_only info, required for frontend permissions\n        ds[\"view_only\"] = all(project(data_source.groups, self.current_user.group_ids).values())\n        self.record_event({\"action\": \"view\", \"object_id\": data_source_id, \"object_type\": \"datasource\"})\n        return ds\n\n    @require_admin\n    def post(self, data_source_id):\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        req = request.get_json(True)\n\n        schema = get_configuration_schema_for_query_runner_type(req[\"type\"])\n        if schema is None:\n            abort(400)\n        try:\n            data_source.options.set_schema(schema)\n            data_source.options.update(filter_none(req[\"options\"]))\n        except ValidationError:\n            abort(400)\n\n        data_source.type = req[\"type\"]\n        data_source.name = req[\"name\"]\n        models.db.session.add(data_source)\n\n        try:\n            models.db.session.commit()\n        except IntegrityError as e:\n            if req[\"name\"] in str(e):\n                abort(\n                    400,\n                    message=\"Data source with the name {} already exists.\".format(req[\"name\"]),\n                )\n\n            abort(400)\n\n        self.record_event({\"action\": \"edit\", \"object_id\": data_source.id, \"object_type\": \"datasource\"})\n\n        return data_source.to_dict(all=True)\n\n    @require_admin\n    def delete(self, data_source_id):\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        data_source.delete()\n\n        self.record_event(\n            {\n                \"action\": \"delete\",\n                \"object_id\": data_source_id,\n                \"object_type\": \"datasource\",\n            }\n        )\n\n        return make_response(\"\", 204)\n\n\nclass DataSourceListResource(BaseResource):\n    @require_permission(\"list_data_sources\")\n    def get(self):\n        if self.current_user.has_permission(\"admin\"):\n            data_sources = models.DataSource.all(self.current_org)\n        else:\n            data_sources = models.DataSource.all(self.current_org, group_ids=self.current_user.group_ids)\n\n        response = {}\n        for ds in data_sources:\n            if ds.id in response:\n                continue\n\n            try:\n                d = ds.to_dict()\n                d[\"view_only\"] = all(project(ds.groups, self.current_user.group_ids).values())\n                response[ds.id] = d\n            except AttributeError:\n                logging.exception(\"Error with DataSource#to_dict (data source id: %d)\", ds.id)\n\n        self.record_event(\n            {\n                \"action\": \"list\",\n                \"object_id\": \"admin/data_sources\",\n                \"object_type\": \"datasource\",\n            }\n        )\n\n        return sorted(list(response.values()), key=lambda d: d[\"name\"].lower())\n\n    @require_admin\n    def post(self):\n        req = request.get_json(True)\n        require_fields(req, (\"options\", \"name\", \"type\"))\n\n        schema = get_configuration_schema_for_query_runner_type(req[\"type\"])\n        if schema is None:\n            abort(400)\n\n        config = ConfigurationContainer(filter_none(req[\"options\"]), schema)\n        if not config.is_valid():\n            abort(400)\n\n        try:\n            datasource = models.DataSource.create_with_group(\n                org=self.current_org, name=req[\"name\"], type=req[\"type\"], options=config\n            )\n\n            models.db.session.commit()\n        except IntegrityError as e:\n            if req[\"name\"] in str(e):\n                abort(\n                    400,\n                    message=\"Data source with the name {} already exists.\".format(req[\"name\"]),\n                )\n\n            abort(400)\n\n        self.record_event(\n            {\n                \"action\": \"create\",\n                \"object_id\": datasource.id,\n                \"object_type\": \"datasource\",\n            }\n        )\n\n        return datasource.to_dict(all=True)\n\n\nclass DataSourceSchemaResource(BaseResource):\n    def get(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n        require_access(data_source, self.current_user, view_only)\n        refresh = request.args.get(\"refresh\") is not None\n\n        if not refresh:\n            cached_schema = data_source.get_cached_schema()\n\n            if cached_schema is not None:\n                return {\"schema\": cached_schema}\n\n        job = get_schema.delay(data_source.id, refresh)\n\n        return serialize_job(job)\n\n\nclass DataSourcePauseResource(BaseResource):\n    @require_admin\n    def post(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n        data = request.get_json(force=True, silent=True)\n        if data:\n            reason = data.get(\"reason\")\n        else:\n            reason = request.args.get(\"reason\")\n\n        data_source.pause(reason)\n\n        self.record_event(\n            {\n                \"action\": \"pause\",\n                \"object_id\": data_source.id,\n                \"object_type\": \"datasource\",\n            }\n        )\n        return data_source.to_dict()\n\n    @require_admin\n    def delete(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n        data_source.resume()\n\n        self.record_event(\n            {\n                \"action\": \"resume\",\n                \"object_id\": data_source.id,\n                \"object_type\": \"datasource\",\n            }\n        )\n        return data_source.to_dict()\n\n\nclass DataSourceTestResource(BaseResource):\n    @require_admin\n    def post(self, data_source_id):\n        data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, self.current_org)\n\n        response = {}\n\n        job = test_connection.delay(data_source.id)\n        while not (job.is_finished or job.is_failed):\n            time.sleep(1)\n            job.refresh()\n\n        if isinstance(job.result, Exception):\n            response = {\"message\": str(job.result), \"ok\": False}\n        else:\n            response = {\"message\": \"success\", \"ok\": True}\n\n        self.record_event(\n            {\n                \"action\": \"test\",\n                \"object_id\": data_source_id,\n                \"object_type\": \"datasource\",\n                \"result\": response,\n            }\n        )\n        return response\n"
  },
  {
    "path": "redash/handlers/databricks.py",
    "content": "from flask import request\nfrom flask_restful import abort\n\nfrom redash import models, redis_connection\nfrom redash.handlers.base import BaseResource, get_object_or_404\nfrom redash.permissions import require_access, view_only\nfrom redash.serializers import serialize_job\nfrom redash.tasks.databricks import (\n    get_database_tables_with_columns,\n    get_databricks_databases,\n    get_databricks_table_columns,\n    get_databricks_tables,\n)\nfrom redash.utils import json_loads\n\n\ndef _get_databricks_data_source(data_source_id, user, org):\n    data_source = get_object_or_404(models.DataSource.get_by_id_and_org, data_source_id, org)\n    require_access(data_source, user, view_only)\n\n    if not data_source.type == \"databricks\":\n        abort(400, message=\"Resource only available for the Databricks query runner.\")\n\n    return data_source\n\n\ndef _databases_key(data_source_id):\n    return \"databricks:databases:{}\".format(data_source_id)\n\n\ndef _tables_key(data_source_id, database_name):\n    return \"databricks:database_tables:{}:{}\".format(data_source_id, database_name)\n\n\ndef _get_databases_from_cache(data_source_id):\n    cache = redis_connection.get(_databases_key(data_source_id))\n    return json_loads(cache) if cache else None\n\n\ndef _get_tables_from_cache(data_source_id, database_name):\n    cache = redis_connection.get(_tables_key(data_source_id, database_name))\n    return json_loads(cache) if cache else None\n\n\nclass DatabricksDatabaseListResource(BaseResource):\n    def get(self, data_source_id):\n        data_source = _get_databricks_data_source(data_source_id, user=self.current_user, org=self.current_org)\n\n        refresh = request.args.get(\"refresh\") is not None\n        if not refresh:\n            cached_databases = _get_databases_from_cache(data_source_id)\n\n            if cached_databases is not None:\n                return cached_databases\n\n        job = get_databricks_databases.delay(data_source.id, redis_key=_databases_key(data_source_id))\n        return serialize_job(job)\n\n\nclass DatabricksSchemaResource(BaseResource):\n    def get(self, data_source_id, database_name):\n        data_source = _get_databricks_data_source(data_source_id, user=self.current_user, org=self.current_org)\n\n        refresh = request.args.get(\"refresh\") is not None\n        if not refresh:\n            cached_tables = _get_tables_from_cache(data_source_id, database_name)\n\n            if cached_tables is not None:\n                return {\"schema\": cached_tables, \"has_columns\": True}\n\n            job = get_databricks_tables.delay(data_source.id, database_name)\n            return serialize_job(job)\n\n        job = get_database_tables_with_columns.delay(\n            data_source.id, database_name, redis_key=_tables_key(data_source_id, database_name)\n        )\n        return serialize_job(job)\n\n\nclass DatabricksTableColumnListResource(BaseResource):\n    def get(self, data_source_id, database_name, table_name):\n        data_source = _get_databricks_data_source(data_source_id, user=self.current_user, org=self.current_org)\n\n        job = get_databricks_table_columns.delay(data_source.id, database_name, table_name)\n        return serialize_job(job)\n"
  },
  {
    "path": "redash/handlers/destinations.py",
    "content": "from flask import make_response, request\nfrom flask_restful import abort\nfrom sqlalchemy.exc import IntegrityError\n\nfrom redash import models\nfrom redash.destinations import (\n    destinations,\n    get_configuration_schema_for_destination_type,\n)\nfrom redash.handlers.base import BaseResource, require_fields\nfrom redash.permissions import require_admin\nfrom redash.utils.configuration import ConfigurationContainer, ValidationError\n\n\nclass DestinationTypeListResource(BaseResource):\n    @require_admin\n    def get(self):\n        return [q.to_dict() for q in destinations.values()]\n\n\nclass DestinationResource(BaseResource):\n    @require_admin\n    def get(self, destination_id):\n        destination = models.NotificationDestination.get_by_id_and_org(destination_id, self.current_org)\n        d = destination.to_dict(all=True)\n        self.record_event(\n            {\n                \"action\": \"view\",\n                \"object_id\": destination_id,\n                \"object_type\": \"destination\",\n            }\n        )\n        return d\n\n    @require_admin\n    def post(self, destination_id):\n        destination = models.NotificationDestination.get_by_id_and_org(destination_id, self.current_org)\n        req = request.get_json(True)\n\n        schema = get_configuration_schema_for_destination_type(req[\"type\"])\n        if schema is None:\n            abort(400)\n\n        try:\n            destination.type = req[\"type\"]\n            destination.name = req[\"name\"]\n            destination.options.set_schema(schema)\n            destination.options.update(req[\"options\"])\n            models.db.session.add(destination)\n            models.db.session.commit()\n        except ValidationError:\n            abort(400)\n        except IntegrityError as e:\n            if \"name\" in str(e):\n                abort(\n                    400,\n                    message=\"Alert Destination with the name {} already exists.\".format(req[\"name\"]),\n                )\n            abort(500)\n\n        return destination.to_dict(all=True)\n\n    @require_admin\n    def delete(self, destination_id):\n        destination = models.NotificationDestination.get_by_id_and_org(destination_id, self.current_org)\n        models.db.session.delete(destination)\n        models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"delete\",\n                \"object_id\": destination_id,\n                \"object_type\": \"destination\",\n            }\n        )\n\n        return make_response(\"\", 204)\n\n\nclass DestinationListResource(BaseResource):\n    def get(self):\n        destinations = models.NotificationDestination.all(self.current_org)\n\n        response = {}\n        for ds in destinations:\n            if ds.id in response:\n                continue\n\n            d = ds.to_dict()\n            response[ds.id] = d\n\n        self.record_event(\n            {\n                \"action\": \"list\",\n                \"object_id\": \"admin/destinations\",\n                \"object_type\": \"destination\",\n            }\n        )\n\n        return list(response.values())\n\n    @require_admin\n    def post(self):\n        req = request.get_json(True)\n        require_fields(req, (\"options\", \"name\", \"type\"))\n\n        schema = get_configuration_schema_for_destination_type(req[\"type\"])\n        if schema is None:\n            abort(400)\n\n        config = ConfigurationContainer(req[\"options\"], schema)\n        if not config.is_valid():\n            abort(400)\n\n        destination = models.NotificationDestination(\n            org=self.current_org,\n            name=req[\"name\"],\n            type=req[\"type\"],\n            options=config,\n            user=self.current_user,\n        )\n\n        try:\n            models.db.session.add(destination)\n            models.db.session.commit()\n        except IntegrityError as e:\n            if \"name\" in str(e):\n                abort(\n                    400,\n                    message=\"Alert Destination with the name {} already exists.\".format(req[\"name\"]),\n                )\n            abort(500)\n\n        return destination.to_dict(all=True)\n"
  },
  {
    "path": "redash/handlers/embed.py",
    "content": "from flask import request\nfrom flask_login import current_user, login_required\n\nfrom redash import models\nfrom redash.handlers import routes\nfrom redash.handlers.base import (\n    get_object_or_404,\n    org_scoped_rule,\n    record_event,\n)\nfrom redash.handlers.static import render_index\nfrom redash.security import csp_allows_embeding\n\nfrom .authentication import current_org\n\n\n@routes.route(\n    org_scoped_rule(\"/embed/query/<query_id>/visualization/<visualization_id>\"),\n    methods=[\"GET\"],\n)\n@login_required\n@csp_allows_embeding\ndef embed(query_id, visualization_id, org_slug=None):\n    record_event(\n        current_org,\n        current_user._get_current_object(),\n        {\n            \"action\": \"view\",\n            \"object_id\": visualization_id,\n            \"object_type\": \"visualization\",\n            \"query_id\": query_id,\n            \"embed\": True,\n            \"referer\": request.headers.get(\"Referer\"),\n        },\n    )\n    return render_index()\n\n\n@routes.route(org_scoped_rule(\"/public/dashboards/<token>\"), methods=[\"GET\"])\n@login_required\n@csp_allows_embeding\ndef public_dashboard(token, org_slug=None):\n    if current_user.is_api_user():\n        dashboard = current_user.object\n    else:\n        api_key = get_object_or_404(models.ApiKey.get_by_api_key, token)\n        dashboard = api_key.object\n\n    record_event(\n        current_org,\n        current_user,\n        {\n            \"action\": \"view\",\n            \"object_id\": dashboard.id,\n            \"object_type\": \"dashboard\",\n            \"public\": True,\n            \"headless\": \"embed\" in request.args,\n            \"referer\": request.headers.get(\"Referer\"),\n        },\n    )\n    return render_index()\n"
  },
  {
    "path": "redash/handlers/events.py",
    "content": "import geolite2\nimport maxminddb\nfrom flask import request\nfrom user_agents import parse as parse_ua\n\nfrom redash.handlers.base import BaseResource, paginate\nfrom redash.permissions import require_admin\n\n\ndef get_location(ip):\n    if ip is None:\n        return \"Unknown\"\n\n    with maxminddb.open_database(geolite2.geolite2_database()) as reader:\n        try:\n            match = reader.get(ip)\n            return match[\"country\"][\"names\"][\"en\"]\n        except Exception:\n            return \"Unknown\"\n\n\ndef event_details(event):\n    details = {}\n    if event.object_type == \"data_source\" and event.action == \"execute_query\":\n        details[\"query\"] = event.additional_properties[\"query\"]\n        details[\"data_source\"] = event.object_id\n    elif event.object_type == \"page\" and event.action == \"view\":\n        details[\"page\"] = event.object_id\n    else:\n        details[\"object_id\"] = event.object_id\n        details[\"object_type\"] = event.object_type\n\n    return details\n\n\ndef serialize_event(event):\n    d = {\n        \"org_id\": event.org_id,\n        \"user_id\": event.user_id,\n        \"action\": event.action,\n        \"object_type\": event.object_type,\n        \"object_id\": event.object_id,\n        \"created_at\": event.created_at,\n    }\n\n    if event.user_id:\n        d[\"user_name\"] = event.additional_properties.get(\"user_name\", \"User {}\".format(event.user_id))\n\n    if not event.user_id:\n        d[\"user_name\"] = event.additional_properties.get(\"api_key\", \"Unknown\")\n\n    d[\"browser\"] = str(parse_ua(event.additional_properties.get(\"user_agent\", \"\")))\n    d[\"location\"] = get_location(event.additional_properties.get(\"ip\"))\n    d[\"details\"] = event_details(event)\n\n    return d\n\n\nclass EventsResource(BaseResource):\n    def post(self):\n        events_list = request.get_json(force=True)\n        for event in events_list:\n            self.record_event(event)\n\n    @require_admin\n    def get(self):\n        page = request.args.get(\"page\", 1, type=int)\n        page_size = request.args.get(\"page_size\", 25, type=int)\n        return paginate(self.current_org.events, page, page_size, serialize_event)\n"
  },
  {
    "path": "redash/handlers/favorites.py",
    "content": "from sqlalchemy.exc import IntegrityError\n\nfrom redash import models\nfrom redash.handlers.base import BaseResource, get_object_or_404\nfrom redash.permissions import require_access, view_only\n\n\nclass QueryFavoriteResource(BaseResource):\n    def post(self, query_id):\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(query, self.current_user, view_only)\n\n        fav = models.Favorite(org_id=self.current_org.id, object=query, user=self.current_user)\n        models.db.session.add(fav)\n\n        try:\n            models.db.session.commit()\n        except IntegrityError as e:\n            if \"unique_favorite\" in str(e):\n                models.db.session.rollback()\n            else:\n                raise e\n\n        self.record_event({\"action\": \"favorite\", \"object_id\": query.id, \"object_type\": \"query\"})\n\n    def delete(self, query_id):\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(query, self.current_user, view_only)\n\n        models.Favorite.query.filter(\n            models.Favorite.object_id == query_id,\n            models.Favorite.object_type == \"Query\",\n            models.Favorite.user == self.current_user,\n        ).delete()\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"favorite\", \"object_id\": query.id, \"object_type\": \"query\"})\n\n\nclass DashboardFavoriteResource(BaseResource):\n    def post(self, object_id):\n        dashboard = get_object_or_404(models.Dashboard.get_by_id_and_org, object_id, self.current_org)\n        fav = models.Favorite(org_id=self.current_org.id, object=dashboard, user=self.current_user)\n        models.db.session.add(fav)\n\n        try:\n            models.db.session.commit()\n        except IntegrityError as e:\n            if \"unique_favorite\" in str(e):\n                models.db.session.rollback()\n            else:\n                raise e\n\n        self.record_event(\n            {\n                \"action\": \"favorite\",\n                \"object_id\": dashboard.id,\n                \"object_type\": \"dashboard\",\n            }\n        )\n\n    def delete(self, object_id):\n        dashboard = get_object_or_404(models.Dashboard.get_by_id_and_org, object_id, self.current_org)\n        models.Favorite.query.filter(\n            models.Favorite.object == dashboard,\n            models.Favorite.user == self.current_user,\n        ).delete()\n        models.db.session.commit()\n        self.record_event(\n            {\n                \"action\": \"unfavorite\",\n                \"object_id\": dashboard.id,\n                \"object_type\": \"dashboard\",\n            }\n        )\n"
  },
  {
    "path": "redash/handlers/groups.py",
    "content": "from flask import request\nfrom flask_restful import abort\n\nfrom redash import models\nfrom redash.handlers.base import BaseResource, get_object_or_404\nfrom redash.permissions import require_admin, require_permission\n\n\nclass GroupListResource(BaseResource):\n    @require_admin\n    def post(self):\n        name = request.json[\"name\"]\n        group = models.Group(name=name, org=self.current_org)\n        models.db.session.add(group)\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"create\", \"object_id\": group.id, \"object_type\": \"group\"})\n\n        return group.to_dict()\n\n    def get(self):\n        if self.current_user.has_permission(\"admin\"):\n            groups = models.Group.all(self.current_org)\n        else:\n            groups = models.Group.query.filter(models.Group.id.in_(self.current_user.group_ids))\n\n        self.record_event({\"action\": \"list\", \"object_id\": \"groups\", \"object_type\": \"group\"})\n\n        return [g.to_dict() for g in groups]\n\n\nclass GroupResource(BaseResource):\n    @require_admin\n    def post(self, group_id):\n        group = models.Group.get_by_id_and_org(group_id, self.current_org)\n\n        if group.type == models.Group.BUILTIN_GROUP:\n            abort(400, message=\"Can't modify built-in groups.\")\n\n        group.name = request.json[\"name\"]\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"edit\", \"object_id\": group.id, \"object_type\": \"group\"})\n\n        return group.to_dict()\n\n    def get(self, group_id):\n        if not (self.current_user.has_permission(\"admin\") or int(group_id) in self.current_user.group_ids):\n            abort(403)\n\n        group = models.Group.get_by_id_and_org(group_id, self.current_org)\n\n        self.record_event({\"action\": \"view\", \"object_id\": group_id, \"object_type\": \"group\"})\n\n        return group.to_dict()\n\n    @require_admin\n    def delete(self, group_id):\n        group = models.Group.get_by_id_and_org(group_id, self.current_org)\n        if group.type == models.Group.BUILTIN_GROUP:\n            abort(400, message=\"Can't delete built-in groups.\")\n\n        members = models.Group.members(group_id)\n        for member in members:\n            member.group_ids.remove(int(group_id))\n            models.db.session.add(member)\n\n        models.db.session.delete(group)\n        models.db.session.commit()\n\n\nclass GroupMemberListResource(BaseResource):\n    @require_admin\n    def post(self, group_id):\n        user_id = request.json[\"user_id\"]\n        user = models.User.get_by_id_and_org(user_id, self.current_org)\n        group = models.Group.get_by_id_and_org(group_id, self.current_org)\n        user.group_ids.append(group.id)\n        models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"add_member\",\n                \"object_id\": group.id,\n                \"object_type\": \"group\",\n                \"member_id\": user.id,\n            }\n        )\n        return user.to_dict()\n\n    @require_permission(\"list_users\")\n    def get(self, group_id):\n        if not (self.current_user.has_permission(\"admin\") or int(group_id) in self.current_user.group_ids):\n            abort(403)\n\n        members = models.Group.members(group_id)\n        return [m.to_dict() for m in members]\n\n\nclass GroupMemberResource(BaseResource):\n    @require_admin\n    def delete(self, group_id, user_id):\n        user = models.User.get_by_id_and_org(user_id, self.current_org)\n        user.group_ids.remove(int(group_id))\n        models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"remove_member\",\n                \"object_id\": group_id,\n                \"object_type\": \"group\",\n                \"member_id\": user.id,\n            }\n        )\n\n\ndef serialize_data_source_with_group(data_source, data_source_group):\n    d = data_source.to_dict()\n    d[\"view_only\"] = data_source_group.view_only\n    return d\n\n\nclass GroupDataSourceListResource(BaseResource):\n    @require_admin\n    def post(self, group_id):\n        data_source_id = request.json[\"data_source_id\"]\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        group = models.Group.get_by_id_and_org(group_id, self.current_org)\n\n        data_source_group = data_source.add_group(group)\n        models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"add_data_source\",\n                \"object_id\": group_id,\n                \"object_type\": \"group\",\n                \"member_id\": data_source.id,\n            }\n        )\n\n        return serialize_data_source_with_group(data_source, data_source_group)\n\n    @require_admin\n    def get(self, group_id):\n        group = get_object_or_404(models.Group.get_by_id_and_org, group_id, self.current_org)\n\n        # TOOD: move to models\n        data_sources = models.DataSource.query.join(models.DataSourceGroup).filter(\n            models.DataSourceGroup.group == group\n        )\n\n        self.record_event({\"action\": \"list\", \"object_id\": group_id, \"object_type\": \"group\"})\n\n        return [ds.to_dict(with_permissions_for=group) for ds in data_sources]\n\n\nclass GroupDataSourceResource(BaseResource):\n    @require_admin\n    def post(self, group_id, data_source_id):\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        group = models.Group.get_by_id_and_org(group_id, self.current_org)\n        view_only = request.json[\"view_only\"]\n\n        data_source_group = data_source.update_group_permission(group, view_only)\n        models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"change_data_source_permission\",\n                \"object_id\": group_id,\n                \"object_type\": \"group\",\n                \"member_id\": data_source.id,\n                \"view_only\": view_only,\n            }\n        )\n\n        return serialize_data_source_with_group(data_source, data_source_group)\n\n    @require_admin\n    def delete(self, group_id, data_source_id):\n        data_source = models.DataSource.get_by_id_and_org(data_source_id, self.current_org)\n        group = models.Group.get_by_id_and_org(group_id, self.current_org)\n\n        data_source.remove_group(group)\n        models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"remove_data_source\",\n                \"object_id\": group_id,\n                \"object_type\": \"group\",\n                \"member_id\": data_source.id,\n            }\n        )\n"
  },
  {
    "path": "redash/handlers/organization.py",
    "content": "from flask_login import current_user, login_required\n\nfrom redash import models\nfrom redash.authentication import current_org\nfrom redash.handlers import routes\nfrom redash.handlers.base import json_response, org_scoped_rule\n\n\n@routes.route(org_scoped_rule(\"/api/organization/status\"), methods=[\"GET\"])\n@login_required\ndef organization_status(org_slug=None):\n    counters = {\n        \"users\": models.User.all(current_org).count(),\n        \"alerts\": models.Alert.all(group_ids=current_user.group_ids).count(),\n        \"data_sources\": models.DataSource.all(current_org, group_ids=current_user.group_ids).count(),\n        \"queries\": models.Query.all_queries(current_user.group_ids, current_user.id, include_drafts=True).count(),\n        \"dashboards\": models.Dashboard.query.filter(\n            models.Dashboard.org == current_org, models.Dashboard.is_archived.is_(False)\n        ).count(),\n    }\n\n    return json_response(dict(object_counters=counters))\n"
  },
  {
    "path": "redash/handlers/permissions.py",
    "content": "from collections import defaultdict\n\nfrom flask import request\nfrom flask_restful import abort\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom redash.handlers.base import BaseResource, get_object_or_404\nfrom redash.models import AccessPermission, Dashboard, Query, User, db\nfrom redash.permissions import ACCESS_TYPES, require_admin_or_owner\n\nmodel_to_types = {\"queries\": Query, \"dashboards\": Dashboard}\n\n\ndef get_model_from_type(type):\n    model = model_to_types.get(type)\n    if model is None:\n        abort(404)\n    return model\n\n\nclass ObjectPermissionsListResource(BaseResource):\n    def get(self, object_type, object_id):\n        model = get_model_from_type(object_type)\n        obj = get_object_or_404(model.get_by_id_and_org, object_id, self.current_org)\n\n        # TODO: include grantees in search to avoid N+1 queries\n        permissions = AccessPermission.find(obj)\n\n        result = defaultdict(list)\n\n        for perm in permissions:\n            result[perm.access_type].append(perm.grantee.to_dict())\n\n        return result\n\n    def post(self, object_type, object_id):\n        model = get_model_from_type(object_type)\n        obj = get_object_or_404(model.get_by_id_and_org, object_id, self.current_org)\n\n        require_admin_or_owner(obj.user_id)\n\n        req = request.get_json(True)\n\n        access_type = req[\"access_type\"]\n\n        if access_type not in ACCESS_TYPES:\n            abort(400, message=\"Unknown access type.\")\n\n        try:\n            grantee = User.get_by_id_and_org(req[\"user_id\"], self.current_org)\n        except NoResultFound:\n            abort(400, message=\"User not found.\")\n\n        permission = AccessPermission.grant(obj, access_type, grantee, self.current_user)\n        db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"grant_permission\",\n                \"object_id\": object_id,\n                \"object_type\": object_type,\n                \"grantee\": grantee.id,\n                \"access_type\": access_type,\n            }\n        )\n\n        return permission.to_dict()\n\n    def delete(self, object_type, object_id):\n        model = get_model_from_type(object_type)\n        obj = get_object_or_404(model.get_by_id_and_org, object_id, self.current_org)\n\n        require_admin_or_owner(obj.user_id)\n\n        req = request.get_json(True)\n        grantee_id = req[\"user_id\"]\n        access_type = req[\"access_type\"]\n\n        grantee = User.query.get(req[\"user_id\"])\n        if grantee is None:\n            abort(400, message=\"User not found.\")\n\n        AccessPermission.revoke(obj, grantee, access_type)\n        db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"revoke_permission\",\n                \"object_id\": object_id,\n                \"object_type\": object_type,\n                \"access_type\": access_type,\n                \"grantee_id\": grantee_id,\n            }\n        )\n\n\nclass CheckPermissionResource(BaseResource):\n    def get(self, object_type, object_id, access_type):\n        model = get_model_from_type(object_type)\n        obj = get_object_or_404(model.get_by_id_and_org, object_id, self.current_org)\n\n        has_access = AccessPermission.exists(obj, access_type, self.current_user)\n\n        return {\"response\": has_access}\n"
  },
  {
    "path": "redash/handlers/queries.py",
    "content": "import sqlparse\nfrom flask import jsonify, request, url_for\nfrom flask_login import login_required\nfrom flask_restful import abort\nfrom funcy import partial\nfrom sqlalchemy.orm.exc import StaleDataError\n\nfrom redash import models, settings\nfrom redash.authentication.org_resolving import current_org\nfrom redash.handlers.base import (\n    BaseResource,\n    filter_by_tags,\n    get_object_or_404,\n    org_scoped_rule,\n    paginate,\n    routes,\n)\nfrom redash.handlers.base import order_results as _order_results\nfrom redash.handlers.query_results import run_query\nfrom redash.models.parameterized_query import ParameterizedQuery\nfrom redash.permissions import (\n    can_modify,\n    not_view_only,\n    require_access,\n    require_admin_or_owner,\n    require_object_modify_permission,\n    require_permission,\n    view_only,\n)\nfrom redash.serializers import QuerySerializer\nfrom redash.utils import collect_parameters_from_request\n\n# Ordering map for relationships\norder_map = {\n    \"name\": \"lowercase_name\",\n    \"-name\": \"-lowercase_name\",\n    \"created_at\": \"created_at\",\n    \"-created_at\": \"-created_at\",\n    \"schedule\": \"interval\",\n    \"-schedule\": \"-interval\",\n    \"runtime\": \"query_results-runtime\",\n    \"-runtime\": \"-query_results-runtime\",\n    \"executed_at\": \"query_results-retrieved_at\",\n    \"-executed_at\": \"-query_results-retrieved_at\",\n    \"created_by\": \"users-name\",\n    \"-created_by\": \"-users-name\",\n    \"starred_at\": \"favorites-created_at\",\n    \"-starred_at\": \"-favorites-created_at\",\n}\n\norder_results = partial(_order_results, default_order=\"-created_at\", allowed_orders=order_map)\n\n\n@routes.route(org_scoped_rule(\"/api/queries/format\"), methods=[\"POST\"])\n@login_required\ndef format_sql_query(org_slug=None):\n    \"\"\"\n    Formats an SQL query using the Python ``sqlparse`` formatter.\n\n    :<json string query: The SQL text to format\n    :>json string query: Formatted SQL text\n    \"\"\"\n    arguments = request.get_json(force=True)\n    query = arguments.get(\"query\", \"\")\n\n    return jsonify({\"query\": sqlparse.format(query, **settings.SQLPARSE_FORMAT_OPTIONS)})\n\n\nclass QuerySearchResource(BaseResource):\n    @require_permission(\"view_query\")\n    def get(self):\n        \"\"\"\n        Search query text, names, and descriptions.\n\n        :qparam string q: Search term\n        :qparam number include_drafts: Whether to include draft in results\n\n        Responds with a list of :ref:`query <query-response-label>` objects.\n        \"\"\"\n        term = request.args.get(\"q\", \"\")\n        if not term:\n            return []\n\n        include_drafts = request.args.get(\"include_drafts\") is not None\n\n        self.record_event({\"action\": \"search\", \"object_type\": \"query\", \"term\": term})\n\n        # this redirects to the new query list API that is aware of search\n        new_location = url_for(\n            \"queries\",\n            q=term,\n            org_slug=current_org.slug,\n            drafts=\"true\" if include_drafts else \"false\",\n        )\n        return {}, 301, {\"Location\": new_location}\n\n\nclass QueryRecentResource(BaseResource):\n    @require_permission(\"view_query\")\n    def get(self):\n        \"\"\"\n        Retrieve up to 10 queries recently modified by the user.\n\n        Responds with a list of :ref:`query <query-response-label>` objects.\n        \"\"\"\n\n        results = models.Query.by_user(self.current_user).order_by(models.Query.updated_at.desc()).limit(10)\n        return QuerySerializer(results, with_last_modified_by=False, with_user=False).serialize()\n\n\nclass BaseQueryListResource(BaseResource):\n    def get_queries(self, search_term):\n        if search_term:\n            results = models.Query.search(\n                search_term,\n                self.current_user.group_ids,\n                self.current_user.id,\n                include_drafts=True,\n                multi_byte_search=current_org.get_setting(\"multi_byte_search_enabled\"),\n            )\n        else:\n            results = models.Query.all_queries(self.current_user.group_ids, self.current_user.id, include_drafts=True)\n        return filter_by_tags(results, models.Query.tags)\n\n    @require_permission(\"view_query\")\n    def get(self):\n        \"\"\"\n        Retrieve a list of queries.\n\n        :qparam number page_size: Number of queries to return per page\n        :qparam number page: Page number to retrieve\n        :qparam number order: Name of column to order by\n        :qparam number q: Full text search term\n\n        Responds with an array of :ref:`query <query-response-label>` objects.\n        \"\"\"\n        # See if we want to do full-text search or just regular queries\n        search_term = request.args.get(\"q\", \"\")\n\n        queries = self.get_queries(search_term)\n\n        results = filter_by_tags(queries, models.Query.tags)\n\n        # order results according to passed order parameter,\n        # special-casing search queries where the database\n        # provides an order by search rank\n        ordered_results = order_results(results, fallback=not bool(search_term))\n\n        page = request.args.get(\"page\", 1, type=int)\n        page_size = request.args.get(\"page_size\", 25, type=int)\n\n        response = paginate(\n            ordered_results,\n            page=page,\n            page_size=page_size,\n            serializer=QuerySerializer,\n            with_stats=True,\n            with_last_modified_by=False,\n        )\n\n        if search_term:\n            self.record_event({\"action\": \"search\", \"object_type\": \"query\", \"term\": search_term})\n        else:\n            self.record_event({\"action\": \"list\", \"object_type\": \"query\"})\n\n        return response\n\n\ndef require_access_to_dropdown_queries(user, query_def):\n    parameters = query_def.get(\"options\", {}).get(\"parameters\", [])\n    dropdown_query_ids = set([str(p[\"queryId\"]) for p in parameters if p[\"type\"] == \"query\"])\n\n    if dropdown_query_ids:\n        groups = models.Query.all_groups_for_query_ids(dropdown_query_ids)\n\n        if len(groups) < len(dropdown_query_ids):\n            abort(\n                400,\n                message=\"You are trying to associate a dropdown query that does not have a matching group. \"\n                \"Please verify the dropdown query id you are trying to associate with this query.\",\n            )\n\n        require_access(dict(groups), user, view_only)\n\n\nclass QueryListResource(BaseQueryListResource):\n    @require_permission(\"create_query\")\n    def post(self):\n        \"\"\"\n        Create a new query.\n\n        :<json number data_source_id: The ID of the data source this query will run on\n        :<json string query: Query text\n        :<json string name:\n        :<json string description:\n        :<json string schedule: Schedule interval, in seconds, for repeated execution of this query\n        :<json object options: Query options\n\n        .. _query-response-label:\n\n        :>json number id: Query ID\n        :>json number latest_query_data_id: ID for latest output data from this query\n        :>json string name:\n        :>json string description:\n        :>json string query: Query text\n        :>json string query_hash: Hash of query text\n        :>json string schedule: Schedule interval, in seconds, for repeated execution of this query\n        :>json string api_key: Key for public access to this query's results.\n        :>json boolean is_archived: Whether this query is displayed in indexes and search results or not.\n        :>json boolean is_draft: Whether this query is a draft or not\n        :>json string updated_at: Time of last modification, in ISO format\n        :>json string created_at: Time of creation, in ISO format\n        :>json number data_source_id: ID of the data source this query will run on\n        :>json object options: Query options\n        :>json number version: Revision version (for update conflict avoidance)\n        :>json number user_id: ID of query creator\n        :>json number last_modified_by_id: ID of user who last modified this query\n        :>json string retrieved_at: Time when query results were last retrieved, in ISO format (may be null)\n        :>json number runtime: Runtime of last query execution, in seconds (may be null)\n        \"\"\"\n        query_def = request.get_json(force=True)\n        data_source = models.DataSource.get_by_id_and_org(query_def.pop(\"data_source_id\"), self.current_org)\n        require_access(data_source, self.current_user, not_view_only)\n        require_access_to_dropdown_queries(self.current_user, query_def)\n\n        for field in [\n            \"id\",\n            \"created_at\",\n            \"api_key\",\n            \"visualizations\",\n            \"latest_query_data\",\n            \"last_modified_by\",\n        ]:\n            query_def.pop(field, None)\n\n        query_def[\"query_text\"] = query_def.pop(\"query\")\n        query_def[\"user\"] = self.current_user\n        query_def[\"data_source\"] = data_source\n        query_def[\"org\"] = self.current_org\n        query_def[\"is_draft\"] = True\n        query = models.Query.create(**query_def)\n        models.db.session.add(query)\n        models.db.session.commit()\n        query.update_latest_result_by_query_hash()\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"create\", \"object_id\": query.id, \"object_type\": \"query\"})\n\n        return QuerySerializer(query, with_visualizations=True).serialize()\n\n\nclass QueryArchiveResource(BaseQueryListResource):\n    def get_queries(self, search_term):\n        if search_term:\n            return models.Query.search(\n                search_term,\n                self.current_user.group_ids,\n                self.current_user.id,\n                include_drafts=False,\n                include_archived=True,\n                multi_byte_search=current_org.get_setting(\"multi_byte_search_enabled\"),\n            )\n        else:\n            return models.Query.all_queries(\n                self.current_user.group_ids,\n                self.current_user.id,\n                include_drafts=False,\n                include_archived=True,\n            )\n\n\nclass MyQueriesResource(BaseResource):\n    @require_permission(\"view_query\")\n    def get(self):\n        \"\"\"\n        Retrieve a list of queries created by the current user.\n\n        :qparam number page_size: Number of queries to return per page\n        :qparam number page: Page number to retrieve\n        :qparam number order: Name of column to order by\n        :qparam number search: Full text search term\n\n        Responds with an array of :ref:`query <query-response-label>` objects.\n        \"\"\"\n        search_term = request.args.get(\"q\", \"\")\n        if search_term:\n            results = models.Query.search_by_user(\n                search_term,\n                self.current_user,\n                multi_byte_search=current_org.get_setting(\"multi_byte_search_enabled\"),\n            )\n        else:\n            results = models.Query.by_user(self.current_user)\n\n        results = filter_by_tags(results, models.Query.tags)\n\n        # order results according to passed order parameter,\n        # special-casing search queries where the database\n        # provides an order by search rank\n        ordered_results = order_results(results, fallback=not bool(search_term))\n\n        page = request.args.get(\"page\", 1, type=int)\n        page_size = request.args.get(\"page_size\", 25, type=int)\n        return paginate(\n            ordered_results,\n            page,\n            page_size,\n            QuerySerializer,\n            with_stats=True,\n            with_last_modified_by=False,\n        )\n\n\nclass QueryResource(BaseResource):\n    @require_permission(\"edit_query\")\n    def post(self, query_id):\n        \"\"\"\n        Modify a query.\n\n        :param query_id: ID of query to update\n        :<json number data_source_id: The ID of the data source this query will run on\n        :<json string query: Query text\n        :<json string name:\n        :<json string description:\n        :<json string schedule: Schedule interval, in seconds, for repeated execution of this query\n        :<json object options: Query options\n\n        Responds with the updated :ref:`query <query-response-label>` object.\n        \"\"\"\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        query_def = request.get_json(force=True)\n\n        require_object_modify_permission(query, self.current_user)\n        require_access_to_dropdown_queries(self.current_user, query_def)\n\n        for field in [\n            \"id\",\n            \"created_at\",\n            \"api_key\",\n            \"visualizations\",\n            \"latest_query_data\",\n            \"user\",\n            \"last_modified_by\",\n            \"org\",\n        ]:\n            query_def.pop(field, None)\n\n        if \"query\" in query_def:\n            query_def[\"query_text\"] = query_def.pop(\"query\")\n\n        if \"tags\" in query_def:\n            query_def[\"tags\"] = [tag for tag in query_def[\"tags\"] if tag]\n\n        if \"data_source_id\" in query_def:\n            data_source = models.DataSource.get_by_id_and_org(query_def[\"data_source_id\"], self.current_org)\n            require_access(data_source, self.current_user, not_view_only)\n\n        query_def[\"last_modified_by\"] = self.current_user\n        query_def[\"changed_by\"] = self.current_user\n        # SQLAlchemy handles the case where a concurrent transaction beats us\n        # to the update. But we still have to make sure that we're not starting\n        # out behind.\n        if \"version\" in query_def and query_def[\"version\"] != query.version:\n            abort(409)\n\n        try:\n            self.update_model(query, query_def)\n            models.db.session.commit()\n            query.update_latest_result_by_query_hash()\n            models.db.session.commit()\n        except StaleDataError:\n            abort(409)\n\n        return QuerySerializer(query, with_visualizations=True).serialize()\n\n    @require_permission(\"view_query\")\n    def get(self, query_id):\n        \"\"\"\n        Retrieve a query.\n\n        :param query_id: ID of query to fetch\n\n        Responds with the :ref:`query <query-response-label>` contents.\n        \"\"\"\n        q = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(q, self.current_user, view_only)\n\n        result = QuerySerializer(q, with_visualizations=True).serialize()\n        result[\"can_edit\"] = can_modify(q, self.current_user)\n\n        self.record_event({\"action\": \"view\", \"object_id\": query_id, \"object_type\": \"query\"})\n\n        return result\n\n    # TODO: move to resource of its own? (POST /queries/{id}/archive)\n    def delete(self, query_id):\n        \"\"\"\n        Archives a query.\n\n        :param query_id: ID of query to archive\n        \"\"\"\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_admin_or_owner(query.user_id)\n        query.archive(self.current_user)\n        models.db.session.commit()\n\n\nclass QueryRegenerateApiKeyResource(BaseResource):\n    @require_permission(\"edit_query\")\n    def post(self, query_id):\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_admin_or_owner(query.user_id)\n        query.regenerate_api_key()\n        models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"regnerate_api_key\",\n                \"object_id\": query_id,\n                \"object_type\": \"query\",\n            }\n        )\n\n        result = QuerySerializer(query).serialize()\n        return result\n\n\nclass QueryForkResource(BaseResource):\n    @require_permission(\"edit_query\")\n    def post(self, query_id):\n        \"\"\"\n        Creates a new query, copying the query text from an existing one.\n\n        :param query_id: ID of query to fork\n\n        Responds with created :ref:`query <query-response-label>` object.\n        \"\"\"\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(query.data_source, self.current_user, not_view_only)\n        forked_query = query.fork(self.current_user)\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"fork\", \"object_id\": query_id, \"object_type\": \"query\"})\n\n        return QuerySerializer(forked_query, with_visualizations=True).serialize()\n\n\nclass QueryRefreshResource(BaseResource):\n    def post(self, query_id):\n        \"\"\"\n        Execute a query, updating the query object with the results.\n\n        :param query_id: ID of query to execute\n\n        Responds with query task details.\n        \"\"\"\n        # TODO: this should actually check for permissions, but because currently you can only\n        # get here either with a user API key or a query one, we can just check whether it's\n        # an api key (meaning this is a query API key, which only grants read access).\n        if self.current_user.is_api_user():\n            abort(403, message=\"Please use a user API key.\")\n\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(query, self.current_user, not_view_only)\n\n        parameter_values = collect_parameters_from_request(request.args)\n        parameterized_query = ParameterizedQuery(query.query_text, org=self.current_org)\n        should_apply_auto_limit = query.options.get(\"apply_auto_limit\", False)\n        return run_query(parameterized_query, parameter_values, query.data_source, query.id, should_apply_auto_limit)\n\n\nclass QueryTagsResource(BaseResource):\n    def get(self):\n        \"\"\"\n        Returns all query tags including those for drafts.\n        \"\"\"\n        tags = models.Query.all_tags(self.current_user, include_drafts=True)\n        return {\"tags\": [{\"name\": name, \"count\": count} for name, count in tags]}\n\n\nclass QueryFavoriteListResource(BaseResource):\n    def get(self):\n        search_term = request.args.get(\"q\")\n\n        if search_term:\n            base_query = models.Query.search(\n                search_term,\n                self.current_user.group_ids,\n                include_drafts=True,\n                limit=None,\n                multi_byte_search=current_org.get_setting(\"multi_byte_search_enabled\"),\n            )\n            favorites = models.Query.favorites(self.current_user, base_query=base_query)\n        else:\n            favorites = models.Query.favorites(self.current_user)\n\n        favorites = filter_by_tags(favorites, models.Query.tags)\n\n        # order results according to passed order parameter,\n        # special-casing search queries where the database\n        # provides an order by search rank\n        ordered_favorites = order_results(favorites, fallback=not bool(search_term))\n\n        page = request.args.get(\"page\", 1, type=int)\n        page_size = request.args.get(\"page_size\", 25, type=int)\n        response = paginate(\n            ordered_favorites,\n            page,\n            page_size,\n            QuerySerializer,\n            with_stats=True,\n            with_last_modified_by=False,\n        )\n\n        self.record_event(\n            {\n                \"action\": \"load_favorites\",\n                \"object_type\": \"query\",\n                \"params\": {\n                    \"q\": search_term,\n                    \"tags\": request.args.getlist(\"tags\"),\n                    \"page\": page,\n                },\n            }\n        )\n\n        return response\n"
  },
  {
    "path": "redash/handlers/query_results.py",
    "content": "import unicodedata\nfrom urllib.parse import quote\n\nimport regex\nfrom flask import make_response, request\nfrom flask_login import current_user\nfrom flask_restful import abort\n\nfrom redash import models, settings\nfrom redash.handlers.base import BaseResource, get_object_or_404, record_event\nfrom redash.models.parameterized_query import (\n    InvalidParameterError,\n    ParameterizedQuery,\n    QueryDetachedFromDataSourceError,\n    dropdown_values,\n)\nfrom redash.permissions import (\n    has_access,\n    not_view_only,\n    require_access,\n    require_any_of_permission,\n    require_permission,\n    view_only,\n)\nfrom redash.serializers import (\n    serialize_job,\n    serialize_query_result,\n    serialize_query_result_to_dsv,\n    serialize_query_result_to_xlsx,\n)\nfrom redash.tasks import Job\nfrom redash.tasks.queries import enqueue_query\nfrom redash.utils import (\n    collect_parameters_from_request,\n    json_dumps,\n    to_filename,\n)\n\n\ndef error_response(message, http_status=400):\n    return {\"job\": {\"status\": 4, \"error\": message}}, http_status\n\n\nerror_messages = {\n    \"unsafe_when_shared\": error_response(\n        \"This query contains potentially unsafe parameters and cannot be executed on a shared dashboard or an embedded visualization.\",\n        403,\n    ),\n    \"unsafe_on_view_only\": error_response(\n        \"This query contains potentially unsafe parameters and cannot be executed with read-only access to this data source.\",\n        403,\n    ),\n    \"no_permission\": error_response(\"You do not have permission to run queries with this data source.\", 403),\n    \"select_data_source\": error_response(\"Please select data source to run this query.\", 401),\n    \"no_data_source\": error_response(\"Target data source not available.\", 401),\n}\n\n\ndef run_query(query, parameters, data_source, query_id, should_apply_auto_limit, max_age=0):\n    if not data_source:\n        return error_messages[\"no_data_source\"]\n\n    if data_source.paused:\n        if data_source.pause_reason:\n            message = \"{} is paused ({}). Please try later.\".format(data_source.name, data_source.pause_reason)\n        else:\n            message = \"{} is paused. Please try later.\".format(data_source.name)\n\n        return error_response(message)\n\n    try:\n        query.apply(parameters)\n    except (InvalidParameterError, QueryDetachedFromDataSourceError) as e:\n        abort(400, message=str(e))\n\n    query_text = data_source.query_runner.apply_auto_limit(query.text, should_apply_auto_limit)\n\n    if query.missing_params:\n        return error_response(\"Missing parameter value for: {}\".format(\", \".join(query.missing_params)))\n\n    if max_age == 0:\n        query_result = None\n    else:\n        query_result = models.QueryResult.get_latest(data_source, query_text, max_age)\n\n    record_event(\n        current_user.org,\n        current_user,\n        {\n            \"action\": \"execute_query\",\n            \"cache\": \"hit\" if query_result else \"miss\",\n            \"object_id\": data_source.id,\n            \"object_type\": \"data_source\",\n            \"query\": query_text,\n            \"query_id\": query_id,\n            \"parameters\": parameters,\n        },\n    )\n\n    if query_result:\n        return {\"query_result\": serialize_query_result(query_result, current_user.is_api_user())}\n    else:\n        job = enqueue_query(\n            query_text,\n            data_source,\n            current_user.id,\n            current_user.is_api_user(),\n            metadata={\n                \"Username\": current_user.get_actual_user(),\n                \"query_id\": query_id,\n            },\n        )\n        return serialize_job(job)\n\n\ndef get_download_filename(query_result, query, filetype):\n    retrieved_at = query_result.retrieved_at.strftime(\"%Y_%m_%d\")\n    if query:\n        query_name = regex.sub(r\"\\p{C}\", \"\", query.name)\n        filename = to_filename(query_name) if query_name != \"\" else str(query.id)\n    else:\n        filename = str(query_result.id)\n    return \"{}_{}.{}\".format(filename, retrieved_at, filetype)\n\n\ndef content_disposition_filenames(attachment_filename):\n    if not isinstance(attachment_filename, str):\n        attachment_filename = attachment_filename.decode(\"utf-8\")\n\n    try:\n        attachment_filename = attachment_filename.encode(\"ascii\")\n    except UnicodeEncodeError:\n        filenames = {\n            \"filename\": unicodedata.normalize(\"NFKD\", attachment_filename).encode(\"ascii\", \"ignore\"),\n            \"filename*\": \"UTF-8''%s\" % quote(attachment_filename, safe=b\"\"),\n        }\n    else:\n        filenames = {\"filename\": attachment_filename}\n\n    return filenames\n\n\nclass QueryResultListResource(BaseResource):\n    @require_permission(\"execute_query\")\n    def post(self):\n        \"\"\"\n        Execute a query (or retrieve recent results).\n\n        :qparam string query: The query text to execute\n        :qparam number query_id: The query object to update with the result (optional)\n        :qparam number max_age: If query results less than `max_age` seconds old are available,\n                                return them, otherwise execute the query; if omitted or -1, returns\n                                any cached result, or executes if not available. Set to zero to\n                                always execute.\n        :qparam number data_source_id: ID of data source to query\n        :qparam object parameters: A set of parameter values to apply to the query.\n        \"\"\"\n        params = request.get_json(force=True)\n\n        query = params[\"query\"]\n        max_age = params.get(\"max_age\", -1)\n        # max_age might have the value of None, in which case calling int(None) will fail\n        if max_age is None:\n            max_age = -1\n        max_age = int(max_age)\n        query_id = params.get(\"query_id\", \"adhoc\")\n        parameters = params.get(\"parameters\", collect_parameters_from_request(request.args))\n\n        parameterized_query = ParameterizedQuery(query, org=self.current_org)\n        should_apply_auto_limit = params.get(\"apply_auto_limit\", False)\n\n        data_source_id = params.get(\"data_source_id\")\n        if data_source_id:\n            data_source = models.DataSource.get_by_id_and_org(params.get(\"data_source_id\"), self.current_org)\n        else:\n            return error_messages[\"select_data_source\"]\n\n        if not has_access(data_source, self.current_user, not_view_only):\n            return error_messages[\"no_permission\"]\n\n        return run_query(\n            parameterized_query,\n            parameters,\n            data_source,\n            query_id,\n            should_apply_auto_limit,\n            max_age,\n        )\n\n\nONE_YEAR = 60 * 60 * 24 * 365.25\n\n\nclass QueryResultDropdownResource(BaseResource):\n    def get(self, query_id):\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(query.data_source, current_user, view_only)\n        try:\n            return dropdown_values(query_id, self.current_org)\n        except QueryDetachedFromDataSourceError as e:\n            abort(400, message=str(e))\n\n\nclass QueryDropdownsResource(BaseResource):\n    def get(self, query_id, dropdown_query_id):\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n        require_access(query, current_user, view_only)\n\n        related_queries_ids = [p[\"queryId\"] for p in query.parameters if p[\"type\"] == \"query\"]\n        if int(dropdown_query_id) not in related_queries_ids:\n            dropdown_query = get_object_or_404(models.Query.get_by_id_and_org, dropdown_query_id, self.current_org)\n            require_access(dropdown_query.data_source, current_user, view_only)\n\n        return dropdown_values(dropdown_query_id, self.current_org)\n\n\nclass QueryResultResource(BaseResource):\n    @staticmethod\n    def add_cors_headers(headers):\n        if \"Origin\" in request.headers:\n            origin = request.headers[\"Origin\"]\n\n            if set([\"*\", origin]) & settings.ACCESS_CONTROL_ALLOW_ORIGIN:\n                headers[\"Access-Control-Allow-Origin\"] = origin\n                headers[\"Access-Control-Allow-Credentials\"] = str(settings.ACCESS_CONTROL_ALLOW_CREDENTIALS).lower()\n\n    @require_any_of_permission((\"view_query\", \"execute_query\"))\n    def options(self, query_id=None, query_result_id=None, filetype=\"json\"):\n        headers = {}\n        self.add_cors_headers(headers)\n\n        if settings.ACCESS_CONTROL_REQUEST_METHOD:\n            headers[\"Access-Control-Request-Method\"] = settings.ACCESS_CONTROL_REQUEST_METHOD\n\n        if settings.ACCESS_CONTROL_ALLOW_HEADERS:\n            headers[\"Access-Control-Allow-Headers\"] = settings.ACCESS_CONTROL_ALLOW_HEADERS\n\n        return make_response(\"\", 200, headers)\n\n    @require_any_of_permission((\"view_query\", \"execute_query\"))\n    def post(self, query_id):\n        \"\"\"\n        Execute a saved query.\n\n        :param number query_id: The ID of the query whose results should be fetched.\n        :param object parameters: The parameter values to apply to the query.\n        :qparam number max_age: If query results less than `max_age` seconds old are available,\n                                return them, otherwise execute the query; if omitted or -1, returns\n                                any cached result, or executes if not available. Set to zero to\n                                always execute.\n        \"\"\"\n        params = request.get_json(force=True, silent=True) or {}\n        parameter_values = params.get(\"parameters\", {})\n\n        max_age = params.get(\"max_age\", -1)\n        # max_age might have the value of None, in which case calling int(None) will fail\n        if max_age is None:\n            max_age = -1\n        max_age = int(max_age)\n\n        query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n\n        allow_executing_with_view_only_permissions = query.parameterized.is_safe\n        if \"apply_auto_limit\" in params:\n            should_apply_auto_limit = params.get(\"apply_auto_limit\", False)\n        else:\n            should_apply_auto_limit = query.options.get(\"apply_auto_limit\", False)\n\n        if has_access(query, self.current_user, allow_executing_with_view_only_permissions):\n            return run_query(\n                query.parameterized,\n                parameter_values,\n                query.data_source,\n                query_id,\n                should_apply_auto_limit,\n                max_age,\n            )\n        else:\n            if not query.parameterized.is_safe:\n                if current_user.is_api_user():\n                    return error_messages[\"unsafe_when_shared\"]\n                else:\n                    return error_messages[\"unsafe_on_view_only\"]\n            else:\n                return error_messages[\"no_permission\"]\n\n    @require_any_of_permission((\"view_query\", \"execute_query\"))\n    def get(self, query_id=None, query_result_id=None, filetype=\"json\"):\n        \"\"\"\n        Retrieve query results.\n\n        :param number query_id: The ID of the query whose results should be fetched\n        :param number query_result_id: the ID of the query result to fetch\n        :param string filetype: Format to return. One of 'json', 'xlsx', or 'csv'. Defaults to 'json'.\n\n        :<json number id: Query result ID\n        :<json string query: Query that produced this result\n        :<json string query_hash: Hash code for query text\n        :<json object data: Query output\n        :<json number data_source_id: ID of data source that produced this result\n        :<json number runtime: Length of execution time in seconds\n        :<json string retrieved_at: Query retrieval date/time, in ISO format\n        \"\"\"\n        # TODO:\n        # This method handles two cases: retrieving result by id & retrieving result by query id.\n        # They need to be split, as they have different logic (for example, retrieving by query id\n        # should check for query parameters and shouldn't cache the result).\n        should_cache = query_result_id is not None\n\n        query_result = None\n        query = None\n\n        if query_result_id:\n            query_result = get_object_or_404(models.QueryResult.get_by_id_and_org, query_result_id, self.current_org)\n\n        if query_id is not None:\n            query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)\n\n            if query_result is None and query is not None and query.latest_query_data_id is not None:\n                query_result = get_object_or_404(\n                    models.QueryResult.get_by_id_and_org,\n                    query.latest_query_data_id,\n                    self.current_org,\n                )\n\n            if query is not None and query_result is not None and self.current_user.is_api_user():\n                if query.query_hash != query_result.query_hash:\n                    abort(404, message=\"No cached result found for this query.\")\n\n        if query_result:\n            require_access(query_result.data_source, self.current_user, view_only)\n\n            if isinstance(self.current_user, models.ApiUser):\n                event = {\n                    \"user_id\": None,\n                    \"org_id\": self.current_org.id,\n                    \"action\": \"api_get\",\n                    \"api_key\": self.current_user.name,\n                    \"file_type\": filetype,\n                    \"user_agent\": request.user_agent.string,\n                    \"ip\": request.remote_addr,\n                }\n\n                if query_id:\n                    event[\"object_type\"] = \"query\"\n                    event[\"object_id\"] = query_id\n                else:\n                    event[\"object_type\"] = \"query_result\"\n                    event[\"object_id\"] = query_result_id\n\n                self.record_event(event)\n\n            response_builders = {\n                \"json\": self.make_json_response,\n                \"xlsx\": self.make_excel_response,\n                \"csv\": self.make_csv_response,\n                \"tsv\": self.make_tsv_response,\n            }\n            response = response_builders[filetype](query_result)\n\n            if len(settings.ACCESS_CONTROL_ALLOW_ORIGIN) > 0:\n                self.add_cors_headers(response.headers)\n\n            if should_cache:\n                response.headers.add_header(\"Cache-Control\", \"private,max-age=%d\" % ONE_YEAR)\n\n            filename = get_download_filename(query_result, query, filetype)\n\n            filenames = content_disposition_filenames(filename)\n            response.headers.add(\"Content-Disposition\", \"attachment\", **filenames)\n\n            return response\n\n        else:\n            abort(404, message=\"No cached result found for this query.\")\n\n    @staticmethod\n    def make_json_response(query_result):\n        data = json_dumps({\"query_result\": query_result.to_dict()})\n        headers = {\"Content-Type\": \"application/json\"}\n        return make_response(data, 200, headers)\n\n    @staticmethod\n    def make_csv_response(query_result):\n        headers = {\"Content-Type\": \"text/csv; charset=UTF-8\"}\n        return make_response(serialize_query_result_to_dsv(query_result, \",\"), 200, headers)\n\n    @staticmethod\n    def make_tsv_response(query_result):\n        headers = {\"Content-Type\": \"text/tab-separated-values; charset=UTF-8\"}\n        return make_response(serialize_query_result_to_dsv(query_result, \"\\t\"), 200, headers)\n\n    @staticmethod\n    def make_excel_response(query_result):\n        headers = {\"Content-Type\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\"}\n        return make_response(serialize_query_result_to_xlsx(query_result), 200, headers)\n\n\nclass JobResource(BaseResource):\n    def get(self, job_id, query_id=None):\n        \"\"\"\n        Retrieve info about a running query job.\n        \"\"\"\n        job = Job.fetch(job_id)\n        return serialize_job(job)\n\n    def delete(self, job_id):\n        \"\"\"\n        Cancel a query job in progress.\n        \"\"\"\n        job = Job.fetch(job_id)\n        job.cancel()\n"
  },
  {
    "path": "redash/handlers/query_snippets.py",
    "content": "from flask import request\nfrom funcy import project\n\nfrom redash import models\nfrom redash.handlers.base import (\n    BaseResource,\n    get_object_or_404,\n    require_fields,\n)\nfrom redash.permissions import require_admin_or_owner\n\n\nclass QuerySnippetResource(BaseResource):\n    def get(self, snippet_id):\n        snippet = get_object_or_404(models.QuerySnippet.get_by_id_and_org, snippet_id, self.current_org)\n\n        self.record_event({\"action\": \"view\", \"object_id\": snippet_id, \"object_type\": \"query_snippet\"})\n\n        return snippet.to_dict()\n\n    def post(self, snippet_id):\n        req = request.get_json(True)\n        params = project(req, (\"trigger\", \"description\", \"snippet\"))\n        snippet = get_object_or_404(models.QuerySnippet.get_by_id_and_org, snippet_id, self.current_org)\n        require_admin_or_owner(snippet.user.id)\n\n        self.update_model(snippet, params)\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"edit\", \"object_id\": snippet.id, \"object_type\": \"query_snippet\"})\n        return snippet.to_dict()\n\n    def delete(self, snippet_id):\n        snippet = get_object_or_404(models.QuerySnippet.get_by_id_and_org, snippet_id, self.current_org)\n        require_admin_or_owner(snippet.user.id)\n        models.db.session.delete(snippet)\n        models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"delete\",\n                \"object_id\": snippet.id,\n                \"object_type\": \"query_snippet\",\n            }\n        )\n\n\nclass QuerySnippetListResource(BaseResource):\n    def post(self):\n        req = request.get_json(True)\n        require_fields(req, (\"trigger\", \"description\", \"snippet\"))\n\n        snippet = models.QuerySnippet(\n            trigger=req[\"trigger\"],\n            description=req[\"description\"],\n            snippet=req[\"snippet\"],\n            user=self.current_user,\n            org=self.current_org,\n        )\n\n        models.db.session.add(snippet)\n        models.db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"create\",\n                \"object_id\": snippet.id,\n                \"object_type\": \"query_snippet\",\n            }\n        )\n\n        return snippet.to_dict()\n\n    def get(self):\n        self.record_event({\"action\": \"list\", \"object_type\": \"query_snippet\"})\n        return [snippet.to_dict() for snippet in models.QuerySnippet.all(org=self.current_org)]\n"
  },
  {
    "path": "redash/handlers/settings.py",
    "content": "from flask import request\n\nfrom redash.handlers.base import BaseResource\nfrom redash.models import Organization, db\nfrom redash.permissions import require_admin\nfrom redash.settings.organization import settings as org_settings\n\n\ndef get_settings_with_defaults(defaults, org):\n    values = org.settings.get(\"settings\", {})\n    settings = {}\n\n    for setting, default_value in defaults.items():\n        current_value = values.get(setting)\n        if current_value is None and default_value is None:\n            continue\n\n        if current_value is None:\n            settings[setting] = default_value\n        else:\n            settings[setting] = current_value\n\n    settings[\"auth_google_apps_domains\"] = org.google_apps_domains\n\n    return settings\n\n\nclass OrganizationSettings(BaseResource):\n    @require_admin\n    def get(self):\n        settings = get_settings_with_defaults(org_settings, self.current_org)\n\n        return {\"settings\": settings}\n\n    @require_admin\n    def post(self):\n        new_values = request.json\n\n        if self.current_org.settings.get(\"settings\") is None:\n            self.current_org.settings[\"settings\"] = {}\n\n        previous_values = {}\n        for k, v in new_values.items():\n            if k == \"auth_google_apps_domains\":\n                previous_values[k] = self.current_org.google_apps_domains\n                self.current_org.settings[Organization.SETTING_GOOGLE_APPS_DOMAINS] = v\n            else:\n                previous_values[k] = self.current_org.get_setting(k, raise_on_missing=False)\n                self.current_org.set_setting(k, v)\n\n        db.session.add(self.current_org)\n        db.session.commit()\n\n        self.record_event(\n            {\n                \"action\": \"edit\",\n                \"object_id\": self.current_org.id,\n                \"object_type\": \"settings\",\n                \"new_values\": new_values,\n                \"previous_values\": previous_values,\n            }\n        )\n\n        settings = get_settings_with_defaults(org_settings, self.current_org)\n\n        return {\"settings\": settings}\n"
  },
  {
    "path": "redash/handlers/setup.py",
    "content": "from flask import g, redirect, render_template, request, url_for\nfrom flask_login import login_user\nfrom wtforms import BooleanField, Form, PasswordField, StringField, validators\nfrom wtforms.fields.html5 import EmailField\n\nfrom redash import settings\nfrom redash.authentication.org_resolving import current_org\nfrom redash.handlers.base import routes\nfrom redash.models import Group, Organization, User, db\nfrom redash.tasks.general import subscribe\n\n\nclass SetupForm(Form):\n    name = StringField(\"Name\", validators=[validators.InputRequired()])\n    email = EmailField(\"Email Address\", validators=[validators.Email()])\n    password = PasswordField(\"Password\", validators=[validators.Length(6)])\n    org_name = StringField(\"Organization Name\", validators=[validators.InputRequired()])\n    security_notifications = BooleanField()\n    newsletter = BooleanField()\n\n\ndef create_org(org_name, user_name, email, password):\n    default_org = Organization(name=org_name, slug=\"default\", settings={})\n    admin_group = Group(\n        name=\"admin\",\n        permissions=Group.ADMIN_PERMISSIONS,\n        org=default_org,\n        type=Group.BUILTIN_GROUP,\n    )\n    default_group = Group(\n        name=\"default\",\n        permissions=Group.DEFAULT_PERMISSIONS,\n        org=default_org,\n        type=Group.BUILTIN_GROUP,\n    )\n\n    db.session.add_all([default_org, admin_group, default_group])\n    db.session.commit()\n\n    user = User(\n        org=default_org,\n        name=user_name,\n        email=email,\n        group_ids=[admin_group.id, default_group.id],\n    )\n    user.hash_password(password)\n\n    db.session.add(user)\n    db.session.commit()\n\n    return default_org, user\n\n\n@routes.route(\"/setup\", methods=[\"GET\", \"POST\"])\ndef setup():\n    if current_org != None or settings.MULTI_ORG:  # noqa: E711\n        return redirect(\"/\")\n\n    form = SetupForm(request.form)\n    form.newsletter.data = True\n    form.security_notifications.data = True\n\n    if request.method == \"POST\" and form.validate():\n        default_org, user = create_org(form.org_name.data, form.name.data, form.email.data, form.password.data)\n\n        g.org = default_org\n        login_user(user)\n\n        # signup to newsletter if needed\n        if form.newsletter.data or form.security_notifications:\n            subscribe.delay(form.data)\n\n        return redirect(url_for(\"redash.index\", org_slug=None))\n\n    return render_template(\"setup.html\", form=form)\n"
  },
  {
    "path": "redash/handlers/static.py",
    "content": "from flask import render_template, send_file\nfrom flask_login import login_required\nfrom werkzeug.utils import safe_join\n\nfrom redash import settings\nfrom redash.handlers import routes\nfrom redash.handlers.authentication import base_href\nfrom redash.handlers.base import org_scoped_rule\nfrom redash.security import csp_allows_embeding\n\n\ndef render_index():\n    if settings.MULTI_ORG:\n        response = render_template(\"multi_org.html\", base_href=base_href())\n    else:\n        full_path = safe_join(settings.STATIC_ASSETS_PATH, \"index.html\")\n        response = send_file(full_path, **dict(max_age=0, conditional=True))\n\n    return response\n\n\n@routes.route(org_scoped_rule(\"/dashboard/<slug>\"), methods=[\"GET\"])\n@login_required\n@csp_allows_embeding\ndef dashboard(slug, org_slug=None):\n    return render_index()\n\n\n@routes.route(org_scoped_rule(\"/<path:path>\"))\n@routes.route(org_scoped_rule(\"/\"))\n@login_required\ndef index(**kwargs):\n    return render_index()\n"
  },
  {
    "path": "redash/handlers/users.py",
    "content": "from disposable_email_domains import blacklist\nfrom flask import request\nfrom flask_login import current_user, login_user\nfrom flask_restful import abort\nfrom funcy import partial, project\nfrom sqlalchemy.exc import IntegrityError\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom redash import limiter, models, settings\nfrom redash.authentication.account import (\n    invite_link_for_user,\n    send_invite_email,\n    send_password_reset_email,\n    send_verify_email,\n)\nfrom redash.handlers.base import (\n    BaseResource,\n    get_object_or_404,\n    paginate,\n    require_fields,\n)\nfrom redash.handlers.base import order_results as _order_results\nfrom redash.permissions import (\n    is_admin_or_owner,\n    require_admin,\n    require_admin_or_owner,\n    require_permission,\n    require_permission_or_owner,\n)\nfrom redash.settings import parse_boolean\n\n# Ordering map for relationships\norder_map = {\n    \"name\": \"name\",\n    \"-name\": \"-name\",\n    \"active_at\": \"active_at\",\n    \"-active_at\": \"-active_at\",\n    \"created_at\": \"created_at\",\n    \"-created_at\": \"-created_at\",\n    \"groups\": \"group_ids\",\n    \"-groups\": \"-group_ids\",\n}\n\norder_results = partial(_order_results, default_order=\"-created_at\", allowed_orders=order_map)\n\n\ndef invite_user(org, inviter, user, send_email=True):\n    d = user.to_dict()\n\n    invite_url = invite_link_for_user(user)\n    if settings.email_server_is_configured() and send_email:\n        send_invite_email(inviter, user, invite_url, org)\n    else:\n        d[\"invite_link\"] = invite_url\n\n    return d\n\n\ndef require_allowed_email(email):\n    # `example.com` and `example.com.` are equal - last dot stands for DNS root but usually is omitted\n    _, domain = email.lower().rstrip(\".\").split(\"@\", 1)\n\n    if domain in blacklist or domain in settings.BLOCKED_DOMAINS:\n        abort(400, message=\"Bad email address.\")\n\n\nclass UserListResource(BaseResource):\n    decorators = BaseResource.decorators + [limiter.limit(\"200/day;50/hour\", methods=[\"POST\"])]\n\n    def get_users(self, disabled, pending, search_term):\n        if disabled:\n            users = models.User.all_disabled(self.current_org)\n        else:\n            users = models.User.all(self.current_org)\n\n        if pending is not None:\n            users = models.User.pending(users, pending)\n\n        if search_term:\n            users = models.User.search(users, search_term)\n            self.record_event(\n                {\n                    \"action\": \"search\",\n                    \"object_type\": \"user\",\n                    \"term\": search_term,\n                    \"pending\": pending,\n                }\n            )\n        else:\n            self.record_event({\"action\": \"list\", \"object_type\": \"user\", \"pending\": pending})\n\n        # order results according to passed order parameter,\n        # special-casing search queries where the database\n        # provides an order by search rank\n        return order_results(users, fallback=not bool(search_term))\n\n    @require_permission(\"list_users\")\n    def get(self):\n        page = request.args.get(\"page\", 1, type=int)\n        page_size = request.args.get(\"page_size\", 25, type=int)\n\n        groups = {group.id: group for group in models.Group.all(self.current_org)}\n\n        def serialize_user(user):\n            d = user.to_dict()\n            user_groups = []\n            for group_id in set(d[\"groups\"]):\n                group = groups.get(group_id)\n\n                if group:\n                    user_groups.append({\"id\": group.id, \"name\": group.name})\n\n            d[\"groups\"] = user_groups\n\n            return d\n\n        search_term = request.args.get(\"q\", \"\")\n\n        disabled = request.args.get(\"disabled\", \"false\")  # get enabled users by default\n        disabled = parse_boolean(disabled)\n\n        pending = request.args.get(\"pending\", None)  # get both active and pending by default\n        if pending is not None:\n            pending = parse_boolean(pending)\n\n        users = self.get_users(disabled, pending, search_term)\n\n        return paginate(users, page, page_size, serialize_user)\n\n    @require_admin\n    def post(self):\n        req = request.get_json(force=True)\n        require_fields(req, (\"name\", \"email\"))\n\n        if \"@\" not in req[\"email\"]:\n            abort(400, message=\"Bad email address.\")\n        require_allowed_email(req[\"email\"])\n\n        user = models.User(\n            org=self.current_org,\n            name=req[\"name\"],\n            email=req[\"email\"],\n            is_invitation_pending=True,\n            group_ids=[self.current_org.default_group.id],\n        )\n\n        try:\n            models.db.session.add(user)\n            models.db.session.commit()\n        except IntegrityError as e:\n            if \"email\" in str(e):\n                abort(400, message=\"Email already taken.\")\n            abort(500)\n\n        self.record_event({\"action\": \"create\", \"object_id\": user.id, \"object_type\": \"user\"})\n\n        should_send_invitation = \"no_invite\" not in request.args\n        return invite_user(self.current_org, self.current_user, user, send_email=should_send_invitation)\n\n\nclass UserInviteResource(BaseResource):\n    @require_admin\n    def post(self, user_id):\n        user = models.User.get_by_id_and_org(user_id, self.current_org)\n        return invite_user(self.current_org, self.current_user, user)\n\n\nclass UserResetPasswordResource(BaseResource):\n    @require_admin\n    def post(self, user_id):\n        user = models.User.get_by_id_and_org(user_id, self.current_org)\n        if user.is_disabled:\n            abort(404, message=\"Not found\")\n        reset_link = send_password_reset_email(user)\n\n        return {\"reset_link\": reset_link}\n\n\nclass UserRegenerateApiKeyResource(BaseResource):\n    def post(self, user_id):\n        user = models.User.get_by_id_and_org(user_id, self.current_org)\n        if user.is_disabled:\n            abort(404, message=\"Not found\")\n        if not is_admin_or_owner(user_id):\n            abort(403)\n\n        user.regenerate_api_key()\n        models.db.session.commit()\n\n        self.record_event({\"action\": \"regnerate_api_key\", \"object_id\": user.id, \"object_type\": \"user\"})\n\n        return user.to_dict(with_api_key=True)\n\n\nclass UserResource(BaseResource):\n    decorators = BaseResource.decorators + [limiter.limit(\"50/hour\", methods=[\"POST\"])]\n\n    def get(self, user_id):\n        require_permission_or_owner(\"list_users\", user_id)\n        user = get_object_or_404(models.User.get_by_id_and_org, user_id, self.current_org)\n\n        self.record_event({\"action\": \"view\", \"object_id\": user_id, \"object_type\": \"user\"})\n\n        return user.to_dict(with_api_key=is_admin_or_owner(user_id))\n\n    def post(self, user_id):  # noqa: C901\n        require_admin_or_owner(user_id)\n        user = models.User.get_by_id_and_org(user_id, self.current_org)\n\n        req = request.get_json(True)\n\n        params = project(req, (\"email\", \"name\", \"password\", \"old_password\", \"group_ids\"))\n\n        if \"password\" in params and \"old_password\" not in params:\n            abort(403, message=\"Must provide current password to update password.\")\n\n        if \"old_password\" in params and not user.verify_password(params[\"old_password\"]):\n            abort(403, message=\"Incorrect current password.\")\n\n        if \"password\" in params:\n            user.hash_password(params.pop(\"password\"))\n            params.pop(\"old_password\")\n\n        if \"group_ids\" in params:\n            if not self.current_user.has_permission(\"admin\"):\n                abort(403, message=\"Must be admin to change groups membership.\")\n\n            for group_id in params[\"group_ids\"]:\n                try:\n                    models.Group.get_by_id_and_org(group_id, self.current_org)\n                except NoResultFound:\n                    abort(400, message=\"Group id {} is invalid.\".format(group_id))\n\n            if len(params[\"group_ids\"]) == 0:\n                params.pop(\"group_ids\")\n\n        if \"email\" in params:\n            require_allowed_email(params[\"email\"])\n\n        email_address_changed = \"email\" in params and params[\"email\"] != user.email\n        needs_to_verify_email = email_address_changed and settings.email_server_is_configured()\n        if needs_to_verify_email:\n            user.is_email_verified = False\n\n        try:\n            self.update_model(user, params)\n            models.db.session.commit()\n\n            if needs_to_verify_email:\n                send_verify_email(user, self.current_org)\n\n            # The user has updated their email or password. This should invalidate all _other_ sessions,\n            # forcing them to log in again. Since we don't want to force _this_ session to have to go\n            # through login again, we call `login_user` in order to update the session with the new identity details.\n            if current_user.id == user.id:\n                login_user(user, remember=True)\n        except IntegrityError as e:\n            if \"email\" in str(e):\n                message = \"Email already taken.\"\n            else:\n                message = \"Error updating record\"\n\n            abort(400, message=message)\n\n        self.record_event(\n            {\n                \"action\": \"edit\",\n                \"object_id\": user.id,\n                \"object_type\": \"user\",\n                \"updated_fields\": list(params.keys()),\n            }\n        )\n\n        return user.to_dict(with_api_key=is_admin_or_owner(user_id))\n\n    @require_admin\n    def delete(self, user_id):\n        user = models.User.get_by_id_and_org(user_id, self.current_org)\n        # admin cannot delete self; current user is an admin (`@require_admin`)\n        # so just check user id\n        if user.id == current_user.id:\n            abort(\n                403,\n                message=\"You cannot delete your own account. \"\n                \"Please ask another admin to do this for you.\",  # fmt: skip\n            )\n        elif not user.is_invitation_pending:\n            abort(\n                403,\n                message=\"You cannot delete activated users. \"\n                \"Please disable the user instead.\",  # fmt: skip\n            )\n        models.db.session.delete(user)\n        models.db.session.commit()\n\n        return user.to_dict(with_api_key=is_admin_or_owner(user_id))\n\n\nclass UserDisableResource(BaseResource):\n    @require_admin\n    def post(self, user_id):\n        user = models.User.get_by_id_and_org(user_id, self.current_org)\n        # admin cannot disable self; current user is an admin (`@require_admin`)\n        # so just check user id\n        if user.id == current_user.id:\n            abort(\n                403,\n                message=\"You cannot disable your own account. \"\n                \"Please ask another admin to do this for you.\",  # fmt: skip\n            )\n        user.disable()\n        models.db.session.commit()\n\n        return user.to_dict(with_api_key=is_admin_or_owner(user_id))\n\n    @require_admin\n    def delete(self, user_id):\n        user = models.User.get_by_id_and_org(user_id, self.current_org)\n        user.enable()\n        models.db.session.commit()\n\n        return user.to_dict(with_api_key=is_admin_or_owner(user_id))\n"
  },
  {
    "path": "redash/handlers/visualizations.py",
    "content": "from flask import request\n\nfrom redash import models\nfrom redash.handlers.base import BaseResource, get_object_or_404\nfrom redash.permissions import (\n    require_object_modify_permission,\n    require_permission,\n)\nfrom redash.serializers import serialize_visualization\n\n\nclass VisualizationListResource(BaseResource):\n    @require_permission(\"edit_query\")\n    def post(self):\n        kwargs = request.get_json(force=True)\n\n        query = get_object_or_404(models.Query.get_by_id_and_org, kwargs.pop(\"query_id\"), self.current_org)\n        require_object_modify_permission(query, self.current_user)\n\n        kwargs[\"query_rel\"] = query\n\n        vis = models.Visualization(**kwargs)\n        models.db.session.add(vis)\n        models.db.session.commit()\n        return serialize_visualization(vis, with_query=False)\n\n\nclass VisualizationResource(BaseResource):\n    @require_permission(\"edit_query\")\n    def post(self, visualization_id):\n        vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)\n        require_object_modify_permission(vis.query_rel, self.current_user)\n\n        kwargs = request.get_json(force=True)\n\n        kwargs.pop(\"id\", None)\n        kwargs.pop(\"query_id\", None)\n\n        self.update_model(vis, kwargs)\n        d = serialize_visualization(vis, with_query=False)\n        models.db.session.commit()\n        return d\n\n    @require_permission(\"edit_query\")\n    def delete(self, visualization_id):\n        vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)\n        require_object_modify_permission(vis.query_rel, self.current_user)\n        self.record_event(\n            {\n                \"action\": \"delete\",\n                \"object_id\": visualization_id,\n                \"object_type\": \"Visualization\",\n            }\n        )\n        models.db.session.delete(vis)\n        models.db.session.commit()\n"
  },
  {
    "path": "redash/handlers/webpack.py",
    "content": "import json\nimport os\n\nfrom flask import url_for\n\nWEBPACK_MANIFEST_PATH = os.path.join(os.path.dirname(__file__), \"../../client/dist/\", \"asset-manifest.json\")\n\n\ndef configure_webpack(app):\n    app.extensions[\"webpack\"] = {\"assets\": None}\n\n    def get_asset(path):\n        assets = app.extensions[\"webpack\"][\"assets\"]\n        # in debug we read in this file each request\n        if assets is None or app.debug:\n            try:\n                with open(WEBPACK_MANIFEST_PATH) as fp:\n                    assets = json.load(fp)\n            except IOError:\n                app.logger.exception(\"Unable to load webpack manifest\")\n                assets = {}\n            app.extensions[\"webpack\"][\"assets\"] = assets\n        return url_for(\"static\", filename=assets.get(path, path))\n\n    @app.context_processor\n    def webpack_assets():\n        return {\"asset_url\": get_asset}\n"
  },
  {
    "path": "redash/handlers/widgets.py",
    "content": "from flask import request\n\nfrom redash import models\nfrom redash.handlers.base import BaseResource\nfrom redash.permissions import (\n    require_access,\n    require_object_modify_permission,\n    require_permission,\n    view_only,\n)\nfrom redash.serializers import serialize_widget\n\n\nclass WidgetListResource(BaseResource):\n    @require_permission(\"edit_dashboard\")\n    def post(self):\n        \"\"\"\n        Add a widget to a dashboard.\n\n        :<json number dashboard_id: The ID for the dashboard being added to\n        :<json visualization_id: The ID of the visualization to put in this widget\n        :<json object options: Widget options\n        :<json string text: Text box contents\n        :<json number width: Width for widget display\n\n        :>json object widget: The created widget\n        \"\"\"\n        widget_properties = request.get_json(force=True)\n        dashboard = models.Dashboard.get_by_id_and_org(widget_properties.get(\"dashboard_id\"), self.current_org)\n        require_object_modify_permission(dashboard, self.current_user)\n\n        widget_properties.pop(\"id\", None)\n\n        visualization_id = widget_properties.pop(\"visualization_id\")\n        if visualization_id:\n            visualization = models.Visualization.get_by_id_and_org(visualization_id, self.current_org)\n            require_access(visualization.query_rel, self.current_user, view_only)\n        else:\n            visualization = None\n\n        widget_properties[\"visualization\"] = visualization\n\n        widget = models.Widget(**widget_properties)\n        models.db.session.add(widget)\n\n        models.db.session.commit()\n        return serialize_widget(widget)\n\n\nclass WidgetResource(BaseResource):\n    @require_permission(\"edit_dashboard\")\n    def post(self, widget_id):\n        \"\"\"\n        Updates a widget in a dashboard.\n        This method currently handles Text Box widgets only.\n\n        :param number widget_id: The ID of the widget to modify\n\n        :<json string text: The new contents of the text box\n        \"\"\"\n        widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n        require_object_modify_permission(widget.dashboard, self.current_user)\n        widget_properties = request.get_json(force=True)\n        widget.text = widget_properties[\"text\"]\n        widget.options = widget_properties[\"options\"]\n        models.db.session.commit()\n        return serialize_widget(widget)\n\n    @require_permission(\"edit_dashboard\")\n    def delete(self, widget_id):\n        \"\"\"\n        Remove a widget from a dashboard.\n\n        :param number widget_id: ID of widget to remove\n        \"\"\"\n        widget = models.Widget.get_by_id_and_org(widget_id, self.current_org)\n        require_object_modify_permission(widget.dashboard, self.current_user)\n        self.record_event({\"action\": \"delete\", \"object_id\": widget_id, \"object_type\": \"widget\"})\n        models.db.session.delete(widget)\n        models.db.session.commit()\n"
  },
  {
    "path": "redash/metrics/__init__.py",
    "content": ""
  },
  {
    "path": "redash/metrics/database.py",
    "content": "import logging\nimport time\n\nfrom flask import g, has_request_context\nfrom sqlalchemy.engine import Engine\nfrom sqlalchemy.event import listens_for\nfrom sqlalchemy.orm.util import _ORMJoin\nfrom sqlalchemy.sql.selectable import Alias, Join\n\nfrom redash import statsd_client\n\nmetrics_logger = logging.getLogger(\"metrics\")\n\n\ndef _table_name_from_select_element(elt):\n    t = elt.froms[0]\n\n    if isinstance(t, Alias):\n        t = t.original.froms[0]\n\n    while isinstance(t, _ORMJoin) or isinstance(t, Join):\n        t = t.left\n\n    return t.name\n\n\n@listens_for(Engine, \"before_execute\")\ndef before_execute(conn, elt, multiparams, params):\n    conn.info.setdefault(\"query_start_time\", []).append(time.time())\n\n\n@listens_for(Engine, \"after_execute\")\ndef after_execute(conn, elt, multiparams, params, result):\n    duration = 1000 * (time.time() - conn.info[\"query_start_time\"].pop(-1))\n    action = elt.__class__.__name__\n\n    if action == \"Select\":\n        name = \"unknown\"\n        try:\n            name = _table_name_from_select_element(elt)\n        except Exception:\n            logging.exception(\"Failed finding table name.\")\n    elif action in [\"Update\", \"Insert\", \"Delete\"]:\n        name = elt.table.name\n    else:\n        # create/drop tables, sqlalchemy internal schema queries, etc\n        return\n\n    action = action.lower()\n\n    statsd_client.timing(\"db.{}.{}\".format(name, action), duration)\n    metrics_logger.debug(\"table=%s query=%s duration=%.2f\", name, action, duration)\n\n    if has_request_context():\n        g.setdefault(\"queries_count\", 0)\n        g.setdefault(\"queries_duration\", 0)\n        g.queries_count += 1\n        g.queries_duration += duration\n\n    return result\n"
  },
  {
    "path": "redash/metrics/request.py",
    "content": "import logging\nimport time\nfrom collections import namedtuple\n\nfrom flask import g, request\n\nfrom redash import statsd_client\n\nmetrics_logger = logging.getLogger(\"metrics\")\n\n\ndef record_request_start_time():\n    g.start_time = time.time()\n\n\ndef calculate_metrics(response):\n    if \"start_time\" not in g:\n        return response\n\n    request_duration = (time.time() - g.start_time) * 1000\n    queries_duration = g.get(\"queries_duration\", 0.0)\n    queries_count = g.get(\"queries_count\", 0.0)\n    endpoint = (request.endpoint or \"unknown\").replace(\".\", \"_\")\n\n    metrics_logger.info(\n        \"method=%s path=%s endpoint=%s status=%d content_type=%s content_length=%d duration=%.2f query_count=%d query_duration=%.2f\",\n        request.method,\n        request.path,\n        endpoint,\n        response.status_code,\n        response.content_type,\n        response.content_length or -1,\n        request_duration,\n        queries_count,\n        queries_duration,\n    )\n\n    statsd_client.timing(\"requests.{}.{}\".format(endpoint, request.method.lower()), request_duration)\n\n    return response\n\n\nMockResponse = namedtuple(\"MockResponse\", [\"status_code\", \"content_type\", \"content_length\"])\n\n\ndef calculate_metrics_on_exception(error):\n    if error is not None:\n        calculate_metrics(MockResponse(500, \"?\", -1))\n\n\ndef init_app(app):\n    app.before_request(record_request_start_time)\n    app.after_request(calculate_metrics)\n    app.teardown_request(calculate_metrics_on_exception)\n"
  },
  {
    "path": "redash/models/__init__.py",
    "content": "import calendar\nimport datetime\nimport logging\nimport numbers\nimport re\nimport time\n\nimport pytz\nfrom sqlalchemy import UniqueConstraint, and_, cast, distinct, func, or_\nfrom sqlalchemy.dialects.postgresql import ARRAY, DOUBLE_PRECISION, JSONB\nfrom sqlalchemy.event import listens_for\nfrom sqlalchemy.ext.hybrid import hybrid_property\nfrom sqlalchemy.orm import (\n    backref,\n    contains_eager,\n    joinedload,\n    load_only,\n    subqueryload,\n)\nfrom sqlalchemy.orm.exc import NoResultFound  # noqa: F401\nfrom sqlalchemy_utils import generic_relationship\nfrom sqlalchemy_utils.models import generic_repr\nfrom sqlalchemy_utils.types import TSVectorType\nfrom sqlalchemy_utils.types.encrypted.encrypted_type import FernetEngine\n\nfrom redash import redis_connection, settings, utils\nfrom redash.destinations import (\n    get_configuration_schema_for_destination_type,\n    get_destination,\n)\nfrom redash.metrics import database  # noqa: F401\nfrom redash.models.base import (\n    Column,\n    GFKBase,\n    SearchBaseQuery,\n    db,\n    gfk_type,\n    key_type,\n    primary_key,\n)\nfrom redash.models.changes import Change, ChangeTrackingMixin  # noqa\nfrom redash.models.mixins import BelongsToOrgMixin, TimestampMixin\nfrom redash.models.organizations import Organization\nfrom redash.models.parameterized_query import (\n    InvalidParameterError,\n    ParameterizedQuery,\n    QueryDetachedFromDataSourceError,\n)\nfrom redash.models.types import (\n    Configuration,\n    EncryptedConfiguration,\n    JSONText,\n    MutableDict,\n    MutableList,\n    json_cast_property,\n)\nfrom redash.models.users import (  # noqa\n    AccessPermission,\n    AnonymousUser,\n    ApiUser,\n    Group,\n    User,\n)\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    BaseQueryRunner,\n    get_configuration_schema_for_query_runner_type,\n    get_query_runner,\n    with_ssh_tunnel,\n)\nfrom redash.utils import (\n    base_url,\n    gen_query_hash,\n    generate_token,\n    json_dumps,\n    json_loads,\n    mustache_render,\n    mustache_render_escape,\n    sentry,\n)\nfrom redash.utils.configuration import ConfigurationContainer\n\nlogger = logging.getLogger(__name__)\n\n\nclass ScheduledQueriesExecutions:\n    KEY_NAME = \"sq:executed_at\"\n\n    def __init__(self):\n        self.executions = {}\n\n    def refresh(self):\n        self.executions = redis_connection.hgetall(self.KEY_NAME)\n\n    def update(self, query_id):\n        redis_connection.hset(self.KEY_NAME, mapping={query_id: time.time()})\n\n    def get(self, query_id):\n        timestamp = self.executions.get(str(query_id))\n        if timestamp:\n            timestamp = utils.dt_from_timestamp(timestamp)\n\n        return timestamp\n\n\nscheduled_queries_executions = ScheduledQueriesExecutions()\n\n\n@generic_repr(\"id\", \"name\", \"type\", \"org_id\", \"created_at\")\nclass DataSource(BelongsToOrgMixin, db.Model):\n    id = primary_key(\"DataSource\")\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n    org = db.relationship(Organization, backref=\"data_sources\")\n\n    name = Column(db.String(255))\n    type = Column(db.String(255))\n    options = Column(\n        \"encrypted_options\",\n        ConfigurationContainer.as_mutable(\n            EncryptedConfiguration(db.Text, settings.DATASOURCE_SECRET_KEY, FernetEngine)\n        ),\n    )\n    queue_name = Column(db.String(255), default=\"queries\")\n    scheduled_queue_name = Column(db.String(255), default=\"scheduled_queries\")\n    created_at = Column(db.DateTime(True), default=db.func.now())\n\n    data_source_groups = db.relationship(\"DataSourceGroup\", back_populates=\"data_source\", cascade=\"all\")\n    __tablename__ = \"data_sources\"\n    __table_args__ = (\n        db.Index(\"data_sources_org_id_name\", \"org_id\", \"name\"),\n        {\"extend_existing\": True},\n    )\n\n    def __eq__(self, other):\n        return self.id == other.id\n\n    def __hash__(self):\n        return hash(self.id)\n\n    def to_dict(self, all=False, with_permissions_for=None):\n        d = {\n            \"id\": self.id,\n            \"name\": self.name,\n            \"type\": self.type,\n            \"syntax\": self.query_runner.syntax,\n            \"paused\": self.paused,\n            \"pause_reason\": self.pause_reason,\n            \"supports_auto_limit\": self.query_runner.supports_auto_limit,\n        }\n\n        if all:\n            schema = get_configuration_schema_for_query_runner_type(self.type)\n            self.options.set_schema(schema)\n            d[\"options\"] = self.options.to_dict(mask_secrets=True)\n            d[\"queue_name\"] = self.queue_name\n            d[\"scheduled_queue_name\"] = self.scheduled_queue_name\n            d[\"groups\"] = self.groups\n\n        if with_permissions_for is not None:\n            d[\"view_only\"] = (\n                db.session.query(DataSourceGroup.view_only)\n                .filter(\n                    DataSourceGroup.group == with_permissions_for,\n                    DataSourceGroup.data_source == self,\n                )\n                .one()[0]\n            )\n\n        return d\n\n    def __str__(self):\n        return str(self.name)\n\n    @classmethod\n    def create_with_group(cls, *args, **kwargs):\n        data_source = cls(*args, **kwargs)\n        data_source_group = DataSourceGroup(data_source=data_source, group=data_source.org.default_group)\n        db.session.add_all([data_source, data_source_group])\n        return data_source\n\n    @classmethod\n    def all(cls, org, group_ids=None):\n        data_sources = cls.query.filter(cls.org == org).order_by(cls.id.asc())\n\n        if group_ids:\n            data_sources = data_sources.join(DataSourceGroup).filter(DataSourceGroup.group_id.in_(group_ids))\n\n        return data_sources.distinct()\n\n    @classmethod\n    def get_by_id(cls, _id):\n        return cls.query.filter(cls.id == _id).one()\n\n    def delete(self):\n        Query.query.filter(Query.data_source == self).update(dict(data_source_id=None, latest_query_data_id=None))\n        QueryResult.query.filter(QueryResult.data_source == self).delete()\n        res = db.session.delete(self)\n        db.session.commit()\n\n        redis_connection.delete(self._schema_key)\n\n        return res\n\n    def get_cached_schema(self):\n        cache = redis_connection.get(self._schema_key)\n        return json_loads(cache) if cache else None\n\n    def get_schema(self, refresh=False):\n        out_schema = None\n        if not refresh:\n            out_schema = self.get_cached_schema()\n\n        if out_schema is None:\n            query_runner = self.query_runner\n            schema = query_runner.get_schema(get_stats=refresh)\n\n            try:\n                out_schema = self._sort_schema(schema)\n            except Exception:\n                logging.exception(\"Error sorting schema columns for data_source {}\".format(self.id))\n                out_schema = schema\n            finally:\n                ttl = int(datetime.timedelta(minutes=settings.SCHEMAS_REFRESH_SCHEDULE, days=7).total_seconds())\n                redis_connection.set(self._schema_key, json_dumps(out_schema), ex=ttl)\n\n        return out_schema\n\n    def _sort_schema(self, schema):\n        return [\n            {**i, \"columns\": sorted(i[\"columns\"], key=lambda x: x[\"name\"] if isinstance(x, dict) else x)}\n            for i in sorted(schema, key=lambda x: x[\"name\"])\n        ]\n\n    @property\n    def _schema_key(self):\n        return \"data_source:schema:{}\".format(self.id)\n\n    @property\n    def _pause_key(self):\n        return \"ds:{}:pause\".format(self.id)\n\n    @property\n    def paused(self):\n        return redis_connection.exists(self._pause_key)\n\n    @property\n    def pause_reason(self):\n        return redis_connection.get(self._pause_key)\n\n    def pause(self, reason=None):\n        redis_connection.set(self._pause_key, reason or \"\")\n\n    def resume(self):\n        redis_connection.delete(self._pause_key)\n\n    def add_group(self, group, view_only=False):\n        dsg = DataSourceGroup(group=group, data_source=self, view_only=view_only)\n        db.session.add(dsg)\n        return dsg\n\n    def remove_group(self, group):\n        DataSourceGroup.query.filter(DataSourceGroup.group == group, DataSourceGroup.data_source == self).delete()\n        db.session.commit()\n\n    def update_group_permission(self, group, view_only):\n        dsg = DataSourceGroup.query.filter(DataSourceGroup.group == group, DataSourceGroup.data_source == self).one()\n        dsg.view_only = view_only\n        db.session.add(dsg)\n        return dsg\n\n    @property\n    def uses_ssh_tunnel(self):\n        return self.options and \"ssh_tunnel\" in self.options\n\n    @property\n    def query_runner(self):\n        query_runner = get_query_runner(self.type, self.options)\n\n        if self.uses_ssh_tunnel:\n            query_runner = with_ssh_tunnel(query_runner, self.options.get(\"ssh_tunnel\"))\n\n        return query_runner\n\n    @classmethod\n    def get_by_name(cls, name):\n        return cls.query.filter(cls.name == name).one()\n\n    # XXX examine call sites to see if a regular SQLA collection would work better\n    @property\n    def groups(self):\n        groups = DataSourceGroup.query.filter(DataSourceGroup.data_source == self)\n        return dict([(group.group_id, group.view_only) for group in groups])\n\n\n@generic_repr(\"id\", \"data_source_id\", \"group_id\", \"view_only\")\nclass DataSourceGroup(db.Model):\n    # XXX drop id, use datasource/group as PK\n    id = primary_key(\"DataSourceGroup\")\n    data_source_id = Column(key_type(\"DataSource\"), db.ForeignKey(\"data_sources.id\"))\n    data_source = db.relationship(DataSource, back_populates=\"data_source_groups\")\n    group_id = Column(key_type(\"Group\"), db.ForeignKey(\"groups.id\"))\n    group = db.relationship(Group, back_populates=\"data_sources\")\n    view_only = Column(db.Boolean, default=False)\n\n    __tablename__ = \"data_source_groups\"\n    __table_args__ = ({\"extend_existing\": True},)\n\n\n@generic_repr(\"id\", \"org_id\", \"data_source_id\", \"query_hash\", \"runtime\", \"retrieved_at\")\nclass QueryResult(db.Model, BelongsToOrgMixin):\n    id = primary_key(\"QueryResult\")\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n    org = db.relationship(Organization)\n    data_source_id = Column(key_type(\"DataSource\"), db.ForeignKey(\"data_sources.id\"))\n    data_source = db.relationship(DataSource, backref=backref(\"query_results\"))\n    query_hash = Column(db.String(32), index=True)\n    query_text = Column(\"query\", db.Text)\n    data = Column(JSONText, nullable=True)\n    runtime = Column(DOUBLE_PRECISION)\n    retrieved_at = Column(db.DateTime(True))\n\n    __tablename__ = \"query_results\"\n\n    def __str__(self):\n        return \"%d | %s | %s\" % (self.id, self.query_hash, self.retrieved_at)\n\n    def to_dict(self):\n        return {\n            \"id\": self.id,\n            \"query_hash\": self.query_hash,\n            \"query\": self.query_text,\n            \"data\": self.data,\n            \"data_source_id\": self.data_source_id,\n            \"runtime\": self.runtime,\n            \"retrieved_at\": self.retrieved_at,\n        }\n\n    @classmethod\n    def unused(cls, days=7):\n        age_threshold = datetime.datetime.now() - datetime.timedelta(days=days)\n        return (cls.query.filter(Query.id.is_(None), cls.retrieved_at < age_threshold).outerjoin(Query)).options(\n            load_only(\"id\")\n        )\n\n    @classmethod\n    def get_latest(cls, data_source, query, max_age=0):\n        query_hash = gen_query_hash(query)\n\n        if max_age == -1 and settings.QUERY_RESULTS_EXPIRED_TTL_ENABLED:\n            max_age = settings.QUERY_RESULTS_EXPIRED_TTL\n\n        if max_age == -1:\n            query = cls.query.filter(cls.query_hash == query_hash, cls.data_source == data_source)\n        else:\n            query = cls.query.filter(\n                cls.query_hash == query_hash,\n                cls.data_source == data_source,\n                (\n                    db.func.timezone(\"utc\", cls.retrieved_at) + datetime.timedelta(seconds=max_age)\n                    >= db.func.timezone(\"utc\", db.func.now())\n                ),\n            )\n\n        return query.order_by(cls.retrieved_at.desc()).first()\n\n    @classmethod\n    def store_result(cls, org, data_source, query_hash, query, data, run_time, retrieved_at):\n        query_result = cls(\n            org_id=org,\n            query_hash=query_hash,\n            query_text=query,\n            runtime=run_time,\n            data_source=data_source,\n            retrieved_at=retrieved_at,\n            data=data,\n        )\n\n        db.session.add(query_result)\n        logging.info(\"Inserted query (%s) data; id=%s\", query_hash, query_result.id)\n\n        return query_result\n\n    @property\n    def groups(self):\n        return self.data_source.groups\n\n\ndef should_schedule_next(previous_iteration, now, interval, time=None, day_of_week=None, failures=0):\n    # if previous_iteration is None, it means the query has never been run before\n    # so we should schedule it immediately\n    if previous_iteration is None:\n        return True\n    # if time exists then interval > 23 hours (82800s)\n    # if day_of_week exists then interval > 6 days (518400s)\n    if time is None:\n        ttl = int(interval)\n        next_iteration = previous_iteration + datetime.timedelta(seconds=ttl)\n    else:\n        hour, minute = time.split(\":\")\n        hour, minute = int(hour), int(minute)\n\n        # The following logic is needed for cases like the following:\n        # - The query scheduled to run at 23:59.\n        # - The scheduler wakes up at 00:01.\n        # - Using naive implementation of comparing timestamps, it will skip the execution.\n        normalized_previous_iteration = previous_iteration.replace(hour=hour, minute=minute)\n\n        if normalized_previous_iteration > previous_iteration:\n            previous_iteration = normalized_previous_iteration - datetime.timedelta(days=1)\n\n        days_delay = int(interval) / 60 / 60 / 24\n\n        days_to_add = 0\n        if day_of_week is not None:\n            days_to_add = list(calendar.day_name).index(day_of_week) - normalized_previous_iteration.weekday()\n\n        next_iteration = (\n            previous_iteration + datetime.timedelta(days=days_delay) + datetime.timedelta(days=days_to_add)\n        ).replace(hour=hour, minute=minute)\n    if failures:\n        try:\n            next_iteration += datetime.timedelta(minutes=2**failures)\n        except OverflowError:\n            return False\n    return now > next_iteration\n\n\n@gfk_type\n@generic_repr(\n    \"id\",\n    \"name\",\n    \"query_hash\",\n    \"version\",\n    \"user_id\",\n    \"org_id\",\n    \"data_source_id\",\n    \"query_hash\",\n    \"last_modified_by_id\",\n    \"is_archived\",\n    \"is_draft\",\n    \"schedule\",\n    \"schedule_failures\",\n)\nclass Query(ChangeTrackingMixin, TimestampMixin, BelongsToOrgMixin, db.Model):\n    id = primary_key(\"Query\")\n    version = Column(db.Integer, default=1)\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n    org = db.relationship(Organization, backref=\"queries\")\n    data_source_id = Column(key_type(\"DataSource\"), db.ForeignKey(\"data_sources.id\"), nullable=True)\n    data_source = db.relationship(DataSource, backref=\"queries\")\n    latest_query_data_id = Column(key_type(\"QueryResult\"), db.ForeignKey(\"query_results.id\"), nullable=True)\n    latest_query_data = db.relationship(QueryResult)\n    name = Column(db.String(255))\n    description = Column(db.String(4096), nullable=True)\n    query_text = Column(\"query\", db.Text)\n    query_hash = Column(db.String(32))\n    api_key = Column(db.String(40), default=lambda: generate_token(40))\n    user_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"))\n    user = db.relationship(User, foreign_keys=[user_id])\n    last_modified_by_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"), nullable=True)\n    last_modified_by = db.relationship(User, backref=\"modified_queries\", foreign_keys=[last_modified_by_id])\n    is_archived = Column(db.Boolean, default=False, index=True)\n    is_draft = Column(db.Boolean, default=True, index=True)\n    schedule = Column(MutableDict.as_mutable(JSONB), nullable=True)\n    interval = json_cast_property(db.Integer, \"schedule\", \"interval\", default=0)\n    schedule_failures = Column(db.Integer, default=0)\n    visualizations = db.relationship(\"Visualization\", cascade=\"all, delete-orphan\")\n    options = Column(MutableDict.as_mutable(JSONB), default={})\n    search_vector = Column(\n        TSVectorType(\n            \"id\",\n            \"name\",\n            \"description\",\n            \"query\",\n            weights={\"name\": \"A\", \"id\": \"B\", \"description\": \"C\", \"query\": \"D\"},\n        ),\n        nullable=True,\n    )\n    tags = Column(\"tags\", MutableList.as_mutable(ARRAY(db.Unicode)), nullable=True)\n\n    query_class = SearchBaseQuery\n    __tablename__ = \"queries\"\n    __mapper_args__ = {\"version_id_col\": version, \"version_id_generator\": False}\n\n    def __str__(self):\n        return str(self.id)\n\n    def archive(self, user=None):\n        db.session.add(self)\n        self.is_archived = True\n        self.schedule = None\n\n        for vis in self.visualizations:\n            for w in vis.widgets:\n                db.session.delete(w)\n\n        for a in self.alerts:\n            db.session.delete(a)\n\n        if user:\n            self.record_changes(user)\n\n    def regenerate_api_key(self):\n        self.api_key = generate_token(40)\n\n    @classmethod\n    def create(cls, **kwargs):\n        query = cls(**kwargs)\n        db.session.add(\n            Visualization(\n                query_rel=query,\n                name=\"Table\",\n                description=\"\",\n                type=\"TABLE\",\n                options={},\n            )\n        )\n        return query\n\n    @classmethod\n    def all_queries(cls, group_ids, user_id=None, include_drafts=False, include_archived=False):\n        query_ids = (\n            db.session.query(distinct(cls.id))\n            .join(DataSourceGroup, Query.data_source_id == DataSourceGroup.data_source_id)\n            .filter(Query.is_archived.is_(include_archived))\n            .filter(DataSourceGroup.group_id.in_(group_ids))\n        )\n        queries = (\n            cls.query.options(\n                joinedload(Query.user),\n                joinedload(Query.latest_query_data).load_only(\"runtime\", \"retrieved_at\"),\n            )\n            .filter(cls.id.in_(query_ids))\n            # Adding outer joins to be able to order by relationship\n            .outerjoin(User, User.id == Query.user_id)\n            .outerjoin(QueryResult, QueryResult.id == Query.latest_query_data_id)\n            .options(contains_eager(Query.user), contains_eager(Query.latest_query_data))\n        )\n\n        if not include_drafts:\n            queries = queries.filter(or_(Query.is_draft.is_(False), Query.user_id == user_id))\n        return queries\n\n    @classmethod\n    def favorites(cls, user, base_query=None):\n        if base_query is None:\n            base_query = cls.all_queries(user.group_ids, user.id, include_drafts=True)\n        return base_query.join(\n            (\n                Favorite,\n                and_(Favorite.object_type == \"Query\", Favorite.object_id == Query.id),\n            )\n        ).filter(Favorite.user_id == user.id)\n\n    @classmethod\n    def all_tags(cls, user, include_drafts=False):\n        queries = cls.all_queries(group_ids=user.group_ids, user_id=user.id, include_drafts=include_drafts)\n\n        tag_column = func.unnest(cls.tags).label(\"tag\")\n        usage_count = func.count(1).label(\"usage_count\")\n\n        query = (\n            db.session.query(tag_column, usage_count)\n            .group_by(tag_column)\n            .filter(Query.id.in_(queries.options(load_only(\"id\"))))\n            .order_by(tag_column)\n        )\n        return query\n\n    @classmethod\n    def by_user(cls, user):\n        return cls.all_queries(user.group_ids, user.id).filter(Query.user == user)\n\n    @classmethod\n    def by_api_key(cls, api_key):\n        return cls.query.filter(cls.api_key == api_key).one()\n\n    @classmethod\n    def past_scheduled_queries(cls):\n        now = utils.utcnow()\n        queries = Query.query.filter(func.jsonb_typeof(Query.schedule) != \"null\").order_by(Query.id)\n        return [\n            query\n            for query in queries\n            if \"until\" in query.schedule\n            and query.schedule[\"until\"] is not None\n            and pytz.utc.localize(datetime.datetime.strptime(query.schedule[\"until\"], \"%Y-%m-%d\")) <= now\n        ]\n\n    @classmethod\n    def outdated_queries(cls):\n        queries = (\n            Query.query.options(joinedload(Query.latest_query_data).load_only(\"retrieved_at\"))\n            .filter(func.jsonb_typeof(Query.schedule) != \"null\")\n            .order_by(Query.id)\n            .all()\n        )\n\n        now = utils.utcnow()\n        outdated_queries = {}\n        scheduled_queries_executions.refresh()\n\n        for query in queries:\n            try:\n                if query.schedule.get(\"disabled\"):\n                    continue\n\n                # Skip queries that have None for all schedule values. It's unclear whether this\n                # something that can happen in practice, but we have a test case for it.\n                if all(value is None for value in query.schedule.values()):\n                    continue\n\n                if query.schedule[\"until\"]:\n                    schedule_until = pytz.utc.localize(datetime.datetime.strptime(query.schedule[\"until\"], \"%Y-%m-%d\"))\n\n                    if schedule_until <= now:\n                        continue\n\n                retrieved_at = scheduled_queries_executions.get(query.id) or (\n                    query.latest_query_data and query.latest_query_data.retrieved_at\n                )\n\n                if should_schedule_next(\n                    retrieved_at,\n                    now,\n                    query.schedule[\"interval\"],\n                    query.schedule[\"time\"],\n                    query.schedule[\"day_of_week\"],\n                    query.schedule_failures,\n                ):\n                    key = \"{}:{}\".format(query.query_hash, query.data_source_id)\n                    outdated_queries[key] = query\n            except Exception as e:\n                query.schedule[\"disabled\"] = True\n                db.session.commit()\n\n                message = (\n                    \"Could not determine if query %d is outdated due to %s. The schedule for this query has been disabled.\"\n                    % (query.id, repr(e))\n                )\n                logging.info(message)\n                sentry.capture_exception(type(e)(message).with_traceback(e.__traceback__))\n\n        return list(outdated_queries.values())\n\n    @classmethod\n    def _do_multi_byte_search(cls, all_queries, term, limit=None):\n        # term examples:\n        #    - word\n        #    - name:word\n        #    - query:word\n        #    - \"multiple words\"\n        #    - name:\"multiple words\"\n        #    - word1 word2 word3\n        #    - word1 \"multiple word\" query:\"select foo\"\n        tokens = re.findall(r'(?:([^:\\s]+):)?(?:\"([^\"]+)\"|(\\S+))', term)\n        conditions = []\n        for token in tokens:\n            key = None\n            if token[0]:\n                key = token[0]\n\n            if token[1]:\n                value = token[1]\n            else:\n                value = token[2]\n\n            pattern = f\"%{value}%\"\n\n            if key == \"id\" and value.isdigit():\n                conditions.append(cls.id.equal(int(value)))\n            elif key == \"name\":\n                conditions.append(cls.name.ilike(pattern))\n            elif key == \"query\":\n                conditions.append(cls.query_text.ilike(pattern))\n            elif key == \"description\":\n                conditions.append(cls.description.ilike(pattern))\n            else:\n                conditions.append(or_(cls.name.ilike(pattern), cls.description.ilike(pattern)))\n\n        return all_queries.filter(and_(*conditions)).order_by(Query.id).limit(limit)\n\n    @classmethod\n    def search(\n        cls,\n        term,\n        group_ids,\n        user_id=None,\n        include_drafts=False,\n        limit=None,\n        include_archived=False,\n        multi_byte_search=False,\n    ):\n        all_queries = cls.all_queries(\n            group_ids,\n            user_id=user_id,\n            include_drafts=include_drafts,\n            include_archived=include_archived,\n        )\n\n        if multi_byte_search:\n            # Since tsvector doesn't work well with CJK languages, use `ilike` too\n            return cls._do_multi_byte_search(all_queries, term, limit)\n\n        # sort the result using the weight as defined in the search vector column\n        return all_queries.search(term, sort=True).limit(limit)\n\n    @classmethod\n    def search_by_user(cls, term, user, limit=None, multi_byte_search=False):\n        if multi_byte_search:\n            # Since tsvector doesn't work well with CJK languages, use `ilike` too\n            return cls._do_multi_byte_search(cls.by_user(user), term, limit)\n\n        return cls.by_user(user).search(term, sort=True).limit(limit)\n\n    @classmethod\n    def recent(cls, group_ids, user_id=None, limit=20):\n        query = (\n            cls.query.filter(Event.created_at > (db.func.current_date() - 7))\n            .join(Event, Query.id == Event.object_id.cast(db.Integer))\n            .join(DataSourceGroup, Query.data_source_id == DataSourceGroup.data_source_id)\n            .filter(\n                Event.action.in_([\"edit\", \"execute\", \"edit_name\", \"edit_description\", \"view_source\"]),\n                Event.object_id is not None,\n                Event.object_type == \"query\",\n                DataSourceGroup.group_id.in_(group_ids),\n                or_(Query.is_draft.is_(False), Query.user_id is user_id),\n                Query.is_archived.is_(False),\n            )\n            .group_by(Event.object_id, Query.id)\n            .order_by(db.desc(db.func.count(0)))\n        )\n\n        if user_id:\n            query = query.filter(Event.user_id == user_id)\n\n        query = query.limit(limit)\n\n        return query\n\n    @classmethod\n    def get_by_id(cls, _id):\n        return cls.query.filter(cls.id == _id).one()\n\n    @classmethod\n    def all_groups_for_query_ids(cls, query_ids):\n        query = \"\"\"SELECT group_id, view_only\n                   FROM queries\n                   JOIN data_source_groups ON queries.data_source_id = data_source_groups.data_source_id\n                   WHERE queries.id in :ids\"\"\"\n\n        return db.session.execute(query, {\"ids\": tuple(query_ids)}).fetchall()\n\n    def update_latest_result_by_query_hash(self):\n        query_hash = self.query_hash\n        data_source_id = self.data_source_id\n        query_result = (\n            QueryResult.query.options(load_only(\"id\"))\n            .filter(\n                QueryResult.query_hash == query_hash,\n                QueryResult.data_source_id == data_source_id,\n            )\n            .order_by(QueryResult.retrieved_at.desc())\n            .first()\n        )\n        if query_result:\n            latest_query_data_id = query_result.id\n            self.latest_query_data_id = latest_query_data_id\n            db.session.add(self)\n\n    @classmethod\n    def update_latest_result(cls, query_result):\n        # TODO: Investigate how big an impact this select-before-update makes.\n        queries = Query.query.filter(\n            Query.query_hash == query_result.query_hash,\n            Query.data_source == query_result.data_source,\n            Query.is_archived.is_(False),\n        )\n\n        for q in queries:\n            q.latest_query_data = query_result\n            # don't auto-update the updated_at timestamp\n            q.skip_updated_at = True\n            db.session.add(q)\n\n        query_ids = [q.id for q in queries]\n        logging.info(\n            \"Updated %s queries with result (%s).\",\n            len(query_ids),\n            query_result.query_hash,\n        )\n\n        return query_ids\n\n    def fork(self, user):\n        forked_list = [\n            \"org\",\n            \"data_source\",\n            \"latest_query_data\",\n            \"description\",\n            \"query_text\",\n            \"query_hash\",\n            \"options\",\n            \"tags\",\n        ]\n        kwargs = {a: getattr(self, a) for a in forked_list}\n\n        # Query.create will add default TABLE visualization, so use constructor to create bare copy of query\n        forked_query = Query(name=\"Copy of (#{}) {}\".format(self.id, self.name), user=user, **kwargs)\n\n        for v in sorted(self.visualizations, key=lambda v: v.id):\n            forked_v = v.copy()\n            forked_v[\"query_rel\"] = forked_query\n            fv = Visualization(**forked_v)  # it will magically add it to `forked_query.visualizations`\n            db.session.add(fv)\n\n        db.session.add(forked_query)\n        return forked_query\n\n    @property\n    def runtime(self):\n        return self.latest_query_data.runtime\n\n    @property\n    def retrieved_at(self):\n        return self.latest_query_data.retrieved_at\n\n    @property\n    def groups(self):\n        if self.data_source is None:\n            return {}\n\n        return self.data_source.groups\n\n    @hybrid_property\n    def lowercase_name(self):\n        \"Optional property useful for sorting purposes.\"\n        return self.name.lower()\n\n    @lowercase_name.expression\n    def lowercase_name(cls):\n        \"The SQLAlchemy expression for the property above.\"\n        return func.lower(cls.name)\n\n    @property\n    def parameters(self):\n        return self.options.get(\"parameters\", [])\n\n    @property\n    def parameterized(self):\n        return ParameterizedQuery(self.query_text, self.parameters, self.org)\n\n    @property\n    def dashboard_api_keys(self):\n        query = \"\"\"SELECT api_keys.api_key\n                   FROM api_keys\n                   JOIN dashboards ON object_id = dashboards.id\n                   JOIN widgets ON dashboards.id = widgets.dashboard_id\n                   JOIN visualizations ON widgets.visualization_id = visualizations.id\n                   WHERE object_type='dashboards'\n                     AND active=true\n                     AND visualizations.query_id = :id\"\"\"\n\n        api_keys = db.session.execute(query, {\"id\": self.id}).fetchall()\n        return [api_key[0] for api_key in api_keys]\n\n    def update_query_hash(self):\n        should_apply_auto_limit = self.options.get(\"apply_auto_limit\", False) if self.options else False\n        query_runner = self.data_source.query_runner if self.data_source else BaseQueryRunner({})\n        query_text = self.query_text\n\n        parameters_dict = {p[\"name\"]: p.get(\"value\") for p in self.parameters} if self.options else {}\n        if any(parameters_dict):\n            try:\n                query_text = self.parameterized.apply(parameters_dict).query\n            except InvalidParameterError as e:\n                logging.info(f\"Unable to update hash for query {self.id} because of invalid parameters: {str(e)}\")\n            except QueryDetachedFromDataSourceError as e:\n                logging.info(\n                    f\"Unable to update hash for query {self.id} because of dropdown query {e.query_id} is unattached from datasource\"\n                )\n\n        self.query_hash = query_runner.gen_query_hash(query_text, should_apply_auto_limit)\n\n\n@listens_for(Query, \"before_insert\")\n@listens_for(Query, \"before_update\")\ndef receive_before_insert_update(mapper, connection, target):\n    target.update_query_hash()\n\n\n@listens_for(Query.user_id, \"set\")\ndef query_last_modified_by(target, val, oldval, initiator):\n    target.last_modified_by_id = val\n\n\n@generic_repr(\"id\", \"object_type\", \"object_id\", \"user_id\", \"org_id\")\nclass Favorite(TimestampMixin, db.Model):\n    id = primary_key(\"Favorite\")\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n\n    object_type = Column(db.Unicode(255))\n    object_id = Column(key_type(\"Favorite\"))\n    object = generic_relationship(object_type, object_id)\n\n    user_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"))\n    user = db.relationship(User, backref=\"favorites\")\n\n    __tablename__ = \"favorites\"\n    __table_args__ = (UniqueConstraint(\"object_type\", \"object_id\", \"user_id\", name=\"unique_favorite\"),)\n\n    @classmethod\n    def is_favorite(cls, user, object):\n        return cls.query.filter(cls.object == object, cls.user_id == user).count() > 0\n\n    @classmethod\n    def are_favorites(cls, user, objects):\n        objects = list(objects)\n        if not objects:\n            return []\n\n        object_type = str(objects[0].__class__.__name__)\n        return [\n            fav.object_id\n            for fav in cls.query.filter(\n                cls.object_id.in_([o.id for o in objects]),\n                cls.object_type == object_type,\n                cls.user_id == user,\n            )\n        ]\n\n\nOPERATORS = {\n    \">\": lambda v, t: v > t,\n    \">=\": lambda v, t: v >= t,\n    \"<\": lambda v, t: v < t,\n    \"<=\": lambda v, t: v <= t,\n    \"==\": lambda v, t: v == t,\n    \"!=\": lambda v, t: v != t,\n    # backward compatibility\n    \"greater than\": lambda v, t: v > t,\n    \"less than\": lambda v, t: v < t,\n    \"equals\": lambda v, t: v == t,\n}\n\n\ndef next_state(op, value, threshold):\n    if isinstance(value, bool):\n        # If it's a boolean cast to string and lower case, because upper cased\n        # boolean value is Python specific and most likely will be confusing to\n        # users.\n        value = str(value).lower()\n        value_is_number = False\n    else:\n        try:\n            value = float(value)\n            value_is_number = True\n        except ValueError:\n            value_is_number = isinstance(value, numbers.Number)\n\n        if value_is_number:\n            try:\n                threshold = float(threshold)\n            except ValueError:\n                return Alert.UNKNOWN_STATE\n        else:\n            value = str(value)\n\n    if op(value, threshold):\n        new_state = Alert.TRIGGERED_STATE\n    elif not value_is_number and op not in [OPERATORS.get(\"!=\"), OPERATORS.get(\"==\"), OPERATORS.get(\"equals\")]:\n        new_state = Alert.UNKNOWN_STATE\n    else:\n        new_state = Alert.OK_STATE\n\n    return new_state\n\n\n@generic_repr(\"id\", \"name\", \"query_id\", \"user_id\", \"state\", \"last_triggered_at\", \"rearm\")\nclass Alert(TimestampMixin, BelongsToOrgMixin, db.Model):\n    UNKNOWN_STATE = \"unknown\"\n    OK_STATE = \"ok\"\n    TRIGGERED_STATE = \"triggered\"\n    TEST_STATE = \"test\"\n\n    id = primary_key(\"Alert\")\n    name = Column(db.String(255))\n    query_id = Column(key_type(\"Query\"), db.ForeignKey(\"queries.id\"))\n    query_rel = db.relationship(Query, backref=backref(\"alerts\", cascade=\"all\"))\n    user_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"))\n    user = db.relationship(User, backref=\"alerts\")\n    options = Column(MutableDict.as_mutable(JSONB), nullable=True)\n    state = Column(db.String(255), default=UNKNOWN_STATE)\n    subscriptions = db.relationship(\"AlertSubscription\", cascade=\"all, delete-orphan\")\n    last_triggered_at = Column(db.DateTime(True), nullable=True)\n    rearm = Column(db.Integer, nullable=True)\n\n    __tablename__ = \"alerts\"\n\n    @classmethod\n    def all(cls, group_ids):\n        return (\n            cls.query.options(joinedload(Alert.user), joinedload(Alert.query_rel))\n            .join(Query)\n            .join(DataSourceGroup, DataSourceGroup.data_source_id == Query.data_source_id)\n            .filter(DataSourceGroup.group_id.in_(group_ids))\n        )\n\n    @classmethod\n    def get_by_id_and_org(cls, object_id, org):\n        return super(Alert, cls).get_by_id_and_org(object_id, org, Query)\n\n    def evaluate(self):\n        data = self.query_rel.latest_query_data.data if self.query_rel.latest_query_data else None\n        new_state = self.UNKNOWN_STATE\n\n        if data and data[\"rows\"] and self.options[\"column\"] in data[\"rows\"][0]:\n            op = OPERATORS.get(self.options[\"op\"], lambda v, t: False)\n\n            if \"selector\" not in self.options:\n                selector = \"first\"\n            else:\n                selector = self.options[\"selector\"]\n\n            try:\n                if selector == \"max\":\n                    max_val = float(\"-inf\")\n                    for i in range(len(data[\"rows\"])):\n                        max_val = max(max_val, float(data[\"rows\"][i][self.options[\"column\"]]))\n                    value = max_val\n                elif selector == \"min\":\n                    min_val = float(\"inf\")\n                    for i in range(len(data[\"rows\"])):\n                        min_val = min(min_val, float(data[\"rows\"][i][self.options[\"column\"]]))\n                    value = min_val\n                else:\n                    value = data[\"rows\"][0][self.options[\"column\"]]\n\n            except ValueError:\n                return self.UNKNOWN_STATE\n\n            threshold = self.options[\"value\"]\n\n            if value is not None:\n                new_state = next_state(op, value, threshold)\n\n        return new_state\n\n    def subscribers(self):\n        return User.query.join(AlertSubscription).filter(AlertSubscription.alert == self)\n\n    def render_template(self, template):\n        if template is None:\n            return \"\"\n\n        data = self.query_rel.latest_query_data.data\n        host = base_url(self.query_rel.org)\n\n        col_name = self.options[\"column\"]\n        if data[\"rows\"] and col_name in data[\"rows\"][0]:\n            result_value = data[\"rows\"][0][col_name]\n        else:\n            result_value = None\n\n        result_table = []  # A two-dimensional array which can rendered as a table in Mustache\n        for row in data[\"rows\"]:\n            result_table.append([row[col[\"name\"]] for col in data[\"columns\"]])\n        context = {\n            \"ALERT_NAME\": self.name,\n            \"ALERT_URL\": \"{host}/alerts/{alert_id}\".format(host=host, alert_id=self.id),\n            \"ALERT_STATUS\": self.state.upper(),\n            \"ALERT_SELECTOR\": self.options[\"selector\"],\n            \"ALERT_CONDITION\": self.options[\"op\"],\n            \"ALERT_THRESHOLD\": self.options[\"value\"],\n            \"QUERY_NAME\": self.query_rel.name,\n            \"QUERY_URL\": \"{host}/queries/{query_id}\".format(host=host, query_id=self.query_rel.id),\n            \"QUERY_RESULT_VALUE\": result_value,\n            \"QUERY_RESULT_ROWS\": data[\"rows\"],\n            \"QUERY_RESULT_COLS\": data[\"columns\"],\n            \"QUERY_RESULT_TABLE\": result_table,\n        }\n        return mustache_render_escape(template, context)\n\n    @property\n    def custom_body(self):\n        template = self.options.get(\"custom_body\", self.options.get(\"template\"))\n        return self.render_template(template)\n\n    @property\n    def custom_subject(self):\n        template = self.options.get(\"custom_subject\")\n        return self.render_template(template)\n\n    @property\n    def groups(self):\n        return self.query_rel.groups\n\n    @property\n    def muted(self):\n        return self.options.get(\"muted\", False)\n\n\ndef generate_slug(ctx):\n    slug = utils.slugify(ctx.current_parameters[\"name\"])\n    tries = 1\n    while Dashboard.query.filter(Dashboard.slug == slug).first() is not None:\n        slug = utils.slugify(ctx.current_parameters[\"name\"]) + \"_\" + str(tries)\n        tries += 1\n    return slug\n\n\n@gfk_type\n@generic_repr(\"id\", \"name\", \"slug\", \"user_id\", \"org_id\", \"version\", \"is_archived\", \"is_draft\")\nclass Dashboard(ChangeTrackingMixin, TimestampMixin, BelongsToOrgMixin, db.Model):\n    id = primary_key(\"Dashboard\")\n    version = Column(db.Integer)\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n    org = db.relationship(Organization, backref=\"dashboards\")\n    slug = Column(db.String(140), index=True, default=generate_slug)\n    name = Column(db.String(100))\n    user_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"))\n    user = db.relationship(User)\n    # layout is no longer used, but kept so we know how to render old dashboards.\n    layout = Column(MutableList.as_mutable(JSONB), default=[])\n    dashboard_filters_enabled = Column(db.Boolean, default=False)\n    is_archived = Column(db.Boolean, default=False, index=True)\n    is_draft = Column(db.Boolean, default=True, index=True)\n    widgets = db.relationship(\"Widget\", backref=\"dashboard\", lazy=\"dynamic\")\n    tags = Column(\"tags\", MutableList.as_mutable(ARRAY(db.Unicode)), nullable=True)\n    options = Column(MutableDict.as_mutable(JSONB), default={})\n\n    __tablename__ = \"dashboards\"\n    __mapper_args__ = {\"version_id_col\": version}\n\n    def __str__(self):\n        return \"%s=%s\" % (self.id, self.name)\n\n    @property\n    def name_as_slug(self):\n        return utils.slugify(self.name)\n\n    @classmethod\n    def all(cls, org, group_ids, user_id):\n        query = (\n            Dashboard.query.options(joinedload(Dashboard.user).load_only(\"id\", \"name\", \"details\", \"email\"))\n            .distinct(cls.lowercase_name, Dashboard.created_at, Dashboard.slug)\n            .outerjoin(Widget)\n            .outerjoin(Visualization)\n            .outerjoin(Query)\n            .outerjoin(DataSourceGroup, Query.data_source_id == DataSourceGroup.data_source_id)\n            .filter(\n                Dashboard.is_archived.is_(False),\n                (DataSourceGroup.group_id.in_(group_ids) | (Dashboard.user_id == user_id)),\n                Dashboard.org == org,\n            )\n        )\n\n        query = query.filter(or_(Dashboard.user_id == user_id, Dashboard.is_draft.is_(False)))\n\n        return query\n\n    @classmethod\n    def search(cls, org, groups_ids, user_id, search_term):\n        # TODO: switch to FTS\n        return cls.all(org, groups_ids, user_id).filter(cls.name.ilike(\"%{}%\".format(search_term)))\n\n    @classmethod\n    def search_by_user(cls, term, user, limit=None):\n        return cls.by_user(user).filter(cls.name.ilike(\"%{}%\".format(term))).limit(limit)\n\n    @classmethod\n    def all_tags(cls, org, user):\n        dashboards = cls.all(org, user.group_ids, user.id)\n\n        tag_column = func.unnest(cls.tags).label(\"tag\")\n        usage_count = func.count(1).label(\"usage_count\")\n\n        query = (\n            db.session.query(tag_column, usage_count)\n            .group_by(tag_column)\n            .filter(Dashboard.id.in_(dashboards.options(load_only(\"id\"))))\n            .order_by(tag_column)\n        )\n        return query\n\n    @classmethod\n    def favorites(cls, user, base_query=None):\n        if base_query is None:\n            base_query = cls.all(user.org, user.group_ids, user.id)\n        return (\n            base_query.distinct(cls.lowercase_name, Dashboard.created_at, Dashboard.slug, Favorite.created_at)\n            .join(\n                (\n                    Favorite,\n                    and_(\n                        Favorite.object_type == \"Dashboard\",\n                        Favorite.object_id == Dashboard.id,\n                    ),\n                )\n            )\n            .filter(Favorite.user_id == user.id)\n        )\n\n    @classmethod\n    def by_user(cls, user):\n        return cls.all(user.org, user.group_ids, user.id).filter(Dashboard.user == user)\n\n    @classmethod\n    def get_by_slug_and_org(cls, slug, org):\n        return cls.query.filter(cls.slug == slug, cls.org == org).one()\n\n    def fork(self, user):\n        forked_list = [\"org\", \"layout\", \"dashboard_filters_enabled\", \"tags\"]\n\n        kwargs = {a: getattr(self, a) for a in forked_list}\n        forked_dashboard = Dashboard(name=\"Copy of (#{}) {}\".format(self.id, self.name), user=user, **kwargs)\n\n        for w in self.widgets:\n            forked_w = w.copy(forked_dashboard.id)\n            fw = Widget(**forked_w)\n            db.session.add(fw)\n\n        forked_dashboard.slug = forked_dashboard.id\n        db.session.add(forked_dashboard)\n        return forked_dashboard\n\n    @hybrid_property\n    def lowercase_name(self):\n        \"Optional property useful for sorting purposes.\"\n        return self.name.lower()\n\n    @lowercase_name.expression\n    def lowercase_name(cls):\n        \"The SQLAlchemy expression for the property above.\"\n        return func.lower(cls.name)\n\n\n@generic_repr(\"id\", \"name\", \"type\", \"query_id\")\nclass Visualization(TimestampMixin, BelongsToOrgMixin, db.Model):\n    id = primary_key(\"Visualization\")\n    type = Column(db.String(100))\n    query_id = Column(key_type(\"Query\"), db.ForeignKey(\"queries.id\"))\n    # query_rel and not query, because db.Model already has query defined.\n    query_rel = db.relationship(Query, back_populates=\"visualizations\")\n    name = Column(db.String(255))\n    description = Column(db.String(4096), nullable=True)\n    options = Column(MutableDict.as_mutable(JSONB), nullable=True)\n\n    __tablename__ = \"visualizations\"\n\n    def __str__(self):\n        return \"%s %s\" % (self.id, self.type)\n\n    @classmethod\n    def get_by_id_and_org(cls, object_id, org):\n        return super(Visualization, cls).get_by_id_and_org(object_id, org, Query)\n\n    def copy(self):\n        return {\n            \"type\": self.type,\n            \"name\": self.name,\n            \"description\": self.description,\n            \"options\": self.options,\n        }\n\n\n@generic_repr(\"id\", \"visualization_id\", \"dashboard_id\")\nclass Widget(TimestampMixin, BelongsToOrgMixin, db.Model):\n    id = primary_key(\"Widget\")\n    visualization_id = Column(key_type(\"Visualization\"), db.ForeignKey(\"visualizations.id\"), nullable=True)\n    visualization = db.relationship(Visualization, backref=backref(\"widgets\", cascade=\"delete\"))\n    text = Column(db.Text, nullable=True)\n    width = Column(db.Integer)\n    options = Column(MutableDict.as_mutable(JSONB), default={})\n    dashboard_id = Column(key_type(\"Dashboard\"), db.ForeignKey(\"dashboards.id\"), index=True)\n\n    __tablename__ = \"widgets\"\n\n    def __str__(self):\n        return \"%s\" % self.id\n\n    @classmethod\n    def get_by_id_and_org(cls, object_id, org):\n        return super(Widget, cls).get_by_id_and_org(object_id, org, Dashboard)\n\n    def copy(self, dashboard_id):\n        return {\n            \"options\": self.options,\n            \"width\": self.width,\n            \"text\": self.text,\n            \"visualization_id\": self.visualization_id,\n            \"dashboard_id\": dashboard_id,\n        }\n\n\n@generic_repr(\"id\", \"object_type\", \"object_id\", \"action\", \"user_id\", \"org_id\", \"created_at\")\nclass Event(db.Model):\n    id = primary_key(\"Event\")\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n    org = db.relationship(Organization, back_populates=\"events\")\n    user_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"), nullable=True)\n    user = db.relationship(User, backref=\"events\")\n    action = Column(db.String(255))\n    object_type = Column(db.String(255))\n    object_id = Column(db.String(255), nullable=True)\n    additional_properties = Column(MutableDict.as_mutable(JSONB), nullable=True, default={})\n    created_at = Column(db.DateTime(True), default=db.func.now())\n\n    __tablename__ = \"events\"\n\n    def __str__(self):\n        return \"%s,%s,%s,%s\" % (\n            self.user_id,\n            self.action,\n            self.object_type,\n            self.object_id,\n        )\n\n    def to_dict(self):\n        return {\n            \"org_id\": self.org_id,\n            \"user_id\": self.user_id,\n            \"action\": self.action,\n            \"object_type\": self.object_type,\n            \"object_id\": self.object_id,\n            \"additional_properties\": self.additional_properties,\n            \"created_at\": self.created_at.isoformat(),\n        }\n\n    @classmethod\n    def record(cls, event):\n        org_id = event.pop(\"org_id\")\n        user_id = event.pop(\"user_id\", None)\n        action = event.pop(\"action\")\n        object_type = event.pop(\"object_type\")\n        object_id = event.pop(\"object_id\", None)\n\n        created_at = datetime.datetime.utcfromtimestamp(event.pop(\"timestamp\"))\n\n        event = cls(\n            org_id=org_id,\n            user_id=user_id,\n            action=action,\n            object_type=object_type,\n            object_id=object_id,\n            additional_properties=event,\n            created_at=created_at,\n        )\n        db.session.add(event)\n        return event\n\n\n@generic_repr(\"id\", \"created_by_id\", \"org_id\", \"active\")\nclass ApiKey(TimestampMixin, GFKBase, db.Model):\n    id = primary_key(\"ApiKey\")\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n    org = db.relationship(Organization)\n    api_key = Column(db.String(255), index=True, default=lambda: generate_token(40))\n    active = Column(db.Boolean, default=True)\n    # 'object' provided by GFKBase\n    object_id = Column(key_type(\"ApiKey\"))\n    created_by_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"), nullable=True)\n    created_by = db.relationship(User)\n\n    __tablename__ = \"api_keys\"\n    __table_args__ = (db.Index(\"api_keys_object_type_object_id\", \"object_type\", \"object_id\"),)\n\n    @classmethod\n    def get_by_api_key(cls, api_key):\n        return cls.query.filter(cls.api_key == api_key, cls.active.is_(True)).one()\n\n    @classmethod\n    def get_by_object(cls, object):\n        return cls.query.filter(\n            cls.object_type == object.__class__.__tablename__,\n            cls.object_id == object.id,\n            cls.active.is_(True),\n        ).first()\n\n    @classmethod\n    def create_for_object(cls, object, user):\n        k = cls(org=user.org, object=object, created_by=user)\n        db.session.add(k)\n        return k\n\n\n@generic_repr(\"id\", \"name\", \"type\", \"user_id\", \"org_id\", \"created_at\")\nclass NotificationDestination(BelongsToOrgMixin, db.Model):\n    id = primary_key(\"NotificationDestination\")\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n    org = db.relationship(Organization, backref=\"notification_destinations\")\n    user_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"))\n    user = db.relationship(User, backref=\"notification_destinations\")\n    name = Column(db.String(255))\n    type = Column(db.String(255))\n    options = Column(\n        \"encrypted_options\",\n        ConfigurationContainer.as_mutable(\n            EncryptedConfiguration(db.Text, settings.DATASOURCE_SECRET_KEY, FernetEngine)\n        ),\n    )\n    created_at = Column(db.DateTime(True), default=db.func.now())\n\n    __tablename__ = \"notification_destinations\"\n    __table_args__ = (db.Index(\"notification_destinations_org_id_name\", \"org_id\", \"name\", unique=True),)\n\n    def __str__(self):\n        return str(self.name)\n\n    def to_dict(self, all=False):\n        d = {\n            \"id\": self.id,\n            \"name\": self.name,\n            \"type\": self.type,\n            \"icon\": self.destination.icon(),\n        }\n\n        if all:\n            schema = get_configuration_schema_for_destination_type(self.type)\n            self.options.set_schema(schema)\n            d[\"options\"] = self.options.to_dict(mask_secrets=True)\n\n        return d\n\n    @property\n    def destination(self):\n        return get_destination(self.type, self.options)\n\n    @classmethod\n    def all(cls, org):\n        notification_destinations = cls.query.filter(cls.org == org).order_by(cls.id.asc())\n\n        return notification_destinations\n\n    def notify(self, alert, query, user, new_state, app, host, metadata):\n        schema = get_configuration_schema_for_destination_type(self.type)\n        self.options.set_schema(schema)\n        return self.destination.notify(alert, query, user, new_state, app, host, metadata, self.options)\n\n\n@generic_repr(\"id\", \"user_id\", \"destination_id\", \"alert_id\")\nclass AlertSubscription(TimestampMixin, db.Model):\n    id = primary_key(\"AlertSubscription\")\n    user_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"))\n    user = db.relationship(User)\n    destination_id = Column(\n        key_type(\"NotificationDestination\"), db.ForeignKey(\"notification_destinations.id\"), nullable=True\n    )\n    destination = db.relationship(NotificationDestination)\n    alert_id = Column(key_type(\"Alert\"), db.ForeignKey(\"alerts.id\"))\n    alert = db.relationship(Alert, back_populates=\"subscriptions\")\n\n    __tablename__ = \"alert_subscriptions\"\n    __table_args__ = (\n        db.Index(\n            \"alert_subscriptions_destination_id_alert_id\",\n            \"destination_id\",\n            \"alert_id\",\n            unique=True,\n        ),\n    )\n\n    def to_dict(self):\n        d = {\"id\": self.id, \"user\": self.user.to_dict(), \"alert_id\": self.alert_id}\n\n        if self.destination:\n            d[\"destination\"] = self.destination.to_dict()\n\n        return d\n\n    @classmethod\n    def all(cls, alert_id):\n        return AlertSubscription.query.join(User).filter(AlertSubscription.alert_id == alert_id)\n\n    def notify(self, alert, query, user, new_state, app, host, metadata):\n        if self.destination:\n            return self.destination.notify(alert, query, user, new_state, app, host, metadata)\n        else:\n            # User email subscription, so create an email destination object\n            config = {\"addresses\": self.user.email}\n            schema = get_configuration_schema_for_destination_type(\"email\")\n            options = ConfigurationContainer(config, schema)\n            destination = get_destination(\"email\", options)\n            return destination.notify(alert, query, user, new_state, app, host, metadata, options)\n\n\n@generic_repr(\"id\", \"trigger\", \"user_id\", \"org_id\")\nclass QuerySnippet(TimestampMixin, db.Model, BelongsToOrgMixin):\n    id = primary_key(\"QuerySnippet\")\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n    org = db.relationship(Organization, backref=\"query_snippets\")\n    trigger = Column(db.String(255), unique=True)\n    description = Column(db.Text)\n    user_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"))\n    user = db.relationship(User, backref=\"query_snippets\")\n    snippet = Column(db.Text)\n\n    __tablename__ = \"query_snippets\"\n\n    @classmethod\n    def all(cls, org):\n        return cls.query.filter(cls.org == org)\n\n    def to_dict(self):\n        d = {\n            \"id\": self.id,\n            \"trigger\": self.trigger,\n            \"description\": self.description,\n            \"snippet\": self.snippet,\n            \"user\": self.user.to_dict(),\n            \"updated_at\": self.updated_at,\n            \"created_at\": self.created_at,\n        }\n\n        return d\n\n\ndef init_db():\n    default_org = Organization(name=\"Default\", slug=\"default\", settings={})\n    admin_group = Group(\n        name=\"admin\",\n        permissions=Group.ADMIN_PERMISSIONS,\n        org=default_org,\n        type=Group.BUILTIN_GROUP,\n    )\n    default_group = Group(\n        name=\"default\",\n        permissions=Group.DEFAULT_PERMISSIONS,\n        org=default_org,\n        type=Group.BUILTIN_GROUP,\n    )\n\n    db.session.add_all([default_org, admin_group, default_group])\n    # XXX remove after fixing User.group_ids\n    db.session.commit()\n    return default_org, admin_group, default_group\n"
  },
  {
    "path": "redash/models/base.py",
    "content": "import functools\n\nfrom flask_sqlalchemy import BaseQuery, SQLAlchemy\nfrom sqlalchemy.dialects.postgresql import UUID\nfrom sqlalchemy.orm import object_session\nfrom sqlalchemy.pool import NullPool\nfrom sqlalchemy_searchable import SearchQueryMixin, make_searchable, vectorizer\n\nfrom redash import settings\nfrom redash.utils import json_dumps, json_loads\n\n\nclass RedashSQLAlchemy(SQLAlchemy):\n    def apply_driver_hacks(self, app, info, options):\n        options.update(json_serializer=json_dumps)\n        if settings.SQLALCHEMY_ENABLE_POOL_PRE_PING:\n            options.update(pool_pre_ping=True)\n        return super(RedashSQLAlchemy, self).apply_driver_hacks(app, info, options)\n\n    def apply_pool_defaults(self, app, options):\n        super(RedashSQLAlchemy, self).apply_pool_defaults(app, options)\n        if settings.SQLALCHEMY_ENABLE_POOL_PRE_PING:\n            options[\"pool_pre_ping\"] = True\n        if settings.SQLALCHEMY_DISABLE_POOL:\n            options[\"poolclass\"] = NullPool\n            # Remove options NullPool does not support:\n            options.pop(\"max_overflow\", None)\n        return options\n\n\ndb = RedashSQLAlchemy(\n    session_options={\"expire_on_commit\": False},\n    engine_options={\"json_serializer\": json_dumps, \"json_deserializer\": json_loads},\n)\n# Make sure the SQLAlchemy mappers are all properly configured first.\n# This is required by SQLAlchemy-Searchable as it adds DDL listeners\n# on the configuration phase of models.\ndb.configure_mappers()\n\n# listen to a few database events to set up functions, trigger updates\n# and indexes for the full text search\nmake_searchable(db.metadata, options={\"regconfig\": \"pg_catalog.simple\"})\n\n\nclass SearchBaseQuery(BaseQuery, SearchQueryMixin):\n    \"\"\"\n    The SQA query class to use when full text search is wanted.\n    \"\"\"\n\n\n@vectorizer(db.Integer)\ndef integer_vectorizer(column):\n    return db.func.cast(column, db.Text)\n\n\n@vectorizer(UUID)\ndef uuid_vectorizer(column):\n    return db.func.cast(column, db.Text)\n\n\nColumn = functools.partial(db.Column, nullable=False)\n\n# AccessPermission and Change use a 'generic foreign key' approach to refer to\n# either queries or dashboards.\n# TODO replace this with association tables.\n_gfk_types = {}\n\n\ndef gfk_type(cls):\n    _gfk_types[cls.__tablename__] = cls\n    return cls\n\n\nclass GFKBase:\n    \"\"\"\n    Compatibility with 'generic foreign key' approach Peewee used.\n    \"\"\"\n\n    object_type = Column(db.String(255))\n    object_id = Column(db.Integer)\n\n    _object = None\n\n    @property\n    def object(self):\n        session = object_session(self)\n        if self._object or not session:\n            return self._object\n        else:\n            object_class = _gfk_types[self.object_type]\n            self._object = session.query(object_class).filter(object_class.id == self.object_id).first()\n            return self._object\n\n    @object.setter\n    def object(self, value):\n        self._object = value\n        self.object_type = value.__class__.__tablename__\n        self.object_id = value.id\n\n\nkey_definitions = settings.dynamic_settings.database_key_definitions((db.Integer, {}))\n\n\ndef key_type(name):\n    return key_definitions[name][0]\n\n\ndef primary_key(name):\n    key_type, kwargs = key_definitions[name]\n    return Column(key_type, primary_key=True, **kwargs)\n"
  },
  {
    "path": "redash/models/changes.py",
    "content": "from sqlalchemy.dialects.postgresql import JSONB\nfrom sqlalchemy.inspection import inspect\nfrom sqlalchemy_utils.models import generic_repr\n\nfrom .base import Column, GFKBase, db, key_type, primary_key\n\n\n@generic_repr(\"id\", \"object_type\", \"object_id\", \"created_at\")\nclass Change(GFKBase, db.Model):\n    id = primary_key(\"Change\")\n    # 'object' defined in GFKBase\n    object_id = Column(key_type(\"Change\"))\n    object_version = Column(db.Integer, default=0)\n    user_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"))\n    user = db.relationship(\"User\", backref=\"changes\")\n    change = Column(JSONB)\n    created_at = Column(db.DateTime(True), default=db.func.now())\n\n    __tablename__ = \"changes\"\n\n    def to_dict(self, full=True):\n        d = {\n            \"id\": self.id,\n            \"object_id\": self.object_id,\n            \"object_type\": self.object_type,\n            \"change_type\": self.change_type,\n            \"object_version\": self.object_version,\n            \"change\": self.change,\n            \"created_at\": self.created_at,\n        }\n\n        if full:\n            d[\"user\"] = self.user.to_dict()\n        else:\n            d[\"user_id\"] = self.user_id\n\n        return d\n\n    @classmethod\n    def last_change(cls, obj):\n        return (\n            cls.query.filter(cls.object_id == obj.id, cls.object_type == obj.__class__.__tablename__)\n            .order_by(cls.object_version.desc())\n            .first()\n        )\n\n\nclass ChangeTrackingMixin:\n    skipped_fields = (\"id\", \"created_at\", \"updated_at\", \"version\")\n    _clean_values = None\n\n    def __init__(self, *a, **kw):\n        super(ChangeTrackingMixin, self).__init__(*a, **kw)\n        self.record_changes(self.user)\n\n    def prep_cleanvalues(self):\n        self.__dict__[\"_clean_values\"] = {}\n        for attr in inspect(self.__class__).column_attrs:\n            (col,) = attr.columns\n            # 'query' is col name but not attr name\n            self._clean_values[col.name] = None\n\n    def __setattr__(self, key, value):\n        if self._clean_values is None:\n            self.prep_cleanvalues()\n        for attr in inspect(self.__class__).column_attrs:\n            (col,) = attr.columns\n            previous = getattr(self, attr.key, None)\n            self._clean_values[col.name] = previous\n\n        super(ChangeTrackingMixin, self).__setattr__(key, value)\n\n    def record_changes(self, changed_by):\n        db.session.add(self)\n        db.session.flush()\n        changes = {}\n        for attr in inspect(self.__class__).column_attrs:\n            (col,) = attr.columns\n            if attr.key not in self.skipped_fields:\n                changes[col.name] = {\n                    \"previous\": self._clean_values[col.name],\n                    \"current\": getattr(self, attr.key),\n                }\n\n        db.session.add(\n            Change(\n                object=self,\n                object_version=self.version,\n                user=changed_by,\n                change=changes,\n            )\n        )\n"
  },
  {
    "path": "redash/models/mixins.py",
    "content": "from sqlalchemy.event import listens_for\n\nfrom .base import Column, db\n\n\nclass TimestampMixin:\n    updated_at = Column(db.DateTime(True), default=db.func.now(), nullable=False)\n    created_at = Column(db.DateTime(True), default=db.func.now(), nullable=False)\n\n\n@listens_for(TimestampMixin, \"before_update\", propagate=True)\ndef timestamp_before_update(mapper, connection, target):\n    # Check if we really want to update the updated_at value\n    if hasattr(target, \"skip_updated_at\"):\n        return\n\n    target.updated_at = db.func.now()\n\n\nclass BelongsToOrgMixin:\n    @classmethod\n    def get_by_id_and_org(cls, object_id, org, org_cls=None):\n        query = cls.query.filter(cls.id == object_id)\n        if org_cls is None:\n            query = query.filter(cls.org == org)\n        else:\n            query = query.join(org_cls).filter(org_cls.org == org)\n        return query.one()\n"
  },
  {
    "path": "redash/models/organizations.py",
    "content": "from sqlalchemy.dialects.postgresql import JSONB\nfrom sqlalchemy.orm.attributes import flag_modified\nfrom sqlalchemy_utils.models import generic_repr\n\nfrom redash.settings.organization import settings as org_settings\n\nfrom .base import Column, db, primary_key\nfrom .mixins import TimestampMixin\nfrom .types import MutableDict\nfrom .users import Group, User\n\n\n@generic_repr(\"id\", \"name\", \"slug\")\nclass Organization(TimestampMixin, db.Model):\n    SETTING_GOOGLE_APPS_DOMAINS = \"google_apps_domains\"\n    SETTING_IS_PUBLIC = \"is_public\"\n\n    id = primary_key(\"Organization\")\n    name = Column(db.String(255))\n    slug = Column(db.String(255), unique=True)\n    settings = Column(MutableDict.as_mutable(JSONB), default={})\n    groups = db.relationship(\"Group\", lazy=\"dynamic\")\n    events = db.relationship(\"Event\", lazy=\"dynamic\", order_by=\"desc(Event.created_at)\")\n\n    __tablename__ = \"organizations\"\n\n    def __str__(self):\n        return \"%s (%s)\" % (self.name, self.id)\n\n    @classmethod\n    def get_by_slug(cls, slug):\n        return cls.query.filter(cls.slug == slug).first()\n\n    @classmethod\n    def get_by_id(cls, _id):\n        return cls.query.filter(cls.id == _id).one()\n\n    @property\n    def default_group(self):\n        return self.groups.filter(Group.name == \"default\", Group.type == Group.BUILTIN_GROUP).first()\n\n    @property\n    def google_apps_domains(self):\n        return self.settings.get(self.SETTING_GOOGLE_APPS_DOMAINS, [])\n\n    @property\n    def is_public(self):\n        return self.settings.get(self.SETTING_IS_PUBLIC, False)\n\n    @property\n    def is_disabled(self):\n        return self.settings.get(\"is_disabled\", False)\n\n    def disable(self):\n        self.settings[\"is_disabled\"] = True\n\n    def enable(self):\n        self.settings[\"is_disabled\"] = False\n\n    def set_setting(self, key, value):\n        if key not in org_settings:\n            raise KeyError(key)\n\n        self.settings.setdefault(\"settings\", {})\n        self.settings[\"settings\"][key] = value\n        flag_modified(self, \"settings\")\n\n    def get_setting(self, key, raise_on_missing=True):\n        if key in self.settings.get(\"settings\", {}):\n            return self.settings[\"settings\"][key]\n\n        if key in org_settings:\n            return org_settings[key]\n\n        if raise_on_missing:\n            raise KeyError(key)\n\n        return None\n\n    @property\n    def admin_group(self):\n        return self.groups.filter(Group.name == \"admin\", Group.type == Group.BUILTIN_GROUP).first()\n\n    def has_user(self, email):\n        return self.users.filter(User.email == email).count() == 1\n"
  },
  {
    "path": "redash/models/parameterized_query.py",
    "content": "import re\nfrom functools import partial\nfrom numbers import Number\n\nimport pystache\nfrom dateutil.parser import parse\nfrom funcy import distinct\n\nfrom redash.utils import mustache_render\n\n\ndef _pluck_name_and_value(default_column, row):\n    row = {k.lower(): v for k, v in row.items()}\n    name_column = \"name\" if \"name\" in row.keys() else default_column.lower()\n    value_column = \"value\" if \"value\" in row.keys() else default_column.lower()\n\n    return {\"name\": row[name_column], \"value\": str(row[value_column])}\n\n\ndef _load_result(query_id, org):\n    from redash import models\n\n    query = models.Query.get_by_id_and_org(query_id, org)\n\n    if query.data_source:\n        query_result = models.QueryResult.get_by_id_and_org(query.latest_query_data_id, org)\n        return query_result.data\n    else:\n        raise QueryDetachedFromDataSourceError(query_id)\n\n\ndef dropdown_values(query_id, org):\n    data = _load_result(query_id, org)\n    first_column = data[\"columns\"][0][\"name\"]\n    pluck = partial(_pluck_name_and_value, first_column)\n    return list(map(pluck, data[\"rows\"]))\n\n\ndef join_parameter_list_values(parameters, schema):\n    updated_parameters = {}\n    for key, value in parameters.items():\n        if isinstance(value, list):\n            definition = next((definition for definition in schema if definition[\"name\"] == key), {})\n            multi_values_options = definition.get(\"multiValuesOptions\", {})\n            separator = str(multi_values_options.get(\"separator\", \",\"))\n            prefix = str(multi_values_options.get(\"prefix\", \"\"))\n            suffix = str(multi_values_options.get(\"suffix\", \"\"))\n            updated_parameters[key] = separator.join([prefix + v + suffix for v in value])\n        else:\n            updated_parameters[key] = value\n    return updated_parameters\n\n\ndef _collect_key_names(nodes):\n    keys = []\n    for node in nodes._parse_tree:\n        if isinstance(node, pystache.parser._EscapeNode):\n            keys.append(node.key)\n        elif isinstance(node, pystache.parser._SectionNode):\n            keys.append(node.key)\n            keys.extend(_collect_key_names(node.parsed))\n\n    return distinct(keys)\n\n\ndef _collect_query_parameters(query):\n    nodes = pystache.parse(query)\n    keys = _collect_key_names(nodes)\n    return keys\n\n\ndef _parameter_names(parameter_values):\n    names = []\n    for key, value in parameter_values.items():\n        if isinstance(value, dict):\n            for inner_key in value.keys():\n                names.append(\"{}.{}\".format(key, inner_key))\n        else:\n            names.append(key)\n\n    return names\n\n\ndef _is_number(string):\n    if isinstance(string, Number):\n        return True\n    else:\n        float(string)\n        return True\n\n\ndef _is_regex_pattern(value, regex):\n    try:\n        if re.compile(regex).fullmatch(value):\n            return True\n        else:\n            return False\n    except re.error:\n        return False\n\n\ndef _is_date(string):\n    parse(string)\n    return True\n\n\ndef _is_date_range(obj):\n    return _is_date(obj[\"start\"]) and _is_date(obj[\"end\"])\n\n\ndef _is_value_within_options(value, dropdown_options, allow_list=False):\n    if isinstance(value, list):\n        return allow_list and set(map(str, value)).issubset(set(dropdown_options))\n    return str(value) in dropdown_options\n\n\nclass ParameterizedQuery:\n    def __init__(self, template, schema=None, org=None):\n        self.schema = schema or []\n        self.org = org\n        self.template = template\n        self.query = template\n        self.parameters = {}\n\n    def apply(self, parameters):\n        invalid_parameter_names = [key for (key, value) in parameters.items() if not self._valid(key, value)]\n        if invalid_parameter_names:\n            raise InvalidParameterError(invalid_parameter_names)\n        else:\n            self.parameters.update(parameters)\n            self.query = mustache_render(self.template, join_parameter_list_values(parameters, self.schema))\n\n        return self\n\n    def _valid(self, name, value):\n        if not self.schema:\n            return True\n\n        definition = next(\n            (definition for definition in self.schema if definition[\"name\"] == name),\n            None,\n        )\n\n        if not definition:\n            return False\n\n        enum_options = definition.get(\"enumOptions\")\n        query_id = definition.get(\"queryId\")\n        regex = definition.get(\"regex\")\n        allow_multiple_values = isinstance(definition.get(\"multiValuesOptions\"), dict)\n\n        if isinstance(enum_options, str):\n            enum_options = enum_options.split(\"\\n\")\n\n        validators = {\n            \"text\": lambda value: isinstance(value, str),\n            \"text-pattern\": lambda value: _is_regex_pattern(value, regex),\n            \"number\": _is_number,\n            \"enum\": lambda value: _is_value_within_options(value, enum_options, allow_multiple_values),\n            \"query\": lambda value: _is_value_within_options(\n                value,\n                [v[\"value\"] for v in dropdown_values(query_id, self.org)],\n                allow_multiple_values,\n            ),\n            \"date\": _is_date,\n            \"datetime-local\": _is_date,\n            \"datetime-with-seconds\": _is_date,\n            \"date-range\": _is_date_range,\n            \"datetime-range\": _is_date_range,\n            \"datetime-range-with-seconds\": _is_date_range,\n        }\n\n        validate = validators.get(definition[\"type\"], lambda x: False)\n\n        try:\n            # multiple error types can be raised here; but we want to convert\n            # all except QueryDetached to InvalidParameterError in `apply`\n            return validate(value)\n        except QueryDetachedFromDataSourceError:\n            raise\n        except Exception:\n            return False\n\n    @property\n    def is_safe(self):\n        text_parameters = [param for param in self.schema if param[\"type\"] == \"text\"]\n        return not any(text_parameters)\n\n    @property\n    def missing_params(self):\n        query_parameters = set(_collect_query_parameters(self.template))\n        return set(query_parameters) - set(_parameter_names(self.parameters))\n\n    @property\n    def text(self):\n        return self.query\n\n\nclass InvalidParameterError(Exception):\n    def __init__(self, parameters):\n        parameter_names = \", \".join(parameters)\n        message = \"The following parameter values are incompatible with their definitions: {}\".format(parameter_names)\n        super(InvalidParameterError, self).__init__(message)\n\n\nclass QueryDetachedFromDataSourceError(Exception):\n    def __init__(self, query_id):\n        self.query_id = query_id\n        super(QueryDetachedFromDataSourceError, self).__init__(\n            \"This query is detached from any data source. Please select a different query.\"\n        )\n"
  },
  {
    "path": "redash/models/types.py",
    "content": "from sqlalchemy.ext.indexable import index_property\nfrom sqlalchemy.ext.mutable import Mutable\nfrom sqlalchemy.types import TypeDecorator\nfrom sqlalchemy_utils import EncryptedType\n\nfrom redash.utils import json_dumps, json_loads\nfrom redash.utils.configuration import ConfigurationContainer\n\nfrom .base import db\n\n\nclass Configuration(TypeDecorator):\n    impl = db.Text\n\n    def process_bind_param(self, value, dialect):\n        return value.to_json()\n\n    def process_result_value(self, value, dialect):\n        return ConfigurationContainer.from_json(value)\n\n\nclass EncryptedConfiguration(EncryptedType):\n    def process_bind_param(self, value, dialect):\n        return super(EncryptedConfiguration, self).process_bind_param(value.to_json(), dialect)\n\n    def process_result_value(self, value, dialect):\n        return ConfigurationContainer.from_json(\n            super(EncryptedConfiguration, self).process_result_value(value, dialect)\n        )\n\n\n# Utilized for cases when JSON size is bigger than JSONB (255MB) or JSON (10MB) limit\nclass JSONText(TypeDecorator):\n    impl = db.Text\n\n    def process_bind_param(self, value, dialect):\n        if value is None:\n            return value\n\n        return json_dumps(value)\n\n    def process_result_value(self, value, dialect):\n        if not value:\n            return value\n        return json_loads(value)\n\n\nclass MutableDict(Mutable, dict):\n    @classmethod\n    def coerce(cls, key, value):\n        \"Convert plain dictionaries to MutableDict.\"\n\n        if not isinstance(value, MutableDict):\n            if isinstance(value, dict):\n                return MutableDict(value)\n\n            # this call will raise ValueError\n            return Mutable.coerce(key, value)\n        else:\n            return value\n\n    def __setitem__(self, key, value):\n        \"Detect dictionary set events and emit change events.\"\n\n        dict.__setitem__(self, key, value)\n        self.changed()\n\n    def __delitem__(self, key):\n        \"Detect dictionary del events and emit change events.\"\n\n        dict.__delitem__(self, key)\n        self.changed()\n\n\nclass MutableList(Mutable, list):\n    def append(self, value):\n        list.append(self, value)\n        self.changed()\n\n    def remove(self, value):\n        list.remove(self, value)\n        self.changed()\n\n    @classmethod\n    def coerce(cls, key, value):\n        if not isinstance(value, MutableList):\n            if isinstance(value, list):\n                return MutableList(value)\n            return Mutable.coerce(key, value)\n        else:\n            return value\n\n\nclass json_cast_property(index_property):\n    \"\"\"\n    A SQLAlchemy index property that is able to cast the\n    entity attribute as the specified cast type. Useful\n    for JSON and JSONB colums for easier querying/filtering.\n    \"\"\"\n\n    def __init__(self, cast_type, *args, **kwargs):\n        super(json_cast_property, self).__init__(*args, **kwargs)\n        self.cast_type = cast_type\n\n    def expr(self, model):\n        expr = super(json_cast_property, self).expr(model)\n        return expr.astext.cast(self.cast_type)\n"
  },
  {
    "path": "redash/models/users.py",
    "content": "import hashlib\nimport itertools\nimport logging\nimport time\nfrom functools import reduce\nfrom operator import or_\n\nfrom flask import current_app, request_started, url_for\nfrom flask_login import AnonymousUserMixin, UserMixin, current_user\nfrom passlib.apps import custom_app_context as pwd_context\nfrom sqlalchemy.dialects.postgresql import ARRAY, JSONB\nfrom sqlalchemy_utils import EmailType\nfrom sqlalchemy_utils.models import generic_repr\n\nfrom redash import redis_connection\nfrom redash.utils import dt_from_timestamp, generate_token\n\nfrom .base import Column, GFKBase, db, key_type, primary_key\nfrom .mixins import BelongsToOrgMixin, TimestampMixin\nfrom .types import MutableDict, MutableList, json_cast_property\n\nlogger = logging.getLogger(__name__)\n\n\nLAST_ACTIVE_KEY = \"users:last_active_at\"\n\n\ndef sync_last_active_at():\n    \"\"\"\n    Update User model with the active_at timestamp from Redis. We first fetch\n    all the user_ids to update, and then fetch the timestamp to minimize the\n    time between fetching the value and updating the DB. This is because there\n    might be a more recent update we skip otherwise.\n    \"\"\"\n    user_ids = redis_connection.hkeys(LAST_ACTIVE_KEY)\n    for user_id in user_ids:\n        timestamp = redis_connection.hget(LAST_ACTIVE_KEY, user_id)\n        active_at = dt_from_timestamp(timestamp)\n        user = User.query.filter(User.id == user_id).first()\n        if user:\n            user.active_at = active_at\n        redis_connection.hdel(LAST_ACTIVE_KEY, user_id)\n    db.session.commit()\n\n\ndef update_user_active_at(sender, *args, **kwargs):\n    \"\"\"\n    Used as a Flask request_started signal callback that adds\n    the current user's details to Redis\n    \"\"\"\n    if current_user.is_authenticated and not current_user.is_api_user():\n        redis_connection.hset(LAST_ACTIVE_KEY, current_user.id, int(time.time()))\n\n\ndef init_app(app):\n    \"\"\"\n    A Flask extension to keep user details updates in Redis and\n    sync it periodically to the database (User.details).\n    \"\"\"\n    request_started.connect(update_user_active_at, app)\n\n\nclass PermissionsCheckMixin:\n    def has_permission(self, permission):\n        return self.has_permissions((permission,))\n\n    def has_permissions(self, permissions):\n        has_permissions = reduce(\n            lambda a, b: a and b,\n            [permission in self.permissions for permission in permissions],\n            True,\n        )\n\n        return has_permissions\n\n\n@generic_repr(\"id\", \"name\", \"email\")\nclass User(TimestampMixin, db.Model, BelongsToOrgMixin, UserMixin, PermissionsCheckMixin):\n    id = primary_key(\"User\")\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n    org = db.relationship(\"Organization\", backref=db.backref(\"users\", lazy=\"dynamic\"))\n    name = Column(db.String(320))\n    email = Column(EmailType)\n    password_hash = Column(db.String(128), nullable=True)\n    group_ids = Column(\n        \"groups\",\n        MutableList.as_mutable(ARRAY(key_type(\"Group\"))),\n        nullable=True,\n    )\n    api_key = Column(db.String(40), default=lambda: generate_token(40), unique=True)\n\n    disabled_at = Column(db.DateTime(True), default=None, nullable=True)\n    details = Column(\n        MutableDict.as_mutable(JSONB),\n        nullable=True,\n        server_default=\"{}\",\n        default={},\n    )\n    active_at = json_cast_property(db.DateTime(True), \"details\", \"active_at\", default=None)\n    _profile_image_url = json_cast_property(db.Text(), \"details\", \"profile_image_url\", default=None)\n    is_invitation_pending = json_cast_property(db.Boolean(True), \"details\", \"is_invitation_pending\", default=False)\n    is_email_verified = json_cast_property(db.Boolean(True), \"details\", \"is_email_verified\", default=True)\n\n    __tablename__ = \"users\"\n    __table_args__ = (db.Index(\"users_org_id_email\", \"org_id\", \"email\", unique=True),)\n\n    def __str__(self):\n        return \"%s (%s)\" % (self.name, self.email)\n\n    def __init__(self, *args, **kwargs):\n        if kwargs.get(\"email\") is not None:\n            kwargs[\"email\"] = kwargs[\"email\"].lower()\n        super(User, self).__init__(*args, **kwargs)\n\n    @property\n    def is_disabled(self):\n        return self.disabled_at is not None\n\n    def disable(self):\n        self.disabled_at = db.func.now()\n\n    def enable(self):\n        self.disabled_at = None\n\n    def regenerate_api_key(self):\n        self.api_key = generate_token(40)\n\n    def to_dict(self, with_api_key=False):\n        profile_image_url = self.profile_image_url\n        if self.is_disabled:\n            assets = current_app.extensions[\"webpack\"][\"assets\"] or {}\n            path = \"images/avatar.svg\"\n            profile_image_url = url_for(\"static\", filename=assets.get(path, path))\n\n        d = {\n            \"id\": self.id,\n            \"name\": self.name,\n            \"email\": self.email,\n            \"profile_image_url\": profile_image_url,\n            \"groups\": self.group_ids,\n            \"updated_at\": self.updated_at,\n            \"created_at\": self.created_at,\n            \"disabled_at\": self.disabled_at,\n            \"is_disabled\": self.is_disabled,\n            \"active_at\": self.active_at,\n            \"is_invitation_pending\": self.is_invitation_pending,\n            \"is_email_verified\": self.is_email_verified,\n        }\n\n        if self.password_hash is None:\n            d[\"auth_type\"] = \"external\"\n        else:\n            d[\"auth_type\"] = \"password\"\n\n        if with_api_key:\n            d[\"api_key\"] = self.api_key\n\n        return d\n\n    @staticmethod\n    def is_api_user():\n        return False\n\n    @property\n    def profile_image_url(self):\n        if self._profile_image_url:\n            return self._profile_image_url\n\n        email_md5 = hashlib.md5(self.email.lower().encode(), usedforsecurity=False).hexdigest()\n        return \"https://www.gravatar.com/avatar/{}?s=40&d=identicon\".format(email_md5)\n\n    @property\n    def permissions(self):\n        # TODO: this should be cached.\n        return list(itertools.chain(*[g.permissions for g in Group.query.filter(Group.id.in_(self.group_ids))]))\n\n    @classmethod\n    def get_by_org(cls, org):\n        return cls.query.filter(cls.org == org)\n\n    @classmethod\n    def get_by_id(cls, _id):\n        return cls.query.filter(cls.id == _id).one()\n\n    @classmethod\n    def get_by_email_and_org(cls, email, org):\n        return cls.get_by_org(org).filter(cls.email == email).one()\n\n    @classmethod\n    def get_by_api_key_and_org(cls, api_key, org):\n        return cls.get_by_org(org).filter(cls.api_key == api_key).one()\n\n    @classmethod\n    def all(cls, org):\n        return cls.get_by_org(org).filter(cls.disabled_at.is_(None))\n\n    @classmethod\n    def all_disabled(cls, org):\n        return cls.get_by_org(org).filter(cls.disabled_at.isnot(None))\n\n    @classmethod\n    def search(cls, base_query, term):\n        term = \"%{}%\".format(term)\n        search_filter = or_(cls.name.ilike(term), cls.email.like(term))\n\n        return base_query.filter(search_filter)\n\n    @classmethod\n    def pending(cls, base_query, pending):\n        if pending:\n            return base_query.filter(cls.is_invitation_pending.is_(True))\n        else:\n            return base_query.filter(cls.is_invitation_pending.isnot(True))  # check for both `false`/`null`\n\n    @classmethod\n    def find_by_email(cls, email):\n        return cls.query.filter(cls.email == email)\n\n    def hash_password(self, password):\n        self.password_hash = pwd_context.hash(password)\n\n    def verify_password(self, password):\n        return self.password_hash and pwd_context.verify(password, self.password_hash)\n\n    def update_group_assignments(self, group_names):\n        groups = Group.find_by_name(self.org, group_names)\n        groups.append(self.org.default_group)\n        self.group_ids = [g.id for g in groups]\n        db.session.add(self)\n        db.session.commit()\n\n    def has_access(self, obj, access_type):\n        return AccessPermission.exists(obj, access_type, grantee=self)\n\n    def get_id(self):\n        identity = hashlib.md5(\n            \"{},{}\".format(self.email, self.password_hash).encode(), usedforsecurity=False\n        ).hexdigest()\n        return \"{0}-{1}\".format(self.id, identity)\n\n    def get_actual_user(self):\n        return repr(self) if self.is_api_user() else self.email\n\n\n@generic_repr(\"id\", \"name\", \"type\", \"org_id\")\nclass Group(db.Model, BelongsToOrgMixin):\n    DEFAULT_PERMISSIONS = [\n        \"create_dashboard\",\n        \"create_query\",\n        \"edit_dashboard\",\n        \"edit_query\",\n        \"view_query\",\n        \"view_source\",\n        \"execute_query\",\n        \"list_users\",\n        \"schedule_query\",\n        \"list_dashboards\",\n        \"list_alerts\",\n        \"list_data_sources\",\n    ]\n    ADMIN_PERMISSIONS = [\"admin\", \"super_admin\"]\n\n    BUILTIN_GROUP = \"builtin\"\n    REGULAR_GROUP = \"regular\"\n\n    id = primary_key(\"Group\")\n    data_sources = db.relationship(\"DataSourceGroup\", back_populates=\"group\", cascade=\"all\")\n    org_id = Column(key_type(\"Organization\"), db.ForeignKey(\"organizations.id\"))\n    org = db.relationship(\"Organization\", back_populates=\"groups\")\n    type = Column(db.String(255), default=REGULAR_GROUP)\n    name = Column(db.String(100))\n    permissions = Column(ARRAY(db.String(255)), default=DEFAULT_PERMISSIONS)\n    created_at = Column(db.DateTime(True), default=db.func.now())\n\n    __tablename__ = \"groups\"\n\n    def __str__(self):\n        return str(self.id)\n\n    def to_dict(self):\n        return {\n            \"id\": self.id,\n            \"name\": self.name,\n            \"permissions\": self.permissions,\n            \"type\": self.type,\n            \"created_at\": self.created_at,\n        }\n\n    @classmethod\n    def all(cls, org):\n        return cls.query.filter(cls.org == org)\n\n    @classmethod\n    def members(cls, group_id):\n        return User.query.filter(User.group_ids.any(group_id))\n\n    @classmethod\n    def find_by_name(cls, org, group_names):\n        result = cls.query.filter(cls.org == org, cls.name.in_(group_names))\n        return list(result)\n\n\n@generic_repr(\"id\", \"object_type\", \"object_id\", \"access_type\", \"grantor_id\", \"grantee_id\")\nclass AccessPermission(GFKBase, db.Model):\n    id = primary_key(\"AccessPermission\")\n    # 'object' defined in GFKBase\n    access_type = Column(db.String(255))\n    grantor_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"))\n    grantor = db.relationship(User, backref=\"grantor\", foreign_keys=[grantor_id])\n    grantee_id = Column(key_type(\"User\"), db.ForeignKey(\"users.id\"))\n    grantee = db.relationship(User, backref=\"grantee\", foreign_keys=[grantee_id])\n\n    __tablename__ = \"access_permissions\"\n\n    @classmethod\n    def grant(cls, obj, access_type, grantee, grantor):\n        grant = cls.query.filter(\n            cls.object_type == obj.__tablename__,\n            cls.object_id == obj.id,\n            cls.access_type == access_type,\n            cls.grantee == grantee,\n            cls.grantor == grantor,\n        ).one_or_none()\n\n        if not grant:\n            grant = cls(\n                object_type=obj.__tablename__,\n                object_id=obj.id,\n                access_type=access_type,\n                grantee=grantee,\n                grantor=grantor,\n            )\n            db.session.add(grant)\n\n        return grant\n\n    @classmethod\n    def revoke(cls, obj, grantee, access_type=None):\n        permissions = cls._query(obj, access_type, grantee)\n        return permissions.delete()\n\n    @classmethod\n    def find(cls, obj, access_type=None, grantee=None, grantor=None):\n        return cls._query(obj, access_type, grantee, grantor)\n\n    @classmethod\n    def exists(cls, obj, access_type, grantee):\n        return cls.find(obj, access_type, grantee).count() > 0\n\n    @classmethod\n    def _query(cls, obj, access_type=None, grantee=None, grantor=None):\n        q = cls.query.filter(cls.object_id == obj.id, cls.object_type == obj.__tablename__)\n\n        if access_type:\n            q = q.filter(AccessPermission.access_type == access_type)\n\n        if grantee:\n            q = q.filter(AccessPermission.grantee == grantee)\n\n        if grantor:\n            q = q.filter(AccessPermission.grantor == grantor)\n\n        return q\n\n    def to_dict(self):\n        d = {\n            \"id\": self.id,\n            \"object_id\": self.object_id,\n            \"object_type\": self.object_type,\n            \"access_type\": self.access_type,\n            \"grantor\": self.grantor_id,\n            \"grantee\": self.grantee_id,\n        }\n        return d\n\n\nclass AnonymousUser(AnonymousUserMixin, PermissionsCheckMixin):\n    @property\n    def permissions(self):\n        return []\n\n    @staticmethod\n    def is_api_user():\n        return False\n\n\nclass ApiUser(UserMixin, PermissionsCheckMixin):\n    def __init__(self, api_key, org, groups, name=None):\n        self.object = None\n        if isinstance(api_key, str):\n            self.id = api_key\n            self.name = name\n        else:\n            self.id = api_key.api_key\n            self.name = \"ApiKey: {}\".format(api_key.id)\n            self.object = api_key.object\n        self.group_ids = groups\n        self.org = org\n\n    def __repr__(self):\n        return \"<{}>\".format(self.name)\n\n    @staticmethod\n    def is_api_user():\n        return True\n\n    @property\n    def org_id(self):\n        if not self.org:\n            return None\n        return self.org.id\n\n    @property\n    def permissions(self):\n        return [\"view_query\"]\n\n    @staticmethod\n    def has_access(obj, access_type):\n        return False\n\n    def get_actual_user(self):\n        return repr(self)\n"
  },
  {
    "path": "redash/monitor.py",
    "content": "from funcy import flatten\nfrom rq import Queue, Worker\nfrom rq.job import Job\nfrom rq.registry import StartedJobRegistry\n\nfrom redash import __version__, redis_connection, rq_redis_connection, settings\nfrom redash.models import Dashboard, Query, QueryResult, Widget, db\n\n\ndef get_redis_status():\n    info = redis_connection.info()\n    return {\n        \"redis_used_memory\": info[\"used_memory\"],\n        \"redis_used_memory_human\": info[\"used_memory_human\"],\n    }\n\n\ndef get_object_counts():\n    status = {}\n    status[\"queries_count\"] = Query.query.count()\n    if settings.FEATURE_SHOW_QUERY_RESULTS_COUNT:\n        status[\"query_results_count\"] = QueryResult.query.count()\n        status[\"unused_query_results_count\"] = QueryResult.unused(settings.QUERY_RESULTS_CLEANUP_MAX_AGE).count()\n    status[\"dashboards_count\"] = Dashboard.query.count()\n    status[\"widgets_count\"] = Widget.query.count()\n    return status\n\n\ndef get_queues_status():\n    return {queue.name: {\"size\": len(queue)} for queue in Queue.all(connection=rq_redis_connection)}\n\n\ndef get_db_sizes():\n    database_metrics = []\n    queries = [\n        [\n            \"Query Results Size\",\n            \"select pg_total_relation_size('query_results') as size from (select 1) as a\",\n        ],\n        [\"Redash DB Size\", \"select pg_database_size(current_database()) as size\"],\n    ]\n    for query_name, query in queries:\n        result = db.session.execute(query).first()\n        database_metrics.append([query_name, result[0]])\n\n    return database_metrics\n\n\ndef get_status():\n    status = {\"version\": __version__, \"workers\": []}\n    status.update(get_redis_status())\n    status.update(get_object_counts())\n    status[\"manager\"] = redis_connection.hgetall(\"redash:status\")\n    status[\"manager\"][\"queues\"] = get_queues_status()\n    status[\"database_metrics\"] = {}\n    status[\"database_metrics\"][\"metrics\"] = get_db_sizes()\n\n    return status\n\n\ndef rq_job_ids():\n    queues = Queue.all(connection=rq_redis_connection)\n\n    started_jobs = [StartedJobRegistry(queue=q).get_job_ids() for q in queues]\n    queued_jobs = [q.job_ids for q in queues]\n\n    return flatten(started_jobs + queued_jobs)\n\n\ndef fetch_jobs(job_ids):\n    return [\n        {\n            \"id\": job.id,\n            \"name\": job.func_name,\n            \"origin\": job.origin,\n            \"enqueued_at\": job.enqueued_at,\n            \"started_at\": job.started_at,\n            \"meta\": job.meta,\n        }\n        for job in Job.fetch_many(job_ids, connection=rq_redis_connection)\n        if job is not None\n    ]\n\n\ndef rq_queues():\n    return {\n        q.name: {\n            \"name\": q.name,\n            \"started\": fetch_jobs(StartedJobRegistry(queue=q).get_job_ids()),\n            \"queued\": len(q.job_ids),\n        }\n        for q in sorted(Queue.all(), key=lambda q: q.name)\n    }\n\n\ndef describe_job(job):\n    return \"{} ({})\".format(job.id, job.func_name.split(\".\").pop()) if job else None\n\n\ndef rq_workers():\n    return [\n        {\n            \"name\": w.name,\n            \"hostname\": w.hostname,\n            \"pid\": w.pid,\n            \"queues\": \", \".join([q.name for q in w.queues]),\n            \"state\": w.state,\n            \"last_heartbeat\": w.last_heartbeat,\n            \"birth_date\": w.birth_date,\n            \"current_job\": describe_job(w.get_current_job()),\n            \"successful_jobs\": w.successful_job_count,\n            \"failed_jobs\": w.failed_job_count,\n            \"total_working_time\": w.total_working_time,\n        }\n        for w in Worker.all()\n    ]\n\n\ndef rq_status():\n    return {\"queues\": rq_queues(), \"workers\": rq_workers()}\n"
  },
  {
    "path": "redash/permissions.py",
    "content": "import functools\n\nfrom flask_login import current_user\nfrom flask_restful import abort\nfrom funcy import flatten\n\nview_only = True\nnot_view_only = False\n\nACCESS_TYPE_VIEW = \"view\"\nACCESS_TYPE_MODIFY = \"modify\"\nACCESS_TYPE_DELETE = \"delete\"\n\nACCESS_TYPES = (ACCESS_TYPE_VIEW, ACCESS_TYPE_MODIFY, ACCESS_TYPE_DELETE)\n\n\ndef has_access(obj, user, need_view_only):\n    if hasattr(obj, \"api_key\") and user.is_api_user():\n        return has_access_to_object(obj, user.id, need_view_only)\n    else:\n        return has_access_to_groups(obj, user, need_view_only)\n\n\ndef has_access_to_object(obj, api_key, need_view_only):\n    if obj.api_key == api_key:\n        return need_view_only\n    elif hasattr(obj, \"dashboard_api_keys\"):\n        # check if api_key belongs to a dashboard containing this query\n        return api_key in obj.dashboard_api_keys and need_view_only\n    else:\n        return False\n\n\ndef has_access_to_groups(obj, user, need_view_only):\n    groups = obj.groups if hasattr(obj, \"groups\") else obj\n\n    if \"admin\" in user.permissions:\n        return True\n\n    matching_groups = set(groups.keys()).intersection(user.group_ids)\n\n    if not matching_groups:\n        return False\n\n    required_level = 1 if need_view_only else 2\n\n    group_level = 1 if all(flatten([groups[group] for group in matching_groups])) else 2\n\n    return required_level <= group_level\n\n\ndef require_access(obj, user, need_view_only):\n    if not has_access(obj, user, need_view_only):\n        abort(403)\n\n\nclass require_permissions:\n    def __init__(self, permissions, allow_one=False):\n        self.permissions = permissions\n        self.allow_one = allow_one\n\n    def __call__(self, fn):\n        @functools.wraps(fn)\n        def decorated(*args, **kwargs):\n            if self.allow_one:\n                has_permissions = any([current_user.has_permission(permission) for permission in self.permissions])\n            else:\n                has_permissions = current_user.has_permissions(self.permissions)\n\n            if has_permissions:\n                return fn(*args, **kwargs)\n            else:\n                abort(403)\n\n        return decorated\n\n\ndef require_permission(permission):\n    return require_permissions((permission,))\n\n\ndef require_any_of_permission(permissions):\n    return require_permissions(permissions, True)\n\n\ndef require_admin(fn):\n    return require_permission(\"admin\")(fn)\n\n\ndef require_super_admin(fn):\n    return require_permission(\"super_admin\")(fn)\n\n\ndef has_permission_or_owner(permission, object_owner_id):\n    return int(object_owner_id) == current_user.id or current_user.has_permission(permission)\n\n\ndef is_admin_or_owner(object_owner_id):\n    return has_permission_or_owner(\"admin\", object_owner_id)\n\n\ndef require_permission_or_owner(permission, object_owner_id):\n    if not has_permission_or_owner(permission, object_owner_id):\n        abort(403)\n\n\ndef require_admin_or_owner(object_owner_id):\n    if not is_admin_or_owner(object_owner_id):\n        abort(403, message=\"You don't have permission to edit this resource.\")\n\n\ndef can_modify(obj, user):\n    return is_admin_or_owner(obj.user_id) or user.has_access(obj, ACCESS_TYPE_MODIFY)\n\n\ndef require_object_modify_permission(obj, user):\n    if not can_modify(obj, user):\n        abort(403)\n"
  },
  {
    "path": "redash/query_runner/__init__.py",
    "content": "import logging\nfrom collections import defaultdict\nfrom contextlib import ExitStack\nfrom functools import wraps\n\nimport sqlparse\nfrom dateutil import parser\nfrom rq.timeouts import JobTimeoutException\nfrom sshtunnel import open_tunnel\n\nfrom redash import settings, utils\nfrom redash.utils.requests_session import (\n    UnacceptableAddressException,\n    requests_or_advocate,\n    requests_session,\n)\n\nlogger = logging.getLogger(__name__)\n\n__all__ = [\n    \"BaseQueryRunner\",\n    \"BaseHTTPQueryRunner\",\n    \"InterruptException\",\n    \"JobTimeoutException\",\n    \"BaseSQLQueryRunner\",\n    \"TYPE_DATETIME\",\n    \"TYPE_BOOLEAN\",\n    \"TYPE_INTEGER\",\n    \"TYPE_STRING\",\n    \"TYPE_DATE\",\n    \"TYPE_FLOAT\",\n    \"SUPPORTED_COLUMN_TYPES\",\n    \"register\",\n    \"get_query_runner\",\n    \"import_query_runners\",\n    \"guess_type\",\n]\n\n# Valid types of columns returned in results:\nTYPE_INTEGER = \"integer\"\nTYPE_FLOAT = \"float\"\nTYPE_BOOLEAN = \"boolean\"\nTYPE_STRING = \"string\"\nTYPE_DATETIME = \"datetime\"\nTYPE_DATE = \"date\"\n\nSUPPORTED_COLUMN_TYPES = set([TYPE_INTEGER, TYPE_FLOAT, TYPE_BOOLEAN, TYPE_STRING, TYPE_DATETIME, TYPE_DATE])\n\n\ndef split_sql_statements(query):\n    def strip_trailing_comments(stmt):\n        idx = len(stmt.tokens) - 1\n        while idx >= 0:\n            tok = stmt.tokens[idx]\n            if tok.is_whitespace or sqlparse.utils.imt(tok, i=sqlparse.sql.Comment, t=sqlparse.tokens.Comment):\n                stmt.tokens[idx] = sqlparse.sql.Token(sqlparse.tokens.Whitespace, \" \")\n            else:\n                break\n            idx -= 1\n        return stmt\n\n    def strip_trailing_semicolon(stmt):\n        idx = len(stmt.tokens) - 1\n        while idx >= 0:\n            tok = stmt.tokens[idx]\n            # we expect that trailing comments already are removed\n            if not tok.is_whitespace:\n                if sqlparse.utils.imt(tok, t=sqlparse.tokens.Punctuation) and tok.value == \";\":\n                    stmt.tokens[idx] = sqlparse.sql.Token(sqlparse.tokens.Whitespace, \" \")\n                break\n            idx -= 1\n        return stmt\n\n    def is_empty_statement(stmt):\n        # copy statement object. `copy.deepcopy` fails to do this, so just re-parse it\n        st = sqlparse.engine.FilterStack()\n        st.stmtprocess.append(sqlparse.filters.StripCommentsFilter())\n        stmt = next(st.run(str(stmt)), None)\n        if stmt is None:\n            return True\n\n        return str(stmt).strip() == \"\"\n\n    stack = sqlparse.engine.FilterStack()\n\n    result = [stmt for stmt in stack.run(query)]\n    result = [strip_trailing_comments(stmt) for stmt in result]\n    result = [strip_trailing_semicolon(stmt) for stmt in result]\n    result = [str(stmt).strip() for stmt in result if not is_empty_statement(stmt)]\n\n    if len(result) > 0:\n        return result\n\n    return [\"\"]  # if all statements were empty - return a single empty statement\n\n\ndef combine_sql_statements(queries):\n    return \";\\n\".join(queries)\n\n\ndef find_last_keyword_idx(parsed_query):\n    for i in reversed(range(len(parsed_query.tokens))):\n        if parsed_query.tokens[i].ttype in sqlparse.tokens.Keyword:\n            return i\n    return -1\n\n\nclass InterruptException(Exception):\n    pass\n\n\nclass NotSupported(Exception):\n    pass\n\n\nclass BaseQueryRunner:\n    deprecated = False\n    should_annotate_query = True\n    noop_query = None\n    limit_query = \" LIMIT 1000\"\n    limit_keywords = [\"LIMIT\", \"OFFSET\"]\n    limit_after_select = False\n\n    def __init__(self, configuration):\n        self.syntax = \"sql\"\n        self.configuration = configuration\n\n    @classmethod\n    def name(cls):\n        return cls.__name__\n\n    @classmethod\n    def type(cls):\n        return cls.__name__.lower()\n\n    @classmethod\n    def enabled(cls):\n        return True\n\n    @property\n    def host(self):\n        \"\"\"Returns this query runner's configured host.\n        This is used primarily for temporarily swapping endpoints when using SSH tunnels to connect to a data source.\n\n        `BaseQueryRunner`'s naïve implementation supports query runner implementations that store endpoints using `host` and `port`\n        configuration values. If your query runner uses a different schema (e.g. a web address), you should override this function.\n        \"\"\"\n        if \"host\" in self.configuration:\n            return self.configuration[\"host\"]\n        else:\n            raise NotImplementedError()\n\n    @host.setter\n    def host(self, host):\n        \"\"\"Sets this query runner's configured host.\n        This is used primarily for temporarily swapping endpoints when using SSH tunnels to connect to a data source.\n\n        `BaseQueryRunner`'s naïve implementation supports query runner implementations that store endpoints using `host` and `port`\n        configuration values. If your query runner uses a different schema (e.g. a web address), you should override this function.\n        \"\"\"\n        if \"host\" in self.configuration:\n            self.configuration[\"host\"] = host\n        else:\n            raise NotImplementedError()\n\n    @property\n    def port(self):\n        \"\"\"Returns this query runner's configured port.\n        This is used primarily for temporarily swapping endpoints when using SSH tunnels to connect to a data source.\n\n        `BaseQueryRunner`'s naïve implementation supports query runner implementations that store endpoints using `host` and `port`\n        configuration values. If your query runner uses a different schema (e.g. a web address), you should override this function.\n        \"\"\"\n        if \"port\" in self.configuration:\n            return self.configuration[\"port\"]\n        else:\n            raise NotImplementedError()\n\n    @port.setter\n    def port(self, port):\n        \"\"\"Sets this query runner's configured port.\n        This is used primarily for temporarily swapping endpoints when using SSH tunnels to connect to a data source.\n\n        `BaseQueryRunner`'s naïve implementation supports query runner implementations that store endpoints using `host` and `port`\n        configuration values. If your query runner uses a different schema (e.g. a web address), you should override this function.\n        \"\"\"\n        if \"port\" in self.configuration:\n            self.configuration[\"port\"] = port\n        else:\n            raise NotImplementedError()\n\n    @classmethod\n    def configuration_schema(cls):\n        return {}\n\n    def annotate_query(self, query, metadata):\n        if not self.should_annotate_query:\n            return query\n\n        annotation = \", \".join([\"{}: {}\".format(k, v) for k, v in metadata.items()])\n        annotated_query = \"/* {} */ {}\".format(annotation, query)\n        return annotated_query\n\n    def test_connection(self):\n        if self.noop_query is None:\n            raise NotImplementedError()\n        data, error = self.run_query(self.noop_query, None)\n\n        if error is not None:\n            raise Exception(error)\n\n    def run_query(self, query, user):\n        raise NotImplementedError()\n\n    def fetch_columns(self, columns):\n        column_names = set()\n        duplicates_counters = defaultdict(int)\n        new_columns = []\n\n        for col in columns:\n            column_name = col[0]\n            while column_name in column_names:\n                duplicates_counters[col[0]] += 1\n                column_name = \"{}{}\".format(col[0], duplicates_counters[col[0]])\n\n            column_names.add(column_name)\n            new_columns.append({\"name\": column_name, \"friendly_name\": column_name, \"type\": col[1]})\n\n        return new_columns\n\n    def get_schema(self, get_stats=False):\n        raise NotSupported()\n\n    def _handle_run_query_error(self, error):\n        if error is None:\n            return\n\n        logger.error(error)\n        raise Exception(f\"Error during query execution. Reason: {error}\")\n\n    def _run_query_internal(self, query):\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            raise Exception(\"Failed running query [%s].\" % query)\n        return results[\"rows\"]\n\n    @classmethod\n    def to_dict(cls):\n        return {\n            \"name\": cls.name(),\n            \"type\": cls.type(),\n            \"configuration_schema\": cls.configuration_schema(),\n            **({\"deprecated\": True} if cls.deprecated else {}),\n        }\n\n    @property\n    def supports_auto_limit(self):\n        return False\n\n    def apply_auto_limit(self, query_text, should_apply_auto_limit):\n        return query_text\n\n    def gen_query_hash(self, query_text, set_auto_limit=False):\n        query_text = self.apply_auto_limit(query_text, set_auto_limit)\n        return utils.gen_query_hash(query_text)\n\n\nclass BaseSQLQueryRunner(BaseQueryRunner):\n    def get_schema(self, get_stats=False):\n        schema_dict = {}\n        self._get_tables(schema_dict)\n        if settings.SCHEMA_RUN_TABLE_SIZE_CALCULATIONS and get_stats:\n            self._get_tables_stats(schema_dict)\n        return list(schema_dict.values())\n\n    def _get_tables(self, schema_dict):\n        return []\n\n    def _get_tables_stats(self, tables_dict):\n        for t in tables_dict.keys():\n            if isinstance(tables_dict[t], dict):\n                res = self._run_query_internal(\"select count(*) as cnt from %s\" % t)\n                tables_dict[t][\"size\"] = res[0][\"cnt\"]\n\n    @property\n    def supports_auto_limit(self):\n        return True\n\n    def query_is_select_no_limit(self, query):\n        parsed_query_list = sqlparse.parse(query)\n        if len(parsed_query_list) == 0:\n            return False\n        parsed_query = parsed_query_list[0]\n        last_keyword_idx = find_last_keyword_idx(parsed_query)\n        # Either invalid query or query that is not select\n        if last_keyword_idx == -1 or parsed_query.tokens[0].value.upper() != \"SELECT\":\n            return False\n\n        no_limit = parsed_query.tokens[last_keyword_idx].value.upper() not in self.limit_keywords\n\n        return no_limit\n\n    def add_limit_to_query(self, query):\n        parsed_query = sqlparse.parse(query)[0]\n        limit_tokens = sqlparse.parse(self.limit_query)[0].tokens\n        length = len(parsed_query.tokens)\n        if not self.limit_after_select:\n            if parsed_query.tokens[length - 1].ttype == sqlparse.tokens.Punctuation:\n                parsed_query.tokens[length - 1 : length - 1] = limit_tokens\n            else:\n                parsed_query.tokens += limit_tokens\n        else:\n            for i in range(length - 1, -1, -1):\n                if parsed_query[i].value.upper() == \"SELECT\":\n                    index = parsed_query.token_index(parsed_query[i + 1])\n                    parsed_query = sqlparse.sql.Statement(\n                        parsed_query.tokens[:index] + limit_tokens + parsed_query.tokens[index:]\n                    )\n                    break\n        return str(parsed_query)\n\n    def apply_auto_limit(self, query_text, should_apply_auto_limit):\n        queries = split_sql_statements(query_text)\n        if should_apply_auto_limit:\n            # we only check for last one in the list because it is the one that we show result\n            last_query = queries[-1]\n            if self.query_is_select_no_limit(last_query):\n                queries[-1] = self.add_limit_to_query(last_query)\n        return combine_sql_statements(queries)\n\n\nclass BaseHTTPQueryRunner(BaseQueryRunner):\n    should_annotate_query = False\n    response_error = \"Endpoint returned unexpected status code\"\n    requires_authentication = False\n    requires_url = True\n    url_title = \"URL base path\"\n    username_title = \"HTTP Basic Auth Username\"\n    password_title = \"HTTP Basic Auth Password\"\n\n    @classmethod\n    def configuration_schema(cls):\n        schema = {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\", \"title\": cls.url_title},\n                \"username\": {\"type\": \"string\", \"title\": cls.username_title},\n                \"password\": {\"type\": \"string\", \"title\": cls.password_title},\n            },\n            \"secret\": [\"password\"],\n            \"order\": [\"url\", \"username\", \"password\"],\n        }\n\n        if cls.requires_url or cls.requires_authentication:\n            schema[\"required\"] = []\n\n        if cls.requires_url:\n            schema[\"required\"] += [\"url\"]\n\n        if cls.requires_authentication:\n            schema[\"required\"] += [\"username\", \"password\"]\n        return schema\n\n    def get_auth(self):\n        username = self.configuration.get(\"username\")\n        password = self.configuration.get(\"password\")\n        if username and password:\n            return (username, password)\n        if self.requires_authentication:\n            raise ValueError(\"Username and Password required\")\n        else:\n            return None\n\n    def get_response(self, url, auth=None, http_method=\"get\", **kwargs):\n        # Get authentication values if not given\n        if auth is None:\n            auth = self.get_auth()\n\n        # Then call requests to get the response from the given endpoint\n        # URL optionally, with the additional requests parameters.\n        error = None\n        response = None\n        try:\n            response = requests_session.request(http_method, url, auth=auth, **kwargs)\n            # Raise a requests HTTP exception with the appropriate reason\n            # for 4xx and 5xx response status codes which is later caught\n            # and passed back.\n            response.raise_for_status()\n\n            # Any other responses (e.g. 2xx and 3xx):\n            if response.status_code != 200:\n                error = \"{} ({}).\".format(self.response_error, response.status_code)\n\n        except requests_or_advocate.HTTPError as exc:\n            logger.exception(exc)\n            error = \"Failed to execute query. \"\n            f\"Return Code: {response.status_code} Reason: {response.text}\"\n        except UnacceptableAddressException as exc:\n            logger.exception(exc)\n            error = \"Can't query private addresses.\"\n        except requests_or_advocate.RequestException as exc:\n            # Catch all other requests exceptions and return the error.\n            logger.exception(exc)\n            error = str(exc)\n\n        # Return response and error.\n        return response, error\n\n\nquery_runners = {}\n\n\ndef register(query_runner_class):\n    global query_runners\n    if query_runner_class.enabled():\n        logger.debug(\n            \"Registering %s (%s) query runner.\",\n            query_runner_class.name(),\n            query_runner_class.type(),\n        )\n        query_runners[query_runner_class.type()] = query_runner_class\n    else:\n        logger.debug(\n            \"%s query runner enabled but not supported, not registering. Either disable or install missing \"\n            \"dependencies.\",\n            query_runner_class.name(),\n        )\n\n\ndef get_query_runner(query_runner_type, configuration):\n    query_runner_class = query_runners.get(query_runner_type, None)\n    if query_runner_class is None:\n        return None\n\n    return query_runner_class(configuration)\n\n\ndef get_configuration_schema_for_query_runner_type(query_runner_type):\n    query_runner_class = query_runners.get(query_runner_type, None)\n    if query_runner_class is None:\n        return None\n\n    return query_runner_class.configuration_schema()\n\n\ndef import_query_runners(query_runner_imports):\n    for runner_import in query_runner_imports:\n        __import__(runner_import)\n\n\ndef guess_type(value):\n    if isinstance(value, bool):\n        return TYPE_BOOLEAN\n    elif isinstance(value, int):\n        return TYPE_INTEGER\n    elif isinstance(value, float):\n        return TYPE_FLOAT\n\n    return guess_type_from_string(value)\n\n\ndef guess_type_from_string(string_value):\n    if string_value == \"\" or string_value is None:\n        return TYPE_STRING\n\n    try:\n        int(string_value)\n        return TYPE_INTEGER\n    except (ValueError, OverflowError):\n        pass\n\n    try:\n        float(string_value)\n        return TYPE_FLOAT\n    except (ValueError, OverflowError):\n        pass\n\n    if str(string_value).lower() in (\"true\", \"false\"):\n        return TYPE_BOOLEAN\n\n    try:\n        parser.parse(string_value)\n        return TYPE_DATETIME\n    except (ValueError, OverflowError):\n        pass\n\n    return TYPE_STRING\n\n\ndef with_ssh_tunnel(query_runner, details):\n    def tunnel(f):\n        @wraps(f)\n        def wrapper(*args, **kwargs):\n            try:\n                remote_host, remote_port = query_runner.host, query_runner.port\n            except NotImplementedError:\n                raise NotImplementedError(\"SSH tunneling is not implemented for this query runner yet.\")\n\n            stack = ExitStack()\n            try:\n                bastion_address = (details[\"ssh_host\"], details.get(\"ssh_port\", 22))\n                remote_address = (remote_host, remote_port)\n                auth = {\n                    \"ssh_username\": details[\"ssh_username\"],\n                    **settings.dynamic_settings.ssh_tunnel_auth(),\n                }\n                server = stack.enter_context(open_tunnel(bastion_address, remote_bind_address=remote_address, **auth))\n            except Exception as error:\n                raise type(error)(\"SSH tunnel: {}\".format(str(error)))\n\n            with stack:\n                try:\n                    query_runner.host, query_runner.port = server.local_bind_address\n                    result = f(*args, **kwargs)\n                finally:\n                    query_runner.host, query_runner.port = remote_host, remote_port\n\n                return result\n\n        return wrapper\n\n    query_runner.run_query = tunnel(query_runner.run_query)\n\n    return query_runner\n"
  },
  {
    "path": "redash/query_runner/amazon_elasticsearch.py",
    "content": "from . import register\nfrom .elasticsearch2 import ElasticSearch2\n\ntry:\n    from botocore import credentials, session\n    from requests_aws_sign import AWSV4Sign\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\nclass AmazonElasticsearchService(ElasticSearch2):\n    @classmethod\n    def name(cls):\n        return \"Amazon Elasticsearch Service\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def type(cls):\n        return \"aws_es\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"server\": {\"type\": \"string\", \"title\": \"Endpoint\"},\n                \"region\": {\"type\": \"string\"},\n                \"access_key\": {\"type\": \"string\", \"title\": \"Access Key\"},\n                \"secret_key\": {\"type\": \"string\", \"title\": \"Secret Key\"},\n                \"use_aws_iam_profile\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Use AWS IAM Profile\",\n                },\n            },\n            \"secret\": [\"secret_key\"],\n            \"order\": [\n                \"server\",\n                \"region\",\n                \"access_key\",\n                \"secret_key\",\n                \"use_aws_iam_profile\",\n            ],\n            \"required\": [\"server\", \"region\"],\n        }\n\n    def __init__(self, configuration):\n        super(AmazonElasticsearchService, self).__init__(configuration)\n\n        region = configuration[\"region\"]\n        cred = None\n        if configuration.get(\"use_aws_iam_profile\", False):\n            cred = credentials.get_credentials(session.Session())\n        else:\n            cred = credentials.Credentials(\n                access_key=configuration.get(\"access_key\", \"\"),\n                secret_key=configuration.get(\"secret_key\", \"\"),\n            )\n\n        self.auth = AWSV4Sign(cred, region, \"es\")\n\n    def get_auth(self):\n        return self.auth\n\n\nregister(AmazonElasticsearchService)\n"
  },
  {
    "path": "redash/query_runner/arango.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_FLOAT,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    from arango import ArangoClient\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\n_TYPE_MAPPINGS = {\n    \"boolean\": TYPE_BOOLEAN,\n    \"number\": TYPE_FLOAT,\n    \"string\": TYPE_STRING,\n    \"array\": TYPE_STRING,\n    \"object\": TYPE_STRING,\n}\n\n\nclass Arango(BaseQueryRunner):\n    noop_query = \"RETURN {'id': 1}\"\n\n    @classmethod\n    def name(cls):\n        return \"ArangoDB\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"host\": {\"type\": \"string\", \"default\": \"127.0.0.1\"},\n                \"port\": {\"type\": \"number\", \"default\": 8529},\n                \"dbname\": {\"type\": \"string\", \"title\": \"Database Name\"},\n                \"timeout\": {\"type\": \"number\", \"default\": 0.0, \"title\": \"AQL Timeout in seconds (0 = no timeout)\"},\n            },\n            \"order\": [\"host\", \"port\", \"user\", \"password\", \"dbname\"],\n            \"required\": [\"host\", \"user\", \"password\", \"dbname\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        try:\n            import arango  # noqa: F401\n        except ImportError:\n            return False\n\n        return True\n\n    @classmethod\n    def type(cls):\n        return \"arangodb\"\n\n    def run_query(self, query, user):\n        client = ArangoClient(hosts=\"{}:{}\".format(self.configuration[\"host\"], self.configuration.get(\"port\", 8529)))\n        db = client.db(\n            self.configuration[\"dbname\"], username=self.configuration[\"user\"], password=self.configuration[\"password\"]\n        )\n\n        try:\n            cursor = db.aql.execute(query, max_runtime=self.configuration.get(\"timeout\", 0.0))\n            result = [i for i in cursor]\n            column_tuples = [(i, TYPE_STRING) for i in result[0].keys()]\n            columns = self.fetch_columns(column_tuples)\n            data = {\n                \"columns\": columns,\n                \"rows\": result,\n            }\n\n            error = None\n        except Exception:\n            raise\n\n        return data, error\n\n\nregister(Arango)\n"
  },
  {
    "path": "redash/query_runner/athena.py",
    "content": "import logging\nimport os\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\nfrom redash.settings import parse_boolean\n\nlogger = logging.getLogger(__name__)\nANNOTATE_QUERY = parse_boolean(os.environ.get(\"ATHENA_ANNOTATE_QUERY\", \"true\"))\nSHOW_EXTRA_SETTINGS = parse_boolean(os.environ.get(\"ATHENA_SHOW_EXTRA_SETTINGS\", \"true\"))\nASSUME_ROLE = parse_boolean(os.environ.get(\"ATHENA_ASSUME_ROLE\", \"false\"))\nOPTIONAL_CREDENTIALS = parse_boolean(os.environ.get(\"ATHENA_OPTIONAL_CREDENTIALS\", \"true\"))\n\ntry:\n    import boto3\n    import pyathena\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\n_TYPE_MAPPINGS = {\n    \"boolean\": TYPE_BOOLEAN,\n    \"tinyint\": TYPE_INTEGER,\n    \"smallint\": TYPE_INTEGER,\n    \"integer\": TYPE_INTEGER,\n    \"bigint\": TYPE_INTEGER,\n    \"double\": TYPE_FLOAT,\n    \"varchar\": TYPE_STRING,\n    \"timestamp\": TYPE_DATETIME,\n    \"date\": TYPE_DATE,\n    \"varbinary\": TYPE_STRING,\n    \"array\": TYPE_STRING,\n    \"map\": TYPE_STRING,\n    \"row\": TYPE_STRING,\n    \"decimal\": TYPE_FLOAT,\n}\n\n\nclass SimpleFormatter:\n    def format(self, operation, parameters=None):\n        return operation\n\n\nclass Athena(BaseQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def name(cls):\n        return \"Amazon Athena\"\n\n    @classmethod\n    def configuration_schema(cls):\n        schema = {\n            \"type\": \"object\",\n            \"properties\": {\n                \"region\": {\"type\": \"string\", \"title\": \"AWS Region\"},\n                \"aws_access_key\": {\"type\": \"string\", \"title\": \"AWS Access Key\"},\n                \"aws_secret_key\": {\"type\": \"string\", \"title\": \"AWS Secret Key\"},\n                \"s3_staging_dir\": {\n                    \"type\": \"string\",\n                    \"title\": \"S3 Staging (Query Results) Bucket Path\",\n                },\n                \"schema\": {\n                    \"type\": \"string\",\n                    \"title\": \"Schema Name\",\n                    \"default\": \"default\",\n                },\n                \"glue\": {\"type\": \"boolean\", \"title\": \"Use Glue Data Catalog\"},\n                \"catalog_ids\": {\n                    \"type\": \"string\",\n                    \"title\": \"Enter Glue Data Catalog IDs, separated by commas (leave blank for default catalog)\",\n                },\n                \"work_group\": {\n                    \"type\": \"string\",\n                    \"title\": \"Athena Work Group\",\n                    \"default\": \"primary\",\n                },\n                \"cost_per_tb\": {\n                    \"type\": \"number\",\n                    \"title\": \"Athena cost per Tb scanned (USD)\",\n                    \"default\": 5,\n                },\n                \"result_reuse_enable\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Reuse Athena query results\",\n                },\n                \"result_reuse_minutes\": {\n                    \"type\": \"number\",\n                    \"title\": \"Minutes to reuse Athena query results\",\n                    \"default\": 60,\n                },\n            },\n            \"required\": [\"region\", \"s3_staging_dir\"],\n            \"extra_options\": [\"glue\", \"catalog_ids\", \"cost_per_tb\", \"result_reuse_enable\", \"result_reuse_minutes\"],\n            \"order\": [\n                \"region\",\n                \"s3_staging_dir\",\n                \"schema\",\n                \"work_group\",\n                \"cost_per_tb\",\n                \"result_reuse_enable\",\n                \"result_reuse_minutes\",\n            ],\n            \"secret\": [\"aws_secret_key\"],\n        }\n\n        if SHOW_EXTRA_SETTINGS:\n            schema[\"properties\"].update(\n                {\n                    \"encryption_option\": {\n                        \"type\": \"string\",\n                        \"title\": \"Encryption Option\",\n                    },\n                    \"kms_key\": {\"type\": \"string\", \"title\": \"KMS Key\"},\n                }\n            )\n            schema[\"extra_options\"].append(\"encryption_option\")\n            schema[\"extra_options\"].append(\"kms_key\")\n\n        if ASSUME_ROLE:\n            del schema[\"properties\"][\"aws_access_key\"]\n            del schema[\"properties\"][\"aws_secret_key\"]\n            schema[\"secret\"] = []\n\n            schema[\"order\"].insert(1, \"iam_role\")\n            schema[\"order\"].insert(2, \"external_id\")\n            schema[\"properties\"].update(\n                {\n                    \"iam_role\": {\"type\": \"string\", \"title\": \"IAM role to assume\"},\n                    \"external_id\": {\n                        \"type\": \"string\",\n                        \"title\": \"External ID to be used while STS assume role\",\n                    },\n                }\n            )\n        else:\n            schema[\"order\"].insert(1, \"aws_access_key\")\n            schema[\"order\"].insert(2, \"aws_secret_key\")\n\n        if not OPTIONAL_CREDENTIALS and not ASSUME_ROLE:\n            schema[\"required\"] += [\"aws_access_key\", \"aws_secret_key\"]\n\n        return schema\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    def annotate_query(self, query, metadata):\n        if ANNOTATE_QUERY:\n            return super(Athena, self).annotate_query(query, metadata)\n        return query\n\n    @classmethod\n    def type(cls):\n        return \"athena\"\n\n    def _get_iam_credentials(self, user=None):\n        if ASSUME_ROLE:\n            role_session_name = \"redash\" if user is None else user.email\n            sts = boto3.client(\"sts\")\n            creds = sts.assume_role(\n                RoleArn=self.configuration.get(\"iam_role\"),\n                RoleSessionName=role_session_name,\n                ExternalId=self.configuration.get(\"external_id\"),\n            )\n            return {\n                \"aws_access_key_id\": creds[\"Credentials\"][\"AccessKeyId\"],\n                \"aws_secret_access_key\": creds[\"Credentials\"][\"SecretAccessKey\"],\n                \"aws_session_token\": creds[\"Credentials\"][\"SessionToken\"],\n                \"region_name\": self.configuration[\"region\"],\n            }\n        else:\n            return {\n                \"aws_access_key_id\": self.configuration.get(\"aws_access_key\", None),\n                \"aws_secret_access_key\": self.configuration.get(\"aws_secret_key\", None),\n                \"region_name\": self.configuration[\"region\"],\n            }\n\n    def __get_schema_from_glue(self, catalog_id=\"\"):\n        client = boto3.client(\"glue\", **self._get_iam_credentials())\n        schema = {}\n\n        database_paginator = client.get_paginator(\"get_databases\")\n        table_paginator = client.get_paginator(\"get_tables\")\n\n        databases_iterator = database_paginator.paginate(\n            **({\"CatalogId\": catalog_id} if catalog_id != \"\" else {}),\n        )\n\n        for databases in databases_iterator:\n            for database in databases[\"DatabaseList\"]:\n                iterator = table_paginator.paginate(\n                    DatabaseName=database[\"Name\"],\n                    **({\"CatalogId\": catalog_id} if catalog_id != \"\" else {}),\n                )\n                for table in iterator.search(\"TableList[]\"):\n                    table_name = \"%s.%s\" % (database[\"Name\"], table[\"Name\"])\n                    if \"StorageDescriptor\" not in table:\n                        logger.warning(\"Glue table doesn't have StorageDescriptor: %s\", table_name)\n                        continue\n                    if table_name not in schema:\n                        schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n                    for column_data in table[\"StorageDescriptor\"][\"Columns\"]:\n                        column = {\n                            \"name\": column_data[\"Name\"],\n                            \"type\": column_data[\"Type\"] if \"Type\" in column_data else None,\n                        }\n                        schema[table_name][\"columns\"].append(column)\n                    for partition in table.get(\"PartitionKeys\", []):\n                        partition_column = {\n                            \"name\": partition[\"Name\"],\n                            \"type\": partition[\"Type\"] if \"Type\" in partition else None,\n                        }\n                        schema[table_name][\"columns\"].append(partition_column)\n        return list(schema.values())\n\n    def get_schema(self, get_stats=False):\n        if self.configuration.get(\"glue\", False):\n            catalog_ids = [id.strip() for id in self.configuration.get(\"catalog_ids\", \"\").split(\",\")]\n            return sum([self.__get_schema_from_glue(catalog_id) for catalog_id in catalog_ids], [])\n\n        schema = {}\n        query = \"\"\"\n        SELECT table_schema, table_name, column_name, data_type\n        FROM information_schema.columns\n        WHERE table_schema NOT IN ('information_schema')\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            table_name = \"{0}.{1}\".format(row[\"table_schema\"], row[\"table_name\"])\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n            schema[table_name][\"columns\"].append({\"name\": row[\"column_name\"], \"type\": row[\"data_type\"]})\n\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        cursor = pyathena.connect(\n            s3_staging_dir=self.configuration[\"s3_staging_dir\"],\n            schema_name=self.configuration.get(\"schema\", \"default\"),\n            encryption_option=self.configuration.get(\"encryption_option\", None),\n            kms_key=self.configuration.get(\"kms_key\", None),\n            work_group=self.configuration.get(\"work_group\", \"primary\"),\n            formatter=SimpleFormatter(),\n            result_reuse_enable=self.configuration.get(\"result_reuse_enable\", False),\n            result_reuse_minutes=self.configuration.get(\"result_reuse_minutes\", 60),\n            **self._get_iam_credentials(user=user),\n        ).cursor()\n\n        try:\n            cursor.execute(query)\n            column_tuples = [(i[0], _TYPE_MAPPINGS.get(i[1], None)) for i in cursor.description]\n            columns = self.fetch_columns(column_tuples)\n            rows = [dict(zip(([c[\"name\"] for c in columns]), r)) for i, r in enumerate(cursor.fetchall())]\n            qbytes = None\n            athena_query_id = None\n            try:\n                qbytes = cursor.data_scanned_in_bytes\n            except AttributeError as e:\n                logger.debug(\"Athena Upstream can't get data_scanned_in_bytes: %s\", e)\n            try:\n                athena_query_id = cursor.query_id\n            except AttributeError as e:\n                logger.debug(\"Athena Upstream can't get query_id: %s\", e)\n\n            price = self.configuration.get(\"cost_per_tb\", 5)\n            data = {\n                \"columns\": columns,\n                \"rows\": rows,\n                \"metadata\": {\n                    \"data_scanned\": qbytes,\n                    \"athena_query_id\": athena_query_id,\n                    \"query_cost\": price * qbytes * 10e-12,\n                },\n            }\n\n            error = None\n        except Exception:\n            if cursor.query_id:\n                cursor.cancel()\n            raise\n\n        return data, error\n\n\nregister(Athena)\n"
  },
  {
    "path": "redash/query_runner/axibase_tsd.py",
    "content": "import csv\nimport logging\nimport uuid\n\nfrom redash.query_runner import (\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    InterruptException,\n    JobTimeoutException,\n    register,\n)\nfrom redash.utils import json_loads\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import atsd_client\n    from atsd_client.exceptions import SQLException\n    from atsd_client.services import MetricsService, SQLService\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\ntypes_map = {\n    \"long\": TYPE_INTEGER,\n    \"bigint\": TYPE_INTEGER,\n    \"integer\": TYPE_INTEGER,\n    \"smallint\": TYPE_INTEGER,\n    \"float\": TYPE_FLOAT,\n    \"double\": TYPE_FLOAT,\n    \"decimal\": TYPE_FLOAT,\n    \"string\": TYPE_STRING,\n    \"date\": TYPE_DATE,\n    \"xsd:dateTimeStamp\": TYPE_DATETIME,\n}\n\n\ndef resolve_redash_type(type_in_atsd):\n    \"\"\"\n    Retrieve corresponding redash type\n    :param type_in_atsd: `str`\n    :return: redash type constant\n    \"\"\"\n    if isinstance(type_in_atsd, dict):\n        type_in_redash = types_map.get(type_in_atsd[\"base\"])\n    else:\n        type_in_redash = types_map.get(type_in_atsd)\n    return type_in_redash\n\n\ndef generate_rows_and_columns(csv_response):\n    \"\"\"\n    Prepare rows and columns in redash format from ATSD csv response\n    :param csv_response: `str`\n    :return: prepared rows and columns\n    \"\"\"\n    meta, data = csv_response.split(\"\\n\", 1)\n    meta = meta[1:]\n\n    meta_with_padding = meta + \"=\" * (4 - len(meta) % 4)\n    meta_decoded = meta_with_padding.decode(\"base64\")\n    meta_json = json_loads(meta_decoded)\n    meta_columns = meta_json[\"tableSchema\"][\"columns\"]\n\n    reader = csv.reader(data.splitlines())\n    next(reader)\n\n    columns = [\n        {\n            \"friendly_name\": i[\"titles\"],\n            \"type\": resolve_redash_type(i[\"datatype\"]),\n            \"name\": i[\"name\"],\n        }\n        for i in meta_columns\n    ]\n    column_names = [c[\"name\"] for c in columns]\n    rows = [dict(zip(column_names, row)) for row in reader]\n    return columns, rows\n\n\nclass AxibaseTSD(BaseQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def name(cls):\n        return \"Axibase Time Series Database\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"protocol\": {\"type\": \"string\", \"title\": \"Protocol\", \"default\": \"http\"},\n                \"hostname\": {\n                    \"type\": \"string\",\n                    \"title\": \"Host\",\n                    \"default\": \"axibase_tsd_hostname\",\n                },\n                \"port\": {\"type\": \"number\", \"title\": \"Port\", \"default\": 8088},\n                \"username\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\", \"title\": \"Password\"},\n                \"timeout\": {\n                    \"type\": \"number\",\n                    \"default\": 600,\n                    \"title\": \"Connection Timeout\",\n                },\n                \"min_insert_date\": {\n                    \"type\": \"string\",\n                    \"title\": \"Metric Minimum Insert Date\",\n                },\n                \"expression\": {\"type\": \"string\", \"title\": \"Metric Filter\"},\n                \"limit\": {\"type\": \"number\", \"default\": 5000, \"title\": \"Metric Limit\"},\n                \"trust_certificate\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Trust SSL Certificate\",\n                },\n            },\n            \"required\": [\"username\", \"password\", \"hostname\", \"protocol\", \"port\"],\n            \"secret\": [\"password\"],\n        }\n\n    def __init__(self, configuration):\n        super(AxibaseTSD, self).__init__(configuration)\n        self.url = \"{0}://{1}:{2}\".format(\n            self.configuration.get(\"protocol\", \"http\"),\n            self.configuration.get(\"hostname\", \"localhost\"),\n            self.configuration.get(\"port\", 8088),\n        )\n\n    def run_query(self, query, user):\n        connection = atsd_client.connect_url(\n            self.url,\n            self.configuration.get(\"username\"),\n            self.configuration.get(\"password\"),\n            verify=self.configuration.get(\"trust_certificate\", False),\n            timeout=self.configuration.get(\"timeout\", 600),\n        )\n        sql = SQLService(connection)\n        query_id = str(uuid.uuid4())\n\n        try:\n            logger.debug(\"SQL running query: %s\", query)\n            data = sql.query_with_params(\n                query,\n                {\"outputFormat\": \"csv\", \"metadataFormat\": \"EMBED\", \"queryId\": query_id},\n            )\n\n            columns, rows = generate_rows_and_columns(data)\n\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n\n        except SQLException as e:\n            data = None\n            error = e.content\n        except (KeyboardInterrupt, InterruptException, JobTimeoutException):\n            sql.cancel_query(query_id)\n            raise\n\n        return data, error\n\n    def get_schema(self, get_stats=False):\n        connection = atsd_client.connect_url(\n            self.url,\n            self.configuration.get(\"username\"),\n            self.configuration.get(\"password\"),\n            verify=self.configuration.get(\"trust_certificate\", False),\n            timeout=self.configuration.get(\"timeout\", 600),\n        )\n        metrics = MetricsService(connection)\n        ml = metrics.list(\n            expression=self.configuration.get(\"expression\", None),\n            minInsertDate=self.configuration.get(\"min_insert_date\", None),\n            limit=self.configuration.get(\"limit\", 5000),\n        )\n        metrics_list = [i.name for i in ml]\n        metrics_list.append(\"atsd_series\")\n        schema = {}\n        default_columns = [\n            \"entity\",\n            \"datetime\",\n            \"time\",\n            \"metric\",\n            \"value\",\n            \"text\",\n            \"tags\",\n            \"entity.tags\",\n            \"metric.tags\",\n        ]\n        for table_name in metrics_list:\n            schema[table_name] = {\n                \"name\": \"'{}'\".format(table_name),\n                \"columns\": default_columns,\n            }\n        values = list(schema.values())\n        return values\n\n\nregister(AxibaseTSD)\n"
  },
  {
    "path": "redash/query_runner/azure_kusto.py",
    "content": "from redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\nfrom redash.utils import json_loads\n\ntry:\n    from azure.kusto.data import (\n        ClientRequestProperties,\n        KustoClient,\n        KustoConnectionStringBuilder,\n    )\n    from azure.kusto.data.exceptions import KustoServiceError\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nTYPES_MAP = {\n    \"boolean\": TYPE_BOOLEAN,\n    \"datetime\": TYPE_DATETIME,\n    \"date\": TYPE_DATE,\n    \"dynamic\": TYPE_STRING,\n    \"guid\": TYPE_STRING,\n    \"int\": TYPE_INTEGER,\n    \"long\": TYPE_INTEGER,\n    \"real\": TYPE_FLOAT,\n    \"string\": TYPE_STRING,\n    \"timespan\": TYPE_STRING,\n    \"decimal\": TYPE_FLOAT,\n}\n\n\ndef _get_data_scanned(kusto_response):\n    try:\n        metadata_table = next(\n            (table for table in kusto_response.tables if table.table_name == \"QueryCompletionInformation\"),\n            None,\n        )\n\n        if metadata_table:\n            resource_usage_json = next(\n                (row[\"Payload\"] for row in metadata_table.rows if row[\"EventTypeName\"] == \"QueryResourceConsumption\"),\n                \"{}\",\n            )\n            resource_usage = json_loads(resource_usage_json).get(\"resource_usage\", {})\n\n        data_scanned = (\n            resource_usage[\"cache\"][\"shards\"][\"cold\"][\"hitbytes\"]\n            + resource_usage[\"cache\"][\"shards\"][\"cold\"][\"missbytes\"]\n            + resource_usage[\"cache\"][\"shards\"][\"hot\"][\"hitbytes\"]\n            + resource_usage[\"cache\"][\"shards\"][\"hot\"][\"missbytes\"]\n            + resource_usage[\"cache\"][\"shards\"][\"bypassbytes\"]\n        )\n\n    except Exception:\n        data_scanned = 0\n\n    return int(data_scanned)\n\n\nclass AzureKusto(BaseQueryRunner):\n    should_annotate_query = False\n    noop_query = \"let noop = datatable (Noop:string)[1]; noop\"\n\n    def __init__(self, configuration):\n        super(AzureKusto, self).__init__(configuration)\n        self.syntax = \"custom\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"cluster\": {\"type\": \"string\"},\n                \"azure_ad_client_id\": {\"type\": \"string\", \"title\": \"Azure AD Client ID\"},\n                \"azure_ad_client_secret\": {\n                    \"type\": \"string\",\n                    \"title\": \"Azure AD Client Secret\",\n                },\n                \"azure_ad_tenant_id\": {\"type\": \"string\", \"title\": \"Azure AD Tenant Id\"},\n                \"database\": {\"type\": \"string\"},\n                \"msi\": {\"type\": \"boolean\", \"title\": \"Use Managed Service Identity\"},\n                \"user_msi\": {\n                    \"type\": \"string\",\n                    \"title\": \"User-assigned managed identity client ID\",\n                },\n            },\n            \"required\": [\n                \"cluster\",\n                \"database\",\n            ],\n            \"order\": [\n                \"cluster\",\n                \"azure_ad_client_id\",\n                \"azure_ad_client_secret\",\n                \"azure_ad_tenant_id\",\n                \"database\",\n            ],\n            \"secret\": [\"azure_ad_client_secret\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def type(cls):\n        return \"azure_kusto\"\n\n    @classmethod\n    def name(cls):\n        return \"Azure Data Explorer (Kusto)\"\n\n    def run_query(self, query, user):\n        cluster = self.configuration[\"cluster\"]\n        msi = self.configuration.get(\"msi\", False)\n        # Managed Service Identity(MSI)\n        if msi:\n            # If user-assigned managed identity is used, the client ID must be provided\n            if self.configuration.get(\"user_msi\"):\n                kcsb = KustoConnectionStringBuilder.with_aad_managed_service_identity_authentication(\n                    cluster,\n                    client_id=self.configuration[\"user_msi\"],\n                )\n            else:\n                kcsb = KustoConnectionStringBuilder.with_aad_managed_service_identity_authentication(cluster)\n        # Service Principal auth\n        else:\n            aad_app_id = self.configuration.get(\"azure_ad_client_id\")\n            app_key = self.configuration.get(\"azure_ad_client_secret\")\n            authority_id = self.configuration.get(\"azure_ad_tenant_id\")\n\n            if not (aad_app_id and app_key and authority_id):\n                raise ValueError(\n                    \"Azure AD Client ID, Client Secret, and Tenant ID are required for Service Principal authentication.\"\n                )\n\n            kcsb = KustoConnectionStringBuilder.with_aad_application_key_authentication(\n                connection_string=cluster,\n                aad_app_id=aad_app_id,\n                app_key=app_key,\n                authority_id=authority_id,\n            )\n\n        client = KustoClient(kcsb)\n\n        request_properties = ClientRequestProperties()\n        request_properties.application = \"redash\"\n\n        if user:\n            request_properties.user = user.email\n            request_properties.set_option(\"request_description\", user.email)\n\n        db = self.configuration[\"database\"]\n        try:\n            response = client.execute(db, query, request_properties)\n\n            result_cols = response.primary_results[0].columns\n            result_rows = response.primary_results[0].rows\n\n            columns = []\n            rows = []\n            for c in result_cols:\n                columns.append(\n                    {\n                        \"name\": c.column_name,\n                        \"friendly_name\": c.column_name,\n                        \"type\": TYPES_MAP.get(c.column_type, None),\n                    }\n                )\n\n            # rows must be [{'column1': value, 'column2': value}]\n            for row in result_rows:\n                rows.append(row.to_dict())\n\n            error = None\n            data = {\n                \"columns\": columns,\n                \"rows\": rows,\n                \"metadata\": {\"data_scanned\": _get_data_scanned(response)},\n            }\n\n        except KustoServiceError as err:\n            data = None\n            error = str(err)\n\n        return data, error\n\n    def get_schema(self, get_stats=False):\n        query = \".show database schema as json\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        schema_as_json = json_loads(results[\"rows\"][0][\"DatabaseSchema\"])\n        tables_list = [\n            *(schema_as_json[\"Databases\"][self.configuration[\"database\"]][\"Tables\"].values()),\n            *(schema_as_json[\"Databases\"][self.configuration[\"database\"]][\"MaterializedViews\"].values()),\n        ]\n\n        schema = {}\n\n        for table in tables_list:\n            table_name = table[\"Name\"]\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            for column in table[\"OrderedColumns\"]:\n                schema[table_name][\"columns\"].append(\n                    {\"name\": column[\"Name\"], \"type\": TYPES_MAP.get(column[\"CslType\"], None)}\n                )\n\n        return list(schema.values())\n\n\nregister(AzureKusto)\n"
  },
  {
    "path": "redash/query_runner/big_query.py",
    "content": "import datetime\nimport logging\nimport socket\nimport time\nfrom base64 import b64decode\n\nfrom redash import settings\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    InterruptException,\n    JobTimeoutException,\n    register,\n)\nfrom redash.utils import json_loads\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import apiclient.errors\n    import google.auth\n    from apiclient.discovery import build\n    from apiclient.errors import HttpError  # noqa: F401\n    from google.oauth2.service_account import Credentials\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\ntypes_map = {\n    \"INTEGER\": TYPE_INTEGER,\n    \"FLOAT\": TYPE_FLOAT,\n    \"BOOLEAN\": TYPE_BOOLEAN,\n    \"STRING\": TYPE_STRING,\n    \"TIMESTAMP\": TYPE_DATETIME,\n    \"DATETIME\": TYPE_DATETIME,\n    \"DATE\": TYPE_DATE,\n}\n\n\ndef transform_cell(field_type, cell_value):\n    if cell_value is None:\n        return None\n    if field_type == \"INTEGER\":\n        return int(cell_value)\n    elif field_type == \"FLOAT\":\n        return float(cell_value)\n    elif field_type == \"BOOLEAN\":\n        return cell_value.lower() == \"true\"\n    elif field_type == \"TIMESTAMP\":\n        return datetime.datetime.fromtimestamp(float(cell_value))\n    return cell_value\n\n\ndef transform_row(row, fields):\n    row_data = {}\n\n    for column_index, cell in enumerate(row[\"f\"]):\n        field = fields[column_index]\n        if field.get(\"mode\") == \"REPEATED\":\n            cell_value = [transform_cell(field[\"type\"], item[\"v\"]) for item in cell[\"v\"]]\n        else:\n            cell_value = transform_cell(field[\"type\"], cell[\"v\"])\n\n        row_data[field[\"name\"]] = cell_value\n\n    return row_data\n\n\ndef _load_key(filename):\n    f = open(filename, \"rb\")\n    try:\n        return f.read()\n    finally:\n        f.close()\n\n\ndef _get_query_results(jobs, project_id, location, job_id, start_index):\n    query_reply = jobs.getQueryResults(\n        projectId=project_id, location=location, jobId=job_id, startIndex=start_index\n    ).execute()\n    logging.debug(\"query_reply %s\", query_reply)\n    if not query_reply[\"jobComplete\"]:\n        time.sleep(1)\n        return _get_query_results(jobs, project_id, location, job_id, start_index)\n\n    return query_reply\n\n\ndef _get_total_bytes_processed_for_resp(bq_response):\n    # BigQuery hides the total bytes processed for queries to tables with row-level access controls.\n    # For these queries the \"totalBytesProcessed\" field may not be defined in the response.\n    return int(bq_response.get(\"totalBytesProcessed\", \"0\"))\n\n\nclass BigQuery(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    def __init__(self, configuration):\n        super().__init__(configuration)\n        self.should_annotate_query = configuration.get(\"useQueryAnnotation\", False)\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"projectId\": {\"type\": \"string\", \"title\": \"Project ID\"},\n                \"jsonKeyFile\": {\"type\": \"string\", \"title\": \"JSON Key File (ADC is used if omitted)\"},\n                \"totalMBytesProcessedLimit\": {\n                    \"type\": \"number\",\n                    \"title\": \"Scanned Data Limit (MB)\",\n                },\n                \"userDefinedFunctionResourceUri\": {\n                    \"type\": \"string\",\n                    \"title\": \"UDF Source URIs (i.e. gs://bucket/date_utils.js, gs://bucket/string_utils.js )\",\n                },\n                \"useStandardSql\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Use Standard SQL\",\n                    \"default\": True,\n                },\n                \"location\": {\"type\": \"string\", \"title\": \"Processing Location\"},\n                \"loadSchema\": {\"type\": \"boolean\", \"title\": \"Load Schema\"},\n                \"maximumBillingTier\": {\n                    \"type\": \"number\",\n                    \"title\": \"Maximum Billing Tier\",\n                },\n                \"useQueryAnnotation\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Use Query Annotation\",\n                    \"default\": False,\n                },\n            },\n            \"required\": [\"projectId\"],\n            \"order\": [\n                \"projectId\",\n                \"jsonKeyFile\",\n                \"loadSchema\",\n                \"useStandardSql\",\n                \"location\",\n                \"totalMBytesProcessedLimit\",\n                \"maximumBillingTier\",\n                \"userDefinedFunctionResourceUri\",\n                \"useQueryAnnotation\",\n            ],\n            \"secret\": [\"jsonKeyFile\"],\n        }\n\n    def annotate_query(self, query, metadata):\n        # Remove \"Job ID\" before annotating the query to avoid cache misses\n        metadata = {k: v for k, v in metadata.items() if k != \"Job ID\"}\n        return super().annotate_query(query, metadata)\n\n    def _get_bigquery_service(self):\n        socket.setdefaulttimeout(settings.BIGQUERY_HTTP_TIMEOUT)\n\n        scopes = [\n            \"https://www.googleapis.com/auth/bigquery\",\n            \"https://www.googleapis.com/auth/drive\",\n        ]\n\n        try:\n            key = json_loads(b64decode(self.configuration[\"jsonKeyFile\"]))\n            creds = Credentials.from_service_account_info(key, scopes=scopes)\n        except KeyError:\n            creds = google.auth.default(scopes=scopes)[0]\n\n        return build(\"bigquery\", \"v2\", credentials=creds, cache_discovery=False)\n\n    def _get_project_id(self):\n        return self.configuration[\"projectId\"]\n\n    def _get_location(self):\n        return self.configuration.get(\"location\")\n\n    def _get_total_bytes_processed(self, jobs, query):\n        job_data = {\"query\": query, \"dryRun\": True}\n\n        if self._get_location():\n            job_data[\"location\"] = self._get_location()\n\n        if self.configuration.get(\"useStandardSql\", False):\n            job_data[\"useLegacySql\"] = False\n\n        response = jobs.query(projectId=self._get_project_id(), body=job_data).execute()\n        return _get_total_bytes_processed_for_resp(response)\n\n    def _get_job_data(self, query):\n        job_data = {\"configuration\": {\"query\": {\"query\": query}}}\n\n        if self._get_location():\n            job_data[\"jobReference\"] = {\"location\": self._get_location()}\n\n        if self.configuration.get(\"useStandardSql\", False):\n            job_data[\"configuration\"][\"query\"][\"useLegacySql\"] = False\n\n        if self.configuration.get(\"userDefinedFunctionResourceUri\"):\n            resource_uris = self.configuration[\"userDefinedFunctionResourceUri\"].split(\",\")\n            job_data[\"configuration\"][\"query\"][\"userDefinedFunctionResources\"] = [\n                {\"resourceUri\": resource_uri} for resource_uri in resource_uris\n            ]\n\n        if \"maximumBillingTier\" in self.configuration:\n            job_data[\"configuration\"][\"query\"][\"maximumBillingTier\"] = self.configuration[\"maximumBillingTier\"]\n\n        return job_data\n\n    def _get_query_result(self, jobs, query):\n        project_id = self._get_project_id()\n        job_data = self._get_job_data(query)\n        insert_response = jobs.insert(projectId=project_id, body=job_data).execute()\n        self.current_job_id = insert_response[\"jobReference\"][\"jobId\"]\n        self.current_job_location = insert_response[\"jobReference\"][\"location\"]\n        current_row = 0\n        query_reply = _get_query_results(\n            jobs,\n            project_id=project_id,\n            location=self.current_job_location,\n            job_id=self.current_job_id,\n            start_index=current_row,\n        )\n\n        logger.debug(\"bigquery replied: %s\", query_reply)\n\n        rows = []\n\n        while (\"rows\" in query_reply) and current_row < int(query_reply[\"totalRows\"]):\n            for row in query_reply[\"rows\"]:\n                rows.append(transform_row(row, query_reply[\"schema\"][\"fields\"]))\n\n            current_row += len(query_reply[\"rows\"])\n\n            query_result_request = {\n                \"projectId\": project_id,\n                \"jobId\": self.current_job_id,\n                \"startIndex\": current_row,\n                \"location\": self.current_job_location,\n            }\n\n            query_reply = jobs.getQueryResults(**query_result_request).execute()\n\n        columns = [\n            {\n                \"name\": f[\"name\"],\n                \"friendly_name\": f[\"name\"],\n                \"type\": \"string\" if f.get(\"mode\") == \"REPEATED\" else types_map.get(f[\"type\"], \"string\"),\n            }\n            for f in query_reply[\"schema\"][\"fields\"]\n        ]\n\n        data = {\n            \"columns\": columns,\n            \"rows\": rows,\n            \"metadata\": {\"data_scanned\": _get_total_bytes_processed_for_resp(query_reply)},\n        }\n\n        return data\n\n    def _get_columns_schema(self, table_data):\n        columns = []\n        for column in table_data.get(\"schema\", {}).get(\"fields\", []):\n            columns.extend(self._get_columns_schema_column(column))\n\n        project_id = self._get_project_id()\n        table_name = table_data[\"id\"].replace(\"%s:\" % project_id, \"\")\n        return {\"name\": table_name, \"columns\": columns}\n\n    def _get_columns_schema_column(self, column):\n        columns = []\n        if column[\"type\"] == \"RECORD\":\n            for field in column[\"fields\"]:\n                columns.append(\"{}.{}\".format(column[\"name\"], field[\"name\"]))\n        else:\n            columns.append(column[\"name\"])\n\n        return columns\n\n    def _get_project_datasets(self, project_id):\n        result = []\n        service = self._get_bigquery_service()\n\n        datasets = service.datasets().list(projectId=project_id).execute()\n        result.extend(datasets.get(\"datasets\", []))\n        nextPageToken = datasets.get(\"nextPageToken\", None)\n\n        while nextPageToken is not None:\n            datasets = service.datasets().list(projectId=project_id, pageToken=nextPageToken).execute()\n            result.extend(datasets.get(\"datasets\", []))\n            nextPageToken = datasets.get(\"nextPageToken\", None)\n\n        return result\n\n    def get_schema(self, get_stats=False):\n        if not self.configuration.get(\"loadSchema\", False):\n            return []\n\n        project_id = self._get_project_id()\n        datasets = self._get_project_datasets(project_id)\n\n        query_base = \"\"\"\n        SELECT table_schema, table_name, field_path, data_type, description\n        FROM `{dataset_id}`.INFORMATION_SCHEMA.COLUMN_FIELD_PATHS\n        WHERE table_schema NOT IN ('information_schema')\n        \"\"\"\n\n        table_query_base = \"\"\"\n        SELECT table_schema, table_name, JSON_VALUE(option_value) as table_description\n        FROM `{dataset_id}`.INFORMATION_SCHEMA.TABLE_OPTIONS\n        WHERE table_schema NOT IN ('information_schema')\n        AND option_name = 'description'\n        \"\"\"\n\n        location_dataset_ids = {}\n        schema = {}\n        for dataset in datasets:\n            dataset_id = dataset[\"datasetReference\"][\"datasetId\"]\n            location = dataset[\"location\"]\n            if self._get_location() and location != self._get_location():\n                logger.debug(\"dataset location is different: %s\", location)\n                continue\n\n            if location not in location_dataset_ids:\n                location_dataset_ids[location] = []\n            location_dataset_ids[location].append(dataset_id)\n\n        for location, datasets in location_dataset_ids.items():\n            queries = []\n            for dataset_id in datasets:\n                query = query_base.format(dataset_id=dataset_id)\n                queries.append(query)\n\n            query = \"\\nUNION ALL\\n\".join(queries)\n            results, error = self.run_query(query, None)\n            if error is not None:\n                self._handle_run_query_error(error)\n\n            for row in results[\"rows\"]:\n                table_name = \"{0}.{1}\".format(row[\"table_schema\"], row[\"table_name\"])\n                if table_name not in schema:\n                    schema[table_name] = {\"name\": table_name, \"columns\": []}\n                schema[table_name][\"columns\"].append(\n                    {\n                        \"name\": row[\"field_path\"],\n                        \"type\": row[\"data_type\"],\n                        \"description\": row[\"description\"],\n                    }\n                )\n\n            table_queries = []\n            for dataset_id in datasets:\n                table_query = table_query_base.format(dataset_id=dataset_id)\n                table_queries.append(table_query)\n\n            table_query = \"\\nUNION ALL\\n\".join(table_queries)\n            results, error = self.run_query(table_query, None)\n            if error is not None:\n                self._handle_run_query_error(error)\n\n            for row in results[\"rows\"]:\n                table_name = \"{0}.{1}\".format(row[\"table_schema\"], row[\"table_name\"])\n                if table_name not in schema:\n                    schema[table_name] = {\"name\": table_name, \"columns\": []}\n                if \"table_description\" in row:\n                    schema[table_name][\"description\"] = row[\"table_description\"]\n\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        logger.debug(\"BigQuery got query: %s\", query)\n\n        bigquery_service = self._get_bigquery_service()\n        jobs = bigquery_service.jobs()\n\n        try:\n            if \"totalMBytesProcessedLimit\" in self.configuration:\n                limitMB = self.configuration[\"totalMBytesProcessedLimit\"]\n                processedMB = self._get_total_bytes_processed(jobs, query) / 1000.0 / 1000.0\n                if limitMB < processedMB:\n                    return (\n                        None,\n                        \"Larger than %d MBytes will be processed (%f MBytes)\" % (limitMB, processedMB),\n                    )\n\n            data = self._get_query_result(jobs, query)\n            error = None\n\n        except apiclient.errors.HttpError as e:\n            data = None\n            if e.resp.status in [400, 404]:\n                error = json_loads(e.content)[\"error\"][\"message\"]\n            else:\n                error = e.content\n        except (KeyboardInterrupt, InterruptException, JobTimeoutException):\n            if self.current_job_id:\n                self._get_bigquery_service().jobs().cancel(\n                    projectId=self._get_project_id(),\n                    jobId=self.current_job_id,\n                    location=self.current_job_location,\n                ).execute()\n\n            raise\n\n        return data, error\n\n\nregister(BigQuery)\n"
  },
  {
    "path": "redash/query_runner/big_query_gce.py",
    "content": "import requests\n\ntry:\n    import google.auth\n    from apiclient.discovery import build\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nfrom redash.query_runner import register\n\nfrom .big_query import BigQuery\n\n\nclass BigQueryGCE(BigQuery):\n    @classmethod\n    def type(cls):\n        return \"bigquery_gce\"\n\n    @classmethod\n    def enabled(cls):\n        if not enabled:\n            return False\n\n        try:\n            # check if we're on a GCE instance\n            requests.get(\"http://metadata.google.internal\")\n        except requests.exceptions.ConnectionError:\n            return False\n\n        return True\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"totalMBytesProcessedLimit\": {\n                    \"type\": \"number\",\n                    \"title\": \"Total MByte Processed Limit\",\n                },\n                \"userDefinedFunctionResourceUri\": {\n                    \"type\": \"string\",\n                    \"title\": \"UDF Source URIs (i.e. gs://bucket/date_utils.js, gs://bucket/string_utils.js )\",\n                },\n                \"useStandardSql\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Use Standard SQL\",\n                    \"default\": True,\n                },\n                \"location\": {\n                    \"type\": \"string\",\n                    \"title\": \"Processing Location\",\n                    \"default\": \"US\",\n                },\n                \"loadSchema\": {\"type\": \"boolean\", \"title\": \"Load Schema\"},\n            },\n        }\n\n    def _get_project_id(self):\n        google.auth.default()[1]\n\n    def _get_bigquery_service(self):\n        creds = google.auth.default(scopes=[\"https://www.googleapis.com/auth/bigquery\"])[0]\n        return build(\"bigquery\", \"v2\", credentials=creds, cache_discovery=False)\n\n\nregister(BigQueryGCE)\n"
  },
  {
    "path": "redash/query_runner/cass.py",
    "content": "import logging\nimport os\nimport ssl\nfrom base64 import b64decode\nfrom tempfile import NamedTemporaryFile\n\nfrom redash.query_runner import BaseQueryRunner, register\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    from cassandra.auth import PlainTextAuthProvider\n    from cassandra.cluster import Cluster\n    from cassandra.util import sortedset\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\ndef generate_ssl_options_dict(protocol, cert_path=None):\n    ssl_options = {\"ssl_version\": getattr(ssl, protocol)}\n    if cert_path is not None:\n        ssl_options[\"ca_certs\"] = cert_path\n        ssl_options[\"cert_reqs\"] = ssl.CERT_REQUIRED\n    return ssl_options\n\n\nclass Cassandra(BaseQueryRunner):\n    noop_query = \"SELECT dateof(now()) FROM system.local\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def custom_json_encoder(cls, dec, o):\n        if isinstance(o, sortedset):\n            return list(o)\n        return None\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\", \"default\": 9042},\n                \"keyspace\": {\"type\": \"string\", \"title\": \"Keyspace name\"},\n                \"username\": {\"type\": \"string\", \"title\": \"Username\"},\n                \"password\": {\"type\": \"string\", \"title\": \"Password\"},\n                \"protocol\": {\n                    \"type\": \"number\",\n                    \"title\": \"Protocol Version\",\n                    \"default\": 3,\n                },\n                \"timeout\": {\"type\": \"number\", \"title\": \"Timeout\", \"default\": 10},\n                \"useSsl\": {\"type\": \"boolean\", \"title\": \"Use SSL\", \"default\": False},\n                \"sslCertificateFile\": {\"type\": \"string\", \"title\": \"SSL Certificate File\"},\n                \"sslProtocol\": {\n                    \"type\": \"string\",\n                    \"title\": \"SSL Protocol\",\n                    \"enum\": [\n                        \"PROTOCOL_SSLv23\",\n                        \"PROTOCOL_TLS\",\n                        \"PROTOCOL_TLS_CLIENT\",\n                        \"PROTOCOL_TLS_SERVER\",\n                        \"PROTOCOL_TLSv1\",\n                        \"PROTOCOL_TLSv1_1\",\n                        \"PROTOCOL_TLSv1_2\",\n                    ],\n                },\n            },\n            \"required\": [\"keyspace\", \"host\", \"useSsl\"],\n            \"secret\": [\"sslCertificateFile\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"Cassandra\"\n\n    def get_schema(self, get_stats=False):\n        query = \"\"\"\n        select release_version from system.local;\n        \"\"\"\n        results, error = self.run_query(query, None)\n        release_version = results[\"rows\"][0][\"release_version\"]\n\n        query = \"\"\"\n        SELECT table_name, column_name\n        FROM system_schema.columns\n        WHERE keyspace_name ='{}';\n        \"\"\".format(\n            self.configuration[\"keyspace\"]\n        )\n\n        if release_version.startswith(\"2\"):\n            query = \"\"\"\n                SELECT columnfamily_name AS table_name, column_name\n                FROM system.schema_columns\n                WHERE keyspace_name ='{}';\n                \"\"\".format(\n                self.configuration[\"keyspace\"]\n            )\n\n        results, error = self.run_query(query, None)\n\n        schema = {}\n        for row in results[\"rows\"]:\n            table_name = row[\"table_name\"]\n            column_name = row[\"column_name\"]\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n            schema[table_name][\"columns\"].append(column_name)\n\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        connection = None\n        cert_path = self._generate_cert_file()\n        if self.configuration.get(\"username\", \"\") and self.configuration.get(\"password\", \"\"):\n            auth_provider = PlainTextAuthProvider(\n                username=\"{}\".format(self.configuration.get(\"username\", \"\")),\n                password=\"{}\".format(self.configuration.get(\"password\", \"\")),\n            )\n            connection = Cluster(\n                [self.configuration.get(\"host\", \"\")],\n                auth_provider=auth_provider,\n                port=self.configuration.get(\"port\", \"\"),\n                protocol_version=self.configuration.get(\"protocol\", 3),\n                ssl_options=self._get_ssl_options(cert_path),\n            )\n        else:\n            connection = Cluster(\n                [self.configuration.get(\"host\", \"\")],\n                port=self.configuration.get(\"port\", \"\"),\n                protocol_version=self.configuration.get(\"protocol\", 3),\n                ssl_options=self._get_ssl_options(cert_path),\n            )\n        session = connection.connect()\n        session.set_keyspace(self.configuration[\"keyspace\"])\n        session.default_timeout = self.configuration.get(\"timeout\", 10)\n        logger.debug(\"Cassandra running query: %s\", query)\n        result = session.execute(query)\n        self._cleanup_cert_file(cert_path)\n\n        column_names = result.column_names\n\n        columns = self.fetch_columns([(c, \"string\") for c in column_names])\n\n        rows = [dict(zip(column_names, row)) for row in result]\n\n        data = {\"columns\": columns, \"rows\": rows}\n\n        return data, None\n\n    def _generate_cert_file(self):\n        cert_encoded_bytes = self.configuration.get(\"sslCertificateFile\", None)\n        if cert_encoded_bytes:\n            with NamedTemporaryFile(mode=\"w\", delete=False) as cert_file:\n                cert_bytes = b64decode(cert_encoded_bytes)\n                cert_file.write(cert_bytes.decode(\"utf-8\"))\n            return cert_file.name\n        return None\n\n    def _cleanup_cert_file(self, cert_path):\n        if cert_path:\n            os.remove(cert_path)\n\n    def _get_ssl_options(self, cert_path):\n        ssl_options = None\n        if self.configuration.get(\"useSsl\", False):\n            ssl_options = generate_ssl_options_dict(protocol=self.configuration[\"sslProtocol\"], cert_path=cert_path)\n        return ssl_options\n\n\nclass ScyllaDB(Cassandra):\n    @classmethod\n    def type(cls):\n        return \"scylla\"\n\n\nregister(Cassandra)\nregister(ScyllaDB)\n"
  },
  {
    "path": "redash/query_runner/clickhouse.py",
    "content": "import logging\nimport re\nfrom urllib.parse import urlparse\nfrom uuid import uuid4\n\nimport requests\n\nfrom redash.query_runner import (\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    register,\n    split_sql_statements,\n)\n\nlogger = logging.getLogger(__name__)\n\n\ndef split_multi_query(query):\n    return [st for st in split_sql_statements(query) if st != \"\"]\n\n\nclass ClickHouse(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\", \"default\": \"http://127.0.0.1:8123\"},\n                \"user\": {\"type\": \"string\", \"default\": \"default\"},\n                \"password\": {\"type\": \"string\"},\n                \"dbname\": {\"type\": \"string\", \"title\": \"Database Name\"},\n                \"timeout\": {\n                    \"type\": \"number\",\n                    \"title\": \"Request Timeout\",\n                    \"default\": 30,\n                },\n                \"verify\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Verify SSL certificate\",\n                    \"default\": True,\n                },\n            },\n            \"order\": [\"url\", \"user\", \"password\", \"dbname\"],\n            \"required\": [\"dbname\"],\n            \"extra_options\": [\"timeout\", \"verify\"],\n            \"secret\": [\"password\"],\n        }\n\n    @property\n    def _url(self):\n        return urlparse(self.configuration[\"url\"])\n\n    @_url.setter\n    def _url(self, url):\n        self.configuration[\"url\"] = url.geturl()\n\n    @property\n    def host(self):\n        return self._url.hostname\n\n    @host.setter\n    def host(self, host):\n        self._url = self._url._replace(netloc=\"{}:{}\".format(host, self._url.port))\n\n    @property\n    def port(self):\n        return self._url.port\n\n    @port.setter\n    def port(self, port):\n        self._url = self._url._replace(netloc=\"{}:{}\".format(self._url.hostname, port))\n\n    def _get_tables(self, schema):\n        query = \"\"\"\n        SELECT database, table, name, type as data_type\n        FROM system.columns\n        WHERE database NOT IN ('system', 'information_schema', 'INFORMATION_SCHEMA')\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            table_name = \"{}.{}\".format(row[\"database\"], row[\"table\"])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append({\"name\": row[\"name\"], \"type\": row[\"data_type\"]})\n\n        return list(schema.values())\n\n    def _send_query(self, data, session_id=None, session_check=None):\n        url = self.configuration.get(\"url\", \"http://127.0.0.1:8123\")\n        timeout = self.configuration.get(\"timeout\", 30)\n\n        params = {\n            \"user\": self.configuration.get(\"user\", \"default\"),\n            \"password\": self.configuration.get(\"password\", \"\"),\n            \"database\": self.configuration[\"dbname\"],\n            \"default_format\": \"JSON\",\n        }\n\n        if session_id:\n            params[\"session_id\"] = session_id\n            params[\"session_check\"] = \"1\" if session_check else \"0\"\n            params[\"session_timeout\"] = timeout\n\n        try:\n            verify = self.configuration.get(\"verify\", True)\n            r = requests.post(\n                url,\n                data=data.encode(\"utf-8\", \"ignore\"),\n                stream=False,\n                timeout=timeout,\n                params=params,\n                verify=verify,\n            )\n\n            if not r.ok:\n                raise Exception(r.text)\n\n            # In certain situations the response body can be empty even if the query was successful, for example\n            # when creating temporary tables.\n            if not r.text:\n                return {}\n\n            response = r.json()\n            if \"exception\" in response:\n                raise Exception(response[\"exception\"])\n\n            return response\n        except requests.RequestException as e:\n            if e.response:\n                details = \"({}, Status Code: {})\".format(e.__class__.__name__, e.response.status_code)\n            else:\n                details = \"({})\".format(e.__class__.__name__)\n            raise Exception(\"Connection error to: {} {}.\".format(url, details))\n\n    @staticmethod\n    def _define_column_type(column):\n        c = column.lower()\n        f = re.search(r\"^nullable\\((.*)\\)$\", c)\n        if f is not None:\n            c = f.group(1)\n        if c.startswith(\"int\") or c.startswith(\"uint\"):\n            return TYPE_INTEGER\n        elif c.startswith(\"float\"):\n            return TYPE_FLOAT\n        elif c == \"datetime\":\n            return TYPE_DATETIME\n        elif c == \"date\":\n            return TYPE_DATE\n        else:\n            return TYPE_STRING\n\n    def _clickhouse_query(self, query, session_id=None, session_check=None):\n        logger.debug(f\"{self.name()} is about to execute query: %s\", query)\n\n        query += \"\\nFORMAT JSON\"\n\n        response = self._send_query(query, session_id, session_check)\n\n        columns = []\n        columns_int64 = []  # db converts value to string if its type equals UInt64\n        columns_totals = {}\n\n        meta = response.get(\"meta\", [])\n        for r in meta:\n            column_name = r[\"name\"]\n            column_type = self._define_column_type(r[\"type\"])\n\n            if r[\"type\"] in (\"Int64\", \"UInt64\", \"Nullable(Int64)\", \"Nullable(UInt64)\"):\n                columns_int64.append(column_name)\n            else:\n                columns_totals[column_name] = \"Total\" if column_type == TYPE_STRING else None\n\n            columns.append({\"name\": column_name, \"friendly_name\": column_name, \"type\": column_type})\n\n        rows = response.get(\"data\", [])\n        for row in rows:\n            for column in columns_int64:\n                try:\n                    row[column] = int(row[column])\n                except TypeError:\n                    row[column] = None\n\n        if \"totals\" in response:\n            totals = response[\"totals\"]\n            for column, value in columns_totals.items():\n                totals[column] = value\n            rows.append(totals)\n\n        return {\"columns\": columns, \"rows\": rows}\n\n    def run_query(self, query, user):\n        queries = split_multi_query(query)\n\n        if not queries:\n            data = None\n            error = \"Query is empty\"\n            return data, error\n\n        try:\n            # If just one query was given no session is needed\n            if len(queries) == 1:\n                data = self._clickhouse_query(queries[0])\n            else:\n                # If more than one query was given, a session is needed. Parameter session_check must be false\n                # for the first query\n                session_id = \"redash_{}\".format(uuid4().hex)\n\n                data = self._clickhouse_query(queries[0], session_id, session_check=False)\n\n                for query in queries[1:]:\n                    data = self._clickhouse_query(query, session_id, session_check=True)\n\n            error = None\n        except Exception as e:\n            data = None\n            logging.exception(e)\n            error = str(e)\n        return data, error\n\n\nregister(ClickHouse)\n"
  },
  {
    "path": "redash/query_runner/cloudwatch.py",
    "content": "import datetime\n\nimport yaml\n\nfrom redash.query_runner import BaseQueryRunner, register\nfrom redash.utils import parse_human_time\n\ntry:\n    import boto3\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\ndef parse_response(results):\n    columns = [\n        {\"name\": \"id\", \"type\": \"string\"},\n        {\"name\": \"label\", \"type\": \"string\"},\n        {\"name\": \"timestamp\", \"type\": \"datetime\"},\n        {\"name\": \"value\", \"type\": \"float\"},\n    ]\n\n    rows = []\n\n    for metric in results:\n        for i, value in enumerate(metric[\"Values\"]):\n            rows.append(\n                {\n                    \"id\": metric[\"Id\"],\n                    \"label\": metric[\"Label\"],\n                    \"timestamp\": metric[\"Timestamps\"][i],\n                    \"value\": value,\n                }\n            )\n\n    return rows, columns\n\n\ndef parse_query(query):\n    query = yaml.safe_load(query)\n\n    for timeKey in [\"StartTime\", \"EndTime\"]:\n        if isinstance(query.get(timeKey), str):\n            query[timeKey] = int(parse_human_time(query[timeKey]).timestamp())\n    if not query.get(\"EndTime\"):\n        query[\"EndTime\"] = int(datetime.datetime.now().timestamp())\n\n    return query\n\n\nclass CloudWatch(BaseQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def name(cls):\n        return \"Amazon CloudWatch\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"region\": {\"type\": \"string\", \"title\": \"AWS Region\"},\n                \"aws_access_key\": {\"type\": \"string\", \"title\": \"AWS Access Key\"},\n                \"aws_secret_key\": {\"type\": \"string\", \"title\": \"AWS Secret Key\"},\n            },\n            \"required\": [\"region\", \"aws_access_key\", \"aws_secret_key\"],\n            \"order\": [\"region\", \"aws_access_key\", \"aws_secret_key\"],\n            \"secret\": [\"aws_secret_key\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    def __init__(self, configuration):\n        super(CloudWatch, self).__init__(configuration)\n        self.syntax = \"yaml\"\n\n    def test_connection(self):\n        self.get_schema()\n\n    def _get_client(self):\n        cloudwatch = boto3.client(\n            \"cloudwatch\",\n            region_name=self.configuration.get(\"region\"),\n            aws_access_key_id=self.configuration.get(\"aws_access_key\"),\n            aws_secret_access_key=self.configuration.get(\"aws_secret_key\"),\n        )\n        return cloudwatch\n\n    def get_schema(self, get_stats=False):\n        client = self._get_client()\n\n        paginator = client.get_paginator(\"list_metrics\")\n\n        metrics = {}\n        for page in paginator.paginate():\n            for metric in page[\"Metrics\"]:\n                if metric[\"Namespace\"] not in metrics:\n                    metrics[metric[\"Namespace\"]] = {\n                        \"name\": metric[\"Namespace\"],\n                        \"columns\": [],\n                    }\n\n                if metric[\"MetricName\"] not in metrics[metric[\"Namespace\"]][\"columns\"]:\n                    metrics[metric[\"Namespace\"]][\"columns\"].append(metric[\"MetricName\"])\n\n        return list(metrics.values())\n\n    def run_query(self, query, user):\n        cloudwatch = self._get_client()\n\n        query = parse_query(query)\n\n        results = []\n        paginator = cloudwatch.get_paginator(\"get_metric_data\")\n        for page in paginator.paginate(**query):\n            results += page[\"MetricDataResults\"]\n\n        rows, columns = parse_response(results)\n\n        return {\"rows\": rows, \"columns\": columns}, None\n\n\nregister(CloudWatch)\n"
  },
  {
    "path": "redash/query_runner/cloudwatch_insights.py",
    "content": "import datetime\nimport time\n\nimport yaml\n\nfrom redash.query_runner import BaseQueryRunner, register\nfrom redash.utils import parse_human_time\n\ntry:\n    import boto3\n    from botocore.exceptions import ParamValidationError  # noqa: F401\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nPOLL_INTERVAL = 3\nTIMEOUT = 180\n\n\ndef parse_response(response):\n    results = response[\"results\"]\n    rows = []\n    field_orders = {}\n\n    for row in results:\n        record = {}\n        rows.append(record)\n\n        for order, col in enumerate(row):\n            if col[\"field\"] == \"@ptr\":\n                continue\n            field = col[\"field\"]\n            record[field] = col[\"value\"]\n            field_orders[field] = max(field_orders.get(field, -1), order)\n\n    fields = sorted(field_orders, key=lambda f: field_orders[f])\n    cols = [\n        {\n            \"name\": f,\n            \"type\": \"datetime\" if f == \"@timestamp\" else \"string\",\n            \"friendly_name\": f,\n        }\n        for f in fields\n    ]\n    return {\n        \"columns\": cols,\n        \"rows\": rows,\n        \"metadata\": {\"data_scanned\": response[\"statistics\"][\"bytesScanned\"]},\n    }\n\n\ndef parse_query(query):\n    query = yaml.safe_load(query)\n\n    for timeKey in [\"startTime\", \"endTime\"]:\n        if isinstance(query.get(timeKey), str):\n            query[timeKey] = int(parse_human_time(query[timeKey]).timestamp())\n    if not query.get(\"endTime\"):\n        query[\"endTime\"] = int(datetime.datetime.now().timestamp())\n\n    return query\n\n\nclass CloudWatchInsights(BaseQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def name(cls):\n        return \"Amazon CloudWatch Logs Insights\"\n\n    @classmethod\n    def type(cls):\n        return \"cloudwatch_insights\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"region\": {\"type\": \"string\", \"title\": \"AWS Region\"},\n                \"aws_access_key\": {\"type\": \"string\", \"title\": \"AWS Access Key\"},\n                \"aws_secret_key\": {\"type\": \"string\", \"title\": \"AWS Secret Key\"},\n            },\n            \"required\": [\"region\", \"aws_access_key\", \"aws_secret_key\"],\n            \"order\": [\"region\", \"aws_access_key\", \"aws_secret_key\"],\n            \"secret\": [\"aws_secret_key\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    def __init__(self, configuration):\n        super(CloudWatchInsights, self).__init__(configuration)\n        self.syntax = \"yaml\"\n\n    def test_connection(self):\n        self.get_schema()\n\n    def _get_client(self):\n        cloudwatch = boto3.client(\n            \"logs\",\n            region_name=self.configuration.get(\"region\"),\n            aws_access_key_id=self.configuration.get(\"aws_access_key\"),\n            aws_secret_access_key=self.configuration.get(\"aws_secret_key\"),\n        )\n        return cloudwatch\n\n    def get_schema(self, get_stats=False):\n        client = self._get_client()\n\n        log_groups = []\n        paginator = client.get_paginator(\"describe_log_groups\")\n\n        for page in paginator.paginate():\n            for group in page[\"logGroups\"]:\n                group_name = group[\"logGroupName\"]\n                fields = client.get_log_group_fields(logGroupName=group_name)\n                log_groups.append(\n                    {\n                        \"name\": group_name,\n                        \"columns\": [field[\"name\"] for field in fields[\"logGroupFields\"]],\n                    }\n                )\n\n        return log_groups\n\n    def run_query(self, query, user):\n        logs = self._get_client()\n\n        query = parse_query(query)\n        query_id = logs.start_query(**query)[\"queryId\"]\n\n        elapsed = 0\n        while True:\n            result = logs.get_query_results(queryId=query_id)\n            if result[\"status\"] == \"Complete\":\n                data = parse_response(result)\n                break\n            if result[\"status\"] in (\"Failed\", \"Timeout\", \"Unknown\", \"Cancelled\"):\n                raise Exception(\"CloudWatch Insights Query Execution Status: {}\".format(result[\"status\"]))\n            elif elapsed > TIMEOUT:\n                raise Exception(\"Request exceeded timeout.\")\n            else:\n                time.sleep(POLL_INTERVAL)\n                elapsed += POLL_INTERVAL\n\n        return data, None\n\n\nregister(CloudWatchInsights)\n"
  },
  {
    "path": "redash/query_runner/corporate_memory.py",
    "content": "\"\"\"Provide the query runner for eccenca Corporate Memory.\n\nseeAlso: https://documentation.eccenca.com/\nseeAlso: https://eccenca.com/\n\"\"\"\n\nimport json\nimport logging\nfrom os import environ\n\nfrom redash.query_runner import BaseQueryRunner\n\nfrom . import register\n\ntry:\n    from cmem.cmempy.dp.proxy.graph import get_graphs_list\n    from cmem.cmempy.queries import (  # noqa: F401\n        QUERY_STRING,\n        QueryCatalog,\n        SparqlQuery,\n    )\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nlogger = logging.getLogger(__name__)\n\n\nclass CorporateMemoryQueryRunner(BaseQueryRunner):\n    \"\"\"Use eccenca Corporate Memory as redash data source\"\"\"\n\n    # These environment keys are used by cmempy\n    KNOWN_CONFIG_KEYS = (\n        \"CMEM_BASE_PROTOCOL\",\n        \"CMEM_BASE_DOMAIN\",\n        \"CMEM_BASE_URI\",\n        \"SSL_VERIFY\",\n        \"REQUESTS_CA_BUNDLE\",\n        \"DP_API_ENDPOINT\",\n        \"DI_API_ENDPOINT\",\n        \"OAUTH_TOKEN_URI\",\n        \"OAUTH_GRANT_TYPE\",\n        \"OAUTH_USER\",\n        \"OAUTH_PASSWORD\",\n        \"OAUTH_CLIENT_ID\",\n        \"OAUTH_CLIENT_SECRET\",\n    )\n\n    # These variables hold secret data and should NOT be logged\n    KNOWN_SECRET_KEYS = (\"OAUTH_PASSWORD\", \"OAUTH_CLIENT_SECRET\")\n\n    # This allows for an easy connection test\n    noop_query = \"SELECT ?noop WHERE {BIND('noop' as ?noop)}\"\n\n    # We do not want to have comment in our sparql queries\n    # FEATURE?: Implement annotate_query in case the metadata is useful somewhere\n    should_annotate_query = False\n\n    def __init__(self, configuration):\n        \"\"\"init the class and configuration\"\"\"\n        super(CorporateMemoryQueryRunner, self).__init__(configuration)\n        \"\"\"\n        FEATURE?: activate SPARQL support in the redash query editor\n            Currently SPARQL syntax seems not to be available for react-ace\n            component. However, the ace editor itself supports sparql mode:\n            https://github.com/ajaxorg/ace/blob/master/lib/ace/mode/sparql.js\n            then we can hopefully do: self.syntax = \"sparql\"\n        FEATURE?: implement the retrieve Query catalog URIs in order to use them in queries\n        FEATURE?: implement a way to use queries from the query catalog\n        FEATURE?: allow a checkbox to NOT use owl:imports imported graphs\n        FEATURE?: allow to use a context graph per data source\n        \"\"\"\n        self.configuration = configuration\n\n    def _setup_environment(self):\n        \"\"\"provide environment for cmempy\n\n        cmempy environment variables need to match key in the properties\n        object of the configuration_schema\n        \"\"\"\n        for key in self.KNOWN_CONFIG_KEYS:\n            if key in environ:\n                environ.pop(key)\n            value = self.configuration.get(key, None)\n            if value is not None:\n                environ[key] = str(value)\n                if key in self.KNOWN_SECRET_KEYS:\n                    logger.info(\"{} set by config\".format(key))\n                else:\n                    logger.info(\"{} set by config to {}\".format(key, environ[key]))\n\n    @staticmethod\n    def _transform_sparql_results(results):\n        \"\"\"transforms a SPARQL query result to a redash query result\n\n        source structure: SPARQL 1.1 Query Results JSON Format\n            - seeAlso: https://www.w3.org/TR/sparql11-results-json/\n\n        target structure: redash result set\n            there is no good documentation available\n            so here an example result set as needed for redash:\n            data = {\n                \"columns\": [ {\"name\": \"name\", \"type\": \"string\", \"friendly_name\": \"friendly name\"}],\n                \"rows\": [\n                    {\"name\": \"value 1\"},\n                    {\"name\": \"value 2\"}\n                ]}\n\n        FEATURE?: During the sparql_row loop, we could check the data types of the\n            values and, in case they are all the same, choose something better than\n            just string.\n        \"\"\"\n        logger.info(\"results are: {}\".format(results))\n        # Not sure why we do not use the json package here but all other\n        # query runner do it the same way :-)\n        sparql_results = results\n        # transform all bindings to redash rows\n        rows = []\n        for sparql_row in sparql_results[\"results\"][\"bindings\"]:\n            row = {}\n            for var in sparql_results[\"head\"][\"vars\"]:\n                try:\n                    row[var] = sparql_row[var][\"value\"]\n                except KeyError:\n                    # not bound SPARQL variables are set as empty strings\n                    row[var] = \"\"\n            rows.append(row)\n        # transform all vars to redash columns\n        columns = []\n        for var in sparql_results[\"head\"][\"vars\"]:\n            columns.append({\"name\": var, \"friendly_name\": var, \"type\": \"string\"})\n        # Not sure why we do not use the json package here but all other\n        # query runner do it the same way :-)\n        return {\"columns\": columns, \"rows\": rows}\n\n    @classmethod\n    def name(cls):\n        return \"eccenca Corporate Memory (with SPARQL)\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def type(cls):\n        return \"corporate_memory\"\n\n    def run_query(self, query, user):\n        \"\"\"send a sparql query to corporate memory\"\"\"\n        query_text = query\n        logger.info(\"about to execute query (user='{}'): {}\".format(user, query_text))\n        query = SparqlQuery(query_text)\n        query_type = query.get_query_type()\n        # type of None means, there is an error in the query\n        # so execution is at least tried on endpoint\n        if query_type not in [\"SELECT\", None]:\n            raise ValueError(\"Queries of type {} can not be processed by redash.\".format(query_type))\n\n        self._setup_environment()\n        try:\n            data = self._transform_sparql_results(query.get_results())\n        except Exception as error:\n            logger.info(\"Error: {}\".format(error))\n            try:\n                # try to load Problem Details for HTTP API JSON\n                details = json.loads(error.response.text)\n                error = \"\"\n                if \"title\" in details:\n                    error += details[\"title\"] + \": \"\n                if \"detail\" in details:\n                    error += details[\"detail\"]\n                    return None, error\n            except Exception:\n                pass\n\n            return None, error\n\n        error = None\n        return data, error\n\n    @classmethod\n    def configuration_schema(cls):\n        \"\"\"provide the configuration of the data source as json schema\"\"\"\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"CMEM_BASE_URI\": {\"type\": \"string\", \"title\": \"Base URL\"},\n                \"OAUTH_GRANT_TYPE\": {\n                    \"type\": \"string\",\n                    \"title\": \"Grant Type\",\n                    \"default\": \"client_credentials\",\n                    \"extendedEnum\": [\n                        {\"value\": \"client_credentials\", \"name\": \"client_credentials\"},\n                        {\"value\": \"password\", \"name\": \"password\"},\n                    ],\n                },\n                \"OAUTH_CLIENT_ID\": {\n                    \"type\": \"string\",\n                    \"title\": \"Client ID (e.g. cmem-service-account)\",\n                    \"default\": \"cmem-service-account\",\n                },\n                \"OAUTH_CLIENT_SECRET\": {\n                    \"type\": \"string\",\n                    \"title\": \"Client Secret - only needed for grant type 'client_credentials'\",\n                },\n                \"OAUTH_USER\": {\n                    \"type\": \"string\",\n                    \"title\": \"User account - only needed for grant type 'password'\",\n                },\n                \"OAUTH_PASSWORD\": {\n                    \"type\": \"string\",\n                    \"title\": \"User Password - only needed for grant type 'password'\",\n                },\n                \"SSL_VERIFY\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Verify SSL certificates for API requests\",\n                    \"default\": True,\n                },\n                \"REQUESTS_CA_BUNDLE\": {\n                    \"type\": \"string\",\n                    \"title\": \"Path to the CA Bundle file (.pem)\",\n                },\n            },\n            \"required\": [\"CMEM_BASE_URI\", \"OAUTH_GRANT_TYPE\", \"OAUTH_CLIENT_ID\"],\n            \"secret\": [\"OAUTH_CLIENT_SECRET\", \"OAUTH_PASSWORD\"],\n            \"extra_options\": [\n                \"OAUTH_GRANT_TYPE\",\n                \"OAUTH_USER\",\n                \"OAUTH_PASSWORD\",\n                \"SSL_VERIFY\",\n                \"REQUESTS_CA_BUNDLE\",\n            ],\n        }\n\n    def get_schema(self, get_stats=False):\n        \"\"\"Get the schema structure (prefixes, graphs).\"\"\"\n        schema = dict()\n        schema[\"1\"] = {\n            \"name\": \"-> Common Prefixes <-\",\n            \"columns\": self._get_common_prefixes_schema(),\n        }\n        schema[\"2\"] = {\"name\": \"-> Graphs <-\", \"columns\": self._get_graphs_schema()}\n        # schema.update(self._get_query_schema())\n        logger.info(schema.values())\n        return schema.values()\n\n    def _get_graphs_schema(self):\n        \"\"\"Get a list of readable graph FROM clause strings.\"\"\"\n        self._setup_environment()\n        graphs = []\n        for graph in get_graphs_list():\n            graphs.append(\"FROM <{}>\".format(graph[\"iri\"]))\n        return graphs\n\n    @staticmethod\n    def _get_common_prefixes_schema():\n        \"\"\"Get a list of SPARQL prefix declarations.\"\"\"\n        common_prefixes = [\n            \"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\",\n            \"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\",\n            \"PREFIX owl: <http://www.w3.org/2002/07/owl#>\",\n            \"PREFIX schema: <http://schema.org/>\",\n            \"PREFIX dct: <http://purl.org/dc/terms/>\",\n            \"PREFIX skos: <http://www.w3.org/2004/02/skos/core#>\",\n        ]\n        return common_prefixes\n\n\nregister(CorporateMemoryQueryRunner)\n"
  },
  {
    "path": "redash/query_runner/couchbase.py",
    "content": "import datetime\nimport logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\ntry:\n    import httplib2  # noqa: F401\n    import requests\nexcept ImportError as e:\n    logger.error(\"Failed to import: \" + str(e))\n\n\nTYPES_MAP = {\n    str: TYPE_STRING,\n    bytes: TYPE_STRING,\n    int: TYPE_INTEGER,\n    float: TYPE_FLOAT,\n    bool: TYPE_BOOLEAN,\n    datetime.datetime: TYPE_DATETIME,\n    datetime.datetime: TYPE_STRING,\n}\n\n\ndef _get_column_by_name(columns, column_name):\n    for c in columns:\n        if \"name\" in c and c[\"name\"] == column_name:\n            return c\n    return None\n\n\ndef parse_results(results):\n    rows = []\n    columns = []\n\n    for row in results:\n        parsed_row = {}\n        for key in row:\n            if isinstance(row[key], dict):\n                for inner_key in row[key]:\n                    column_name = \"{}.{}\".format(key, inner_key)\n                    if _get_column_by_name(columns, column_name) is None:\n                        columns.append(\n                            {\n                                \"name\": column_name,\n                                \"friendly_name\": column_name,\n                                \"type\": TYPES_MAP.get(type(row[key][inner_key]), TYPE_STRING),\n                            }\n                        )\n\n                    parsed_row[column_name] = row[key][inner_key]\n\n            else:\n                if _get_column_by_name(columns, key) is None:\n                    columns.append(\n                        {\n                            \"name\": key,\n                            \"friendly_name\": key,\n                            \"type\": TYPES_MAP.get(type(row[key]), TYPE_STRING),\n                        }\n                    )\n\n                parsed_row[key] = row[key]\n\n        rows.append(parsed_row)\n    return rows, columns\n\n\nclass Couchbase(BaseQueryRunner):\n    should_annotate_query = False\n    noop_query = \"Select 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"protocol\": {\"type\": \"string\", \"default\": \"http\"},\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\n                    \"type\": \"string\",\n                    \"title\": \"Port (Defaults: 8095 - Analytics, 8093 - N1QL)\",\n                    \"default\": \"8095\",\n                },\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n            },\n            \"required\": [\"host\", \"user\", \"password\"],\n            \"order\": [\"protocol\", \"host\", \"port\", \"user\", \"password\"],\n            \"secret\": [\"password\"],\n        }\n\n    def __init__(self, configuration):\n        super(Couchbase, self).__init__(configuration)\n\n    @classmethod\n    def enabled(cls):\n        return True\n\n    def test_connection(self):\n        self.call_service(self.noop_query, \"\")\n\n    def get_buckets(self, query, name_param):\n        defaultColumns = [\"meta().id\"]\n        result = self.call_service(query, \"\").json()[\"results\"]\n        schema = {}\n        for row in result:\n            table_name = row.get(name_param)\n            schema[table_name] = {\"name\": table_name, \"columns\": defaultColumns}\n\n        return list(schema.values())\n\n    def get_schema(self, get_stats=False):\n        try:\n            # Try fetch from Analytics\n            return self.get_buckets(\n                \"SELECT ds.GroupName as name FROM Metadata.`Dataset` ds where ds.DataverseName <> 'Metadata'\",\n                \"name\",\n            )\n        except Exception:\n            # Try fetch from N1QL\n            return self.get_buckets(\"select name from system:keyspaces\", \"name\")\n\n    def call_service(self, query, user):\n        try:\n            user = self.configuration.get(\"user\")\n            password = self.configuration.get(\"password\")\n            protocol = self.configuration.get(\"protocol\", \"http\")\n            host = self.configuration.get(\"host\")\n            port = self.configuration.get(\"port\", 8095)\n            params = {\"statement\": query}\n\n            url = \"%s://%s:%s/query/service\" % (protocol, host, port)\n\n            r = requests.post(url, params=params, auth=(user, password))\n            r.raise_for_status()\n            return r\n        except requests.exceptions.HTTPError as err:\n            if err.response.status_code == 401:\n                raise Exception(\"Wrong username/password\")\n            raise Exception(\"Couchbase connection error\")\n\n    def run_query(self, query, user):\n        result = self.call_service(query, user)\n\n        rows, columns = parse_results(result.json()[\"results\"])\n        data = {\"columns\": columns, \"rows\": rows}\n\n        return data, None\n\n    @classmethod\n    def name(cls):\n        return \"Couchbase\"\n\n\nregister(Couchbase)\n"
  },
  {
    "path": "redash/query_runner/csv.py",
    "content": "import io\nimport logging\n\nimport yaml\n\nfrom redash.query_runner import BaseQueryRunner, NotSupported, register\nfrom redash.utils.requests_session import (\n    UnacceptableAddressException,\n    requests_or_advocate,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import numpy as np\n    import pandas as pd\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\nclass CSV(BaseQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def name(cls):\n        return \"CSV\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {},\n        }\n\n    def __init__(self, configuration):\n        super(CSV, self).__init__(configuration)\n        self.syntax = \"yaml\"\n\n    def test_connection(self):\n        pass\n\n    def run_query(self, query, user):\n        path = \"\"\n        ua = \"\"\n        args = {}\n        try:\n            args = yaml.safe_load(query)\n            path = args[\"url\"]\n            args.pop(\"url\", None)\n            ua = args[\"user-agent\"]\n            args.pop(\"user-agent\", None)\n        except Exception:\n            pass\n\n        try:\n            response = requests_or_advocate.get(url=path, headers={\"User-agent\": ua})\n            workbook = pd.read_csv(io.BytesIO(response.content), sep=\",\", **args)\n\n            df = workbook.copy()\n            data = {\"columns\": [], \"rows\": []}\n            conversions = [\n                {\n                    \"pandas_type\": np.integer,\n                    \"redash_type\": \"integer\",\n                },\n                {\n                    \"pandas_type\": np.inexact,\n                    \"redash_type\": \"float\",\n                },\n                {\n                    \"pandas_type\": np.datetime64,\n                    \"redash_type\": \"datetime\",\n                    \"to_redash\": lambda x: x.strftime(\"%Y-%m-%d %H:%M:%S\"),\n                },\n                {\"pandas_type\": np.bool_, \"redash_type\": \"boolean\"},\n                {\"pandas_type\": np.object_, \"redash_type\": \"string\"},\n            ]\n            labels = []\n            for dtype, label in zip(df.dtypes, df.columns):\n                for conversion in conversions:\n                    if issubclass(dtype.type, conversion[\"pandas_type\"]):\n                        data[\"columns\"].append(\n                            {\"name\": label, \"friendly_name\": label, \"type\": conversion[\"redash_type\"]}\n                        )\n                        labels.append(label)\n                        func = conversion.get(\"to_redash\")\n                        if func:\n                            df[label] = df[label].apply(func)\n                        break\n            data[\"rows\"] = df[labels].replace({np.nan: None}).to_dict(orient=\"records\")\n\n            error = None\n        except KeyboardInterrupt:\n            error = \"Query cancelled by user.\"\n            data = None\n        except UnacceptableAddressException:\n            error = \"Can't query private addresses.\"\n            data = None\n        except Exception as e:\n            error = \"Error reading {0}. {1}\".format(path, str(e))\n            data = None\n\n        return data, error\n\n    def get_schema(self):\n        raise NotSupported()\n\n\nregister(CSV)\n"
  },
  {
    "path": "redash/query_runner/databend.py",
    "content": "try:\n    import re\n\n    from databend_sqlalchemy import connector\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nfrom redash.query_runner import (\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\n\nclass Databend(BaseQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\", \"default\": \"localhost\"},\n                \"port\": {\"type\": \"string\", \"default\": \"8000\"},\n                \"username\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\", \"default\": \"\"},\n                \"database\": {\"type\": \"string\"},\n                \"secure\": {\"type\": \"boolean\", \"default\": False},\n            },\n            \"order\": [\"username\", \"password\", \"host\", \"port\", \"database\"],\n            \"required\": [\"username\", \"database\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def name(cls):\n        return \"Databend\"\n\n    @classmethod\n    def type(cls):\n        return \"databend\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @staticmethod\n    def _define_column_type(column_type):\n        c = column_type.lower()\n        f = re.search(r\"^nullable\\((.*)\\)$\", c)\n        if f is not None:\n            c = f.group(1)\n        if c.startswith(\"int\") or c.startswith(\"uint\"):\n            return TYPE_INTEGER\n        elif c.startswith(\"float\"):\n            return TYPE_FLOAT\n        elif c == \"datetime\":\n            return TYPE_DATETIME\n        elif c == \"date\":\n            return TYPE_DATE\n        else:\n            return TYPE_STRING\n\n    def run_query(self, query, user):\n        host = self.configuration.get(\"host\") or \"localhost\"\n        port = self.configuration.get(\"port\") or \"8000\"\n        username = self.configuration.get(\"username\") or \"root\"\n        password = self.configuration.get(\"password\") or \"\"\n        database = self.configuration.get(\"database\") or \"default\"\n        secure = self.configuration.get(\"secure\") or False\n        connection = connector.connect(f\"databend://{username}:{password}@{host}:{port}/{database}?secure={secure}\")\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(query)\n            columns = self.fetch_columns([(i[0], self._define_column_type(i[1])) for i in cursor.description])\n            rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in cursor]\n\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n        finally:\n            connection.close()\n\n        return data, error\n\n    def get_schema(self, get_stats=False):\n        query = \"\"\"\n        SELECT TABLE_SCHEMA,\n               TABLE_NAME,\n               COLUMN_NAME\n        FROM INFORMATION_SCHEMA.COLUMNS\n        WHERE TABLE_SCHEMA NOT IN ('information_schema', 'system')\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        schema = {}\n\n        for row in results[\"rows\"]:\n            table_name = \"{}.{}\".format(row[\"table_schema\"], row[\"table_name\"])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"column_name\"])\n\n        return list(schema.values())\n\n    def _get_tables(self):\n        query = \"\"\"\n        SELECT TABLE_SCHEMA,\n               TABLE_NAME,\n               COLUMN_NAME\n        FROM INFORMATION_SCHEMA.COLUMNS\n        WHERE TABLE_SCHEMA NOT IN ('information_schema', 'system')\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        schema = {}\n\n        for row in results[\"rows\"]:\n            table_name = \"{}.{}\".format(row[\"table_schema\"], row[\"table_name\"])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"column_name\"])\n\n        return list(schema.values())\n\n\nregister(Databend)\n"
  },
  {
    "path": "redash/query_runner/databricks.py",
    "content": "import datetime\nimport logging\nimport os\n\nfrom redash import __version__, statsd_client\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    NotSupported,\n    register,\n    split_sql_statements,\n)\nfrom redash.settings import cast_int_or_default\n\ntry:\n    import pyodbc\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nTYPES_MAP = {\n    str: TYPE_STRING,\n    bool: TYPE_BOOLEAN,\n    datetime.date: TYPE_DATE,\n    datetime.datetime: TYPE_DATETIME,\n    int: TYPE_INTEGER,\n    float: TYPE_FLOAT,\n}\n\nROW_LIMIT = cast_int_or_default(os.environ.get(\"DATABRICKS_ROW_LIMIT\"), 20000)\n\nlogger = logging.getLogger(__name__)\n\n\ndef _build_odbc_connection_string(**kwargs):\n    return \";\".join([f\"{k}={v}\" for k, v in kwargs.items()])\n\n\nclass Databricks(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1\"\n    should_annotate_query = False\n\n    @classmethod\n    def type(cls):\n        return \"databricks\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\"},\n                \"http_path\": {\"type\": \"string\", \"title\": \"HTTP Path\"},\n                # We're using `http_password` here for legacy reasons\n                \"http_password\": {\"type\": \"string\", \"title\": \"Access Token\"},\n            },\n            \"order\": [\"host\", \"http_path\", \"http_password\"],\n            \"secret\": [\"http_password\"],\n            \"required\": [\"host\", \"http_path\", \"http_password\"],\n        }\n\n    def _get_cursor(self):\n        user_agent = \"Redash/{} (Databricks)\".format(__version__.split(\"-\")[0])\n        connection_string = _build_odbc_connection_string(\n            Driver=\"Simba\",\n            UID=\"token\",\n            PORT=\"443\",\n            SSL=\"1\",\n            THRIFTTRANSPORT=\"2\",\n            SPARKSERVERTYPE=\"3\",\n            AUTHMECH=3,\n            # Use the query as is without rewriting:\n            UseNativeQuery=\"1\",\n            # Automatically reconnect to the cluster if an error occurs\n            AutoReconnect=\"1\",\n            # Minimum interval between consecutive polls for query execution status (1ms)\n            AsyncExecPollInterval=\"1\",\n            UserAgentEntry=user_agent,\n            HOST=self.configuration[\"host\"],\n            PWD=self.configuration[\"http_password\"],\n            HTTPPath=self.configuration[\"http_path\"],\n        )\n\n        connection = pyodbc.connect(connection_string, autocommit=True)\n        return connection.cursor()\n\n    def run_query(self, query, user):\n        try:\n            cursor = self._get_cursor()\n\n            statements = split_sql_statements(query)\n            for stmt in statements:\n                cursor.execute(stmt)\n\n            if cursor.description is not None:\n                result_set = cursor.fetchmany(ROW_LIMIT)\n                columns = self.fetch_columns([(i[0], TYPES_MAP.get(i[1], TYPE_STRING)) for i in cursor.description])\n\n                rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in result_set]\n\n                data = {\"columns\": columns, \"rows\": rows}\n\n                if len(result_set) >= ROW_LIMIT and cursor.fetchone() is not None:\n                    logger.warning(\"Truncated result set.\")\n                    statsd_client.incr(\"redash.query_runner.databricks.truncated\")\n                    data[\"truncated\"] = True\n                error = None\n            else:\n                error = None\n                data = {\n                    \"columns\": [{\"name\": \"result\", \"type\": TYPE_STRING}],\n                    \"rows\": [{\"result\": \"No data was returned.\"}],\n                }\n\n            cursor.close()\n        except pyodbc.Error as e:\n            if len(e.args) > 1:\n                error = str(e.args[1])\n            else:\n                error = str(e)\n            data = None\n\n        return data, error\n\n    def get_schema(self):\n        raise NotSupported()\n\n    def get_databases(self):\n        query = \"SHOW DATABASES\"\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        first_column_name = results[\"columns\"][0][\"name\"]\n        return [row[first_column_name] for row in results[\"rows\"]]\n\n    def get_database_tables(self, database_name):\n        schema = {}\n        cursor = self._get_cursor()\n\n        cursor.tables(schema=database_name)\n\n        for table in cursor:\n            table_name = \"{}.{}\".format(table[1], table[2])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n        return list(schema.values())\n\n    def get_database_tables_with_columns(self, database_name):\n        schema = {}\n        cursor = self._get_cursor()\n\n        # load tables first, otherwise tables without columns are not showed\n        cursor.tables(schema=database_name)\n\n        for table in cursor:\n            table_name = \"{}.{}\".format(table[1], table[2])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n        cursor.columns(schema=database_name)\n\n        for column in cursor:\n            table_name = \"{}.{}\".format(column[1], column[2])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append({\"name\": column[3], \"type\": column[5]})\n\n        return list(schema.values())\n\n    def get_table_columns(self, database_name, table_name):\n        cursor = self._get_cursor()\n        cursor.columns(schema=database_name, table=table_name)\n        return [{\"name\": column[3], \"type\": column[5]} for column in cursor]\n\n\nregister(Databricks)\n"
  },
  {
    "path": "redash/query_runner/db2.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    InterruptException,\n    JobTimeoutException,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import select\n\n    import ibm_db_dbi\n\n    types_map = {\n        ibm_db_dbi.NUMBER: TYPE_INTEGER,\n        ibm_db_dbi.BIGINT: TYPE_INTEGER,\n        ibm_db_dbi.ROWID: TYPE_INTEGER,\n        ibm_db_dbi.FLOAT: TYPE_FLOAT,\n        ibm_db_dbi.DECIMAL: TYPE_FLOAT,\n        ibm_db_dbi.DATE: TYPE_DATE,\n        ibm_db_dbi.TIME: TYPE_DATETIME,\n        ibm_db_dbi.DATETIME: TYPE_DATETIME,\n        ibm_db_dbi.BINARY: TYPE_STRING,\n        ibm_db_dbi.XML: TYPE_STRING,\n        ibm_db_dbi.TEXT: TYPE_STRING,\n        ibm_db_dbi.STRING: TYPE_STRING,\n    }\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\nclass DB2(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1 FROM SYSIBM.SYSDUMMY1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"host\": {\"type\": \"string\", \"default\": \"127.0.0.1\"},\n                \"port\": {\"type\": \"number\", \"default\": 50000},\n                \"dbname\": {\"type\": \"string\", \"title\": \"Database Name\"},\n            },\n            \"order\": [\"host\", \"port\", \"user\", \"password\", \"dbname\"],\n            \"required\": [\"dbname\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"db2\"\n\n    @classmethod\n    def enabled(cls):\n        try:\n            import ibm_db  # noqa: F401\n        except ImportError:\n            return False\n\n        return True\n\n    def _get_definitions(self, schema, query):\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            if row[\"TABLE_SCHEMA\"] != \"public\":\n                table_name = \"{}.{}\".format(row[\"TABLE_SCHEMA\"], row[\"TABLE_NAME\"])\n            else:\n                table_name = row[\"TABLE_NAME\"]\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"COLUMN_NAME\"])\n\n    def _get_tables(self, schema):\n        query = \"\"\"\n        SELECT rtrim(t.TABSCHEMA) as table_schema,\n               t.TABNAME as table_name,\n               c.COLNAME as column_name\n        from syscat.tables t\n        join syscat.columns c\n        on t.TABSCHEMA = c.TABSCHEMA AND t.TABNAME = c.TABNAME\n        WHERE t.type IN ('T') and t.TABSCHEMA not in ('SYSIBM')\n        \"\"\"\n        self._get_definitions(schema, query)\n\n        return list(schema.values())\n\n    def _get_connection(self):\n        self.connection_string = \"DATABASE={};HOSTNAME={};PORT={};PROTOCOL=TCPIP;UID={};PWD={};\".format(\n            self.configuration[\"dbname\"],\n            self.configuration[\"host\"],\n            self.configuration[\"port\"],\n            self.configuration[\"user\"],\n            self.configuration[\"password\"],\n        )\n        connection = ibm_db_dbi.connect(self.connection_string, \"\", \"\")\n\n        return connection\n\n    def run_query(self, query, user):\n        connection = self._get_connection()\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(query)\n\n            if cursor.description is not None:\n                columns = self.fetch_columns([(i[0], types_map.get(i[1], None)) for i in cursor.description])\n                rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in cursor]\n\n                data = {\"columns\": columns, \"rows\": rows}\n                error = None\n            else:\n                error = \"Query completed but it returned no data.\"\n                data = None\n        except (select.error, OSError):\n            error = \"Query interrupted. Please retry.\"\n            data = None\n        except ibm_db_dbi.DatabaseError as e:\n            error = str(e)\n            data = None\n        except (KeyboardInterrupt, InterruptException, JobTimeoutException):\n            connection.cancel()\n            raise\n        finally:\n            connection.close()\n\n        return data, error\n\n\nregister(DB2)\n"
  },
  {
    "path": "redash/query_runner/dgraph.py",
    "content": "import json\n\ntry:\n    import pydgraph\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nfrom redash.query_runner import BaseQueryRunner, register\n\n\ndef reduce_item(reduced_item, key, value):\n    \"\"\"From https://github.com/vinay20045/json-to-csv\"\"\"\n    # Reduction Condition 1\n    if isinstance(value, list):\n        for i, sub_item in enumerate(value):\n            reduce_item(reduced_item, \"{}.{}\".format(key, i), sub_item)\n\n    # Reduction Condition 2\n    elif isinstance(value, dict):\n        sub_keys = value.keys()\n        for sub_key in sub_keys:\n            reduce_item(reduced_item, \"{}.{}\".format(key, sub_key), value[sub_key])\n\n    # Base Condition\n    else:\n        reduced_item[key] = value\n\n\nclass Dgraph(BaseQueryRunner):\n    should_annotate_query = False\n    noop_query = \"\"\"\n    {\n      test() {\n      }\n    }\n    \"\"\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"servers\": {\"type\": \"string\"},\n            },\n            \"order\": [\"servers\", \"user\", \"password\"],\n            \"required\": [\"servers\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"dgraph\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    def run_dgraph_query_raw(self, query):\n        servers = self.configuration.get(\"servers\")\n\n        client_stub = pydgraph.DgraphClientStub(servers)\n        client = pydgraph.DgraphClient(client_stub)\n\n        txn = client.txn(read_only=True)\n        try:\n            response_raw = txn.query(query)\n\n            data = json.loads(response_raw.json)\n\n            return data\n\n        except Exception as e:\n            raise e\n        finally:\n            txn.discard()\n            client_stub.close()\n\n    def run_query(self, query, user):\n        data = None\n        error = None\n\n        try:\n            data = self.run_dgraph_query_raw(query)\n\n            first_key = next(iter(list(data.keys())))\n            first_node = data[first_key]\n\n            data_to_be_processed = first_node\n\n            processed_data = []\n            header = []\n            # use logic from https://github.com/vinay20045/json-to-csv\n            for item in data_to_be_processed:\n                reduced_item = {}\n                reduce_item(reduced_item, first_key, item)\n\n                header += reduced_item.keys()\n\n                processed_data.append(reduced_item)\n\n            header = list(set(header))\n\n            columns = [{\"name\": c, \"friendly_name\": c, \"type\": \"string\"} for c in header]\n\n            # finally, assemble both the columns and data\n            data = {\"columns\": columns, \"rows\": processed_data}\n        except Exception as e:\n            error = e\n\n        return data, error\n\n    def get_schema(self, get_stats=False):\n        \"\"\"Queries Dgraph for all the predicates, their types, their tokenizers, etc.\n\n        Dgraph only has one schema, and there's no such things as columns\"\"\"\n        query = \"schema {}\"\n\n        results = self.run_dgraph_query_raw(query)\n\n        schema = {}\n\n        for row in results[\"schema\"]:\n            table_name = row[\"predicate\"]\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n        return list(schema.values())\n\n\nregister(Dgraph)\n"
  },
  {
    "path": "redash/query_runner/drill.py",
    "content": "import logging\nimport os\nimport re\n\nfrom dateutil import parser\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    BaseHTTPQueryRunner,\n    guess_type,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\n\n# Convert Drill string value to actual type\ndef convert_type(string_value, actual_type):\n    if string_value is None or string_value == \"\":\n        return \"\"\n\n    if actual_type == TYPE_INTEGER:\n        return int(string_value)\n\n    if actual_type == TYPE_FLOAT:\n        return float(string_value)\n\n    if actual_type == TYPE_BOOLEAN:\n        return str(string_value).lower() == \"true\"\n\n    if actual_type == TYPE_DATETIME:\n        return parser.parse(string_value)\n\n    return str(string_value)\n\n\n# Parse Drill API response and translate it to accepted format\ndef parse_response(data):\n    cols = data[\"columns\"]\n    rows = data[\"rows\"]\n\n    if len(cols) == 0:\n        return {\"columns\": [], \"rows\": []}\n\n    first_row = rows[0]\n    columns = []\n    types = {}\n\n    for c in cols:\n        columns.append({\"name\": c, \"type\": guess_type(first_row[c]), \"friendly_name\": c})\n\n    for col in columns:\n        types[col[\"name\"]] = col[\"type\"]\n\n    for row in rows:\n        for key, value in row.items():\n            row[key] = convert_type(value, types[key])\n\n    return {\"columns\": columns, \"rows\": rows}\n\n\nclass Drill(BaseHTTPQueryRunner):\n    noop_query = \"select version from sys.version\"\n    response_error = \"Drill API returned unexpected status code\"\n    requires_authentication = False\n    requires_url = True\n    url_title = \"Drill URL\"\n    username_title = \"Username\"\n    password_title = \"Password\"\n\n    @classmethod\n    def name(cls):\n        return \"Apache Drill\"\n\n    @classmethod\n    def configuration_schema(cls):\n        schema = super(Drill, cls).configuration_schema()\n        # Since Drill itself can act as aggregator of various datasources,\n        # it can contain quite a lot of schemas in `INFORMATION_SCHEMA`\n        # We added this to improve user experience and let users focus only on desired schemas.\n        schema[\"properties\"][\"allowed_schemas\"] = {\n            \"type\": \"string\",\n            \"title\": \"List of schemas to use in schema browser (comma separated)\",\n        }\n        schema[\"order\"] += [\"allowed_schemas\"]\n        return schema\n\n    def run_query(self, query, user):\n        drill_url = os.path.join(self.configuration[\"url\"], \"query.json\")\n\n        payload = {\"queryType\": \"SQL\", \"query\": query}\n\n        response, error = self.get_response(drill_url, http_method=\"post\", json=payload)\n        if error is not None:\n            return None, error\n\n        return parse_response(response.json()), None\n\n    def get_schema(self, get_stats=False):\n        query = \"\"\"\n        SELECT DISTINCT\n            TABLE_SCHEMA,\n            TABLE_NAME,\n            COLUMN_NAME\n        FROM\n            INFORMATION_SCHEMA.`COLUMNS`\n        WHERE\n                TABLE_SCHEMA not in ('INFORMATION_SCHEMA', 'information_schema', 'sys')\n            and TABLE_SCHEMA not like '%.information_schema'\n            and TABLE_SCHEMA not like '%.INFORMATION_SCHEMA'\n\n        \"\"\"\n        allowed_schemas = self.configuration.get(\"allowed_schemas\")\n        if allowed_schemas:\n            query += \"and TABLE_SCHEMA in ({})\".format(\n                \", \".join(\n                    [\n                        \"'{}'\".format(re.sub(\"[^a-zA-Z0-9_.`]\", \"\", allowed_schema))\n                        for allowed_schema in allowed_schemas.split(\",\")\n                    ]\n                )\n            )\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        schema = {}\n\n        for row in results[\"rows\"]:\n            table_name = \"{}.{}\".format(row[\"TABLE_SCHEMA\"], row[\"TABLE_NAME\"])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"COLUMN_NAME\"])\n\n        return list(schema.values())\n\n\nregister(Drill)\n"
  },
  {
    "path": "redash/query_runner/druid.py",
    "content": "try:\n    from pydruid.db import connect\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\nTYPES_MAP = {1: TYPE_STRING, 2: TYPE_INTEGER, 3: TYPE_BOOLEAN}\n\n\nclass Druid(BaseQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\", \"default\": \"localhost\"},\n                \"port\": {\"type\": \"number\", \"default\": 8082},\n                \"scheme\": {\"type\": \"string\", \"default\": \"http\"},\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n            },\n            \"order\": [\"scheme\", \"host\", \"port\", \"user\", \"password\"],\n            \"required\": [\"host\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    def run_query(self, query, user):\n        connection = connect(\n            host=self.configuration[\"host\"],\n            port=self.configuration[\"port\"],\n            path=\"/druid/v2/sql/\",\n            scheme=(self.configuration.get(\"scheme\") or \"http\"),\n            user=(self.configuration.get(\"user\") or None),\n            password=(self.configuration.get(\"password\") or None),\n        )\n\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(query)\n            columns = self.fetch_columns([(i[0], TYPES_MAP.get(i[1], None)) for i in cursor.description])\n            rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in cursor]\n\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n        finally:\n            connection.close()\n\n        return data, error\n\n    def get_schema(self, get_stats=False):\n        query = \"\"\"\n        SELECT TABLE_SCHEMA,\n               TABLE_NAME,\n               COLUMN_NAME\n        FROM INFORMATION_SCHEMA.COLUMNS\n        WHERE TABLE_SCHEMA <> 'INFORMATION_SCHEMA'\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        schema = {}\n\n        for row in results[\"rows\"]:\n            table_name = \"{}.{}\".format(row[\"TABLE_SCHEMA\"], row[\"TABLE_NAME\"])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"COLUMN_NAME\"])\n\n        return list(schema.values())\n\n\nregister(Druid)\n"
  },
  {
    "path": "redash/query_runner/duckdb.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    InterruptException,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import duckdb\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n# Map DuckDB types to Redash column types\nTYPES_MAP = {\n    \"BOOLEAN\": TYPE_BOOLEAN,\n    \"TINYINT\": TYPE_INTEGER,\n    \"SMALLINT\": TYPE_INTEGER,\n    \"INTEGER\": TYPE_INTEGER,\n    \"BIGINT\": TYPE_INTEGER,\n    \"HUGEINT\": TYPE_INTEGER,\n    \"REAL\": TYPE_FLOAT,\n    \"DOUBLE\": TYPE_FLOAT,\n    \"DECIMAL\": TYPE_FLOAT,\n    \"VARCHAR\": TYPE_STRING,\n    \"BLOB\": TYPE_STRING,\n    \"DATE\": TYPE_DATE,\n    \"TIMESTAMP\": TYPE_DATETIME,\n    \"TIMESTAMP WITH TIME ZONE\": TYPE_DATETIME,\n    \"TIME\": TYPE_DATETIME,\n    \"INTERVAL\": TYPE_STRING,\n    \"UUID\": TYPE_STRING,\n    \"JSON\": TYPE_STRING,\n    \"STRUCT\": TYPE_STRING,\n    \"MAP\": TYPE_STRING,\n    \"UNION\": TYPE_STRING,\n}\n\n\nclass DuckDB(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    def __init__(self, configuration):\n        super().__init__(configuration)\n        self.dbpath = configuration.get(\"dbpath\", \":memory:\")\n        exts = configuration.get(\"extensions\", \"\")\n        self.extensions = [e.strip() for e in exts.split(\",\") if e.strip()]\n        self._connect()\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"dbpath\": {\n                    \"type\": \"string\",\n                    \"title\": \"Database Path\",\n                    \"default\": \":memory:\",\n                },\n                \"extensions\": {\n                    \"type\": \"string\",\n                    \"title\": \"Extensions (comma separated)\",\n                },\n            },\n            \"order\": [\"dbpath\", \"extensions\"],\n            \"required\": [\"dbpath\"],\n        }\n\n    @classmethod\n    def enabled(cls) -> bool:\n        return enabled\n\n    def _connect(self) -> None:\n        self.con = duckdb.connect(self.dbpath)\n        for ext in self.extensions:\n            try:\n                if \".\" in ext:\n                    prefix, name = ext.split(\".\", 1)\n                    if prefix == \"community\":\n                        self.con.execute(f\"INSTALL {name} FROM community\")\n                        self.con.execute(f\"LOAD {name}\")\n                    else:\n                        raise Exception(\"Unknown extension prefix.\")\n                else:\n                    self.con.execute(f\"INSTALL {ext}\")\n                    self.con.execute(f\"LOAD {ext}\")\n            except Exception as e:\n                logger.warning(\"Failed to load extension %s: %s\", ext, e)\n\n    def run_query(self, query, user) -> tuple:\n        try:\n            cursor = self.con.cursor()\n            cursor.execute(query)\n            columns = self.fetch_columns(\n                [(d[0], TYPES_MAP.get(d[1].upper(), TYPE_STRING)) for d in cursor.description]\n            )\n            rows = [dict(zip((col[\"name\"] for col in columns), row)) for row in cursor.fetchall()]\n            data = {\"columns\": columns, \"rows\": rows}\n            return data, None\n        except duckdb.InterruptException:\n            raise InterruptException(\"Query cancelled by user.\")\n        except Exception as e:\n            logger.exception(\"Error running query: %s\", e)\n            return None, str(e)\n\n    def get_schema(self, get_stats=False) -> list:\n        tables_query = \"\"\"\n            SELECT table_catalog, table_schema, table_name FROM information_schema.tables\n            WHERE table_schema NOT IN ('information_schema', 'pg_catalog');\n        \"\"\"\n        tables_results, error = self.run_query(tables_query, None)\n        if error:\n            raise Exception(f\"Failed to get tables: {error}\")\n\n        schema = {}\n        for table_row in tables_results[\"rows\"]:\n            # Include catalog (database) in the full table name for MotherDuck support\n            catalog = table_row[\"table_catalog\"]\n            schema_name = table_row[\"table_schema\"]\n            table_name = table_row[\"table_name\"]\n\n            # Skip catalog prefix for default local databases (memory, temp)\n            # but include it for MotherDuck and attached databases\n            if catalog.lower() in (\"memory\", \"temp\", \"system\"):\n                full_table_name = f\"{schema_name}.{table_name}\"\n                describe_query = f'DESCRIBE \"{schema_name}\".\"{table_name}\";'\n            else:\n                full_table_name = f\"{catalog}.{schema_name}.{table_name}\"\n                describe_query = f'DESCRIBE \"{catalog}\".\"{schema_name}\".\"{table_name}\";'\n\n            schema[full_table_name] = {\"name\": full_table_name, \"columns\": []}\n            columns_results, error = self.run_query(describe_query, None)\n            if error:\n                logger.warning(\"Failed to describe table %s: %s\", full_table_name, error)\n                continue\n\n            for col_row in columns_results[\"rows\"]:\n                col = {\"name\": col_row[\"column_name\"], \"type\": col_row[\"column_type\"]}\n                schema[full_table_name][\"columns\"].append(col)\n\n                if col_row[\"column_type\"].startswith(\"STRUCT(\"):\n                    schema[full_table_name][\"columns\"].extend(\n                        self._expand_struct_fields(col[\"name\"], col_row[\"column_type\"])\n                    )\n\n        return list(schema.values())\n\n    def _expand_struct_fields(self, base_name: str, struct_type: str) -> list:\n        \"\"\"Recursively expand STRUCT(...) definitions into pseudo-columns.\"\"\"\n        fields = []\n        # strip STRUCT( ... )\n        inner = struct_type[len(\"STRUCT(\") : -1].strip()\n        # careful: nested structs, so parse comma-separated parts properly\n        depth, current, parts = 0, [], []\n        for c in inner:\n            if c == \"(\":\n                depth += 1\n            elif c == \")\":\n                depth -= 1\n            if c == \",\" and depth == 0:\n                parts.append(\"\".join(current).strip())\n                current = []\n            else:\n                current.append(c)\n        if current:\n            parts.append(\"\".join(current).strip())\n\n        for part in parts:\n            # each part looks like: \"fieldname TYPE\"\n            fname, ftype = part.split(\" \", 1)\n            colname = f\"{base_name}.{fname}\"\n            fields.append({\"name\": colname, \"type\": ftype})\n            if ftype.startswith(\"STRUCT(\"):\n                fields.extend(self._expand_struct_fields(colname, ftype))\n        return fields\n\n\nregister(DuckDB)\n"
  },
  {
    "path": "redash/query_runner/e6data.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\ntry:\n    from e6data_python_connector import Connection\n\n    enabled = True\n\nexcept ImportError:\n    enabled = False\n\n\nlogger = logging.getLogger(__name__)\n\nE6DATA_TYPES_MAPPING = {\n    \"INT\": TYPE_INTEGER,\n    \"BYTE\": TYPE_INTEGER,\n    \"INTEGER\": TYPE_INTEGER,\n    \"LONG\": TYPE_INTEGER,\n    \"SHORT\": TYPE_INTEGER,\n    \"FLOAT\": TYPE_FLOAT,\n    \"DOUBLE\": TYPE_FLOAT,\n    \"STRING\": TYPE_STRING,\n    \"DATETIME\": TYPE_DATETIME,\n    \"BINARY\": TYPE_INTEGER,\n    \"ARRAY\": TYPE_STRING,\n    \"MAP\": TYPE_STRING,\n    \"STRUCT\": TYPE_STRING,\n    \"UNION_TYPE\": TYPE_STRING,\n    \"DECIMAL_TYPE\": TYPE_FLOAT,\n    \"DATE\": TYPE_DATE,\n    \"INT96\": TYPE_INTEGER,\n    \"BOOLEAN\": TYPE_BOOLEAN,\n    \"CHAR\": TYPE_STRING,\n}\n\n\nclass e6data(BaseQueryRunner):\n    limit_query = \" LIMIT 1000\"\n\n    should_annotate_query = False\n\n    def __init__(self, configuration):\n        super().__init__(configuration)\n        self.connection = Connection(\n            host=self.configuration.get(\"host\"),\n            port=self.configuration.get(\"port\"),\n            username=self.configuration.get(\"username\"),\n            database=self.configuration.get(\"database\"),\n            password=self.configuration.get(\"password\"),\n        )\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\"},\n                \"username\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"catalog\": {\"type\": \"string\"},\n                \"database\": {\"type\": \"string\"},\n            },\n            \"order\": [\n                \"host\",\n                \"port\",\n                \"username\",\n                \"password\",\n                \"catalog\",\n                \"database\",\n            ],\n            \"required\": [\"host\", \"port\", \"username\", \"password\", \"catalog\", \"database\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def type(cls):\n        return \"e6data\"\n\n    def run_query(self, query, user):\n        cursor = None\n        try:\n            cursor = self.connection.cursor(catalog_name=self.configuration.get(\"catalog\"))\n            cursor.execute(query)\n            results = cursor.fetchall()\n            description = cursor.description\n            columns = []\n            for c in description:\n                column_name, column_type = c[0], E6DATA_TYPES_MAPPING.get(c[1], None)\n                columns.append({\"name\": column_name, \"type\": column_type})\n            rows = [dict(zip([c[\"name\"] for c in columns], r)) for r in results]\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n\n        except Exception as error:\n            logger.debug(error)\n            data = None\n        finally:\n            if cursor is not None:\n                cursor.clear()\n                cursor.close()\n\n        return data, error\n\n    def test_connection(self):\n        self.noop_query = \"SELECT 1\"\n\n        data, error = self.run_query(self.noop_query, None)\n\n        if error is not None:\n            raise Exception(error)\n\n    def get_schema(self, get_stats=False):\n        tables = self.connection.get_tables(self.configuration.get(\"catalog\"), self.configuration.get(\"database\"))\n\n        schema = list()\n\n        for table_name in tables:\n            columns = self.connection.get_columns(\n                self.configuration.get(\"catalog\"),\n                self.configuration.get(\"database\"),\n                table_name,\n            )\n            columns_with_type = []\n\n            for column in columns:\n                redash_type = E6DATA_TYPES_MAPPING.get(column[\"fieldType\"], None)\n                columns_with_type.append({\"name\": column[\"fieldName\"], \"type\": redash_type})\n\n            table_schema = {\"name\": table_name, \"columns\": columns_with_type}\n\n            schema.append(table_schema)\n\n        return schema\n\n\nregister(e6data)\n"
  },
  {
    "path": "redash/query_runner/elasticsearch.py",
    "content": "import logging\nimport urllib.error\nimport urllib.parse\nimport urllib.request\n\nimport requests\nfrom requests.auth import HTTPBasicAuth\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    JobTimeoutException,\n    register,\n)\nfrom redash.utils import json_loads\n\ntry:\n    import http.client as http_client\nexcept ImportError:\n    # Python 2\n    import http.client as http_client\n\nlogger = logging.getLogger(__name__)\n\nELASTICSEARCH_TYPES_MAPPING = {\n    \"integer\": TYPE_INTEGER,\n    \"long\": TYPE_INTEGER,\n    \"float\": TYPE_FLOAT,\n    \"double\": TYPE_FLOAT,\n    \"boolean\": TYPE_BOOLEAN,\n    \"string\": TYPE_STRING,\n    \"date\": TYPE_DATE,\n    \"object\": TYPE_STRING,\n    # \"geo_point\" TODO: Need to split to 2 fields somehow\n}\n\nELASTICSEARCH_BUILTIN_FIELDS_MAPPING = {\"_id\": \"Id\", \"_score\": \"Score\"}\n\nPYTHON_TYPES_MAPPING = {\n    str: TYPE_STRING,\n    bytes: TYPE_STRING,\n    bool: TYPE_BOOLEAN,\n    int: TYPE_INTEGER,\n    float: TYPE_FLOAT,\n}\n\n\nclass BaseElasticSearch(BaseQueryRunner):\n    should_annotate_query = False\n    DEBUG_ENABLED = False\n    deprecated = True\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"server\": {\"type\": \"string\", \"title\": \"Base URL\"},\n                \"basic_auth_user\": {\"type\": \"string\", \"title\": \"Basic Auth User\"},\n                \"basic_auth_password\": {\n                    \"type\": \"string\",\n                    \"title\": \"Basic Auth Password\",\n                },\n            },\n            \"order\": [\"server\", \"basic_auth_user\", \"basic_auth_password\"],\n            \"secret\": [\"basic_auth_password\"],\n            \"required\": [\"server\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return False\n\n    def __init__(self, configuration):\n        super(BaseElasticSearch, self).__init__(configuration)\n        self.syntax = \"json\"\n\n        if self.DEBUG_ENABLED:\n            http_client.HTTPConnection.debuglevel = 1\n\n            # you need to initialize logging, otherwise you will not see anything from requests\n            logging.basicConfig()\n            logging.getLogger().setLevel(logging.DEBUG)\n            requests_log = logging.getLogger(\"requests.packages.urllib3\")\n            requests_log.setLevel(logging.DEBUG)\n            requests_log.propagate = True\n\n            logger.setLevel(logging.DEBUG)\n\n        self.server_url = self.configuration.get(\"server\", \"\")\n        if self.server_url and self.server_url[-1] == \"/\":\n            self.server_url = self.server_url[:-1]\n\n        basic_auth_user = self.configuration.get(\"basic_auth_user\", None)\n        basic_auth_password = self.configuration.get(\"basic_auth_password\", None)\n        self.auth = None\n        if basic_auth_user and basic_auth_password:\n            self.auth = HTTPBasicAuth(basic_auth_user, basic_auth_password)\n\n    def _get_mappings(self, url):\n        mappings = {}\n        error = None\n        try:\n            r = requests.get(url, auth=self.auth)\n            r.raise_for_status()\n\n            mappings = r.json()\n        except requests.HTTPError as e:\n            logger.exception(e)\n            error = \"Failed to execute query. Return Code: {0}   Reason: {1}\".format(r.status_code, r.text)\n            mappings = None\n        except requests.exceptions.RequestException as e:\n            logger.exception(e)\n            error = \"Connection refused\"\n            mappings = None\n\n        return mappings, error\n\n    def _get_query_mappings(self, url):\n        mappings_data, error = self._get_mappings(url)\n        if error:\n            return mappings_data, error\n\n        mappings = {}\n        for index_name in mappings_data:\n            index_mappings = mappings_data[index_name]\n            for m in index_mappings.get(\"mappings\", {}):\n                if not isinstance(index_mappings[\"mappings\"][m], dict):\n                    continue\n                if \"properties\" not in index_mappings[\"mappings\"][m]:\n                    continue\n                for property_name in index_mappings[\"mappings\"][m][\"properties\"]:\n                    property_data = index_mappings[\"mappings\"][m][\"properties\"][property_name]\n                    if property_name not in mappings:\n                        property_type = property_data.get(\"type\", None)\n                        if property_type:\n                            if property_type in ELASTICSEARCH_TYPES_MAPPING:\n                                mappings[property_name] = ELASTICSEARCH_TYPES_MAPPING[property_type]\n                            else:\n                                mappings[property_name] = TYPE_STRING\n                                # raise Exception(\"Unknown property type: {0}\".format(property_type))\n\n        return mappings, error\n\n    def get_schema(self, *args, **kwargs):\n        def parse_doc(doc, path=None):\n            \"\"\"Recursively parse a doc type dictionary\"\"\"\n            path = path or []\n            result = []\n            for field, description in doc[\"properties\"].items():\n                if \"properties\" in description:\n                    result.extend(parse_doc(description, path + [field]))\n                else:\n                    result.append(\".\".join(path + [field]))\n            return result\n\n        schema = {}\n        url = \"{0}/_mappings\".format(self.server_url)\n        mappings, error = self._get_mappings(url)\n\n        if mappings:\n            # make a schema for each index\n            # the index contains a mappings dict with documents\n            # in a hierarchical format\n            for name, index in mappings.items():\n                columns = []\n                schema[name] = {\"name\": name}\n                for doc, items in index[\"mappings\"].items():\n                    columns.extend(parse_doc(items))\n\n                # remove duplicates\n                # sort alphabetically\n                schema[name][\"columns\"] = sorted(set(columns))\n        return list(schema.values())\n\n    def _parse_results(self, mappings, result_fields, raw_result, result_columns, result_rows):  # noqa: C901\n        def add_column_if_needed(mappings, column_name, friendly_name, result_columns, result_columns_index):\n            if friendly_name not in result_columns_index:\n                result_columns.append(\n                    {\n                        \"name\": friendly_name,\n                        \"friendly_name\": friendly_name,\n                        \"type\": mappings.get(column_name, \"string\"),\n                    }\n                )\n                result_columns_index[friendly_name] = result_columns[-1]\n\n        def get_row(rows, row):\n            if row is None:\n                row = {}\n                rows.append(row)\n            return row\n\n        def collect_value(mappings, row, key, value, type):\n            if result_fields and key not in result_fields_index:\n                return\n\n            mappings[key] = type\n            add_column_if_needed(mappings, key, key, result_columns, result_columns_index)\n            row[key] = value\n\n        def collect_aggregations(mappings, rows, parent_key, data, row, result_columns, result_columns_index):\n            if isinstance(data, dict):\n                for key, value in data.items():\n                    val = collect_aggregations(\n                        mappings,\n                        rows,\n                        parent_key if key == \"buckets\" else key,\n                        value,\n                        row,\n                        result_columns,\n                        result_columns_index,\n                    )\n                    if val:\n                        row = get_row(rows, row)\n                        collect_value(mappings, row, key, val, \"long\")\n\n                for data_key in [\"value\", \"doc_count\"]:\n                    if data_key not in data:\n                        continue\n                    if \"key\" in data and len(list(data.keys())) == 2:\n                        key_is_string = \"key_as_string\" in data\n                        collect_value(\n                            mappings,\n                            row,\n                            data[\"key\"] if not key_is_string else data[\"key_as_string\"],\n                            data[data_key],\n                            \"long\" if not key_is_string else \"string\",\n                        )\n                    else:\n                        return data[data_key]\n\n            elif isinstance(data, list):\n                for value in data:\n                    result_row = get_row(rows, row)\n                    collect_aggregations(\n                        mappings,\n                        rows,\n                        parent_key,\n                        value,\n                        result_row,\n                        result_columns,\n                        result_columns_index,\n                    )\n                    if \"doc_count\" in value:\n                        collect_value(\n                            mappings,\n                            result_row,\n                            \"doc_count\",\n                            value[\"doc_count\"],\n                            \"integer\",\n                        )\n                    if \"key\" in value:\n                        if \"key_as_string\" in value:\n                            collect_value(\n                                mappings,\n                                result_row,\n                                parent_key,\n                                value[\"key_as_string\"],\n                                \"string\",\n                            )\n                        else:\n                            collect_value(mappings, result_row, parent_key, value[\"key\"], \"string\")\n\n            return None\n\n        result_columns_index = {c[\"name\"]: c for c in result_columns}\n\n        result_fields_index = {}\n        if result_fields:\n            for r in result_fields:\n                result_fields_index[r] = None\n\n        if \"error\" in raw_result:\n            error = raw_result[\"error\"]\n            if len(error) > 10240:\n                error = error[:10240] + \"... continues\"\n\n            raise Exception(error)\n        elif \"aggregations\" in raw_result:\n            if result_fields:\n                for field in result_fields:\n                    add_column_if_needed(mappings, field, field, result_columns, result_columns_index)\n\n            for key, data in raw_result[\"aggregations\"].items():\n                collect_aggregations(\n                    mappings,\n                    result_rows,\n                    key,\n                    data,\n                    None,\n                    result_columns,\n                    result_columns_index,\n                )\n\n            logger.debug(\"result_rows %s\", str(result_rows))\n            logger.debug(\"result_columns %s\", str(result_columns))\n        elif \"hits\" in raw_result and \"hits\" in raw_result[\"hits\"]:\n            if result_fields:\n                for field in result_fields:\n                    add_column_if_needed(mappings, field, field, result_columns, result_columns_index)\n\n            for h in raw_result[\"hits\"][\"hits\"]:\n                row = {}\n\n                column_name = \"_source\" if \"_source\" in h else \"fields\"\n                for column in h[column_name]:\n                    if result_fields and column not in result_fields_index:\n                        continue\n\n                    add_column_if_needed(mappings, column, column, result_columns, result_columns_index)\n\n                    value = h[column_name][column]\n                    row[column] = value[0] if isinstance(value, list) and len(value) == 1 else value\n\n                result_rows.append(row)\n        else:\n            raise Exception(\"Redash failed to parse the results it got from Elasticsearch.\")\n\n    def test_connection(self):\n        try:\n            r = requests.get(\"{0}/_cluster/health\".format(self.server_url), auth=self.auth)\n            r.raise_for_status()\n        except requests.HTTPError as e:\n            logger.exception(e)\n            raise Exception(\"Failed to execute query. Return Code: {0}   Reason: {1}\".format(r.status_code, r.text))\n        except requests.exceptions.RequestException as e:\n            logger.exception(e)\n            raise Exception(\"Connection refused\")\n\n\nclass Kibana(BaseElasticSearch):\n    @classmethod\n    def enabled(cls):\n        return True\n\n    def _execute_simple_query(self, url, auth, _from, mappings, result_fields, result_columns, result_rows):\n        url += \"&from={0}\".format(_from)\n        r = requests.get(url, auth=self.auth)\n        r.raise_for_status()\n\n        raw_result = r.json()\n\n        self._parse_results(mappings, result_fields, raw_result, result_columns, result_rows)\n\n        total = raw_result[\"hits\"][\"total\"]\n        result_size = len(raw_result[\"hits\"][\"hits\"])\n        logger.debug(\"Result Size: {0}  Total: {1}\".format(result_size, total))\n\n        return raw_result[\"hits\"][\"total\"]\n\n    def run_query(self, query, user):\n        try:\n            error = None\n\n            logger.debug(query)\n            query_params = json_loads(query)\n\n            index_name = query_params[\"index\"]\n            query_data = query_params[\"query\"]\n            size = int(query_params.get(\"size\", 500))\n            limit = int(query_params.get(\"limit\", 500))\n            result_fields = query_params.get(\"fields\", None)\n            sort = query_params.get(\"sort\", None)\n\n            if not self.server_url:\n                error = \"Missing configuration key 'server'\"\n                return None, error\n\n            url = \"{0}/{1}/_search?\".format(self.server_url, index_name)\n            mapping_url = \"{0}/{1}/_mapping\".format(self.server_url, index_name)\n\n            mappings, error = self._get_query_mappings(mapping_url)\n            if error:\n                return None, error\n\n            if sort:\n                url += \"&sort={0}\".format(urllib.parse.quote_plus(sort))\n\n            url += \"&q={0}\".format(urllib.parse.quote_plus(query_data))\n\n            logger.debug(\"Using URL: {0}\".format(url))\n            logger.debug(\"Using Query: {0}\".format(query_data))\n\n            result_columns = []\n            result_rows = []\n            if isinstance(query_data, str):\n                _from = 0\n                while True:\n                    query_size = size if limit >= (_from + size) else (limit - _from)\n                    self._execute_simple_query(\n                        url + \"&size={0}\".format(query_size),\n                        self.auth,\n                        _from,\n                        mappings,\n                        result_fields,\n                        result_columns,\n                        result_rows,\n                    )\n                    _from += size\n                    if _from >= limit:\n                        break\n            else:\n                # TODO: Handle complete ElasticSearch queries (JSON based sent over HTTP POST)\n                raise Exception(\"Advanced queries are not supported\")\n\n            data = {\"columns\": result_columns, \"rows\": result_rows}\n        except requests.HTTPError as e:\n            logger.exception(e)\n            r = e.response\n            error = \"Failed to execute query. Return Code: {0}   Reason: {1}\".format(r.status_code, r.text)\n            data = None\n        except requests.exceptions.RequestException as e:\n            logger.exception(e)\n            error = \"Connection refused\"\n            data = None\n\n        return data, error\n\n\nclass ElasticSearch(BaseElasticSearch):\n    @classmethod\n    def enabled(cls):\n        return True\n\n    @classmethod\n    def name(cls):\n        return \"Elasticsearch\"\n\n    def run_query(self, query, user):\n        try:\n            error = None\n\n            logger.debug(query)\n            query_dict = json_loads(query)\n\n            index_name = query_dict.pop(\"index\", \"\")\n            result_fields = query_dict.pop(\"result_fields\", None)\n\n            if not self.server_url:\n                error = \"Missing configuration key 'server'\"\n                return None, error\n\n            url = \"{0}/{1}/_search\".format(self.server_url, index_name)\n            mapping_url = \"{0}/{1}/_mapping\".format(self.server_url, index_name)\n\n            mappings, error = self._get_query_mappings(mapping_url)\n            if error:\n                return None, error\n\n            logger.debug(\"Using URL: %s\", url)\n            logger.debug(\"Using query: %s\", query_dict)\n            r = requests.get(url, json=query_dict, auth=self.auth)\n            r.raise_for_status()\n            logger.debug(\"Result: %s\", r.json())\n\n            result_columns = []\n            result_rows = []\n            self._parse_results(mappings, result_fields, r.json(), result_columns, result_rows)\n\n            data = {\"columns\": result_columns, \"rows\": result_rows}\n        except (KeyboardInterrupt, JobTimeoutException) as e:\n            logger.exception(e)\n            raise\n        except requests.HTTPError as e:\n            logger.exception(e)\n            error = \"Failed to execute query. Return Code: {0}   Reason: {1}\".format(r.status_code, r.text)\n            data = None\n        except requests.exceptions.RequestException as e:\n            logger.exception(e)\n            error = \"Connection refused\"\n            data = None\n\n        return data, error\n\n\nregister(Kibana)\nregister(ElasticSearch)\n"
  },
  {
    "path": "redash/query_runner/elasticsearch2.py",
    "content": "import json\nimport logging\nfrom typing import Optional, Tuple\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseHTTPQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\nELASTICSEARCH_TYPES_MAPPING = {\n    \"integer\": TYPE_INTEGER,\n    \"long\": TYPE_INTEGER,\n    \"float\": TYPE_FLOAT,\n    \"double\": TYPE_FLOAT,\n    \"boolean\": TYPE_BOOLEAN,\n    \"string\": TYPE_STRING,\n    \"date\": TYPE_DATE,\n    \"object\": TYPE_STRING,\n}\n\n\nTYPES_MAP = {\n    str: TYPE_STRING,\n    int: TYPE_INTEGER,\n    float: TYPE_FLOAT,\n    bool: TYPE_BOOLEAN,\n}\n\n\nclass ElasticSearch2(BaseHTTPQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def name(cls):\n        return \"Elasticsearch\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.syntax = \"json\"\n\n    def get_response(self, url, auth=None, http_method=\"get\", **kwargs):\n        url = \"{}{}\".format(self.configuration[\"url\"], url)\n        headers = kwargs.pop(\"headers\", {})\n        headers[\"Accept\"] = \"application/json\"\n        return super().get_response(url, auth, http_method, headers=headers, **kwargs)\n\n    def test_connection(self):\n        _, error = self.get_response(\"/_cluster/health\")\n        if error is not None:\n            raise Exception(error)\n\n    def run_query(self, query, user):\n        query, url, result_fields = self._build_query(query)\n        response, error = self.get_response(url, http_method=\"post\", json=query)\n        query_results = response.json()\n        data = self._parse_results(result_fields, query_results)\n        error = None\n        return data, error\n\n    def _build_query(self, query: str) -> Tuple[dict, str, Optional[list]]:\n        query = json.loads(query)\n        index_name = query.pop(\"index\", \"\")\n        result_fields = query.pop(\"result_fields\", None)\n        url = \"/{}/_search\".format(index_name)\n        return query, url, result_fields\n\n    @classmethod\n    def _parse_mappings(cls, mappings_data: dict):\n        mappings = {}\n\n        def _parse_properties(prefix: str, properties: dict):\n            for property_name, property_data in properties.items():\n                if property_name not in mappings:\n                    property_type = property_data.get(\"type\", None)\n                    nested_properties = property_data.get(\"properties\", None)\n                    if property_type:\n                        mappings[index_name][prefix + property_name] = ELASTICSEARCH_TYPES_MAPPING.get(\n                            property_type, TYPE_STRING\n                        )\n                    elif nested_properties:\n                        new_prefix = prefix + property_name + \".\"\n                        _parse_properties(new_prefix, nested_properties)\n\n        for index_name in mappings_data:\n            mappings[index_name] = {}\n            index_mappings = mappings_data[index_name]\n            try:\n                for m in index_mappings.get(\"mappings\", {}):\n                    _parse_properties(\"\", index_mappings[\"mappings\"][m][\"properties\"])\n            except KeyError:\n                _parse_properties(\"\", index_mappings[\"mappings\"][\"properties\"])\n\n        return mappings\n\n    def get_mappings(self):\n        response, error = self.get_response(\"/_mappings\")\n        return self._parse_mappings(response.json())\n\n    def get_schema(self, *args, **kwargs):\n        schema = {}\n        for name, columns in self.get_mappings().items():\n            schema[name] = {\"name\": name, \"columns\": list(columns.keys())}\n        return list(schema.values())\n\n    @classmethod\n    def _parse_results(cls, result_fields, raw_result):  # noqa: C901\n        result_columns = []\n        result_rows = []\n        result_columns_index = {c[\"name\"]: c for c in result_columns}\n        result_fields_index = {}\n\n        def add_column_if_needed(column_name, value=None):\n            if column_name not in result_columns_index:\n                result_columns.append(\n                    {\n                        \"name\": column_name,\n                        \"friendly_name\": column_name,\n                        \"type\": TYPES_MAP.get(type(value), TYPE_STRING),\n                    }\n                )\n                result_columns_index[column_name] = result_columns[-1]\n\n        def get_row(rows, row):\n            if row is None:\n                row = {}\n                rows.append(row)\n            return row\n\n        def collect_value(row, key, value):\n            if result_fields and key not in result_fields_index:\n                return\n\n            add_column_if_needed(key, value)\n            row[key] = value\n\n        def parse_bucket_to_row(data, row, agg_key):\n            sub_agg_key = \"\"\n            for key, item in data.items():\n                if key == \"key_as_string\":\n                    continue\n                if key == \"key\":\n                    if \"key_as_string\" in data:\n                        collect_value(row, agg_key, data[\"key_as_string\"])\n                    else:\n                        collect_value(row, agg_key, data[\"key\"])\n                    continue\n\n                if isinstance(item, (str, int, float)):\n                    collect_value(row, agg_key + \".\" + key, item)\n                elif isinstance(item, dict):\n                    if \"buckets\" not in item:\n                        for sub_key, sub_item in item.items():\n                            collect_value(\n                                row,\n                                agg_key + \".\" + key + \".\" + sub_key,\n                                sub_item,\n                            )\n                    else:\n                        sub_agg_key = key\n\n            return sub_agg_key\n\n        def parse_buckets_list(rows, parent_key, data, row, depth):\n            if len(rows) > 0 and depth == 0:\n                row = rows.pop()\n\n            for value in data:\n                row = row.copy()\n                sub_agg_key = parse_bucket_to_row(value, row, parent_key)\n\n                if sub_agg_key == \"\":\n                    rows.append(row)\n                else:\n                    depth += 1\n                    parse_buckets_list(rows, sub_agg_key, value[sub_agg_key][\"buckets\"], row, depth)\n\n        def collect_aggregations(rows, parent_key, data, row, depth):\n            row = get_row(rows, row)\n            parse_bucket_to_row(data, row, parent_key)\n\n            if \"buckets\" in data:\n                parse_buckets_list(rows, parent_key, data[\"buckets\"], row, depth)\n\n            return None\n\n        def get_flatten_results(dd, separator=\".\", prefix=\"\"):\n            if isinstance(dd, dict):\n                return {\n                    prefix + separator + k if prefix else k: v\n                    for kk, vv in dd.items()\n                    for k, v in get_flatten_results(vv, separator, kk).items()\n                }\n            elif isinstance(dd, list) and len(dd) == 1:\n                return {prefix: dd[0]}\n            else:\n                return {prefix: dd}\n\n        if result_fields:\n            for r in result_fields:\n                result_fields_index[r] = None\n\n        if \"error\" in raw_result:\n            error = raw_result[\"error\"]\n            if len(error) > 10240:\n                error = error[:10240] + \"... continues\"\n\n            raise Exception(error)\n        elif \"aggregations\" in raw_result:\n            for key, data in raw_result[\"aggregations\"].items():\n                collect_aggregations(result_rows, key, data, None, 0)\n\n        elif \"hits\" in raw_result and \"hits\" in raw_result[\"hits\"]:\n            for h in raw_result[\"hits\"][\"hits\"]:\n                row = {}\n\n                fields_parameter_name = \"_source\" if \"_source\" in h else \"fields\"\n                for column in h[fields_parameter_name]:\n                    if result_fields and column not in result_fields_index:\n                        continue\n\n                    unested_results = get_flatten_results({column: h[fields_parameter_name][column]})\n\n                    for column_name, value in unested_results.items():\n                        add_column_if_needed(column_name, value=value)\n                        row[column_name] = value\n\n                result_rows.append(row)\n        else:\n            raise Exception(\"Redash failed to parse the results it got from Elasticsearch.\")\n\n        return {\"columns\": result_columns, \"rows\": result_rows}\n\n\nclass OpenDistroSQLElasticSearch(ElasticSearch2):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.syntax = \"sql\"\n\n    def _build_query(self, query: str) -> Tuple[dict, str, Optional[list]]:\n        sql_query = {\"query\": query}\n        sql_query_url = \"/_opendistro/_sql\"\n        return sql_query, sql_query_url, None\n\n    @classmethod\n    def name(cls):\n        return \"Open Distro SQL Elasticsearch\"\n\n    @classmethod\n    def type(cls):\n        return \"elasticsearch2_OpenDistroSQLElasticSearch\"\n\n\nclass XPackSQLElasticSearch(ElasticSearch2):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.syntax = \"sql\"\n\n    def _build_query(self, query: str) -> Tuple[dict, str, Optional[list]]:\n        sql_query = {\"query\": query}\n        sql_query_url = \"/_xpack/sql\"\n        return sql_query, sql_query_url, None\n\n    @classmethod\n    def _parse_results(cls, result_fields, raw_result):\n        error = raw_result.get(\"error\")\n        if error:\n            raise Exception(error)\n\n        rv = {\n            \"columns\": [\n                {\n                    \"name\": c[\"name\"],\n                    \"friendly_name\": c[\"name\"],\n                    \"type\": ELASTICSEARCH_TYPES_MAPPING.get(c[\"type\"], \"string\"),\n                }\n                for c in raw_result[\"columns\"]\n            ],\n            \"rows\": [],\n        }\n        query_results_rows = raw_result[\"rows\"]\n\n        for query_results_row in query_results_rows:\n            result_row = dict()\n            for column, column_value in zip(rv[\"columns\"], query_results_row):\n                result_row[column[\"name\"]] = column_value\n            rv[\"rows\"].append(result_row)\n\n        return rv\n\n    @classmethod\n    def name(cls):\n        return \"X-Pack SQL Elasticsearch\"\n\n    @classmethod\n    def type(cls):\n        return \"elasticsearch2_XPackSQLElasticSearch\"\n\n\nregister(ElasticSearch2)\nregister(OpenDistroSQLElasticSearch)\nregister(XPackSQLElasticSearch)\n"
  },
  {
    "path": "redash/query_runner/exasol.py",
    "content": "import datetime\n\nfrom redash.query_runner import (\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\n\ndef _exasol_type_mapper(val, data_type):\n    if val is None:\n        return None\n    elif data_type[\"type\"] == \"DECIMAL\":\n        if data_type[\"scale\"] == 0 and data_type[\"precision\"] < 16:\n            return int(val)\n        elif data_type[\"scale\"] == 0 and data_type[\"precision\"] >= 16:\n            return val\n        else:\n            return float(val)\n    elif data_type[\"type\"] == \"DATE\":\n        return datetime.date(int(val[0:4]), int(val[5:7]), int(val[8:10]))\n    elif data_type[\"type\"] == \"TIMESTAMP\":\n        return datetime.datetime(\n            int(val[0:4]),\n            int(val[5:7]),\n            int(val[8:10]),  # year, month, day\n            int(val[11:13]),\n            int(val[14:16]),\n            int(val[17:19]),  # hour, minute, second\n            int(val[20:26].ljust(6, \"0\")) if len(val) > 20 else 0,\n        )  # microseconds (if available)\n    else:\n        return val\n\n\ndef _type_mapper(data_type):\n    if data_type[\"type\"] == \"DECIMAL\":\n        if data_type[\"scale\"] == 0 and data_type[\"precision\"] < 16:\n            return TYPE_INTEGER\n        elif data_type[\"scale\"] == 0 and data_type[\"precision\"] >= 16:\n            return TYPE_STRING\n        else:\n            return TYPE_FLOAT\n    elif data_type[\"type\"] == \"DATE\":\n        return TYPE_DATE\n    elif data_type[\"type\"] == \"TIMESTAMP\":\n        return TYPE_DATETIME\n    else:\n        return TYPE_STRING\n\n\ntry:\n    import pyexasol\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\nclass Exasol(BaseQueryRunner):\n    noop_query = \"SELECT 1 FROM DUAL\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\", \"default\": 8563},\n                \"encrypted\": {\"type\": \"boolean\", \"title\": \"Enable SSL Encryption\"},\n            },\n            \"required\": [\"host\", \"port\", \"user\", \"password\"],\n            \"order\": [\"host\", \"port\", \"user\", \"password\", \"encrypted\"],\n            \"secret\": [\"password\"],\n        }\n\n    def _get_connection(self):\n        exahost = \"%s:%s\" % (\n            self.configuration.get(\"host\", None),\n            self.configuration.get(\"port\", 8563),\n        )\n        return pyexasol.connect(\n            dsn=exahost,\n            user=self.configuration.get(\"user\", None),\n            password=self.configuration.get(\"password\", None),\n            encryption=self.configuration.get(\"encrypted\", True),\n            compression=True,\n            json_lib=\"rapidjson\",\n            fetch_mapper=_exasol_type_mapper,\n        )\n\n    def run_query(self, query, user):\n        connection = self._get_connection()\n        statement = None\n        error = None\n        try:\n            statement = connection.execute(query)\n            columns = [\n                {\"name\": n, \"friendly_name\": n, \"type\": _type_mapper(t)} for (n, t) in statement.columns().items()\n            ]\n            cnames = statement.column_names()\n\n            rows = [dict(zip(cnames, row)) for row in statement]\n            data = {\"columns\": columns, \"rows\": rows}\n        finally:\n            if statement is not None:\n                statement.close()\n\n            connection.close()\n\n        return data, error\n\n    def get_schema(self, get_stats=False):\n        query = \"\"\"\n        SELECT\n            COLUMN_SCHEMA,\n            COLUMN_TABLE,\n            COLUMN_NAME\n        FROM EXA_ALL_COLUMNS\n        \"\"\"\n\n        connection = self._get_connection()\n        statement = None\n        try:\n            statement = connection.execute(query)\n            result = {}\n\n            for schema, table_name, column in statement:\n                table_name_with_schema = \"%s.%s\" % (schema, table_name)\n\n                if table_name_with_schema not in result:\n                    result[table_name_with_schema] = {\n                        \"name\": table_name_with_schema,\n                        \"columns\": [],\n                    }\n\n                result[table_name_with_schema][\"columns\"].append(column)\n        finally:\n            if statement is not None:\n                statement.close()\n\n            connection.close()\n\n        return result.values()\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n\nregister(Exasol)\n"
  },
  {
    "path": "redash/query_runner/excel.py",
    "content": "import logging\n\nimport yaml\n\nfrom redash.query_runner import BaseQueryRunner, NotSupported, register\nfrom redash.utils.requests_session import (\n    UnacceptableAddressException,\n    requests_or_advocate,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import numpy as np\n    import openpyxl  # noqa: F401\n    import pandas as pd\n    import xlrd  # noqa: F401\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\nclass Excel(BaseQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {},\n        }\n\n    def __init__(self, configuration):\n        super(Excel, self).__init__(configuration)\n        self.syntax = \"yaml\"\n\n    def test_connection(self):\n        pass\n\n    def run_query(self, query, user):\n        path = \"\"\n        ua = \"\"\n        args = {}\n        try:\n            args = yaml.safe_load(query)\n            path = args[\"url\"]\n            args.pop(\"url\", None)\n            ua = args[\"user-agent\"]\n            args.pop(\"user-agent\", None)\n\n        except Exception:\n            pass\n\n        try:\n            response = requests_or_advocate.get(url=path, headers={\"User-agent\": ua})\n            workbook = pd.read_excel(response.content, **args)\n\n            df = workbook.copy()\n            data = {\"columns\": [], \"rows\": []}\n            conversions = [\n                {\n                    \"pandas_type\": np.integer,\n                    \"redash_type\": \"integer\",\n                },\n                {\n                    \"pandas_type\": np.inexact,\n                    \"redash_type\": \"float\",\n                },\n                {\n                    \"pandas_type\": np.datetime64,\n                    \"redash_type\": \"datetime\",\n                    \"to_redash\": lambda x: x.strftime(\"%Y-%m-%d %H:%M:%S\"),\n                },\n                {\"pandas_type\": np.bool_, \"redash_type\": \"boolean\"},\n                {\"pandas_type\": np.object_, \"redash_type\": \"string\"},\n            ]\n            labels = []\n            for dtype, label in zip(df.dtypes, df.columns):\n                for conversion in conversions:\n                    if issubclass(dtype.type, conversion[\"pandas_type\"]):\n                        data[\"columns\"].append(\n                            {\"name\": label, \"friendly_name\": label, \"type\": conversion[\"redash_type\"]}\n                        )\n                        labels.append(label)\n                        func = conversion.get(\"to_redash\")\n                        if func:\n                            df[label] = df[label].apply(func)\n                        break\n            data[\"rows\"] = df[labels].replace({np.nan: None}).to_dict(orient=\"records\")\n\n            error = None\n        except KeyboardInterrupt:\n            error = \"Query cancelled by user.\"\n            data = None\n        except UnacceptableAddressException:\n            error = \"Can't query private addresses.\"\n            data = None\n        except Exception as e:\n            error = \"Error reading {0}. {1}\".format(path, str(e))\n            data = None\n\n        return data, error\n\n    def get_schema(self):\n        raise NotSupported()\n\n\nregister(Excel)\n"
  },
  {
    "path": "redash/query_runner/files/rds-combined-ca-bundle.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEEjCCAvqgAwIBAgIJAM2ZN/+nPi27MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD\nVQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi\nMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h\nem9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBhZi1zb3V0aC0xIFJvb3QgQ0Ew\nHhcNMTkxMDI4MTgwNTU4WhcNMjQxMDI2MTgwNTU4WjCBlTELMAkGA1UEBhMCVVMx\nEDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM\nGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx\nJjAkBgNVBAMMHUFtYXpvbiBSRFMgYWYtc291dGgtMSBSb290IENBMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwR2351uPMZaJk2gMGT+1sk8HE9MQh2rc\n/sCnbxGn2p1c7Oi9aBbd/GiFijeJb2BXvHU+TOq3d3Jjqepq8tapXVt4ojbTJNyC\nJ5E7r7KjTktKdLxtBE1MK25aY+IRJjtdU6vG3KiPKUT1naO3xs3yt0F76WVuFivd\n9OHv2a+KHvPkRUWIxpmAHuMY9SIIMmEZtVE7YZGx5ah0iO4JzItHcbVR0y0PBH55\narpFBddpIVHCacp1FUPxSEWkOpI7q0AaU4xfX0fe1BV5HZYRKpBOIp1TtZWvJD+X\njGUtL1BEsT5vN5g9MkqdtYrC+3SNpAk4VtpvJrdjraI/hhvfeXNnAwIDAQABo2Mw\nYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUEEi/\nWWMcBJsoGXg+EZwkQ0MscZQwHwYDVR0jBBgwFoAUEEi/WWMcBJsoGXg+EZwkQ0Ms\ncZQwDQYJKoZIhvcNAQELBQADggEBAGDZ5js5Pc/gC58LJrwMPXFhJDBS8QuDm23C\nFFUdlqucskwOS3907ErK1ZkmVJCIqFLArHqskFXMAkRZ2PNR7RjWLqBs+0znG5yH\nhRKb4DXzhUFQ18UBRcvT6V6zN97HTRsEEaNhM/7k8YLe7P8vfNZ28VIoJIGGgv9D\nwQBBvkxQ71oOmAG0AwaGD0ORGUfbYry9Dz4a4IcUsZyRWRMADixgrFv6VuETp26s\n/+z+iqNaGWlELBKh3iQCT6Y/1UnkPLO42bxrCSyOvshdkYN58Q2gMTE1SVTqyo8G\nLw8lLAz9bnvUSgHzB3jRrSx6ggF/WRMRYlR++y6LXP4SAsSAaC0=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEEjCCAvqgAwIBAgIJAJYM4LxvTZA6MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD\nVQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi\nMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h\nem9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBldS1zb3V0aC0xIFJvb3QgQ0Ew\nHhcNMTkxMDMwMjAyMDM2WhcNMjQxMDI4MjAyMDM2WjCBlTELMAkGA1UEBhMCVVMx\nEDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM\nGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx\nJjAkBgNVBAMMHUFtYXpvbiBSRFMgZXUtc291dGgtMSBSb290IENBMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqM921jXCXeqpRNCS9CBPOe5N7gMaEt+D\ns5uR3riZbqzRlHGiF1jZihkXfHAIQewDwy+Yz+Oec1aEZCQMhUHxZJPusuX0cJfj\nb+UluFqHIijL2TfXJ3D0PVLLoNTQJZ8+GAPECyojAaNuoHbdVqxhOcznMsXIXVFq\nyVLKDGvyKkJjai/iSPDrQMXufg3kWt0ISjNLvsG5IFXgP4gttsM8i0yvRd4QcHoo\nDjvH7V3cS+CQqW5SnDrGnHToB0RLskE1ET+oNOfeN9PWOxQprMOX/zmJhnJQlTqD\nQP7jcf7SddxrKFjuziFiouskJJyNDsMjt1Lf60+oHZhed2ogTeifGwIDAQABo2Mw\nYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUFBAF\ncgJe/BBuZiGeZ8STfpkgRYQwHwYDVR0jBBgwFoAUFBAFcgJe/BBuZiGeZ8STfpkg\nRYQwDQYJKoZIhvcNAQELBQADggEBAKAYUtlvDuX2UpZW9i1QgsjFuy/ErbW0dLHU\ne/IcFtju2z6RLZ+uF+5A8Kme7IKG1hgt8s+w9TRVQS/7ukQzoK3TaN6XKXRosjtc\no9Rm4gYWM8bmglzY1TPNaiI4HC7546hSwJhubjN0bXCuj/0sHD6w2DkiGuwKNAef\nyTu5vZhPkeNyXLykxkzz7bNp2/PtMBnzIp+WpS7uUDmWyScGPohKMq5PqvL59z+L\nZI3CYeMZrJ5VpXUg3fNNIz/83N3G0sk7wr0ohs/kHTP7xPOYB0zD7Ku4HA0Q9Swf\nWX0qr6UQgTPMjfYDLffI7aEId0gxKw1eGYc6Cq5JAZ3ipi/cBFc=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEEjCCAvqgAwIBAgIJANew34ehz5l8MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD\nVQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi\nMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h\nem9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0Ew\nHhcNMTkwNTEwMjE0ODI3WhcNMjQwNTA4MjE0ODI3WjCBlTELMAkGA1UEBhMCVVMx\nEDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM\nGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx\nJjAkBgNVBAMMHUFtYXpvbiBSRFMgbWUtc291dGgtMSBSb290IENBMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp7BYV88MukcY+rq0r79+C8UzkT30fEfT\naPXbx1d6M7uheGN4FMaoYmL+JE1NZPaMRIPTHhFtLSdPccInvenRDIatcXX+jgOk\nUA6lnHQ98pwN0pfDUyz/Vph4jBR9LcVkBbe0zdoKKp+HGbMPRU0N2yNrog9gM5O8\ngkU/3O2csJ/OFQNnj4c2NQloGMUpEmedwJMOyQQfcUyt9CvZDfIPNnheUS29jGSw\nERpJe/AENu8Pxyc72jaXQuD+FEi2Ck6lBkSlWYQFhTottAeGvVFNCzKszCntrtqd\nrdYUwurYsLTXDHv9nW2hfDUQa0mhXf9gNDOBIVAZugR9NqNRNyYLHQIDAQABo2Mw\nYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU54cf\nDjgwBx4ycBH8+/r8WXdaiqYwHwYDVR0jBBgwFoAU54cfDjgwBx4ycBH8+/r8WXda\niqYwDQYJKoZIhvcNAQELBQADggEBAIIMTSPx/dR7jlcxggr+O6OyY49Rlap2laKA\neC/XI4ySP3vQkIFlP822U9Kh8a9s46eR0uiwV4AGLabcu0iKYfXjPkIprVCqeXV7\nny9oDtrbflyj7NcGdZLvuzSwgl9SYTJp7PVCZtZutsPYlbJrBPHwFABvAkMvRtDB\nhitIg4AESDGPoCl94sYHpfDfjpUDMSrAMDUyO6DyBdZH5ryRMAs3lGtsmkkNUrso\naTW6R05681Z0mvkRdb+cdXtKOSuDZPoe2wJJIaz3IlNQNSrB5TImMYgmt6iAsFhv\n3vfTSTKrZDNTJn4ybG6pq1zWExoXsktZPylJly6R3RBwV6nwqBM=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBjCCAu6gAwIBAgIJAMc0ZzaSUK51MA0GCSqGSIb3DQEBCwUAMIGPMQswCQYD\nVQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi\nMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h\nem9uIFJEUzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkw\nODIyMTcwODUwWhcNMjQwODIyMTcwODUwWjCBjzELMAkGA1UEBhMCVVMxEDAOBgNV\nBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoMGUFtYXpv\nbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxIDAeBgNV\nBAMMF0FtYXpvbiBSRFMgUm9vdCAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEArXnF/E6/Qh+ku3hQTSKPMhQQlCpoWvnIthzX6MK3p5a0eXKZ\noWIjYcNNG6UwJjp4fUXl6glp53Jobn+tWNX88dNH2n8DVbppSwScVE2LpuL+94vY\n0EYE/XxN7svKea8YvlrqkUBKyxLxTjh+U/KrGOaHxz9v0l6ZNlDbuaZw3qIWdD/I\n6aNbGeRUVtpM6P+bWIoxVl/caQylQS6CEYUk+CpVyJSkopwJlzXT07tMoDL5WgX9\nO08KVgDNz9qP/IGtAcRduRcNioH3E9v981QO1zt/Gpb2f8NqAjUUCUZzOnij6mx9\nMcZ+9cWX88CRzR0vQODWuZscgI08NvM69Fn2SQIDAQABo2MwYTAOBgNVHQ8BAf8E\nBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUc19g2LzLA5j0Kxc0LjZa\npmD/vB8wHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJKoZIhvcN\nAQELBQADggEBAHAG7WTmyjzPRIM85rVj+fWHsLIvqpw6DObIjMWokpliCeMINZFV\nynfgBKsf1ExwbvJNzYFXW6dihnguDG9VMPpi2up/ctQTN8tm9nDKOy08uNZoofMc\nNUZxKCEkVKZv+IL4oHoeayt8egtv3ujJM6V14AstMQ6SwvwvA93EP/Ug2e4WAXHu\ncbI1NAbUgVDqp+DRdfvZkgYKryjTWd/0+1fS8X1bBZVWzl7eirNVnHbSH2ZDpNuY\n0SBd8dj5F6ld3t58ydZbrTHze7JJOd8ijySAp4/kiu9UfZWuTPABzDa/DSdz9Dk/\nzPW4CXXvhLmE02TA9/HeCw3KEHIwicNuEfw=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEEDCCAvigAwIBAgIJAKFMXyltvuRdMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD\nVQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi\nMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h\nem9uIFJEUzElMCMGA1UEAwwcQW1hem9uIFJEUyBCZXRhIFJvb3QgMjAxOSBDQTAe\nFw0xOTA4MTkxNzM4MjZaFw0yNDA4MTkxNzM4MjZaMIGUMQswCQYDVQQGEwJVUzEQ\nMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UECgwZ\nQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEl\nMCMGA1UEAwwcQW1hem9uIFJEUyBCZXRhIFJvb3QgMjAxOSBDQTCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBAMkZdnIH9ndatGAcFo+DppGJ1HUt4x+zeO+0\nZZ29m0sfGetVulmTlv2d5b66e+QXZFWpcPQMouSxxYTW08TbrQiZngKr40JNXftA\natvzBqIImD4II0ZX5UEVj2h98qe/ypW5xaDN7fEa5e8FkYB1TEemPaWIbNXqchcL\ntV7IJPr3Cd7Z5gZJlmujIVDPpMuSiNaal9/6nT9oqN+JSM1fx5SzrU5ssg1Vp1vv\n5Xab64uOg7wCJRB9R2GC9XD04odX6VcxUAGrZo6LR64ZSifupo3l+R5sVOc5i8NH\nskdboTzU9H7+oSdqoAyhIU717PcqeDum23DYlPE2nGBWckE+eT8CAwEAAaNjMGEw\nDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFK2hDBWl\nsbHzt/EHd0QYOooqcFPhMB8GA1UdIwQYMBaAFK2hDBWlsbHzt/EHd0QYOooqcFPh\nMA0GCSqGSIb3DQEBCwUAA4IBAQAO/718k8EnOqJDx6wweUscGTGL/QdKXUzTVRAx\nJUsjNUv49mH2HQVEW7oxszfH6cPCaupNAddMhQc4C/af6GHX8HnqfPDk27/yBQI+\nyBBvIanGgxv9c9wBbmcIaCEWJcsLp3HzXSYHmjiqkViXwCpYfkoV3Ns2m8bp+KCO\ny9XmcCKRaXkt237qmoxoh2sGmBHk2UlQtOsMC0aUQ4d7teAJG0q6pbyZEiPyKZY1\nXR/UVxMJL0Q4iVpcRS1kaNCMfqS2smbLJeNdsan8pkw1dvPhcaVTb7CvjhJtjztF\nYfDzAI5794qMlWxwilKMmUvDlPPOTen8NNHkLwWvyFCH7Doh\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEFjCCAv6gAwIBAgIJAMzYZJ+R9NBVMA0GCSqGSIb3DQEBCwUAMIGXMQswCQYD\nVQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi\nMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h\nem9uIFJEUzEoMCYGA1UEAwwfQW1hem9uIFJEUyBQcmV2aWV3IFJvb3QgMjAxOSBD\nQTAeFw0xOTA4MjEyMjI5NDlaFw0yNDA4MjEyMjI5NDlaMIGXMQswCQYDVQQGEwJV\nUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE\nCgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE\nUzEoMCYGA1UEAwwfQW1hem9uIFJEUyBQcmV2aWV3IFJvb3QgMjAxOSBDQTCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7kkS6vjgKKQTPynC2NjdN5aPPV\nO71G0JJS/2ARVBVJd93JLiGovVJilfWYfwZCs4gTRSSjrUD4D4HyqCd6A+eEEtJq\nM0DEC7i0dC+9WNTsPszuB206Jy2IUmxZMIKJAA1NHSbIMjB+b6/JhbSUi7nKdbR/\nbrj83bF+RoSA+ogrgX7mQbxhmFcoZN9OGaJgYKsKWUt5Wqv627KkGodUK8mDepgD\nS3ZfoRQRx3iceETpcmHJvaIge6+vyDX3d9Z22jmvQ4AKv3py2CmU2UwuhOltFDwB\n0ddtb39vgwrJxaGfiMRHpEP1DfNLWHAnA69/pgZPwIggidS+iBPUhgucMp8CAwEA\nAaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE\nFGnTGpQuQ2H/DZlXMQijZEhjs7TdMB8GA1UdIwQYMBaAFGnTGpQuQ2H/DZlXMQij\nZEhjs7TdMA0GCSqGSIb3DQEBCwUAA4IBAQC3xz1vQvcXAfpcZlngiRWeqU8zQAMQ\nLZPCFNv7PVk4pmqX+ZiIRo4f9Zy7TrOVcboCnqmP/b/mNq0gVF4O+88jwXJZD+f8\n/RnABMZcnGU+vK0YmxsAtYU6TIb1uhRFmbF8K80HHbj9vSjBGIQdPCbvmR2zY6VJ\nBYM+w9U9hp6H4DVMLKXPc1bFlKA5OBTgUtgkDibWJKFOEPW3UOYwp9uq6pFoN0AO\nxMTldqWFsOF3bJIlvOY0c/1EFZXu3Ns6/oCP//Ap9vumldYMUZWmbK+gK33FPOXV\n8BQ6jNC29icv7lLDpRPwjibJBXX+peDR5UK4FdYcswWEB1Tix5X8dYu6\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSYwJAYDVQQDDB1BbWF6b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQTAeFw0xOTEw\nMjgxODA2NTNaFw0yNDEwMjgxODA2NTNaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE\nCAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u\nIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE\nAwwYQW1hem9uIFJEUyBhZi1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEAvtV1OqmFa8zCVQSKOvPUJERLVFtd4rZmDpImc5rIoeBk7w/P\n9lcKUJjO8R/w1a2lJXx3oQ81tiY0Piw6TpT62YWVRMWrOw8+Vxq1dNaDSFp9I8d0\nUHillSSbOk6FOrPDp+R6AwbGFqUDebbN5LFFoDKbhNmH1BVS0a6YNKpGigLRqhka\ncClPslWtPqtjbaP3Jbxl26zWzLo7OtZl98dR225pq8aApNBwmtgA7Gh60HK/cX0t\n32W94n8D+GKSg6R4MKredVFqRTi9hCCNUu0sxYPoELuM+mHiqB5NPjtm92EzCWs+\n+vgWhMc6GxG+82QSWx1Vj8sgLqtE/vLrWddf5QIDAQABo2YwZDAOBgNVHQ8BAf8E\nBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuLB4gYVJrSKJj/Gz\npqc6yeA+RcAwHwYDVR0jBBgwFoAUEEi/WWMcBJsoGXg+EZwkQ0MscZQwDQYJKoZI\nhvcNAQELBQADggEBABauYOZxUhe9/RhzGJ8MsWCz8eKcyDVd4FCnY6Qh+9wcmYNT\nLtnD88LACtJKb/b81qYzcB0Em6+zVJ3Z9jznfr6buItE6es9wAoja22Xgv44BTHL\nrimbgMwpTt3uEMXDffaS0Ww6YWb3pSE0XYI2ISMWz+xRERRf+QqktSaL39zuiaW5\ntfZMre+YhohRa/F0ZQl3RCd6yFcLx4UoSPqQsUl97WhYzwAxZZfwvLJXOc4ATt3u\nVlCUylNDkaZztDJc/yN5XQoK9W5nOt2cLu513MGYKbuarQr8f+gYU8S+qOyuSRSP\nNRITzwCRVnsJE+2JmcRInn/NcanB7uOGqTvJ9+c=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSYwJAYDVQQDDB1BbWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQTAeFw0xOTEw\nMzAyMDIxMzBaFw0yNDEwMzAyMDIxMzBaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE\nCAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u\nIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE\nAwwYQW1hem9uIFJEUyBldS1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEAtEyjYcajx6xImJn8Vz1zjdmL4ANPgQXwF7+tF7xccmNAZETb\nbzb3I9i5fZlmrRaVznX+9biXVaGxYzIUIR3huQ3Q283KsDYnVuGa3mk690vhvJbB\nQIPgKa5mVwJppnuJm78KqaSpi0vxyCPe3h8h6LLFawVyWrYNZ4okli1/U582eef8\nRzJp/Ear3KgHOLIiCdPDF0rjOdCG1MOlDLixVnPn9IYOciqO+VivXBg+jtfc5J+L\nAaPm0/Yx4uELt1tkbWkm4BvTU/gBOODnYziITZM0l6Fgwvbwgq5duAtKW+h031lC\n37rEvrclqcp4wrsUYcLAWX79ZyKIlRxcAdvEhQIDAQABo2YwZDAOBgNVHQ8BAf8E\nBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU7zPyc0azQxnBCe7D\nb9KAadH1QSEwHwYDVR0jBBgwFoAUFBAFcgJe/BBuZiGeZ8STfpkgRYQwDQYJKoZI\nhvcNAQELBQADggEBAFGaNiYxg7yC/xauXPlaqLCtwbm2dKyK9nIFbF/7be8mk7Q3\nMOA0of1vGHPLVQLr6bJJpD9MAbUcm4cPAwWaxwcNpxOjYOFDaq10PCK4eRAxZWwF\nNJRIRmGsl8NEsMNTMCy8X+Kyw5EzH4vWFl5Uf2bGKOeFg0zt43jWQVOX6C+aL3Cd\npRS5MhmYpxMG8irrNOxf4NVFE2zpJOCm3bn0STLhkDcV/ww4zMzObTJhiIb5wSWn\nEXKKWhUXuRt7A2y1KJtXpTbSRHQxE++69Go1tWhXtRiULCJtf7wF2Ksm0RR/AdXT\n1uR1vKyH5KBJPX3ppYkQDukoHTFR0CpB+G84NLo=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSYwJAYDVQQDDB1BbWF6b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQTAeFw0xOTA1\nMTAyMTU4NDNaFw0yNTA2MDExMjAwMDBaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE\nCAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u\nIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE\nAwwYQW1hem9uIFJEUyBtZS1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEAudOYPZH+ihJAo6hNYMB5izPVBe3TYhnZm8+X3IoaaYiKtsp1\nJJhkTT0CEejYIQ58Fh4QrMUyWvU8qsdK3diNyQRoYLbctsBPgxBR1u07eUJDv38/\nC1JlqgHmMnMi4y68Iy7ymv50QgAMuaBqgEBRI1R6Lfbyrb2YvH5txjJyTVMwuCfd\nYPAtZVouRz0JxmnfsHyxjE+So56uOKTDuw++Ho4HhZ7Qveej7XB8b+PIPuroknd3\nFQB5RVbXRvt5ZcVD4F2fbEdBniF7FAF4dEiofVCQGQ2nynT7dZdEIPfPdH3n7ZmE\nlAOmwHQ6G83OsiHRBLnbp+QZRgOsjkHJxT20bQIDAQABo2YwZDAOBgNVHQ8BAf8E\nBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUOEVDM7VomRH4HVdA\nQvIMNq2tXOcwHwYDVR0jBBgwFoAU54cfDjgwBx4ycBH8+/r8WXdaiqYwDQYJKoZI\nhvcNAQELBQADggEBAHhvMssj+Th8IpNePU6RH0BiL6o9c437R3Q4IEJeFdYL+nZz\nPW/rELDPvLRUNMfKM+KzduLZ+l29HahxefejYPXtvXBlq/E/9czFDD4fWXg+zVou\nuDXhyrV4kNmP4S0eqsAP/jQHPOZAMFA4yVwO9hlqmePhyDnszCh9c1PfJSBh49+b\n4w7i/L3VBOMt8j3EKYvqz0gVfpeqhJwL4Hey8UbVfJRFJMJzfNHpePqtDRAY7yjV\nPYquRaV2ab/E+/7VFkWMM4tazYz/qsYA2jSH+4xDHvYk8LnsbcrF9iuidQmEc5sb\nFgcWaSKG4DJjcI5k7AJLWcXyTDt21Ci43LE+I9Q=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECDCCAvCgAwIBAgICVIYwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MDQxNzEz\nMDRaFw0yNDA4MjIxNzA4NTBaMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEmMCQGA1UEAwwdQW1h\nem9uIFJEUyBhcC1zb3V0aC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQDUYOz1hGL42yUCrcsMSOoU8AeD/3KgZ4q7gP+vAz1WnY9K/kim\neWN/2Qqzlo3+mxSFQFyD4MyV3+CnCPnBl9Sh1G/F6kThNiJ7dEWSWBQGAB6HMDbC\nBaAsmUc1UIz8sLTL3fO+S9wYhA63Wun0Fbm/Rn2yk/4WnJAaMZcEtYf6e0KNa0LM\np/kN/70/8cD3iz3dDR8zOZFpHoCtf0ek80QqTich0A9n3JLxR6g6tpwoYviVg89e\nqCjQ4axxOkWWeusLeTJCcY6CkVyFvDAKvcUl1ytM5AiaUkXblE7zDFXRM4qMMRdt\nlPm8d3pFxh0fRYk8bIKnpmtOpz3RIctDrZZxAgMBAAGjZjBkMA4GA1UdDwEB/wQE\nAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBT99wKJftD3jb4sHoHG\ni3uGlH6W6TAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG\n9w0BAQsFAAOCAQEAZ17hhr3dII3hUfuHQ1hPWGrpJOX/G9dLzkprEIcCidkmRYl+\nhu1Pe3caRMh/17+qsoEErmnVq5jNY9X1GZL04IZH8YbHc7iRHw3HcWAdhN8633+K\njYEB2LbJ3vluCGnCejq9djDb6alOugdLMJzxOkHDhMZ6/gYbECOot+ph1tQuZXzD\ntZ7prRsrcuPBChHlPjmGy8M9z8u+kF196iNSUGC4lM8vLkHM7ycc1/ZOwRq9aaTe\niOghbQQyAEe03MWCyDGtSmDfr0qEk+CHN+6hPiaL8qKt4s+V9P7DeK4iW08ny8Ox\nAVS7u0OK/5+jKMAMrKwpYrBydOjTUTHScocyNw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBzCCAu+gAwIBAgICQ2QwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MDUxODQ2\nMjlaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h\nem9uIFJEUyBzYS1lYXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP\nADCCAQoCggEBAMMvR+ReRnOzqJzoaPipNTt1Z2VA968jlN1+SYKUrYM3No+Vpz0H\nM6Tn0oYB66ByVsXiGc28ulsqX1HbHsxqDPwvQTKvO7SrmDokoAkjJgLocOLUAeld\n5AwvUjxGRP6yY90NV7X786MpnYb2Il9DIIaV9HjCmPt+rjy2CZjS0UjPjCKNfB8J\nbFjgW6GGscjeyGb/zFwcom5p4j0rLydbNaOr9wOyQrtt3ZQWLYGY9Zees/b8pmcc\nJt+7jstZ2UMV32OO/kIsJ4rMUn2r/uxccPwAc1IDeRSSxOrnFKhW3Cu69iB3bHp7\nJbawY12g7zshE4I14sHjv3QoXASoXjx4xgMCAwEAAaNmMGQwDgYDVR0PAQH/BAQD\nAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFI1Fc/Ql2jx+oJPgBVYq\nccgP0pQ8MB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3\nDQEBCwUAA4IBAQB4VVVabVp70myuYuZ3vltQIWqSUMhkaTzehMgGcHjMf9iLoZ/I\n93KiFUSGnek5cRePyS9wcpp0fcBT3FvkjpUdCjVtdttJgZFhBxgTd8y26ImdDDMR\n4+BUuhI5msvjL08f+Vkkpu1GQcGmyFVPFOy/UY8iefu+QyUuiBUnUuEDd49Hw0Fn\n/kIPII6Vj82a2mWV/Q8e+rgN8dIRksRjKI03DEoP8lhPlsOkhdwU6Uz9Vu6NOB2Q\nLs1kbcxAc7cFSyRVJEhh12Sz9d0q/CQSTFsVJKOjSNQBQfVnLz1GwO/IieUEAr4C\njkTntH0r1LX5b/GwN4R887LvjAEdTbg1his7\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECDCCAvCgAwIBAgIDAIkHMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJV\nUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE\nCgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE\nUzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkwOTA2MTc0\nMDIxWhcNMjQwODIyMTcwODUwWjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldh\nc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxIjAgBgNVBAoMGUFtYXpvbiBXZWIg\nU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxJTAjBgNVBAMMHEFt\nYXpvbiBSRFMgdXMtd2VzdC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQDD2yzbbAl77OofTghDMEf624OvU0eS9O+lsdO0QlbfUfWa1Kd6\n0WkgjkLZGfSRxEHMCnrv4UPBSK/Qwn6FTjkDLgemhqBtAnplN4VsoDL+BkRX4Wwq\n/dSQJE2b+0hm9w9UMVGFDEq1TMotGGTD2B71eh9HEKzKhGzqiNeGsiX4VV+LJzdH\nuM23eGisNqmd4iJV0zcAZ+Gbh2zK6fqTOCvXtm7Idccv8vZZnyk1FiWl3NR4WAgK\nAkvWTIoFU3Mt7dIXKKClVmvssG8WHCkd3Xcb4FHy/G756UZcq67gMMTX/9fOFM/v\nl5C0+CHl33Yig1vIDZd+fXV1KZD84dEJfEvHAgMBAAGjZjBkMA4GA1UdDwEB/wQE\nAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBR+ap20kO/6A7pPxo3+\nT3CfqZpQWjAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG\n9w0BAQsFAAOCAQEAHCJky2tPjPttlDM/RIqExupBkNrnSYnOK4kr9xJ3sl8UF2DA\nPAnYsjXp3rfcjN/k/FVOhxwzi3cXJF/2Tjj39Bm/OEfYTOJDNYtBwB0VVH4ffa/6\ntZl87jaIkrxJcreeeHqYMnIxeN0b/kliyA+a5L2Yb0VPjt9INq34QDc1v74FNZ17\n4z8nr1nzg4xsOWu0Dbjo966lm4nOYIGBRGOKEkHZRZ4mEiMgr3YLkv8gSmeitx57\nZ6dVemNtUic/LVo5Iqw4n3TBS0iF2C1Q1xT/s3h+0SXZlfOWttzSluDvoMv5PvCd\npFjNn+aXLAALoihL1MJSsxydtsLjOBro5eK0Vw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEDDCCAvSgAwIBAgICOFAwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTAxNzQ2\nMjFaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h\nem9uIFJEUyBhcC1ub3J0aGVhc3QtMiAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAzU72e6XbaJbi4HjJoRNjKxzUEuChKQIt7k3CWzNnmjc5\n8I1MjCpa2W1iw1BYVysXSNSsLOtUsfvBZxi/1uyMn5ZCaf9aeoA9UsSkFSZBjOCN\nDpKPCmfV1zcEOvJz26+1m8WDg+8Oa60QV0ou2AU1tYcw98fOQjcAES0JXXB80P2s\n3UfkNcnDz+l4k7j4SllhFPhH6BQ4lD2NiFAP4HwoG6FeJUn45EPjzrydxjq6v5Fc\ncQ8rGuHADVXotDbEhaYhNjIrsPL+puhjWfhJjheEw8c4whRZNp6gJ/b6WEes/ZhZ\nh32DwsDsZw0BfRDUMgUn8TdecNexHUw8vQWeC181hwIDAQABo2YwZDAOBgNVHQ8B\nAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUwW9bWgkWkr0U\nlrOsq2kvIdrECDgwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ\nKoZIhvcNAQELBQADggEBAEugF0Gj7HVhX0ehPZoGRYRt3PBuI2YjfrrJRTZ9X5wc\n9T8oHmw07mHmNy1qqWvooNJg09bDGfB0k5goC2emDiIiGfc/kvMLI7u+eQOoMKj6\nmkfCncyRN3ty08Po45vTLBFZGUvtQmjM6yKewc4sXiASSBmQUpsMbiHRCL72M5qV\nobcJOjGcIdDTmV1BHdWT+XcjynsGjUqOvQWWhhLPrn4jWe6Xuxll75qlrpn3IrIx\nCRBv/5r7qbcQJPOgwQsyK4kv9Ly8g7YT1/vYBlR3cRsYQjccw5ceWUj2DrMVWhJ4\nprf+E3Aa4vYmLLOUUvKnDQ1k3RGNu56V0tonsQbfsaM=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECjCCAvKgAwIBAgICEzUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTAyMDUy\nMjVaFw0yNDA4MjIxNzA4NTBaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEoMCYGA1UEAwwfQW1h\nem9uIFJEUyBjYS1jZW50cmFsLTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAOxHqdcPSA2uBjsCP4DLSlqSoPuQ/X1kkJLusVRKiQE2zayB\nviuCBt4VB9Qsh2rW3iYGM+usDjltGnI1iUWA5KHcvHszSMkWAOYWLiMNKTlg6LCp\nXnE89tvj5dIH6U8WlDvXLdjB/h30gW9JEX7S8supsBSci2GxEzb5mRdKaDuuF/0O\nqvz4YE04pua3iZ9QwmMFuTAOYzD1M72aOpj+7Ac+YLMM61qOtU+AU6MndnQkKoQi\nqmUN2A9IFaqHFzRlSdXwKCKUA4otzmz+/N3vFwjb5F4DSsbsrMfjeHMo6o/nb6Nh\nYDb0VJxxPee6TxSuN7CQJ2FxMlFUezcoXqwqXD0CAwEAAaNmMGQwDgYDVR0PAQH/\nBAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFDGGpon9WfIpsggE\nCxHq8hZ7E2ESMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqG\nSIb3DQEBCwUAA4IBAQAvpeQYEGZvoTVLgV9rd2+StPYykMsmFjWQcyn3dBTZRXC2\nlKq7QhQczMAOhEaaN29ZprjQzsA2X/UauKzLR2Uyqc2qOeO9/YOl0H3qauo8C/W9\nr8xqPbOCDLEXlOQ19fidXyyEPHEq5WFp8j+fTh+s8WOx2M7IuC0ANEetIZURYhSp\nxl9XOPRCJxOhj7JdelhpweX0BJDNHeUFi0ClnFOws8oKQ7sQEv66d5ddxqqZ3NVv\nRbCvCtEutQMOUMIuaygDlMn1anSM8N7Wndx8G6+Uy67AnhjGx7jw/0YPPxopEj6x\nJXP8j0sJbcT9K/9/fPVLNT25RvQ/93T2+IQL4Ca2\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBzCCAu+gAwIBAgICYpgwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTExNzMx\nNDhaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h\nem9uIFJEUyBldS13ZXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP\nADCCAQoCggEBAMk3YdSZ64iAYp6MyyKtYJtNzv7zFSnnNf6vv0FB4VnfITTMmOyZ\nLXqKAT2ahZ00hXi34ewqJElgU6eUZT/QlzdIu359TEZyLVPwURflL6SWgdG01Q5X\nO++7fSGcBRyIeuQWs9FJNIIqK8daF6qw0Rl5TXfu7P9dBc3zkgDXZm2DHmxGDD69\n7liQUiXzoE1q2Z9cA8+jirDioJxN9av8hQt12pskLQumhlArsMIhjhHRgF03HOh5\ntvi+RCfihVOxELyIRTRpTNiIwAqfZxxTWFTgfn+gijTmd0/1DseAe82aYic8JbuS\nEMbrDduAWsqrnJ4GPzxHKLXX0JasCUcWyMECAwEAAaNmMGQwDgYDVR0PAQH/BAQD\nAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFPLtsq1NrwJXO13C9eHt\nsLY11AGwMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3\nDQEBCwUAA4IBAQAnWBKj5xV1A1mYd0kIgDdkjCwQkiKF5bjIbGkT3YEFFbXoJlSP\n0lZZ/hDaOHI8wbLT44SzOvPEEmWF9EE7SJzkvSdQrUAWR9FwDLaU427ALI3ngNHy\nlGJ2hse1fvSRNbmg8Sc9GBv8oqNIBPVuw+AJzHTacZ1OkyLZrz1c1QvwvwN2a+Jd\nvH0V0YIhv66llKcYDMUQJAQi4+8nbRxXWv6Gq3pvrFoorzsnkr42V3JpbhnYiK+9\nnRKd4uWl62KRZjGkfMbmsqZpj2fdSWMY1UGyN1k+kDmCSWYdrTRDP0xjtIocwg+A\nJ116n4hV/5mbA0BaPiS2krtv17YAeHABZcvz\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECjCCAvKgAwIBAgICV2YwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTExOTM2\nMjBaFw0yNDA4MjIxNzA4NTBaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEoMCYGA1UEAwwfQW1h\nem9uIFJEUyBldS1jZW50cmFsLTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAMEx54X2pHVv86APA0RWqxxRNmdkhAyp2R1cFWumKQRofoFv\nn+SPXdkpIINpMuEIGJANozdiEz7SPsrAf8WHyD93j/ZxrdQftRcIGH41xasetKGl\nI67uans8d+pgJgBKGb/Z+B5m+UsIuEVekpvgpwKtmmaLFC/NCGuSsJoFsRqoa6Gh\nm34W6yJoY87UatddCqLY4IIXaBFsgK9Q/wYzYLbnWM6ZZvhJ52VMtdhcdzeTHNW0\n5LGuXJOF7Ahb4JkEhoo6TS2c0NxB4l4MBfBPgti+O7WjR3FfZHpt18A6Zkq6A2u6\nD/oTSL6c9/3sAaFTFgMyL3wHb2YlW0BPiljZIqECAwEAAaNmMGQwDgYDVR0PAQH/\nBAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFOcAToAc6skWffJa\nTnreaswAfrbcMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqG\nSIb3DQEBCwUAA4IBAQA1d0Whc1QtspK496mFWfFEQNegLh0a9GWYlJm+Htcj5Nxt\nDAIGXb+8xrtOZFHmYP7VLCT5Zd2C+XytqseK/+s07iAr0/EPF+O2qcyQWMN5KhgE\ncXw2SwuP9FPV3i+YAm11PBVeenrmzuk9NrdHQ7TxU4v7VGhcsd2C++0EisrmquWH\nmgIfmVDGxphwoES52cY6t3fbnXmTkvENvR+h3rj+fUiSz0aSo+XZUGHPgvuEKM/W\nCBD9Smc9CBoBgvy7BgHRgRUmwtABZHFUIEjHI5rIr7ZvYn+6A0O6sogRfvVYtWFc\nqpyrW1YX8mD0VlJ8fGKM3G+aCOsiiPKDV/Uafrm+\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECDCCAvCgAwIBAgICGAcwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTIxODE5\nNDRaFw0yNDA4MjIxNzA4NTBaMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEmMCQGA1UEAwwdQW1h\nem9uIFJEUyBldS1ub3J0aC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQCiIYnhe4UNBbdBb/nQxl5giM0XoVHWNrYV5nB0YukA98+TPn9v\nAoj1RGYmtryjhrf01Kuv8SWO+Eom95L3zquoTFcE2gmxCfk7bp6qJJ3eHOJB+QUO\nXsNRh76fwDzEF1yTeZWH49oeL2xO13EAx4PbZuZpZBttBM5zAxgZkqu4uWQczFEs\nJXfla7z2fvWmGcTagX10O5C18XaFroV0ubvSyIi75ue9ykg/nlFAeB7O0Wxae88e\nuhiBEFAuLYdqWnsg3459NfV8Yi1GnaitTym6VI3tHKIFiUvkSiy0DAlAGV2iiyJE\nq+DsVEO4/hSINJEtII4TMtysOsYPpINqeEzRAgMBAAGjZjBkMA4GA1UdDwEB/wQE\nAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRR0UpnbQyjnHChgmOc\nhnlc0PogzTAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG\n9w0BAQsFAAOCAQEAKJD4xVzSf4zSGTBJrmamo86jl1NHQxXUApAZuBZEc8tqC6TI\nT5CeoSr9CMuVC8grYyBjXblC4OsM5NMvmsrXl/u5C9dEwtBFjo8mm53rOOIm1fxl\nI1oYB/9mtO9ANWjkykuLzWeBlqDT/i7ckaKwalhLODsRDO73vRhYNjsIUGloNsKe\npxw3dzHwAZx4upSdEVG4RGCZ1D0LJ4Gw40OfD69hfkDfRVVxKGrbEzqxXRvovmDc\ntKLdYZO/6REoca36v4BlgIs1CbUXJGLSXUwtg7YXGLSVBJ/U0+22iGJmBSNcoyUN\ncjPFD9JQEhDDIYYKSGzIYpvslvGc4T5ISXFiuQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBzCCAu+gAwIBAgICZIEwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTIyMTMy\nMzJaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h\nem9uIFJEUyBldS13ZXN0LTIgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP\nADCCAQoCggEBALGiwqjiF7xIjT0Sx7zB3764K2T2a1DHnAxEOr+/EIftWKxWzT3u\nPFwS2eEZcnKqSdRQ+vRzonLBeNLO4z8aLjQnNbkizZMBuXGm4BqRm1Kgq3nlLDQn\n7YqdijOq54SpShvR/8zsO4sgMDMmHIYAJJOJqBdaus2smRt0NobIKc0liy7759KB\n6kmQ47Gg+kfIwxrQA5zlvPLeQImxSoPi9LdbRoKvu7Iot7SOa+jGhVBh3VdqndJX\n7tm/saj4NE375csmMETFLAOXjat7zViMRwVorX4V6AzEg1vkzxXpA9N7qywWIT5Y\nfYaq5M8i6vvLg0CzrH9fHORtnkdjdu1y+0MCAwEAAaNmMGQwDgYDVR0PAQH/BAQD\nAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFFOhOx1yt3Z7mvGB9jBv\n2ymdZwiOMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3\nDQEBCwUAA4IBAQBehqY36UGDvPVU9+vtaYGr38dBbp+LzkjZzHwKT1XJSSUc2wqM\nhnCIQKilonrTIvP1vmkQi8qHPvDRtBZKqvz/AErW/ZwQdZzqYNFd+BmOXaeZWV0Q\noHtDzXmcwtP8aUQpxN0e1xkWb1E80qoy+0uuRqb/50b/R4Q5qqSfJhkn6z8nwB10\n7RjLtJPrK8igxdpr3tGUzfAOyiPrIDncY7UJaL84GFp7WWAkH0WG3H8Y8DRcRXOU\nmqDxDLUP3rNuow3jnGxiUY+gGX5OqaZg4f4P6QzOSmeQYs6nLpH0PiN00+oS1BbD\nbpWdZEttILPI+vAYkU4QuBKKDjJL6HbSd+cn\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECDCCAvCgAwIBAgIDAIVCMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJV\nUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE\nCgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE\nUzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkwOTEzMTcw\nNjQxWhcNMjQwODIyMTcwODUwWjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldh\nc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxIjAgBgNVBAoMGUFtYXpvbiBXZWIg\nU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxJTAjBgNVBAMMHEFt\nYXpvbiBSRFMgdXMtZWFzdC0yIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQDE+T2xYjUbxOp+pv+gRA3FO24+1zCWgXTDF1DHrh1lsPg5k7ht\n2KPYzNc+Vg4E+jgPiW0BQnA6jStX5EqVh8BU60zELlxMNvpg4KumniMCZ3krtMUC\nau1NF9rM7HBh+O+DYMBLK5eSIVt6lZosOb7bCi3V6wMLA8YqWSWqabkxwN4w0vXI\n8lu5uXXFRemHnlNf+yA/4YtN4uaAyd0ami9+klwdkZfkrDOaiy59haOeBGL8EB/c\ndbJJlguHH5CpCscs3RKtOOjEonXnKXldxarFdkMzi+aIIjQ8GyUOSAXHtQHb3gZ4\nnS6Ey0CMlwkB8vUObZU9fnjKJcL5QCQqOfwvAgMBAAGjZjBkMA4GA1UdDwEB/wQE\nAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQUPuRHohPxx4VjykmH\n6usGrLL1ETAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG\n9w0BAQsFAAOCAQEAUdR9Vb3y33Yj6X6KGtuthZ08SwjImVQPtknzpajNE5jOJAh8\nquvQnU9nlnMO85fVDU1Dz3lLHGJ/YG1pt1Cqq2QQ200JcWCvBRgdvH6MjHoDQpqZ\nHvQ3vLgOGqCLNQKFuet9BdpsHzsctKvCVaeBqbGpeCtt3Hh/26tgx0rorPLw90A2\nV8QSkZJjlcKkLa58N5CMM8Xz8KLWg3MZeT4DmlUXVCukqK2RGuP2L+aME8dOxqNv\nOnOz1zrL5mR2iJoDpk8+VE/eBDmJX40IJk6jBjWoxAO/RXq+vBozuF5YHN1ujE92\ntO8HItgTp37XT8bJBAiAnt5mxw+NLSqtxk2QdQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEDDCCAvSgAwIBAgICY4kwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTMyMDEx\nNDJaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h\nem9uIFJEUyBhcC1zb3V0aGVhc3QtMSAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAr5u9OuLL/OF/fBNUX2kINJLzFl4DnmrhnLuSeSnBPgbb\nqddjf5EFFJBfv7IYiIWEFPDbDG5hoBwgMup5bZDbas+ZTJTotnnxVJTQ6wlhTmns\neHECcg2pqGIKGrxZfbQhlj08/4nNAPvyYCTS0bEcmQ1emuDPyvJBYDDLDU6AbCB5\n6Z7YKFQPTiCBblvvNzchjLWF9IpkqiTsPHiEt21sAdABxj9ityStV3ja/W9BfgxH\nwzABSTAQT6FbDwmQMo7dcFOPRX+hewQSic2Rn1XYjmNYzgEHisdUsH7eeXREAcTw\n61TRvaLH8AiOWBnTEJXPAe6wYfrcSd1pD0MXpoB62wIDAQABo2YwZDAOBgNVHQ8B\nAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUytwMiomQOgX5\nIchd+2lDWRUhkikwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ\nKoZIhvcNAQELBQADggEBACf6lRDpfCD7BFRqiWM45hqIzffIaysmVfr+Jr+fBTjP\nuYe/ba1omSrNGG23bOcT9LJ8hkQJ9d+FxUwYyICQNWOy6ejicm4z0C3VhphbTPqj\nyjpt9nG56IAcV8BcRJh4o/2IfLNzC/dVuYJV8wj7XzwlvjysenwdrJCoLadkTr1h\neIdG6Le07sB9IxrGJL9e04afk37h7c8ESGSE4E+oS4JQEi3ATq8ne1B9DQ9SasXi\nIRmhNAaISDzOPdyLXi9N9V9Lwe/DHcja7hgLGYx3UqfjhLhOKwp8HtoZORixAmOI\nHfILgNmwyugAbuZoCazSKKBhQ0wgO0WZ66ZKTMG8Oho=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBzCCAu+gAwIBAgICUYkwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTYxODIx\nMTVaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h\nem9uIFJEUyB1cy13ZXN0LTIgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP\nADCCAQoCggEBANCEZBZyu6yJQFZBJmSUZfSZd3Ui2gitczMKC4FLr0QzkbxY+cLa\nuVONIOrPt4Rwi+3h/UdnUg917xao3S53XDf1TDMFEYp4U8EFPXqCn/GXBIWlU86P\nPvBN+gzw3nS+aco7WXb+woTouvFVkk8FGU7J532llW8o/9ydQyDIMtdIkKTuMfho\nOiNHSaNc+QXQ32TgvM9A/6q7ksUoNXGCP8hDOkSZ/YOLiI5TcdLh/aWj00ziL5bj\npvytiMZkilnc9dLY9QhRNr0vGqL0xjmWdoEXz9/OwjmCihHqJq+20MJPsvFm7D6a\n2NKybR9U+ddrjb8/iyLOjURUZnj5O+2+OPcCAwEAAaNmMGQwDgYDVR0PAQH/BAQD\nAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFEBxMBdv81xuzqcK5TVu\npHj+Aor8MB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3\nDQEBCwUAA4IBAQBZkfiVqGoJjBI37aTlLOSjLcjI75L5wBrwO39q+B4cwcmpj58P\n3sivv+jhYfAGEbQnGRzjuFoyPzWnZ1DesRExX+wrmHsLLQbF2kVjLZhEJMHF9eB7\nGZlTPdTzHErcnuXkwA/OqyXMpj9aghcQFuhCNguEfnROY9sAoK2PTfnTz9NJHL+Q\nUpDLEJEUfc0GZMVWYhahc0x38ZnSY2SKacIPECQrTI0KpqZv/P+ijCEcMD9xmYEb\njL4en+XKS1uJpw5fIU5Sj0MxhdGstH6S84iAE5J3GM3XHklGSFwwqPYvuTXvANH6\nuboynxRgSae59jIlAK6Jrr6GWMwQRbgcaAlW\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEDDCCAvSgAwIBAgICEkYwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTYxOTUz\nNDdaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h\nem9uIFJEUyBhcC1zb3V0aGVhc3QtMiAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAufodI2Flker8q7PXZG0P0vmFSlhQDw907A6eJuF/WeMo\nGHnll3b4S6nC3oRS3nGeRMHbyU2KKXDwXNb3Mheu+ox+n5eb/BJ17eoj9HbQR1cd\ngEkIciiAltf8gpMMQH4anP7TD+HNFlZnP7ii3geEJB2GGXSxgSWvUzH4etL67Zmn\nTpGDWQMB0T8lK2ziLCMF4XAC/8xDELN/buHCNuhDpxpPebhct0T+f6Arzsiswt2j\n7OeNeLLZwIZvVwAKF7zUFjC6m7/VmTQC8nidVY559D6l0UhhU0Co/txgq3HVsMOH\nPbxmQUwJEKAzQXoIi+4uZzHFZrvov/nDTNJUhC6DqwIDAQABo2YwZDAOBgNVHQ8B\nAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUwaZpaCme+EiV\nM5gcjeHZSTgOn4owHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ\nKoZIhvcNAQELBQADggEBAAR6a2meCZuXO2TF9bGqKGtZmaah4pH2ETcEVUjkvXVz\nsl+ZKbYjrun+VkcMGGKLUjS812e7eDF726ptoku9/PZZIxlJB0isC/0OyixI8N4M\nNsEyvp52XN9QundTjkl362bomPnHAApeU0mRbMDRR2JdT70u6yAzGLGsUwMkoNnw\n1VR4XKhXHYGWo7KMvFrZ1KcjWhubxLHxZWXRulPVtGmyWg/MvE6KF+2XMLhojhUL\n+9jB3Fpn53s6KMx5tVq1x8PukHmowcZuAF8k+W4gk8Y68wIwynrdZrKRyRv6CVtR\nFZ8DeJgoNZT3y/GT254VqMxxfuy2Ccb/RInd16tEvVk=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEDDCCAvSgAwIBAgICOYIwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTcyMDA1\nMjlaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h\nem9uIFJEUyBhcC1ub3J0aGVhc3QtMyAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEA4dMak8W+XW8y/2F6nRiytFiA4XLwePadqWebGtlIgyCS\nkbug8Jv5w7nlMkuxOxoUeD4WhI6A9EkAn3r0REM/2f0aYnd2KPxeqS2MrtdxxHw1\nxoOxk2x0piNSlOz6yog1idsKR5Wurf94fvM9FdTrMYPPrDabbGqiBMsZZmoHLvA3\nZ+57HEV2tU0Ei3vWeGIqnNjIekS+E06KhASxrkNU5vi611UsnYZlSi0VtJsH4UGV\nLhnHl53aZL0YFO5mn/fzuNG/51qgk/6EFMMhaWInXX49Dia9FnnuWXwVwi6uX1Wn\n7kjoHi5VtmC8ZlGEHroxX2DxEr6bhJTEpcLMnoQMqwIDAQABo2YwZDAOBgNVHQ8B\nAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUsUI5Cb3SWB8+\ngv1YLN/ABPMdxSAwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ\nKoZIhvcNAQELBQADggEBAJAF3E9PM1uzVL8YNdzb6fwJrxxqI2shvaMVmC1mXS+w\nG0zh4v2hBZOf91l1EO0rwFD7+fxoI6hzQfMxIczh875T6vUXePKVOCOKI5wCrDad\nzQbVqbFbdhsBjF4aUilOdtw2qjjs9JwPuB0VXN4/jY7m21oKEOcnpe36+7OiSPjN\nxngYewCXKrSRqoj3mw+0w/+exYj3Wsush7uFssX18av78G+ehKPIVDXptOCP/N7W\n8iKVNeQ2QGTnu2fzWsGUSvMGyM7yqT+h1ILaT//yQS8er511aHMLc142bD4D9VSy\nDgactwPDTShK/PXqhvNey9v/sKXm4XatZvwcc8KYlW4=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEDDCCAvSgAwIBAgICcEUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTgxNjU2\nMjBaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h\nem9uIFJEUyBhcC1ub3J0aGVhc3QtMSAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAndtkldmHtk4TVQAyqhAvtEHSMb6pLhyKrIFved1WO3S7\n+I+bWwv9b2W/ljJxLq9kdT43bhvzonNtI4a1LAohS6bqyirmk8sFfsWT3akb+4Sx\n1sjc8Ovc9eqIWJCrUiSvv7+cS7ZTA9AgM1PxvHcsqrcUXiK3Jd/Dax9jdZE1e15s\nBEhb2OEPE+tClFZ+soj8h8Pl2Clo5OAppEzYI4LmFKtp1X/BOf62k4jviXuCSst3\nUnRJzE/CXtjmN6oZySVWSe0rQYuyqRl6//9nK40cfGKyxVnimB8XrrcxUN743Vud\nQQVU0Esm8OVTX013mXWQXJHP2c0aKkog8LOga0vobQIDAQABo2YwZDAOBgNVHQ8B\nAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQULmoOS1mFSjj+\nsnUPx4DgS3SkLFYwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ\nKoZIhvcNAQELBQADggEBAAkVL2P1M2/G9GM3DANVAqYOwmX0Xk58YBHQu6iiQg4j\nb4Ky/qsZIsgT7YBsZA4AOcPKQFgGTWhe9pvhmXqoN3RYltN8Vn7TbUm/ZVDoMsrM\ngwv0+TKxW1/u7s8cXYfHPiTzVSJuOogHx99kBW6b2f99GbP7O1Sv3sLq4j6lVvBX\nFiacf5LAWC925nvlTzLlBgIc3O9xDtFeAGtZcEtxZJ4fnGXiqEnN4539+nqzIyYq\nnvlgCzyvcfRAxwltrJHuuRu6Maw5AGcd2Y0saMhqOVq9KYKFKuD/927BTrbd2JVf\n2sGWyuPZPCk3gq+5pCjbD0c6DkhcMGI6WwxvM5V/zSM=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBzCCAu+gAwIBAgICJDQwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTgxNzAz\nMTVaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h\nem9uIFJEUyBldS13ZXN0LTMgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP\nADCCAQoCggEBAL9bL7KE0n02DLVtlZ2PL+g/BuHpMYFq2JnE2RgompGurDIZdjmh\n1pxfL3nT+QIVMubuAOy8InRfkRxfpxyjKYdfLJTPJG+jDVL+wDcPpACFVqoV7Prg\npVYEV0lc5aoYw4bSeYFhdzgim6F8iyjoPnObjll9mo4XsHzSoqJLCd0QC+VG9Fw2\nq+GDRZrLRmVM2oNGDRbGpGIFg77aRxRapFZa8SnUgs2AqzuzKiprVH5i0S0M6dWr\ni+kk5epmTtkiDHceX+dP/0R1NcnkCPoQ9TglyXyPdUdTPPRfKCq12dftqll+u4mV\nARdN6WFjovxax8EAP2OAUTi1afY+1JFMj+sCAwEAAaNmMGQwDgYDVR0PAQH/BAQD\nAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLfhrbrO5exkCVgxW0x3\nY2mAi8lNMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3\nDQEBCwUAA4IBAQAigQ5VBNGyw+OZFXwxeJEAUYaXVoP/qrhTOJ6mCE2DXUVEoJeV\nSxScy/TlFA9tJXqmit8JH8VQ/xDL4ubBfeMFAIAo4WzNWDVoeVMqphVEcDWBHsI1\nAETWzfsapRS9yQekOMmxg63d/nV8xewIl8aNVTHdHYXMqhhik47VrmaVEok1UQb3\nO971RadLXIEbVd9tjY5bMEHm89JsZDnDEw1hQXBb67Elu64OOxoKaHBgUH8AZn/2\nzFsL1ynNUjOhCSAA15pgd1vjwc0YsBbAEBPcHBWYBEyME6NLNarjOzBl4FMtATSF\nwWCKRGkvqN8oxYhwR2jf2rR5Mu4DWkK5Q8Ep\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBzCCAu+gAwIBAgICJVUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTkxODE2\nNTNaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz\naGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT\nZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h\nem9uIFJEUyB1cy1lYXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP\nADCCAQoCggEBAM3i/k2u6cqbMdcISGRvh+m+L0yaSIoOXjtpNEoIftAipTUYoMhL\nInXGlQBVA4shkekxp1N7HXe1Y/iMaPEyb3n+16pf3vdjKl7kaSkIhjdUz3oVUEYt\ni8Z/XeJJ9H2aEGuiZh3kHixQcZczn8cg3dA9aeeyLSEnTkl/npzLf//669Ammyhs\nXcAo58yvT0D4E0D/EEHf2N7HRX7j/TlyWvw/39SW0usiCrHPKDLxByLojxLdHzso\nQIp/S04m+eWn6rmD+uUiRteN1hI5ncQiA3wo4G37mHnUEKo6TtTUh+sd/ku6a8HK\nglMBcgqudDI90s1OpuIAWmuWpY//8xEG2YECAwEAAaNmMGQwDgYDVR0PAQH/BAQD\nAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFPqhoWZcrVY9mU7tuemR\nRBnQIj1jMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3\nDQEBCwUAA4IBAQB6zOLZ+YINEs72heHIWlPZ8c6WY8MDU+Be5w1M+BK2kpcVhCUK\nPJO4nMXpgamEX8DIiaO7emsunwJzMSvavSPRnxXXTKIc0i/g1EbiDjnYX9d85DkC\nE1LaAUCmCZBVi9fIe0H2r9whIh4uLWZA41oMnJx/MOmo3XyMfQoWcqaSFlMqfZM4\n0rNoB/tdHLNuV4eIdaw2mlHxdWDtF4oH+HFm+2cVBUVC1jXKrFv/euRVtsTT+A6i\nh2XBHKxQ1Y4HgAn0jACP2QSPEmuoQEIa57bEKEcZsBR8SDY6ZdTd2HLRIApcCOSF\nMRM8CKLeF658I0XgF8D5EsYoKPsA+74Z+jDH\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEETCCAvmgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSUwIwYDVQQDDBxBbWF6b24gUkRTIEJldGEgUm9vdCAyMDE5IENBMB4XDTE5MDgy\nMDE3MTAwN1oXDTI0MDgxOTE3MzgyNlowgZkxCzAJBgNVBAYTAlVTMRMwEQYDVQQI\nDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMSIwIAYDVQQKDBlBbWF6b24g\nV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMSowKAYDVQQD\nDCFBbWF6b24gUkRTIEJldGEgdXMtZWFzdC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDTNCOlotQcLP8TP82U2+nk0bExVuuMVOgFeVMx\nvbUHZQeIj9ikjk+jm6eTDnnkhoZcmJiJgRy+5Jt69QcRbb3y3SAU7VoHgtraVbxF\nQDh7JEHI9tqEEVOA5OvRrDRcyeEYBoTDgh76ROco2lR+/9uCvGtHVrMCtG7BP7ZB\nsSVNAr1IIRZZqKLv2skKT/7mzZR2ivcw9UeBBTUf8xsfiYVBvMGoEsXEycjYdf6w\nWV+7XS7teNOc9UgsFNN+9AhIBc1jvee5E//72/4F8pAttAg/+mmPUyIKtekNJ4gj\nOAR2VAzGx1ybzWPwIgOudZFHXFduxvq4f1hIRPH0KbQ/gkRrAgMBAAGjZjBkMA4G\nA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTkvpCD\n6C43rar9TtJoXr7q8dkrrjAfBgNVHSMEGDAWgBStoQwVpbGx87fxB3dEGDqKKnBT\n4TANBgkqhkiG9w0BAQsFAAOCAQEAJd9fOSkwB3uVdsS+puj6gCER8jqmhd3g/J5V\nZjk9cKS8H0e8pq/tMxeJ8kpurPAzUk5RkCspGt2l0BSwmf3ahr8aJRviMX6AuW3/\ng8aKplTvq/WMNGKLXONa3Sq8591J+ce8gtOX/1rDKmFI4wQ/gUzOSYiT991m7QKS\nFr6HMgFuz7RNJbb3Fy5cnurh8eYWA7mMv7laiLwTNsaro5qsqErD5uXuot6o9beT\na+GiKinEur35tNxAr47ax4IRubuIzyfCrezjfKc5raVV2NURJDyKP0m0CCaffAxE\nqn2dNfYc3v1D8ypg3XjHlOzRo32RB04o8ALHMD9LSwsYDLpMag==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEFzCCAv+gAwIBAgICFSUwDQYJKoZIhvcNAQELBQAwgZcxCzAJBgNVBAYTAlVT\nMRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK\nDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT\nMSgwJgYDVQQDDB9BbWF6b24gUkRTIFByZXZpZXcgUm9vdCAyMDE5IENBMB4XDTE5\nMDgyMTIyMzk0N1oXDTI0MDgyMTIyMjk0OVowgZwxCzAJBgNVBAYTAlVTMRMwEQYD\nVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMSIwIAYDVQQKDBlBbWF6\nb24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMS0wKwYD\nVQQDDCRBbWF6b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIDIwMTkgQ0EwggEiMA0G\nCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0dB/U7qRnSf05wOi7m10Pa2uPMTJv\nr6U/3Y17a5prq5Zr4++CnSUYarG51YuIf355dKs+7Lpzs782PIwCmLpzAHKWzix6\npOaTQ+WZ0+vUMTxyqgqWbsBgSCyP7pVBiyqnmLC/L4az9XnscrbAX4pNaoJxsuQe\nmzBo6yofjQaAzCX69DuqxFkVTRQnVy7LCFkVaZtjNAftnAHJjVgQw7lIhdGZp9q9\nIafRt2gteihYfpn+EAQ/t/E4MnhrYs4CPLfS7BaYXBycEKC5Muj1l4GijNNQ0Efo\nxG8LSZz7SNgUvfVwiNTaqfLP3AtEAWiqxyMyh3VO+1HpCjT7uNBFtmF3AgMBAAGj\nZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW\nBBQtinkdrj+0B2+qdXngV2tgHnPIujAfBgNVHSMEGDAWgBRp0xqULkNh/w2ZVzEI\no2RIY7O03TANBgkqhkiG9w0BAQsFAAOCAQEAtJdqbCxDeMc8VN1/RzCabw9BIL/z\n73Auh8eFTww/sup26yn8NWUkfbckeDYr1BrXa+rPyLfHpg06kwR8rBKyrs5mHwJx\nbvOzXD/5WTdgreB+2Fb7mXNvWhenYuji1MF+q1R2DXV3I05zWHteKX6Dajmx+Uuq\nYq78oaCBSV48hMxWlp8fm40ANCL1+gzQ122xweMFN09FmNYFhwuW+Ao+Vv90ZfQG\nPYwTvN4n/gegw2TYcifGZC2PNX74q3DH03DXe5fvNgRW5plgz/7f+9mS+YHd5qa9\ntYTPUvoRbi169ou6jicsMKUKPORHWhiTpSCWR1FMMIbsAcsyrvtIsuaGCQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID/jCCAuagAwIBAgIQdOCSuA9psBpQd8EI368/0DANBgkqhkiG9w0BAQsFADCB\nlzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB\nbWF6b24gUkRTIHNhLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjEwNTE5MTgwNjI2WhgPMjA2MTA1MTkxOTA2MjZaMIGXMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv\nbiBSRFMgc2EtZWFzdC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN6ftL6w8v3dB2yW\nLjCxSP1D7ZsOTeLZOSCz1Zv0Gkd0XLhil5MdHOHBvwH/DrXqFU2oGzCRuAy+aZis\nDardJU6ChyIQIciXCO37f0K23edhtpXuruTLLwUwzeEPdcnLPCX+sWEn9Y5FPnVm\npCd6J8edH2IfSGoa9LdErkpuESXdidLym/w0tWG/O2By4TabkNSmpdrCL00cqI+c\nprA8Bx1jX8/9sY0gpAovtuFaRN+Ivg3PAnWuhqiSYyQ5nC2qDparOWuDiOhpY56E\nEgmTvjwqMMjNtExfYx6Rv2Ndu50TriiNKEZBzEtkekwXInTupmYTvc7U83P/959V\nUiQ+WSMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU4uYHdH0+\nbUeh81Eq2l5/RJbW+vswDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB\nAQBhxcExJ+w74bvDknrPZDRgTeMLYgbVJjx2ExH7/Ac5FZZWcpUpFwWMIJJxtewI\nAnhryzM3tQYYd4CG9O+Iu0+h/VVfW7e4O3joWVkxNMb820kQSEwvZfA78aItGwOY\nWSaFNVRyloVicZRNJSyb1UL9EiJ9ldhxm4LTT0ax+4ontI7zTx6n6h8Sr6r/UOvX\nd9T5aUUENWeo6M9jGupHNn3BobtL7BZm2oS8wX8IVYj4tl0q5T89zDi2x0MxbsIV\n5ZjwqBQ5JWKv7ASGPb+z286RjPA9R2knF4lJVZrYuNV90rHvI/ECyt/JrDqeljGL\nBLl1W/UsvZo6ldLIpoMbbrb5\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBDCCAuygAwIBAgIQUfVbqapkLYpUqcLajpTJWzANBgkqhkiG9w0BAQsFADCB\nmjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB\nbWF6b24gUkRTIG1lLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV\nBAcMB1NlYXR0bGUwIBcNMjIwNTA2MjMyMDA5WhgPMjA2MjA1MDcwMDIwMDlaMIGa\nMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j\nLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt\nYXpvbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJIeovu3\newI9FVitXMQzvkh34aQ6WyI4NO3YepfJaePiv3cnyFGYHN2S1cR3UQcLWgypP5va\nj6bfroqwGbCbZZcb+6cyOB4ceKO9Ws1UkcaGHnNDcy5gXR7aCW2OGTUfinUuhd2d\n5bOGgV7JsPbpw0bwJ156+MwfOK40OLCWVbzy8B1kITs4RUPNa/ZJnvIbiMu9rdj4\n8y7GSFJLnKCjlOFUkNI5LcaYvI1+ybuNgphT3nuu5ZirvTswGakGUT/Q0J3dxP0J\npDfg5Sj/2G4gXiaM0LppVOoU5yEwVewhQ250l0eQAqSrwPqAkdTg9ng360zqCFPE\nJPPcgI1tdGUgneECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU\n/2AJVxWdZxc8eJgdpbwpW7b0f7IwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB\nCwUAA4IBAQBYm63jTu2qYKJ94gKnqc+oUgqmb1mTXmgmp/lXDbxonjszJDOXFbri\n3CCO7xB2sg9bd5YWY8sGKHaWmENj3FZpCmoefbUx++8D7Mny95Cz8R32rNcwsPTl\nebpd9A/Oaw5ug6M0x/cNr0qzF8Wk9Dx+nFEimp8RYQdKvLDfNFZHjPa1itnTiD8M\nTorAqj+VwnUGHOYBsT/0NY12tnwXdD+ATWfpEHdOXV+kTMqFFwDyhfgRVNpTc+os\nygr8SwhnSCpJPB/EYl2S7r+tgAbJOkuwUvGT4pTqrzDQEhwE7swgepnHC87zhf6l\nqN6mVpSnQKQLm6Ob5TeCEFgcyElsF5bH\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrjCCAjSgAwIBAgIRAOxu0I1QuMAhIeszB3fJIlkwCgYIKoZIzj0EAwMwgZYx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h\nem9uIFJEUyB1cy13ZXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjEwNTI0MjIwNjU5WhgPMjEyMTA1MjQyMzA2NTlaMIGWMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS\nRFMgdXMtd2VzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEz4bylRcGqqDWdP7gQIIoTHdBK6FNtKH1\n4SkEIXRXkYDmRvL9Bci1MuGrwuvrka5TDj4b7e+csY0llEzHpKfq6nJPFljoYYP9\nuqHFkv77nOpJJ633KOr8IxmeHW5RXgrZo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G\nA1UdDgQWBBQQikVz8wmjd9eDFRXzBIU8OseiGzAOBgNVHQ8BAf8EBAMCAYYwCgYI\nKoZIzj0EAwMDaAAwZQIwf06Mcrpw1O0EBLBBrp84m37NYtOkE/0Z0O+C7D41wnXi\nEQdn6PXUVgdD23Gj82SrAjEAklhKs+liO1PtN15yeZR1Io98nFve+lLptaLakZcH\n+hfFuUtCqMbaI8CdvJlKnPqT\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGCTCCA/GgAwIBAgIRALyWMTyCebLZOGcZZQmkmfcwDQYJKoZIhvcNAQEMBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI0MjAyODAzWhgPMjEyMTA1MjQyMTI4MDNa\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTMgUm9vdCBDQSBSU0E0MDk2IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\nwGFiyDyCrGqgdn4fXG12cxKAAfVvhMea1mw5h9CVRoavkPqhzQpAitSOuMB9DeiP\nwQyqcsiGl/cTEau4L+AUBG8b9v26RlY48exUYBXj8CieYntOT9iNw5WtdYJa3kF/\nJxgI+HDMzE9cmHDs5DOO3S0uwZVyra/xE1ymfSlpOeUIOTpHRJv97CBUEpaZMUW5\nSr6GruuOwFVpO5FX3A/jQlcS+UN4GjSRgDUJuqg6RRQldEZGCVCCmodbByvI2fGm\nreGpsPJD54KkmAX08nOR8e5hkGoHxq0m2DLD4SrOFmt65vG47qnuwplWJjtk9B3Z\n9wDoopwZLBOtlkPIkUllWm1P8EuHC1IKOA+wSP6XdT7cy8S77wgyHzR0ynxv7q/l\nvlZtH30wnNqFI0y9FeogD0TGMCHcnGqfBSicJXPy9T4fU6f0r1HwqKwPp2GArwe7\ndnqLTj2D7M9MyVtFjEs6gfGWXmu1y5uDrf+CszurE8Cycoma+OfjjuVQgWOCy7Nd\njJswPxAroTzVfpgoxXza4ShUY10woZu0/J+HmNmqK7lh4NS75q1tz75in8uTZDkV\nbe7GK+SEusTrRgcf3tlgPjSTWG3veNzFDF2Vn1GLJXmuZfhdlVQDBNXW4MNREExS\ndG57kJjICpT+r8X+si+5j51gRzkSnMYs7VHulpxfcwECAwEAAaNCMEAwDwYDVR0T\nAQH/BAUwAwEB/zAdBgNVHQ4EFgQU4JWOpDBmUBuWKvGPZelw87ezhL8wDgYDVR0P\nAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBRNLMql7itvXSEFQRAnyOjivHz\nl5IlWVQjAbOUr6ogZcwvK6YpxNAFW5zQr8F+fdkiypLz1kk5irx9TIpff0BWC9hQ\n/odMPO8Gxn8+COlSvc+dLsF2Dax3Hvz0zLeKMo+cYisJOzpdR/eKd0/AmFdkvQoM\nAOK9n0yYvVJU2IrSgeJBiiCarpKSeAktEVQ4rvyacQGr+QAPkkjRwm+5LHZKK43W\nnNnggRli9N/27qYtc5bgr3AaQEhEXMI4RxPRXCLsod0ehMGWyRRK728a+6PMMJAJ\nWHOU0x7LCEMPP/bvpLj3BdvSGqNor4ZtyXEbwREry1uzsgODeRRns5acPwTM6ff+\nCmxO2NZ0OktIUSYRmf6H/ZFlZrIhV8uWaIwEJDz71qvj7buhQ+RFDZ9CNL64C0X6\nmf0zJGEpddjANHaaVky+F4gYMtEy2K2Lcm4JGTdyIzUoIe+atzCnRp0QeIcuWtF+\ns8AjDYCVFNypcMmqbRmNpITSnOoCHSRuVkY3gutVoYyMLbp8Jm9SJnCIlEWTA6Rm\nwADOMGZJVn5/XRTRuetVOB3KlQDjs9OO01XN5NzGSZO2KT9ngAUfh9Eqhf1iRWSP\nnZlRbQ2NRCuY/oJ5N59mLGxnNJSE7giEKEBRhTQ/XEPIUYAUPD5fca0arKRJwbol\nl9Se1Hsq0ZU5f+OZKQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGATCCA+mgAwIBAgIRAK7vlRrGVEePJpW1VHMXdlIwDQYJKoZIhvcNAQEMBQAw\ngZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo\nQW1hem9uIFJEUyBhZi1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTAgFw0yMTA1MTkxOTI4NDNaGA8yMTIxMDUxOTIwMjg0M1owgZgx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h\nem9uIFJEUyBhZi1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH\nU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMZiHOQC6x4o\neC7vVOMCGiN5EuLqPYHdceFPm4h5k/ZejXTf7kryk6aoKZKsDIYihkaZwXVS7Y/y\n7Ig1F1ABi2jD+CYprj7WxXbhpysmN+CKG7YC3uE4jSvfvUnpzionkQbjJsRJcrPO\ncZJM4FVaVp3mlHHtvnM+K3T+ni4a38nAd8xrv1na4+B8ZzZwWZXarfg8lJoGskSn\nou+3rbGQ0r+XlUP03zWujHoNlVK85qUIQvDfTB7n3O4s1XNGvkfv3GNBhYRWJYlB\n4p8T+PFN8wG+UOByp1gV7BD64RnpuZ8V3dRAlO6YVAmINyG5UGrPzkIbLtErUNHO\n4iSp4UqYvztDqJWWHR/rA84ef+I9RVwwZ8FQbjKq96OTnPrsr63A5mXTC9dXKtbw\nXNJPQY//FEdyM3K8sqM0IdCzxCA1MXZ8+QapWVjwyTjUwFvL69HYky9H8eAER59K\n5I7u/CWWeCy2R1SYUBINc3xxLr0CGGukcWPEZW2aPo5ibW5kepU1P/pzdMTaTfao\nF42jSFXbc7gplLcSqUgWwzBnn35HLTbiZOFBPKf6vRRu8aRX9atgHw/EjCebi2xP\nxIYr5Ub8u0QVHIqcnF1/hVzO/Xz0chj3E6VF/yTXnsakm+W1aM2QkZbFGpga+LMy\nmFCtdPrELjea2CfxgibaJX1Q4rdEpc8DAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wHQYDVR0OBBYEFDSaycEyuspo/NOuzlzblui8KotFMA4GA1UdDwEB/wQEAwIB\nhjANBgkqhkiG9w0BAQwFAAOCAgEAbosemjeTRsL9o4v0KadBUNS3V7gdAH+X4vH2\nEe1Jc91VOGLdd/s1L9UX6bhe37b9WjUD69ur657wDW0RzxMYgQdZ27SUl0tEgGGp\ncCmVs1ky3zEN+Hwnhkz+OTmIg1ufq0W2hJgJiluAx2r1ib1GB+YI3Mo3rXSaBYUk\nbgQuujYPctf0PA153RkeICE5GI3OaJ7u6j0caYEixBS3PDHt2MJWexITvXGwHWwc\nCcrC05RIrTUNOJaetQw8smVKYOfRImEzLLPZ5kf/H3Cbj8BNAFNsa10wgvlPuGOW\nXLXqzNXzrG4V3sjQU5YtisDMagwYaN3a6bBf1wFwFIHQoAPIgt8q5zaQ9WI+SBns\nIl6rd4zfvjq/BPmt0uI7rVg/cgbaEg/JDL2neuM9CJAzmKxYxLQuHSX2i3Fy4Y1B\ncnxnRQETCRZNPGd00ADyxPKVoYBC45/t+yVusArFt+2SVLEGiFBr23eG2CEZu+HS\nnDEgIfQ4V3YOTUNa86wvbAss1gbbnT/v1XCnNGClEWCWNCSRjwV2ZmQ/IVTmNHPo\n7axTTBBJbKJbKzFndCnuxnDXyytdYRgFU7Ly3sa27WS2KFyFEDebLFRHQEfoYqCu\nIupSqBSbXsR3U10OTjc9z6EPo1nuV6bdz+gEDthmxKa1NI+Qb1kvyliXQHL2lfhr\n5zT5+Bs=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIF/zCCA+egAwIBAgIRAOLV6zZcL4IV2xmEneN1GwswDQYJKoZIhvcNAQEMBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyB1cy13ZXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUxOTE5MDg1OFoYDzIxMjEwNTE5MjAwODU4WjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIHVzLXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7koAKGXXlLixN\nfVjhuqvz0WxDeTQfhthPK60ekRpftkfE5QtnYGzeovaUAiS58MYVzqnnTACDwcJs\nIGTFE6Wd7sB6r8eI/3CwI1pyJfxepubiQNVAQG0zJETOVkoYKe/5KnteKtnEER3X\ntCBRdV/rfbxEDG9ZAsYfMl6zzhEWKF88G6xhs2+VZpDqwJNNALvQuzmTx8BNbl5W\nRUWGq9CQ9GK9GPF570YPCuURW7kl35skofudE9bhURNz51pNoNtk2Z3aEeRx3ouT\nifFJlzh+xGJRHqBG7nt5NhX8xbg+vw4xHCeq1aAe6aVFJ3Uf9E2HzLB4SfIT9bRp\nP7c9c0ySGt+3n+KLSHFf/iQ3E4nft75JdPjeSt0dnyChi1sEKDi0tnWGiXaIg+J+\nr1ZtcHiyYpCB7l29QYMAdD0TjfDwwPayLmq//c20cPmnSzw271VwqjUT0jYdrNAm\ngV+JfW9t4ixtE3xF2jaUh/NzL3bAmN5v8+9k/aqPXlU1BgE3uPwMCjrfn7V0I7I1\nWLpHyd9jF3U/Ysci6H6i8YKgaPiOfySimQiDu1idmPld659qerutUSemQWmPD3bE\ndcjZolmzS9U0Ujq/jDF1YayN3G3xvry1qWkTci0qMRMu2dZu30Herugh9vsdTYkf\n00EqngPbqtIVLDrDjEQLqPcb8QvWFQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/\nMB0GA1UdDgQWBBQBqg8Za/L0YMHURGExHfvPyfLbOTAOBgNVHQ8BAf8EBAMCAYYw\nDQYJKoZIhvcNAQEMBQADggIBACAGPMa1QL7P/FIO7jEtMelJ0hQlQepKnGtbKz4r\nXq1bUX1jnLvnAieR9KZmeQVuKi3g3CDU6b0mDgygS+FL1KDDcGRCSPh238Ou8KcG\nHIxtt3CMwMHMa9gmdcMlR5fJF9vhR0C56KM2zvyelUY51B/HJqHwGvWuexryXUKa\nwq1/iK2/d9mNeOcjDvEIj0RCMI8dFQCJv3PRCTC36XS36Tzr6F47TcTw1c3mgKcs\nxpcwt7ezrXMUunzHS4qWAA5OGdzhYlcv+P5GW7iAA7TDNrBF+3W4a/6s9v2nQAnX\nUvXd9ul0ob71377UhZbJ6SOMY56+I9cJOOfF5QvaL83Sz29Ij1EKYw/s8TYdVqAq\n+dCyQZBkMSnDFLVe3J1KH2SUSfm3O98jdPORQrUlORQVYCHPls19l2F6lCmU7ICK\nhRt8EVSpXm4sAIA7zcnR2nU00UH8YmMQLnx5ok9YGhuh3Ehk6QlTQLJux6LYLskd\n9YHOLGW/t6knVtV78DgPqDeEx/Wu/5A8R0q7HunpWxr8LCPBK6hksZnOoUhhb8IP\nvl46Ve5Tv/FlkyYr1RTVjETmg7lb16a8J0At14iLtpZWmwmuv4agss/1iBVMXfFk\n+ZGtx5vytWU5XJmsfKA51KLsMQnhrLxb3X3zC+JRCyJoyc8++F3YEcRi2pkRYE3q\nHing\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID/zCCAuegAwIBAgIRAI+asxQA/MB1cGyyrC0MPpkwDQYJKoZIhvcNAQELBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyBjYS13ZXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIzMDkxMzIwMjEzNFoYDzIwNjMwOTEzMjEyMTMzWjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGNhLXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMHvQITTZcfl2O\nyfzRIAPKwzzlc8eXWdXef7VUsbezg3lm9RC+vArO4JuAzta/aLw1D94wPSRm9JXX\nNkP3obO6Ql80/0doooU6BAPceD0xmEWC4aCFT/5KWsD6Sy2/Rjwq3NKBTwzxLwYK\nGqVsBp8AdrzDTmdRETC+Dg2czEo32mTDAA1uMgqrz6xxeTYroj8NTSTp6jfE6C0n\nYgzYmVQCEIjHqI49j7k3jfT3P2skCVKGJwQzoZnerFacKzXsDB18uIqU7NaMc2cX\nkOd0gRqpyKOzAHU2m5/S4jw4UHdkoI3E7nkayuen8ZPKH2YqWtTXUrXGhSTT34nX\nyiFgu+vTAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHzz1NTd\nTOm9zAv4d8l6XCFKSdJfMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC\nAQEAodBvd0cvXQYhFBef2evnuI9XA+AC/Q9P1nYtbp5MPA4aFhy5v9rjW8wwJX14\nl+ltd2o3tz8PFDBZ1NX2ooiWVlZthQxKn1/xDVKsTXHbYUXItPQ3jI5IscB5IML8\noCzAbkoLXsSPNOVFP5P4l4cZEMqHGRnBag7hLJZvmvzZSBnz+ioC2jpjVluF8kDX\nfQGNjqPECik68CqbSV0SaQ0cgEoYTDjwON5ZLBeS8sxR2abE/gsj4VFYl5w/uEBd\nw3Tt9uGfIy+wd2tNj6isGC6PcbPMjA31jd+ifs2yNzigqkcYTTWFtnvh4a8xiecm\nGHu2EgH0Jqzz500N7L3uQdPkdg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECTCCAvGgAwIBAgIRANxgyBbnxgTEOpDul2ZnC0UwDQYJKoZIhvcNAQELBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjEwNjEwMTgxOTA3WhgPMjA2MTA2MTAxOTE5MDda\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nxnwSDAChrMkfk5TA4Dk8hKzStDlSlONzmd3fTG0Wqr5+x3EmFT6Ksiu/WIwEl9J2\nK98UI7vYyuZfCxUKb1iMPeBdVGqk0zb92GpURd+Iz/+K1ps9ZLeGBkzR8mBmAi1S\nOfpwKiTBzIv6E8twhEn4IUpHsdcuX/2Y78uESpJyM8O5CpkG0JaV9FNEbDkJeBUQ\nAo2qqNcH4R0Qcr5pyeqA9Zto1RswgL06BQMI9dTpfwSP5VvkvcNUaLl7Zv5WzLQE\nJzORWePvdPzzvWEkY/3FPjxBypuYwssKaERW0fkPDmPtykktP9W/oJolKUFI6pXp\ny+Y6p6/AVdnQD2zZjW5FhQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBT+jEKs96LC+/X4BZkUYUkzPfXdqTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI\nhvcNAQELBQADggEBAIGQqgqcQ6XSGkmNebzR6DhadTbfDmbYeN5N0Vuzv+Tdmufb\ntMGjdjnYMg4B+IVnTKQb+Ox3pL9gbX6KglGK8HupobmIRtwKVth+gYYz3m0SL/Nk\nhaWPYzOm0x3tJm8jSdufJcEob4/ATce9JwseLl76pSWdl5A4lLjnhPPKudUDfH+1\nBLNUi3lxpp6GkC8aWUPtupnhZuXddolTLOuA3GwTZySI44NfaFRm+o83N1jp+EwD\n6e94M4cTRzjUv6J3MZmSbdtQP/Tk1uz2K4bQZGP0PZC3bVpqiesdE/xr+wbu8uHr\ncM1JXH0AmXf1yIkTgyWzmvt0k1/vgcw5ixAqvvE=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEATCCAumgAwIBAgIRAMhw98EQU18mIji+unM2YH8wDQYJKoZIhvcNAQELBQAw\ngZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo\nQW1hem9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTAgFw0yMjA2MDYyMTQyMjJaGA8yMDYyMDYwNjIyNDIyMlowgZgx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h\nem9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwH\nU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeeRoLfTm+7\nvqm7ZlFSx+1/CGYHyYrOOryM4/Z3dqYVHFMgWTR7V3ziO8RZ6yUanrRcWVX3PZbF\nAfX0KFE8OgLsXEZIX8odSrq86+/Th5eZOchB2fDBsUB7GuN2rvFBbM8lTI9ivVOU\nlbuTnYyb55nOXN7TpmH2bK+z5c1y9RVC5iQsNAl6IJNvSN8VCqXh31eK5MlKB4DT\n+Y3OivCrSGsjM+UR59uZmwuFB1h+icE+U0p9Ct3Mjq3MzSX5tQb6ElTNGlfmyGpW\nKh7GQ5XU1KaKNZXoJ37H53woNSlq56bpVrKI4uv7ATpdpFubOnSLtpsKlpLdR3sy\nWs245200pC8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUp0ki\n6+eWvsnBjQhMxwMW5pwn7DgwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUA\nA4IBAQB2V8lv0aqbYQpj/bmVv/83QfE4vOxKCJAHv7DQ35cJsTyBdF+8pBczzi3t\n3VNL5IUgW6WkyuUOWnE0eqAFOUVj0yTS1jSAtfl3vOOzGJZmWBbqm9BKEdu1D8O6\nsB8bnomwiab2tNDHPmUslpdDqdabbkWwNWzLJ97oGFZ7KNODMEPXWKWNxg33iHfS\n/nlmnrTVI3XgaNK9qLZiUrxu9Yz5gxi/1K+sG9/Dajd32ZxjRwDipOLiZbiXQrsd\nqzIMY4GcWf3g1gHL5mCTfk7dG22h/rhPyGV0svaDnsb+hOt6sv1McMN6Y3Ou0mtM\n/UaAXojREmJmTSCNvs2aBny3/2sy\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrjCCAjSgAwIBAgIRAMnRxsKLYscJV8Qv5pWbL7swCgYIKoZIzj0EAwMwgZYx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h\nem9uIFJEUyBzYS1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjEwNTE5MTgxNjAxWhgPMjEyMTA1MTkxOTE2MDFaMIGWMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS\nRFMgc2EtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEjFOCZgTNVKxLKhUxffiDEvTLFhrmIqdO\ndKqVdgDoELEzIHWDdC+19aDPitbCYtBVHl65ITu/9pn6mMUl5hhUNtfZuc6A+Iw1\nsBe0v0qI3y9Q9HdQYrGgeHDh8M5P7E2ho0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G\nA1UdDgQWBBS5L7/8M0TzoBZk39Ps7BkfTB4yJTAOBgNVHQ8BAf8EBAMCAYYwCgYI\nKoZIzj0EAwMDaAAwZQIwI43O0NtWKTgnVv9z0LO5UMZYgSve7GvGTwqktZYCMObE\nrUI4QerXM9D6JwLy09mqAjEAypfkdLyVWtaElVDUyHFkihAS1I1oUxaaDrynLNQK\nOu/Ay+ns+J+GyvyDUjBpVVW1\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIF/jCCA+agAwIBAgIQR71Z8lTO5Sj+as2jB7IWXzANBgkqhkiG9w0BAQwFADCB\nlzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB\nbWF6b24gUkRTIHVzLXdlc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjEwNTI0MjIwMzIwWhgPMjEyMTA1MjQyMzAzMjBaMIGXMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv\nbiBSRFMgdXMtd2VzdC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAM977bHIs1WJijrS\nXQMfUOhmlJjr2v0K0UjPl52sE1TJ76H8umo1yR4T7Whkd9IwBHNGKXCJtJmMr9zp\nfB38eLTu+5ydUAXdFuZpRMKBWwPVe37AdJRKqn5beS8HQjd3JXAgGKUNNuE92iqF\nqi2fIqFMpnJXWo0FIW6s2Dl2zkORd7tH0DygcRi7lgVxCsw1BJQhFJon3y+IV8/F\nbnbUXSNSDUnDW2EhvWSD8L+t4eiXYsozhDAzhBvojpxhPH9OB7vqFYw5qxFx+G0t\nlSLX5iWi1jzzc3XyGnB6WInZDVbvnvJ4BGZ+dTRpOCvsoMIn9bz4EQTvu243c7aU\nHbS/kvnCASNt+zk7C6lbmaq0AGNztwNj85Opn2enFciWZVnnJ/4OeefUWQxD0EPp\nSjEd9Cn2IHzkBZrHCg+lWZJQBKbUVS0lLIMSsLQQ6WvR38jY7D2nxM1A93xWxwpt\nZtQnYRCVXH6zt2OwDAFePInWwxUjR5t/wu3XxPgpSfrmTi3WYtr1wFypAJ811e/P\nyBtswWUQ6BNJQvy+KnOEeGfOwmtdDFYR+GOCfvCihzrKJrxOtHIieehR5Iw3cbXG\nsm4pDzfMUVvDDz6C2M6PRlJhhClbatHCjik9hxFYEsAlqtVVK9pxaz9i8hOqSFQq\nkJSQsgWw+oM/B2CyjcSqkSQEu8RLAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w\nHQYDVR0OBBYEFPmrdxpRRgu3IcaB5BTqlprcKdTsMA4GA1UdDwEB/wQEAwIBhjAN\nBgkqhkiG9w0BAQwFAAOCAgEAVdlxWjPvVKky3kn8ZizeM4D+EsLw9dWLau2UD/ls\nzwDCFoT6euagVeCknrn+YEl7g20CRYT9iaonGoMUPuMR/cdtPL1W/Rf40PSrGf9q\nQuxavWiHLEXOQTCtCaVZMokkvjuuLNDXyZnstgECuiZECTwhexUF4oiuhyGk9o01\nQMaiz4HX4lgk0ozALUvEzaNd9gWEwD2qe+rq9cQMTVq3IArUkvTIftZUaVUMzr0O\ned1+zAsNa9nJhURJ/6anJPJjbQgb5qA1asFcp9UaMT1ku36U3gnR1T/BdgG2jX3X\nUm0UcaGNVPrH1ukInWW743pxWQb7/2sumEEMVh+jWbB18SAyLI4WIh4lkurdifzS\nIuTFp8TEx+MouISFhz/vJDWZ84tqoLVjkEcP6oDypq9lFoEzHDJv3V1CYcIgOusT\nk1jm9P7BXdTG7TYzUaTb9USb6bkqkD9EwJAOSs7DI94aE6rsSws2yAHavjAMfuMZ\nsDAZvkqS2Qg2Z2+CI6wUZn7mzkJXbZoqRjDvChDXEB1mIhzVXhiNW/CR5WKVDvlj\n9v1sdGByh2pbxcLQtVaq/5coM4ANgphoNz3pOYUPWHS+JUrIivBZ+JobjXcxr3SN\n9iDzcu5/FVVNbq7+KN/nvPMngT+gduEN5m+EBjm8GukJymFG0m6BENRA0QSDqZ7k\nzDY=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECTCCAvGgAwIBAgIRAK5EYG3iHserxMqgg+0EFjgwDQYJKoZIhvcNAQELBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI0MjAyMzE2WhgPMjA2MTA1MjQyMTIzMTZa\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\ns1L6TtB84LGraLHVC+rGPhLBW2P0oN/91Rq3AnYwqDOuTom7agANwEjvLq7dSRG/\nsIfZsSV/ABTgArZ5sCmLjHFZAo8Kd45yA9byx20RcYtAG8IZl+q1Cri+s0XefzyO\nU6mlfXZkVe6lzjlfXBkrlE/+5ifVbJK4dqOS1t9cWIpgKqv5fbE6Qbq4LVT+5/WM\nVd2BOljuBMGMzdZubqFKFq4mzTuIYfnBm7SmHlZfTdfBYPP1ScNuhpjuzw4n3NCR\nEdU6dQv04Q6th4r7eiOCwbWI9LkmVbvBe3ylhH63lApC7MiiPYLlB13xBubVHVhV\nq1NHoNTi+zA3MN9HWicRxQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBSuxoqm0/wjNiZLvqv+JlQwsDvTPDAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI\nhvcNAQELBQADggEBAFfTK/j5kv90uIbM8VaFdVbr/6weKTwehafT0pAk1bfLVX+7\nuf8oHgYiyKTTl0DFQicXejghXTeyzwoEkWSR8c6XkhD5vYG3oESqmt/RGvvoxz11\nrHHy7yHYu7RIUc3VQG60c4qxXv/1mWySGwVwJrnuyNT9KZXPevu3jVaWOVHEILaK\nHvzQ2YEcWBPmde/zEseO2QeeGF8FL45Q1d66wqIP4nNUd2pCjeTS5SpB0MMx7yi9\nki1OH1pv8tOuIdimtZ7wkdB8+JSZoaJ81b8sRrydRwJyvB88rftuI3YB4WwGuONT\nZezUPsmaoK69B0RChB0ofDpAaviF9V3xOWvVZfo=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGDzCCA/egAwIBAgIRAI0sMNG2XhaBMRN3zD7ZyoEwDQYJKoZIhvcNAQEMBQAw\ngZ8xCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE4MDYGA1UEAwwv\nQW1hem9uIFJEUyBQcmV2aWV3IHVzLWVhc3QtMiBSb290IENBIFJTQTQwOTYgRzEx\nEDAOBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjA1NzUwWhgPMjEyMTA1MTgyMTU3\nNTBaMIGfMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNl\ncywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExODA2BgNV\nBAMML0FtYXpvbiBSRFMgUHJldmlldyB1cy1lYXN0LTIgUm9vdCBDQSBSU0E0MDk2\nIEcxMRAwDgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\nCgKCAgEAh/otSiCu4Uw3hu7OJm0PKgLsLRqBmUS6jihcrkxfN2SHmp2zuRflkweU\nBhMkebzL+xnNvC8okzbgPWtUxSmDnIRhE8J7bvSKFlqs/tmEdiI/LMqe/YIKcdsI\n20UYmvyLIjtDaJIh598SHHlF9P8DB5jD8snJfhxWY+9AZRN+YVTltgQAAgayxkWp\nM1BbvxpOnz4CC00rE0eqkguXIUSuobb1vKqdKIenlYBNxm2AmtgvQfpsBIQ0SB+8\n8Zip8Ef5rtjSw5J3s2Rq0aYvZPfCVIsKYepIboVwXtD7E9J31UkB5onLBQlaHaA6\nXlH4srsMmrew5d2XejQGy/lGZ1nVWNsKO0x/Az2QzY5Kjd6AlXZ8kq6H68hscA5i\nOMbNlXzeEQsZH0YkId3+UsEns35AAjZv4qfFoLOu8vDotWhgVNT5DfdbIWZW3ZL8\nqbmra3JnCHuaTwXMnc25QeKgVq7/rG00YB69tCIDwcf1P+tFJWxvaGtV0g2NthtB\na+Xo09eC0L53gfZZ3hZw1pa3SIF5dIZ6RFRUQ+lFOux3Q/I3u+rYstYw7Zxc4Zeo\nY8JiedpQXEAnbw2ECHix/L6mVWgiWCiDzBnNLLdbmXjJRnafNSndSfFtHCnY1SiP\naCrNpzwZIJejoV1zDlWAMO+gyS28EqzuIq3WJK/TFE7acHkdKIcCAwEAAaNCMEAw\nDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUrmV1YASnuudfmqAZP4sKGTvScaEw\nDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBGpEKeQoPvE85tN/25\nqHFkys9oHDl93DZ62EnOqAUKLd6v0JpCyEiop4nlrJe+4KrBYVBPyKOJDcIqE2Sp\n3cvgJXLhY4i46VM3Qxe8yuYF1ElqBpg3jJVj/sCQnYz9dwoAMWIJFaDWOvmU2E7M\nMRaKx+sPXFkIjiDA6Bv0m+VHef7aedSYIY7IDltEQHuXoqNacGrYo3I50R+fZs88\n/mB3e/V7967e99D6565yf9Lcjw4oQf2Hy7kl/6P9AuMz0LODnGITwh2TKk/Zo3RU\nVgq25RDrT4xJK6nFHyjUF6+4cOBxVpimmFw/VP1zaXT8DN5r4HyJ9p4YuSK8ha5N\n2pJc/exvU8Nv2+vS/efcDZWyuEdZ7eh1IJWQZlOZKIAONfRDRTpeQHJ3zzv3QVYy\nt78pYp/eWBHyVIfEE8p2lFKD4279WYe+Uvdb8c4Jm4TJwqkSJV8ifID7Ub80Lsir\nlPAU3OCVTBeVRFPXT2zpC4PB4W6KBSuj6OOcEu2y/HgWcoi7Cnjvp0vFTUhDFdus\nWz3ucmJjfVsrkEO6avDKu4SwdbVHsk30TVAwPd6srIdi9U6MOeOQSOSE4EsrrS7l\nSVmu2QIDUVFpm8QAHYplkyWIyGkupyl3ashH9mokQhixIU/Pzir0byePxHLHrwLu\n1axqeKpI0F5SBUPsaVNYY2uNFg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECDCCAvCgAwIBAgIQCREfzzVyDTMcNME+gWnTCTANBgkqhkiG9w0BAQsFADCB\nnDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB\nbWF6b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G\nA1UEBwwHU2VhdHRsZTAgFw0yMTA1MjQyMDQyMzNaGA8yMDYxMDUyNDIxNDIzM1ow\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAO\nBgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL\n1MT6br3L/4Pq87DPXtcjlXN3cnbNk2YqRAZHJayStTz8VtsFcGPJOpk14geRVeVk\ne9uKFHRbcyr/RM4owrJTj5X4qcEuATYZbo6ou/rW2kYzuWFZpFp7lqm0vasV4Z9F\nfChlhwkNks0UbM3G+psCSMNSoF19ERunj7w2c4E62LwujkeYLvKGNepjnaH10TJL\n2krpERd+ZQ4jIpObtRcMH++bTrvklc+ei8W9lqrVOJL+89v2piN3Ecdd389uphst\nqQdb1BBVXbhUrtuGHgVf7zKqN1SkCoktoWxVuOprVWhSvr7akaWeq0UmlvbEsujU\nvADqxGMcJFyCzxx3CkJjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O\nBBYEFFk8UJmlhoxFT3PP12PvhvazHjT4MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG\n9w0BAQsFAAOCAQEAfFtr2lGoWVXmWAsIo2NYre7kzL8Xb9Tx7desKxCCz5HOOvIr\n8JMB1YK6A7IOvQsLJQ/f1UnKRh3X3mJZjKIywfrMSh0FiDf+rjcEzXxw2dGtUem4\nA+WMvIA3jwxnJ90OQj5rQ8bg3iPtE6eojzo9vWQGw/Vu48Dtw1DJo9210Lq/6hze\nhPhNkFh8fMXNT7Q1Wz/TJqJElyAQGNOXhyGpHKeb0jHMMhsy5UNoW5hLeMS5ffao\nTBFWEJ1gVfxIU9QRxSh+62m46JIg+dwDlWv8Aww14KgepspRbMqDuaM2cinoejv6\nt3dyOyHHrsOyv3ffZUKtQhQbQr+sUcL89lARsg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID/zCCAuegAwIBAgIRAIJLTMpzGNxqHZ4t+c1MlCIwDQYJKoZIhvcNAQELBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyBhcC1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyNTIxMzAzM1oYDzIwNjEwNTI1MjIzMDMzWjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGFwLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtdHut0ZhJ9Nn2\nMpVafFcwHdoEzx06okmmhjJsNy4l9QYVeh0UUoek0SufRNMRF4d5ibzpgZol0Y92\n/qKWNe0jNxhEj6sXyHsHPeYtNBPuDMzThfbvsLK8z7pBP7vVyGPGuppqW/6m4ZBB\nlcc9fsf7xpZ689iSgoyjiT6J5wlVgmCx8hFYc/uvcRtfd8jAHvheug7QJ3zZmIye\nV4htOW+fRVWnBjf40Q+7uTv790UAqs0Zboj4Yil+hER0ibG62y1g71XcCyvcVpto\n2/XW7Y9NCgMNqQ7fGN3wR1gjtSYPd7DO32LTzYhutyvfbpAZjsAHnoObmoljcgXI\nQjfBcCFpAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJI3aWLg\nCS5xqU5WYVaeT5s8lpO0MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC\nAQEAUwATpJOcGVOs3hZAgJwznWOoTzOVJKfrqBum7lvkVH1vBwxBl9CahaKj3ZOt\nYYp2qJzhDUWludL164DL4ZjS6eRedLRviyy5cRy0581l1MxPWTThs27z+lCC14RL\nPJZNVYYdl7Jy9Q5NsQ0RBINUKYlRY6OqGDySWyuMPgno2GPbE8aynMdKP+f6G/uE\nYHOf08gFDqTsbyfa70ztgVEJaRooVf5JJq4UQtpDvVswW2reT96qi6tXPKHN5qp3\n3wI0I1Mp4ePmiBKku2dwYzPfrJK/pQlvu0Gu5lKOQ65QdotwLAAoaFqrf9za1yYs\nINUkHLWIxDds+4OHNYcerGp5Dw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGCTCCA/GgAwIBAgIRAIO6ldra1KZvNWJ0TA1ihXEwDQYJKoZIhvcNAQEMBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjE0NTA1WhgPMjEyMTA1MjEyMjQ1MDVa\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\nsDN52Si9pFSyZ1ruh3xAN0nVqEs960o2IK5CPu/ZfshFmzAwnx/MM8EHt/jMeZtj\nSM58LADAsNDL01ELpFZATjgZQ6xNAyXRXE7RiTRUvNkK7O3o2qAGbLnJq/UqF7Sw\nLRnB8V6hYOv+2EjVnohtGCn9SUFGZtYDjWXsLd4ML4Zpxv0a5LK7oEC7AHzbUR7R\njsjkrXqSv7GE7bvhSOhMkmgxgj1F3J0b0jdQdtyyj109aO0ATUmIvf+Bzadg5AI2\nA9UA+TUcGeebhpHu8AP1Hf56XIlzPpaQv3ZJ4vzoLaVNUC7XKzAl1dlvCl7Klg/C\n84qmbD/tjZ6GHtzpLKgg7kQEV7mRoXq8X4wDX2AFPPQl2fv+Kbe+JODqm5ZjGegm\nuskABBi8IFv1hYx9jEulZPxC6uD/09W2+niFm3pirnlWS83BwVDTUBzF+CooUIMT\njhWkIIZGDDgMJTzouBHfoSJtS1KpUZi99m2WyVs21MNKHeWAbs+zmI6TO5iiMC+T\nuB8spaOiHFO1573Fmeer4sy3YA6qVoqVl6jjTQqOdy3frAMbCkwH22/crV8YA+08\nhLeHXrMK+6XUvU+EtHAM3VzcrLbuYJUI2XJbzTj5g0Eb8I8JWsHvWHR5K7Z7gceR\n78AzxQmoGEfV6KABNWKsgoCQnfb1BidDJIe3BsI0A6UCAwEAAaNCMEAwDwYDVR0T\nAQH/BAUwAwEB/zAdBgNVHQ4EFgQUABp0MlB14MSHgAcuNSOhs3MOlUcwDgYDVR0P\nAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCv4CIOBSQi/QR9NxdRgVAG/pAh\ntFJhV7OWb/wqwsNKFDtg6tTxwaahdCfWpGWId15OUe7G9LoPiKiwM9C92n0ZeHRz\n4ewbrQVo7Eu1JI1wf0rnZJISL72hVYKmlvaWaacHhWxvsbKLrB7vt6Cknxa+S993\nKf8i2Psw8j5886gaxhiUtzMTBwoDWak8ZaK7m3Y6C6hXQk08+3pnIornVSFJ9dlS\nPAqt5UPwWmrEfF+0uIDORlT+cvrAwgSp7nUF1q8iasledycZ/BxFgQqzNwnkBDwQ\nZ/aM52ArGsTzfMhkZRz9HIEhz1/0mJw8gZtDVQroD8778h8zsx2SrIz7eWQ6uWsD\nQEeSWXpcheiUtEfzkDImjr2DLbwbA23c9LoexUD10nwohhoiQQg77LmvBVxeu7WU\nE63JqaYUlOLOzEmNJp85zekIgR8UTkO7Gc+5BD7P4noYscI7pPOL5rP7YLg15ZFi\nega+G53NTckRXz4metsd8XFWloDjZJJq4FfD60VuxgXzoMNT9wpFTNSH42PR2s9L\nI1vcl3w8yNccs9se2utM2nLsItZ3J0m/+QSRiw9hbrTYTcM9sXki0DtH2kyIOwYf\nlOrGJDiYOIrXSQK36H0gQ+8omlrUTvUj4msvkXuQjlfgx6sgp2duOAfnGxE7uHnc\nUhnJzzoe6M+LfGHkVQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICuDCCAj2gAwIBAgIQSAG6j2WHtWUUuLGJTPb1nTAKBggqhkjOPQQDAzCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIGFwLW5vcnRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyMDE2MzgyNloYDzIxMjEwNTIwMTczODI2WjCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIGFwLW5vcnRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2eqwU4FOzW8RV1W381Bd\nolhDOrqoMqzWli21oDUt7y8OnXM/lmAuOS6sr8Nt61BLVbONdbr+jgCYw75KabrK\nZGg3siqvMOgabIKkKuXO14wtrGyGDt7dnKXg5ERGYOZlo0IwQDAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBS1Acp2WYxOcblv5ikZ3ZIbRCCW+zAOBgNVHQ8BAf8E\nBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAJL84J08PBprxmsAKPTotBuVI3MyW1r8\nxQ0i8lgCQUf8GcmYjQ0jI4oZyv+TuYJAcwIxAP9Xpzq0Docxb+4N1qVhpiOfWt1O\nFnemFiy9m1l+wv6p3riQMPV7mBVpklmijkIv3Q==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECTCCAvGgAwIBAgIRALZLcqCVIJ25maDPE3sbPCIwDQYJKoZIhvcNAQELBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjEzOTM5WhgPMjA2MTA1MjEyMjM5Mzla\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nypKc+6FfGx6Gl6fQ78WYS29QoKgQiur58oxR3zltWeg5fqh9Z85K5S3UbRSTqWWu\nXcfnkz0/FS07qHX+nWAGU27JiQb4YYqhjZNOAq8q0+ptFHJ6V7lyOqXBq5xOzO8f\n+0DlbJSsy7GEtJp7d7QCM3M5KVY9dENVZUKeJwa8PC5StvwPx4jcLeZRJC2rAVDG\nSW7NAInbATvr9ssSh03JqjXb+HDyywiqoQ7EVLtmtXWimX+0b3/2vhqcH5jgcKC9\nIGFydrjPbv4kwMrKnm6XlPZ9L0/3FMzanXPGd64LQVy51SI4d5Xymn0Mw2kMX8s6\nNf05OsWcDzJ1n6/Q1qHSxQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBRmaIc8eNwGP7i6P7AJrNQuK6OpFzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI\nhvcNAQELBQADggEBAIBeHfGwz3S2zwIUIpqEEI5/sMySDeS+3nJR+woWAHeO0C8i\nBJdDh+kzzkP0JkWpr/4NWz84/IdYo1lqASd1Kopz9aT1+iROXaWr43CtbzjXb7/X\nZv7eZZFC8/lS5SROq42pPWl4ekbR0w8XGQElmHYcWS41LBfKeHCUwv83ATF0XQ6I\n4t+9YSqZHzj4vvedrvcRInzmwWJaal9s7Z6GuwTGmnMsN3LkhZ+/GD6oW3pU/Pyh\nEtWqffjsLhfcdCs3gG8x9BbkcJPH5aPAVkPn4wc8wuXg6xxb9YGsQuY930GWTYRf\nschbgjsuqznW4HHakq4WNhs1UdTSTKkRdZz7FUQ=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEDzCCAvegAwIBAgIRAM2zAbhyckaqRim63b+Tib8wDQYJKoZIhvcNAQELBQAw\ngZ8xCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE4MDYGA1UEAwwv\nQW1hem9uIFJEUyBQcmV2aWV3IHVzLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzEx\nEDAOBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjA0OTQ1WhgPMjA2MTA1MTgyMTQ5\nNDVaMIGfMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNl\ncywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExODA2BgNV\nBAMML0FtYXpvbiBSRFMgUHJldmlldyB1cy1lYXN0LTIgUm9vdCBDQSBSU0EyMDQ4\nIEcxMRAwDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEA1ybjQMH1MkbvfKsWJaCTXeCSN1SG5UYid+Twe+TjuSqaXWonyp4WRR5z\ntlkqq+L2MWUeQQAX3S17ivo/t84mpZ3Rla0cx39SJtP3BiA2BwfUKRjhPwOjmk7j\n3zrcJjV5k1vSeLNOfFFSlwyDiVyLAE61lO6onBx+cRjelu0egMGq6WyFVidTdCmT\nQ9Zw3W6LTrnPvPmEyjHy2yCHzH3E50KSd/5k4MliV4QTujnxYexI2eR8F8YQC4m3\nDYjXt/MicbqA366SOoJA50JbgpuVv62+LSBu56FpzY12wubmDZsdn4lsfYKiWxUy\nuc83a2fRXsJZ1d3whxrl20VFtLFHFQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/\nMB0GA1UdDgQWBBRC0ytKmDYbfz0Bz0Psd4lRQV3aNTAOBgNVHQ8BAf8EBAMCAYYw\nDQYJKoZIhvcNAQELBQADggEBAGv8qZu4uaeoF6zsbumauz6ea6tdcWt+hGFuwGrb\ntRbI85ucAmVSX06x59DJClsb4MPhL1XmqO3RxVMIVVfRwRHWOsZQPnXm8OYQ2sny\nrYuFln1COOz1U/KflZjgJmxbn8x4lYiTPZRLarG0V/OsCmnLkQLPtEl/spMu8Un7\nr3K8SkbWN80gg17Q8EV5mnFwycUx9xsTAaFItuG0en9bGsMgMmy+ZsDmTRbL+lcX\nFq8r4LT4QjrFz0shrzCwuuM4GmcYtBSxlacl+HxYEtAs5k10tmzRf6OYlY33tGf6\n1tkYvKryxDPF/EDgGp/LiBwx6ixYMBfISoYASt4V/ylAlHA=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICtTCCAjqgAwIBAgIRAK9BSZU6nIe6jqfODmuVctYwCgYIKoZIzj0EAwMwgZkx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h\nem9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjEwNTIxMjIxMzA5WhgPMjEyMTA1MjEyMzEzMDlaMIGZMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv\nbiBSRFMgY2EtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEUkEERcgxneT5H+P+fERcbGmf\nbVx+M7rNWtgWUr6w+OBENebQA9ozTkeSg4c4M+qdYSObFqjxITdYxT1z/nHz1gyx\nOKAhLjWu+nkbRefqy3RwXaWT680uUaAP6ccnkZOMo0IwQDAPBgNVHRMBAf8EBTAD\nAQH/MB0GA1UdDgQWBBSN6fxlg0s5Wny08uRBYZcQ3TUoyzAOBgNVHQ8BAf8EBAMC\nAYYwCgYIKoZIzj0EAwMDaQAwZgIxAORaz+MBVoFBTmZ93j2G2vYTwA6T5hWzBWrx\nCrI54pKn5g6At56DBrkjrwZF5T1enAIxAJe/LZ9xpDkAdxDgGJFN8gZYLRWc0NRy\nRb4hihy5vj9L+w9uKc9VfEBIFuhT7Z3ljg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEADCCAuigAwIBAgIQB/57HSuaqUkLaasdjxUdPjANBgkqhkiG9w0BAQsFADCB\nmDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB\nbWF6b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUxOTE3NDAzNFoYDzIwNjEwNTE5MTg0MDM0WjCBmDEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6\nb24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbkaoVsUS76o\nTgLFmcnaB8cswBk1M3Bf4IVRcwWT3a1HeJSnaJUqWHCJ+u3ip/zGVOYl0gN1MgBb\nMuQRIJiB95zGVcIa6HZtx00VezDTr3jgGWRHmRjNVCCHGmxOZWvJjsIE1xavT/1j\nQYV/ph4EZEIZ/qPq7e3rHohJaHDe23Z7QM9kbyqp2hANG2JtU/iUhCxqgqUHNozV\nZd0l5K6KnltZQoBhhekKgyiHqdTrH8fWajYl5seD71bs0Axowb+Oh0rwmrws3Db2\nDh+oc2PwREnjHeca9/1C6J2vhY+V0LGaJmnnIuOANrslx2+bgMlyhf9j0Bv8AwSi\ndSWsobOhNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQb7vJT\nVciLN72yJGhaRKLn6Krn2TAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD\nggEBAAxEj8N9GslReAQnNOBpGl8SLgCMTejQ6AW/bapQvzxrZrfVOZOYwp/5oV0f\n9S1jcGysDM+DrmfUJNzWxq2Y586R94WtpH4UpJDGqZp+FuOVJL313te4609kopzO\nlDdmd+8z61+0Au93wB1rMiEfnIMkOEyt7D2eTFJfJRKNmnPrd8RjimRDlFgcLWJA\n3E8wca67Lz/G0eAeLhRHIXv429y8RRXDtKNNz0wA2RwURWIxyPjn1fHjA9SPDkeW\nE1Bq7gZj+tBnrqz+ra3yjZ2blss6Ds3/uRY6NYqseFTZWmQWT7FolZEnT9vMUitW\nI0VynUbShVpGf6946e0vgaaKw20=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID/jCCAuagAwIBAgIQGyUVTaVjYJvWhroVEiHPpDANBgkqhkiG9w0BAQsFADCB\nlzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB\nbWF6b24gUkRTIHVzLXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjEwNTE5MTkwNDA2WhgPMjA2MTA1MTkyMDA0MDZaMIGXMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv\nbiBSRFMgdXMtd2VzdC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANhyXpJ0t4nigRDZ\nEwNtFOem1rM1k8k5XmziHKDvDk831p7QsX9ZOxl/BT59Pu/P+6W6SvasIyKls1sW\nFJIjFF+6xRQcpoE5L5evMgN/JXahpKGeQJPOX9UEXVW5B8yi+/dyUitFT7YK5LZA\nMqWBN/LtHVPa8UmE88RCDLiKkqiv229tmwZtWT7nlMTTCqiAHMFcryZHx0pf9VPh\nx/iPV8p2gBJnuPwcz7z1kRKNmJ8/cWaY+9w4q7AYlAMaq/rzEqDaN2XXevdpsYAK\nTMMj2kji4x1oZO50+VPNfBl5ZgJc92qz1ocF95SAwMfOUsP8AIRZkf0CILJYlgzk\n/6u6qZECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm5jfcS9o\n+LwL517HpB6hG+PmpBswDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB\nAQAcQ6lsqxi63MtpGk9XK8mCxGRLCad51+MF6gcNz6i6PAqhPOoKCoFqdj4cEQTF\nF8dCfa3pvfJhxV6RIh+t5FCk/y6bWT8Ls/fYKVo6FhHj57bcemWsw/Z0XnROdVfK\nYqbc7zvjCPmwPHEqYBhjU34NcY4UF9yPmlLOL8uO1JKXa3CAR0htIoW4Pbmo6sA4\n6P0co/clW+3zzsQ92yUCjYmRNeSbdXbPfz3K/RtFfZ8jMtriRGuO7KNxp8MqrUho\nHK8O0mlSUxGXBZMNicfo7qY8FD21GIPH9w5fp5oiAl7lqFzt3E3sCLD3IiVJmxbf\nfUwpGd1XZBBSdIxysRLM6j48\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrTCCAjOgAwIBAgIQU+PAILXGkpoTcpF200VD/jAKBggqhkjOPQQDAzCBljEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6\nb24gUkRTIGFwLWVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTAgFw0yMTA1MjUyMTQ1MTFaGA8yMTIxMDUyNTIyNDUxMVowgZYxCzAJBgNV\nBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD\nVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE\nUyBhcC1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw\ndjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT3tFKE8Kw1sGQAvNLlLhd8OcGhlc7MiW/s\nNXm3pOiCT4vZpawKvHBzD76Kcv+ZZzHRxQEmG1/muDzZGlKR32h8AAj+NNO2Wy3d\nCKTtYMiVF6Z2zjtuSkZQdjuQbe4eQ7qjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD\nVR0OBBYEFAiSQOp16Vv0Ohpvqcbd2j5RmhYNMA4GA1UdDwEB/wQEAwIBhjAKBggq\nhkjOPQQDAwNoADBlAjBVsi+5Ape0kOhMt/WFkANkslD4qXA5uqhrfAtH29Xzz2NV\ntR7akiA771OaIGB/6xsCMQCZt2egCtbX7J0WkuZ2KivTh66jecJr5DHvAP4X2xtS\nF/5pS+AUhcKTEGjI9jDH3ew=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICuDCCAj2gAwIBAgIQT5mGlavQzFHsB7hV6Mmy6TAKBggqhkjOPQQDAzCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyNDIwNTAxNVoYDzIxMjEwNTI0MjE1MDE1WjCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEcm4BBBjYK7clwm0HJRWS\nflt3iYwoJbIXiXn9c1y3E+Vb7bmuyKhS4eO8mwO4GefUcXObRfoHY2TZLhMJLVBQ\n7MN2xDc0RtZNj07BbGD3VAIFRTDX0mH9UNYd0JQM3t/Oo0IwQDAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBRrd5ITedfAwrGo4FA9UaDaGFK3rjAOBgNVHQ8BAf8E\nBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPBNqmVv1IIA3EZyQ6XuVf4gj79/DMO8\nbkicNS1EcBpUqbSuU4Zwt2BYc8c/t7KVOQIxAOHoWkoKZPiKyCxfMtJpCZySUG+n\nsXgB/LOyWE5BJcXUfm+T1ckeNoWeUUMOLmnJjg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECTCCAvGgAwIBAgIRAJcDeinvdNrDQBeJ8+t38WQwDQYJKoZIhvcNAQELBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNCBSb290IENBIFJTQTIwNDggRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjIwNTI1MTY0OTE2WhgPMjA2MjA1MjUxNzQ5MTZa\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTQgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nk8DBNkr9tMoIM0NHoFiO7cQfSX0cOMhEuk/CHt0fFx95IBytx7GHCnNzpM27O5z6\nx6iRhfNnx+B6CrGyCzOjxvPizneY+h+9zfvNz9jj7L1I2uYMuiNyOKR6FkHR46CT\n1CiArfVLLPaTqgD/rQjS0GL2sLHS/0dmYipzynnZcs613XT0rAWdYDYgxDq7r/Yi\nXge5AkWQFkMUq3nOYDLCyGGfQqWKkwv6lZUHLCDKf+Y0Uvsrj8YGCI1O8mF0qPCQ\nlmlfaDvbuBu1AV+aabmkvyFj3b8KRIlNLEtQ4N8KGYR2Jdb82S4YUGIOAt4wuuFt\n1B7AUDLk3V/u+HTWiwfoLQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBSNpcjz6ArWBtAA+Gz6kyyZxrrgdDAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI\nhvcNAQELBQADggEBAGJEd7UgOzHYIcQRSF7nSYyjLROyalaIV9AX4WXW/Cqlul1c\nMblP5etDZm7A/thliZIWAuyqv2bNicmS3xKvNy6/QYi1YgxZyy/qwJ3NdFl067W0\nt8nGo29B+EVK94IPjzFHWShuoktIgp+dmpijB7wkTIk8SmIoe9yuY4+hzgqk+bo4\nms2SOXSN1DoQ75Xv+YmztbnZM8MuWhL1T7hA4AMorzTQLJ9Pof8SpSdMHeDsHp0R\n01jogNFkwy25nw7cL62nufSuH2fPYGWXyNDg+y42wKsKWYXLRgUQuDVEJ2OmTFMB\nT0Vf7VuNijfIA9hkN2d3K53m/9z5WjGPSdOjGhg=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID/jCCAuagAwIBAgIQRiwspKyrO0xoxDgSkqLZczANBgkqhkiG9w0BAQsFADCB\nlzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB\nbWF6b24gUkRTIHVzLXdlc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjEwNTI0MjE1OTAwWhgPMjA2MTA1MjQyMjU5MDBaMIGXMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv\nbiBSRFMgdXMtd2VzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL53Jk3GsKiu+4bx\njDfsevWbwPCNJ3H08Zp7GWhvI3Tgi39opfHYv2ku2BKFjK8N2L6RvNPSR8yplv5j\nY0tK0U+XVNl8o0ibhqRDhbTuh6KL8CFINWYzAajuxFS+CF0U6c1Q3tXLBdALxA7l\nFlXJ71QrP06W31kRe7kvgrvO7qWU3/OzUf9qYw4LSiR1/VkvvRCTqcVNw09clw/M\nJbw6FSgweN65M9j7zPbjGAXSHkXyxH1Erin2fa+B9PE4ZDgX9cp2C1DHewYJQL/g\nSepwwcudVNRN1ibKH7kpMrgPnaNIVNx5sXVsTjk6q2ZqYw3SVHegltJpLy/cZReP\nmlivF2kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUmTcQd6o1\nCuS65MjBrMwQ9JJjmBwwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB\nAQAKSDSIzl956wVddPThf2VAzI8syw9ngSwsEHZvxVGHBvu5gg618rDyguVCYX9L\n4Kw/xJrk6S3qxOS2ZDyBcOpsrBskgahDFIunzoRP3a18ARQVq55LVgfwSDQiunch\nBd05cnFGLoiLkR5rrkgYaP2ftn3gRBRaf0y0S3JXZ2XB3sMZxGxavYq9mfiEcwB0\nLMTMQ1NYzahIeG6Jm3LqRqR8HkzP/Ztq4dT2AtSLvFebbNMiWqeqT7OcYp94HTYT\nzqrtaVdUg9bwyAUCDgy0GV9RHDIdNAOInU/4LEETovrtuBU7Z1q4tcHXvN6Hd1H8\ngMb0mCG5I393qW5hFsA/diFb\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECTCCAvGgAwIBAgIRAPQAvihfjBg/JDbj6U64K98wDQYJKoZIhvcNAQELBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIwMTYyODQxWhgPMjA2MTA1MjAxNzI4NDFa\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nvJ9lgyksCxkBlY40qOzI1TCj/Q0FVGuPL/Z1Mw2YN0l+41BDv0FHApjTUkIKOeIP\nnwDwpXTa3NjYbk3cOZ/fpH2rYJ++Fte6PNDGPgKppVCUh6x3jiVZ1L7wOgnTdK1Q\nTrw8440IDS5eLykRHvz8OmwvYDl0iIrt832V0QyOlHTGt6ZJ/aTQKl12Fy3QBLv7\nstClPzvHTrgWqVU6uidSYoDtzHbU7Vda7YH0wD9IUoMBf7Tu0rqcE4uH47s2XYkc\nSdLEoOg/Ngs7Y9B1y1GCyj3Ux7hnyvCoRTw014QyNB7dTatFMDvYlrRDGG14KeiU\nUL7Vo/+EejWI31eXNLw84wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBQkgTWFsNg6wA3HbbihDQ4vpt1E2zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI\nhvcNAQELBQADggEBAGz1Asiw7hn5WYUj8RpOCzpE0h/oBZcnxP8wulzZ5Xd0YxWO\n0jYUcUk3tTQy1QvoY+Q5aCjg6vFv+oFBAxkib/SmZzp4xLisZIGlzpJQuAgRkwWA\n6BVMgRS+AaOMQ6wKPgz1x4v6T0cIELZEPq3piGxvvqkcLZKdCaeC3wCS6sxuafzZ\n4qA3zMwWuLOzRftgX2hQto7d/2YkRXga7jSvQl3id/EI+xrYoH6zIWgjdU1AUaNq\nNGT7DIo47vVMfnd9HFZNhREsd4GJE83I+JhTqIxiKPNxrKgESzyADmNPt0gXDnHo\ntbV1pMZz5HpJtjnP/qVZhEK5oB0tqlKPv9yx074=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICuTCCAj6gAwIBAgIRAKp1Rn3aL/g/6oiHVIXtCq8wCgYIKoZIzj0EAwMwgZsx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h\nem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTAgFw0yMTA1MjQyMDMyMTdaGA8yMTIxMDUyNDIxMzIxN1owgZsx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h\nem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGTYWPILeBJXfcL3Dz4z\nEWMUq78xB1HpjBwHoTURYfcMd5r96BTVG6yaUBWnAVCMeeD6yTG9a1eVGNhG14Hk\nZAEjgLiNB7RRbEG5JZ/XV7W/vODh09WCst2y9SLKsdgeAaNCMEAwDwYDVR0TAQH/\nBAUwAwEB/zAdBgNVHQ4EFgQUoE0qZHmDCDB+Bnm8GUa/evpfPwgwDgYDVR0PAQH/\nBAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCnil5MMwhY3qoXv0xvcKZGxGPaBV15\n0CCssCKn0oVtdJQfJQ3Jrf3RSaEyijXIJsoCMQC35iJi4cWoNX3N/qfgnHohW52O\nB5dg0DYMqy5cNZ40+UcAanRMyqNQ6P7fy3umGco=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICtzCCAj2gAwIBAgIQPXnDTPegvJrI98qz8WxrMjAKBggqhkjOPQQDAzCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUxODIxNDAxMloYDzIxMjEwNTE4MjI0MDEyWjCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEI0sR7gwutK5AB46hM761\ngcLTGBIYlURSEoM1jcBwy56CL+3CJKZwLLyJ7qoOKfWbu5GsVLUTWS8MV6Nw33cx\n2KQD2svb694wi+Px2f4n9+XHkEFQw8BbiodDD7RZA70fo0IwQDAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBTQSioOvnVLEMXwNSDg+zgln/vAkjAOBgNVHQ8BAf8E\nBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAMwu1hqm5Bc98uE/E0B5iMYbBQ4kpMxO\ntP8FTfz5UR37HUn26nXE0puj6S/Ffj4oJgIwXI7s2c26tFQeqzq6u3lrNJHp5jC9\nUxlo/hEJOLoDj5jnpxo8dMAtCNoQPaHdfL0P\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIF/jCCA+agAwIBAgIQEM1pS+bWfBJeu/6j1yIIFzANBgkqhkiG9w0BAQwFADCB\nlzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB\nbWF6b24gUkRTIGNhLXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjMwOTE5MjIwMTM5WhgPMjEyMzA5MTkyMzAxMzlaMIGXMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv\nbiBSRFMgY2Etd2VzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Pyp8p5z6HnlGB\ndaOj78gZ3ABufxnBFiu5NdFiGoMrS+eY//xxr2iKbnynJAzjmn5A6VKMNxtbuYIZ\nWKAzDb/HrWlIYD2w7ZVBXpylfPhiz3jLNsl03WdPNnEruCcivhY2QMewEVtzjPU0\nofdbZlO2KpF3biv1gjPuIuE7AUyQAbWnWTlrzETAVWLboJJRRqxASSkFUHNLXod7\now02FwlAhcnCp9gSe1SKRDrpvvEvYQBAFB7owfnoQzOGDdd87RGyYfyuW8aFI2Z0\nLHNvsA0dTafO4Rh986c72kDL7ijICQdr5OTgZR2OnuESLk1DSK4xYJ4fA6jb5dJ5\n+xsI6tCPykWCW98aO/pha35OsrVNifL/5cH5pdv/ecgQGdffJB+Vdj6f/ZMwR6s/\nRm37cQ9l3tU8eu/qpzsFjLq1ZUzDaVDWgMW9t49+q/zjhdmbPOabZDao7nHXrVRw\nrwPHWCmEY4OmH6ikEKQW3AChFjOdSg4me/J0Jr5l5jKggLPHWbNLRO8qTTK6N8qk\nui3aJDi+XQfsTPARXIw4UFErArNImTsoZVyqfX7I4shp0qZbEhP6kRAbfPljw5kW\nYat7ZlXqDanjsreqbLTaOU10P0rC0/4Ctv5cLSKCrzRLWtpXxhKa2wJTQ74G6fAZ\n1oUA79qg3F8nyM+ZzDsfNI854+PNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w\nHQYDVR0OBBYEFLRWiDabEQZNkzEPUCr1ZVJV6xpwMA4GA1UdDwEB/wQEAwIBhjAN\nBgkqhkiG9w0BAQwFAAOCAgEATkVVzkkGBjEtLGDtERi+fSpIV0MxwAsA4PAeBBmb\nmyxo90jz6kWkKM1Wm4BkZM8/mq5VbxPef1kxHfb5CHksCL6SgG5KujfIvht+KT2a\nMRJB+III3CbcTy0HtwCX5AlPIbXWydhQFoJTW/OkpecUWoyFM6SqYeYZx1itJpxl\nsXshLjYOvw+QgvxRsDxqUfkcaC/N2yhu/30Zo2P8msJfAFry2UmA/TBrWOQKVQxl\nEe/yWgp4U/bC/GZnjWnWDTwkRFGQtI4wjxbVuX6V4FTLCT7kIoHBhG+zOSduJRn3\nAxej7gkEXEVc/PAnwp/kSJ/b0/JONLWdjGUFkyiMn1yJlhJ2sg39vepBN5r6yVYU\nnJWoZAuupRpoIKfmC3/cZanXqYbYl4yxzX/PMB4kAACfdxGxLawjnnBjSzaWokXs\nYVh2TjWpUMwLOi0RB2mtPUjHdDLKtjOTZ1zHZnR/wVp9BmVI1BXYnz5PAqU5XqeD\nEmanyaAuFCeyol1EtbQhgtysThQ+vwYAXMm2iKzJxq0hik8wyG8X55FhnGEOGV3u\nxxq7odd3/8BXkc3dGdBPQtH+k5glaQyPnAsLVAIUvyzTmy58saL+nJnQY4mmRrwV\n1jJA7nnkaklI/L5fvfCg0W+TMinCOAGd+GQ4hK2SAsJLtcqiBgPf2wJHO8wiwUh9\nLuw=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrjCCAjWgAwIBAgIQGKVv+5VuzEZEBzJ+bVfx2zAKBggqhkjOPQQDAzCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjEwNTE5MTc1MDU5WhgPMjEyMTA1MTkxODUwNTlaMIGXMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS\nRFMgYXAtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs\nZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMqdLJ0tZF/DGFZTKZDrGRJZID8ivC2I\nJRCYTWweZKCKSCAzoiuGGHzJhr5RlLHQf/QgmFcgXsdmO2n3CggzhA4tOD9Ip7Lk\nP05eHd2UPInyPCHRgmGjGb0Z+RdQ6zkitKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd\nBgNVHQ4EFgQUC1yhRgVqU5bR8cGzOUCIxRpl4EYwDgYDVR0PAQH/BAQDAgGGMAoG\nCCqGSM49BAMDA2cAMGQCMG0c/zLGECRPzGKJvYCkpFTCUvdP4J74YP0v/dPvKojL\nt/BrR1Tg4xlfhaib7hPc7wIwFvgqHes20CubQnZmswbTKLUrgSUW4/lcKFpouFd2\nt2/ewfi/0VhkeUW+IiHhOMdU\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGCTCCA/GgAwIBAgIRAOXxJuyXVkbfhZCkS/dOpfEwDQYJKoZIhvcNAQEMBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI1MjE1OTEwWhgPMjEyMTA1MjUyMjU5MTBa\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\nxiP4RDYm4tIS12hGgn1csfO8onQDmK5SZDswUpl0HIKXOUVVWkHNlINkVxbdqpqH\nFhbyZmNN6F/EWopotMDKe1B+NLrjNQf4zefv2vyKvPHJXhxoKmfyuTd5Wk8k1F7I\nlNwLQzznB+ElhrLIDJl9Ro8t31YBBNFRGAGEnxyACFGcdkjlsa52UwfYrwreEg2l\ngW5AzqHgjFfj9QRLydeU/n4bHm0F1adMsV7P3rVwilcUlqsENDwXnWyPEyv3sw6F\nwNemLEs1129mB77fwvySb+lLNGsnzr8w4wdioZ74co+T9z2ca+eUiP+EQccVw1Is\nD4Fh57IjPa6Wuc4mwiUYKkKY63+38aCfEWb0Qoi+zW+mE9nek6MOQ914cN12u5LX\ndBoYopphRO5YmubSN4xcBy405nIdSdbrAVWwxXnVVyjqjknmNeqQsPZaxAhdoKhV\nAqxNr8AUAdOAO6Sz3MslmcLlDXFihrEEOeUbpg/m1mSUUHGbu966ajTG1FuEHHwS\n7WB52yxoJo/tHvt9nAWnh3uH5BHmS8zn6s6CGweWKbX5yICnZ1QFR1e4pogxX39v\nXD6YcNOO+Vn+HY4nXmjgSYVC7l+eeP8eduMg1xJujzjrbmrXU+d+cBObgdTOAlpa\nJFHaGwYw1osAwPCo9cZ2f04yitBfj9aPFia8ASKldakCAwEAAaNCMEAwDwYDVR0T\nAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqKS+ltlior0SyZKYAkJ/efv55towDgYDVR0P\nAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAdElvp8bW4B+Cv+1WSN87dg6TN\nwGyIjJ14/QYURgyrZiYpUmZpj+/pJmprSWXu4KNyqHftmaidu7cdjL5nCAvAfnY5\n/6eDDbX4j8Gt9fb/6H9y0O0dn3mUPSEKG0crR+JRFAtPhn/2FNvst2P82yguWLv0\npHjHVUVcq+HqDMtUIJsTPYjSh9Iy77Q6TOZKln9dyDOWJpCSkiUWQtMAKbCSlvzd\nzTs/ahqpT+zLfGR1SR+T3snZHgQnbnemmz/XtlKl52NxccARwfcEEKaCRQyGq/pR\n0PVZasyJS9JY4JfQs4YOdeOt4UMZ8BmW1+BQWGSkkb0QIRl8CszoKofucAlqdPcO\nIT/ZaMVhI580LFGWiQIizWFskX6lqbCyHqJB3LDl8gJISB5vNTHOHpvpMOMs5PYt\ncRl5Mrksx5MKMqG7y5R734nMlZxQIHjL5FOoOxTBp9KeWIL/Ib89T2QDaLw1SQ+w\nihqWBJ4ZdrIMWYpP3WqM+MXWk7WAem+xsFJdR+MDgOOuobVQTy5dGBlPks/6gpjm\nrO9TjfQ36ppJ3b7LdKUPeRfnYmlR5RU4oyYJ//uLbClI443RZAgxaCXX/nyc12lr\neVLUMNF2abLX4/VF63m2/Z9ACgMRfqGshPssn1NN33OonrotQoj4S3N9ZrjvzKt8\niHcaqd60QKpfiH2A3A==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICuDCCAj2gAwIBAgIQPaVGRuu86nh/ylZVCLB0MzAKBggqhkjOPQQDAzCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIGFwLW5vcnRoZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyNTIyMDMxNloYDzIxMjEwNTI1MjMwMzE2WjCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIGFwLW5vcnRoZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEexNURoB9KE93MEtEAlJG\nobz4LS/pD2hc8Gczix1WhVvpJ8bN5zCDXaKdnDMCebetyRQsmQ2LYlfmCwpZwSDu\n0zowB11Pt3I5Avu2EEcuKTlKIDMBeZ1WWuOd3Tf7MEAMo0IwQDAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBSaYbZPBvFLikSAjpa8mRJvyArMxzAOBgNVHQ8BAf8E\nBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAOEJkuh3Zjb7Ih/zuNRd1RBqmIYcnyw0\nnwUZczKXry+9XebYj3VQxSRNadrarPWVqgIxAMg1dyGoDAYjY/L/9YElyMnvHltO\nPwpJShmqHvCLc/mXMgjjYb/akK7yGthvW6j/uQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGCDCCA/CgAwIBAgIQChu3v5W1Doil3v6pgRIcVzANBgkqhkiG9w0BAQwFADCB\nnDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB\nbWF6b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G\nA1UEBwwHU2VhdHRsZTAgFw0yMTA1MTgyMTM0MTVaGA8yMTIxMDUxODIyMzQxNVow\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBCZXRhIHVzLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO\nBgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1\nFUGQ5tf3OwpDR6hGBxhUcrkwKZhaXP+1St1lSOQvjG8wXT3RkKzRGMvb7Ee0kzqI\nmzKKe4ASIhtV3UUWdlNmP0EA3XKnif6N79MismTeGkDj75Yzp5A6tSvqByCgxIjK\nJqpJrch3Dszoyn8+XhwDxMZtkUa5nQVdJgPzJ6ltsQ8E4SWLyLtTu0S63jJDkqYY\nS7cQblk7y7fel+Vn+LS5dGTdRRhMvSzEnb6mkVBaVzRyVX90FNUED06e8q+gU8Ob\nhtvQlf9/kRzHwRAdls2YBhH40ZeyhpUC7vdtPwlmIyvW5CZ/QiG0yglixnL6xahL\npbmTuTSA/Oqz4UGQZv2WzHe1lD2gRHhtFX2poQZeNQX8wO9IcUhrH5XurW/G9Xwl\nSat9CMPERQn4KC3HSkat4ir2xaEUrjfg6c4XsGyh2Pk/LZ0gLKum0dyWYpWP4JmM\nRQNjrInXPbMhzQObozCyFT7jYegS/3cppdyy+K1K7434wzQGLU1gYXDKFnXwkX8R\nbRKgx2pHNbH5lUddjnNt75+e8m83ygSq/ZNBUz2Ur6W2s0pl6aBjwaDES4VfWYlI\njokcmrGvJNDfQWygb1k00eF2bzNeNCHwgWsuo3HSxVgc/WGsbcGrTlDKfz+g3ich\nbXUeUidPhRiv5UQIVCLIHpHuin3bj9lQO/0t6p+tAQIDAQABo0IwQDAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBSFmMBgm5IsRv3hLrvDPIhcPweXYTAOBgNVHQ8B\nAf8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAAa2EuozymOsQDJlEi7TqnyA2OhT\nGXPfYqCyMJVkfrqNgcnsNpCAiNEiZbb+8sIPXnT8Ay8hrwJYEObJ5b7MHXpLuyft\nz0Pu1oFLKnQxKjNxrIsCvaB4CRRdYjm1q7EqGhMGv76se9stOxkOqO9it31w/LoU\nENDk7GLsSqsV1OzYLhaH8t+MaNP6rZTSNuPrHwbV3CtBFl2TAZ7iKgKOhdFz1Hh9\nPez0lG+oKi4mHZ7ajov6PD0W7njn5KqzCAkJR6OYmlNVPjir+c/vUtEs0j+owsMl\ng7KE5g4ZpTRShyh5BjCFRK2tv0tkqafzNtxrKC5XNpEkqqVTCnLcKG+OplIEadtr\nC7UWf4HyhCiR+xIyxFyR05p3uY/QQU/5uza7GlK0J+U1sBUytx7BZ+Fo8KQfPPqV\nCqDCaYUksoJcnJE/KeoksyqNQys7sDGJhkd0NeUGDrFLKHSLhIwAMbEWnqGxvhli\nE7sP2E5rI/I9Y9zTbLIiI8pfeZlFF8DBdoP/Hzg8pqsiE/yiXSFTKByDwKzGwNqz\nF0VoFdIZcIbLdDbzlQitgGpJtvEL7HseB0WH7B2PMMD8KPJlYvPveO3/6OLzCsav\n+CAkvk47NQViKMsUTKOA0JDCW+u981YRozxa3K081snhSiSe83zIPBz1ikldXxO9\n6YYLNPRrj3mi9T/f\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrjCCAjSgAwIBAgIRAMkvdFnVDb0mWWFiXqnKH68wCgYIKoZIzj0EAwMwgZYx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h\nem9uIFJEUyB1cy13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjEwNTE5MTkxMzI0WhgPMjEyMTA1MTkyMDEzMjRaMIGWMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS\nRFMgdXMtd2VzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEy86DB+9th/0A5VcWqMSWDxIUblWTt/R0\nao6Z2l3vf2YDF2wt1A2NIOGpfQ5+WAOJO/IQmnV9LhYo+kacB8sOnXdQa6biZZkR\nIyouUfikVQAKWEJnh1Cuo5YMM4E2sUt5o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G\nA1UdDgQWBBQ8u3OnecANmG8OoT7KLWDuFzZwBTAOBgNVHQ8BAf8EBAMCAYYwCgYI\nKoZIzj0EAwMDaAAwZQIwQ817qkb7mWJFnieRAN+m9W3E0FLVKaV3zC5aYJUk2fcZ\nTaUx3oLp3jPLGvY5+wgeAjEA6wAicAki4ZiDfxvAIuYiIe1OS/7H5RA++R8BH6qG\niRzUBM/FItFpnkus7u/eTkvo\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrzCCAjWgAwIBAgIQS/+Ryfgb/IOVEa1pWoe8oTAKBggqhkjOPQQDAzCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGFwLXNvdXRoLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjIwNjA2MjE1NDQyWhgPMjEyMjA2MDYyMjU0NDJaMIGXMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS\nRFMgYXAtc291dGgtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs\nZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDsX6fhdUWBQpYTdseBD/P3s96Dtw2Iw\nOrXKNToCnmX5nMkUGdRn9qKNiz1pw3EPzaPxShbYwQ7LYP09ENK/JN4QQjxMihxC\njLFxS85nhBQQQGRCWikDAe38mD8fSvREQKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd\nBgNVHQ4EFgQUIh1xZiseQYFjPYKJmGbruAgRH+AwDgYDVR0PAQH/BAQDAgGGMAoG\nCCqGSM49BAMDA2gAMGUCMFudS4zLy+UUGrtgNLtRMcu/DZ9BUzV4NdHxo0bkG44O\nthnjl4+wTKI6VbyAbj2rkgIxAOHps8NMITU5DpyiMnKTxV8ubb/WGHrLl0BjB8Lw\nETVJk5DNuZvsIIcm7ykk6iL4Tw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGBDCCA+ygAwIBAgIQDcEmNIAVrDpUw5cH5ynutDANBgkqhkiG9w0BAQwFADCB\nmjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB\nbWF6b24gUkRTIG1lLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV\nBAcMB1NlYXR0bGUwIBcNMjIwNTA3MDA0MDIzWhgPMjEyMjA1MDcwMTQwMjNaMIGa\nMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j\nLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt\nYXpvbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKvADk8t\nFl9bFlU5sajLPPDSOUpPAkKs6iPlz+27o1GJC88THcOvf3x0nVAcu9WYe9Qaas+4\nj4a0vv51agqyODRD/SNi2HnqW7DbtLPAm6KBHe4twl28ItB/JD5g7u1oPAHFoXMS\ncH1CZEAs5RtlZGzJhcBXLFsHNv/7+SCLyZ7+2XFh9OrtgU4wMzkHoRNndhfwV5bu\n17bPTwuH+VxH37zXf1mQ/KjhuJos0C9dL0FpjYBAuyZTAWhZKs8dpSe4DI544z4w\ngkwUB4bC2nA1TBzsywEAHyNuZ/xRjNpWvx0ToWAA2iFJqC3VO3iKcnBplMvaUuMt\njwzVSNBnKcoabXCZL2XDLt4YTZR8FSwz05IvsmwcPB7uNTBXq3T9sjejW8QQK3vT\ntzyfLq4jKmQE7PoS6cqYm+hEPm2hDaC/WP9bp3FdEJxZlPH26fq1b7BWYWhQ9pBA\nNv9zTnzdR1xohTyOJBUFQ81ybEzabqXqVXUIANqIOaNcTB09/sLJ7+zuMhp3mwBu\nLtjfJv8PLuT1r63bU3seROhKA98b5KfzjvbvPSg3vws78JQyoYGbqNyDfyjVjg3U\nv//AdVuPie6PNtdrW3upZY4Qti5IjP9e3kimaJ+KAtTgMRG56W0WxD3SP7+YGGbG\nKhntDOkKsN39hLpn9UOafTIqFu7kIaueEy/NAgMBAAGjQjBAMA8GA1UdEwEB/wQF\nMAMBAf8wHQYDVR0OBBYEFHAems86dTwdZbLe8AaPy3kfIUVoMA4GA1UdDwEB/wQE\nAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAOBHpp0ICx81kmeoBcZTrMdJs2gnhcd85\nFoSCjXx9H5XE5rmN/lQcxxOgj8hr3uPuLdLHu+i6THAyzjrl2NA1FWiqpfeECGmy\n0jm7iZsYORgGQYp/VKnDrwnKNSqlZvOuRr0kfUexwFlr34Y4VmupvEOK/RdGsd3S\n+3hiemcHse9ST/sJLHx962AWMkN86UHPscJEe4+eT3f2Wyzg6La8ARwdWZSNS+WH\nZfybrncMmuiXuUdHv9XspPsqhKgtHhcYeXOGUtrwQPLe3+VJZ0LVxhlTWr9951GZ\nGfmWwTV/9VsyKVaCFIXeQ6L+gjcKyEzYF8wpMtQlSc7FFqwgC4bKxvMBSaRy88Nr\nlV2+tJD/fr8zGUeBK44Emon0HKDBWGX+/Hq1ZIv0Da0S+j6LbA4fusWxtGfuGha+\nluhHgVInCpALIOamiBEdGhILkoTtx7JrYppt3/Raqg9gUNCOOYlCvGhqX7DXeEfL\nDGabooiY2FNWot6h04JE9nqGj5QqT8D6t/TL1nzxhRPzbcSDIHUd/b5R+a0bAA+7\nYTU6JqzEVCWKEIEynYmqikgLMGB/OzWsgyEL6822QW6hJAQ78XpbNeCzrICF4+GC\n7KShLnwuWoWpAb26268lvOEvCTFM47VC6jNQl97md+2SA9Ma81C9wflid2M83Wle\ncuLMVcQZceE=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEADCCAuigAwIBAgIQAhAteLRCvizAElaWORFU2zANBgkqhkiG9w0BAQsFADCB\nmDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB\nbWF6b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyMDE3MDkxNloYDzIwNjEwNTIwMTgwOTE2WjCBmDEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6\nb24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+qg7JAcOVKjh\nN83SACnBFZPyB63EusfDr/0V9ZdL8lKcmZX9sv/CqoBo3N0EvBqHQqUUX6JvFb7F\nXrMUZ740kr28gSRALfXTFgNODjXeDsCtEkKRTkac/UM8xXHn+hR7UFRPHS3e0GzI\niLiwQWDkr0Op74W8aM0CfaVKvh2bp4BI1jJbdDnQ9OKXpOxNHGUf0ZGb7TkNPkgI\nb2CBAc8J5o3H9lfw4uiyvl6Fz5JoP+A+zPELAioYBXDrbE7wJeqQDJrETWqR9VEK\nBXURCkVnHeaJy123MpAX2ozf4pqk0V0LOEOZRS29I+USF5DcWr7QIXR/w2I8ws1Q\n7ys+qbE+kQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQFJ16n\n1EcCMOIhoZs/F9sR+Jy++zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD\nggEBAOc5nXbT3XTDEZsxX2iD15YrQvmL5m13B3ImZWpx/pqmObsgx3/dg75rF2nQ\nqS+Vl+f/HLh516pj2BPP/yWCq12TRYigGav8UH0qdT3CAClYy2o+zAzUJHm84oiB\nud+6pFVGkbqpsY+QMpJUbZWu52KViBpJMYsUEy+9cnPSFRVuRAHjYynSiLk2ZEjb\nWkdc4x0nOZR5tP0FgrX0Ve2KcjFwVQJVZLgOUqmFYQ/G0TIIGTNh9tcmR7yp+xJR\nA2tbPV2Z6m9Yxx4E8lLEPNuoeouJ/GR4CkMEmF8cLwM310t174o3lKKUXJ4Vs2HO\nWj2uN6R9oI+jGLMSswTzCNV1vgc=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICuDCCAj6gAwIBAgIRAOocLeZWjYkG/EbHmscuy8gwCgYIKoZIzj0EAwMwgZsx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h\nem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTAgFw0yMTA1MjEyMTUwMDFaGA8yMTIxMDUyMTIyNTAwMVowgZsx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h\nem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABCEr3jq1KtRncnZfK5cq\nbtY0nW6ZG3FMbh7XwBIR6Ca0f8llGZ4vJEC1pXgiM/4Dh045B9ZIzNrR54rYOIfa\n2NcYZ7mk06DjIQML64hbAxbQzOAuNzLPx268MrlL2uW2XaNCMEAwDwYDVR0TAQH/\nBAUwAwEB/zAdBgNVHQ4EFgQUln75pChychwN4RfHl+tOinMrfVowDgYDVR0PAQH/\nBAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMGiyPINRU1mwZ4Crw01vpuPvxZxb2IOr\nyX3RNlOIu4We1H+5dQk5tIvH8KGYFbWEpAIxAO9NZ6/j9osMhLgZ0yj0WVjb+uZx\nYlZR9fyFisY/jNfX7QhSk+nrc3SFLRUNtpXrng==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBTCCAu2gAwIBAgIRAKiaRZatN8eiz9p0s0lu0rQwDQYJKoZIhvcNAQELBQAw\ngZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq\nQW1hem9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYD\nVQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMDIzNVoYDzIwNjEwNTIxMjMwMjM1WjCB\nmjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB\nbWF6b24gUkRTIGNhLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV\nBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCygVMf\nqB865IR9qYRBRFHn4eAqGJOCFx+UbraQZmjr/mnRqSkY+nhbM7Pn/DWOrRnxoh+w\nq5F9ZxdZ5D5T1v6kljVwxyfFgHItyyyIL0YS7e2h7cRRscCM+75kMedAP7icb4YN\nLfWBqfKHbHIOqvvQK8T6+Emu/QlG2B5LvuErrop9K0KinhITekpVIO4HCN61cuOe\nCADBKF/5uUJHwS9pWw3uUbpGUwsLBuhJzCY/OpJlDqC8Y9aToi2Ivl5u3/Q/sKjr\n6AZb9lx4q3J2z7tJDrm5MHYwV74elGSXoeoG8nODUqjgklIWAPrt6lQ3WJpO2kug\n8RhCdSbWkcXHfX95AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE\nFOIxhqTPkKVqKBZvMWtKewKWDvDBMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B\nAQsFAAOCAQEAqoItII89lOl4TKvg0I1EinxafZLXIheLcdGCxpjRxlZ9QMQUN3yb\ny/8uFKBL0otbQgJEoGhxm4h0tp54g28M6TN1U0332dwkjYxUNwvzrMaV5Na55I2Z\n1hq4GB3NMXW+PvdtsgVOZbEN+zOyOZ5MvJHEQVkT3YRnf6avsdntltcRzHJ16pJc\nY8rR7yWwPXh1lPaPkxddrCtwayyGxNbNmRybjR48uHRhwu7v2WuAMdChL8H8bp89\nTQLMrMHgSbZfee9hKhO4Zebelf1/cslRSrhkG0ESq6G5MUINj6lMg2g6F0F7Xz2v\nncD/vuRN5P+vT8th/oZ0Q2Gc68Pun0cn/g==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID/zCCAuegAwIBAgIRAJYlnmkGRj4ju/2jBQsnXJYwDQYJKoZIhvcNAQELBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyB1cy1lYXN0LTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyMTIzMDQ0NFoYDzIwNjEwNTIyMDAwNDQ0WjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIHVzLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC74V3eigv+pCj5\nnqDBqplY0Jp16pTeNB06IKbzb4MOTvNde6QjsZxrE1xUmprT8LxQqN9tI3aDYEYk\nb9v4F99WtQVgCv3Y34tYKX9NwWQgwS1vQwnIR8zOFBYqsAsHEkeJuSqAB12AYUSd\nZv2RVFjiFmYJho2X30IrSLQfS/IE3KV7fCyMMm154+/K1Z2IJlcissydEAwgsUHw\nedrE6CxJVkkJ3EvIgG4ugK/suxd8eEMztaQYJwSdN8TdfT59LFuSPl7zmF3fIBdJ\n//WexcQmGabaJ7Xnx+6o2HTfkP8Zzzzaq8fvjAcvA7gyFH5EP26G2ZqMG+0y4pTx\nSPVTrQEXAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIWWuNEF\nsUMOC82XlfJeqazzrkPDMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC\nAQEAgClmxcJaQTGpEZmjElL8G2Zc8lGc+ylGjiNlSIw8X25/bcLRptbDA90nuP+q\nzXAMhEf0ccbdpwxG/P5a8JipmHgqQLHfpkvaXx+0CuP++3k+chAJ3Gk5XtY587jX\n+MJfrPgjFt7vmMaKmynndf+NaIJAYczjhJj6xjPWmGrjM3MlTa9XesmelMwP3jep\nbApIWAvCYVjGndbK9byyMq1nyj0TUzB8oJZQooaR3MMjHTmADuVBylWzkRMxbKPl\n4Nlsk4Ef1JvIWBCzsMt+X17nuKfEatRfp3c9tbpGlAE/DSP0W2/Lnayxr4RpE9ds\nICF35uSis/7ZlsftODUe8wtpkQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrjCCAjOgAwIBAgIQS7vMpOTVq2Jw457NdZ2ffjAKBggqhkjOPQQDAzCBljEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6\nb24gUkRTIGNhLXdlc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTAgFw0yMzA5MTkyMjExNDNaGA8yMTIzMDkxOTIzMTE0M1owgZYxCzAJBgNV\nBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD\nVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE\nUyBjYS13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw\ndjAQBgcqhkjOPQIBBgUrgQQAIgNiAARdgGSs/F2lpWKqS1ZpcmatFED1JurmNbXG\nSqhv1A/geHrKCS15MPwjtnfZiujYKY4fNkCCUseoGDwkC4281nwkokvnfWR1/cXy\nLxfACoXNxsI4b+37CezSUBl48/5p1/OjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD\nVR0OBBYEFFhLokGBuJGwKJhZcYSYKyZIitJtMA4GA1UdDwEB/wQEAwIBhjAKBggq\nhkjOPQQDAwNpADBmAjEA8aQQlzJRHbqFsRY4O3u/cN0T8dzjcqnYn4NV1w+jvhzt\nQPJLB+ggGyQhoFR6G2UrAjEA0be8OP5MWXD8d01KKbo5Dpy6TwukF5qoJmkFJKS3\nbKfEMvFWxXoV06HNZFWdI80u\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIF/zCCA+egAwIBAgIRAPvvd+MCcp8E36lHziv0xhMwDQYJKoZIhvcNAQEMBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyB1cy1lYXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyMTIzMTEwNloYDzIxMjEwNTIyMDAxMTA2WjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIHVzLWVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDbvwekKIKGcV/s\nlDU96a71ZdN2pTYkev1X2e2/ICb765fw/i1jP9MwCzs8/xHBEQBJSxdfO4hPeNx3\nENi0zbM+TrMKliS1kFVe1trTTEaHYjF8BMK9yTY0VgSpWiGxGwg4tshezIA5lpu8\nsF6XMRxosCEVCxD/44CFqGZTzZaREIvvFPDTXKJ6yOYnuEkhH3OcoOajHN2GEMMQ\nShuyRFDQvYkqOC/Q5icqFbKg7eGwfl4PmimdV7gOVsxSlw2s/0EeeIILXtHx22z3\n8QBhX25Lrq2rMuaGcD3IOMBeBo2d//YuEtd9J+LGXL9AeOXHAwpvInywJKAtXTMq\nWsy3LjhuANFrzMlzjR2YdjkGVzeQVx3dKUzJ2//Qf7IXPSPaEGmcgbxuatxjnvfT\nH85oeKr3udKnXm0Kh7CLXeqJB5ITsvxI+Qq2iXtYCc+goHNR01QJwtGDSzuIMj3K\nf+YMrqBXZgYBwU2J/kCNTH31nfw96WTbOfNGwLwmVRDgguzFa+QzmQsJW4FTDMwc\n7cIjwdElQQVA+Gqa67uWmyDKAnoTkudmgAP+OTBkhnmc6NJuZDcy6f/iWUdl0X0u\n/tsfgXXR6ZovnHonM13ANiN7VmEVqFlEMa0VVmc09m+2FYjjlk8F9sC7Rc4wt214\n7u5YvCiCsFZwx44baP5viyRZgkJVpQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/\nMB0GA1UdDgQWBBQgCZCsc34nVTRbWsniXBPjnUTQ2DAOBgNVHQ8BAf8EBAMCAYYw\nDQYJKoZIhvcNAQEMBQADggIBAAQas3x1G6OpsIvQeMS9BbiHG3+kU9P/ba6Rrg+E\nlUz8TmL04Bcd+I+R0IyMBww4NznT+K60cFdk+1iSmT8Q55bpqRekyhcdWda1Qu0r\nJiTi7zz+3w2v66akofOnGevDpo/ilXGvCUJiLOBnHIF0izUqzvfczaMZGJT6xzKq\nPcEVRyAN1IHHf5KnGzUlVFv9SGy47xJ9I1vTk24JU0LWkSLzMMoxiUudVmHSqJtN\nu0h+n/x3Q6XguZi1/C1KOntH56ewRh8n5AF7c+9LJJSRM9wunb0Dzl7BEy21Xe9q\n03xRYjf5wn8eDELB8FZPa1PrNKXIOLYM9egdctbKEcpSsse060+tkyBrl507+SJT\n04lvJ4tcKjZFqxn+bUkDQvXYj0D3WK+iJ7a8kZJPRvz8BDHfIqancY8Tgw+69SUn\nWqIb+HNZqFuRs16WFSzlMksqzXv6wcDSyI7aZOmCGGEcYW9NHk8EuOnOQ+1UMT9C\nQb1GJcipjRzry3M4KN/t5vN3hIetB+/PhmgTO4gKhBETTEyPC3HC1QbdVfRndB6e\nU/NF2U/t8U2GvD26TTFLK4pScW7gyw4FQyXWs8g8FS8f+R2yWajhtS9++VDJQKom\nfAUISoCH+PlPRJpu/nHd1Zrddeiiis53rBaLbXu2J1Q3VqjWOmtj0HjxJJxWnYmz\nPqj2\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGATCCA+mgAwIBAgIRAI/U4z6+GF8/znpHM8Dq8G0wDQYJKoZIhvcNAQEMBQAw\ngZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo\nQW1hem9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTAgFw0yMjA2MDYyMTQ4MThaGA8yMTIyMDYwNjIyNDgxOFowgZgx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h\nem9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH\nU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK5WqMvyq888\n3uuOtEj1FcP6iZhqO5kJurdJF59Otp2WCg+zv6I+QwaAspEWHQsKD405XfFsTGKV\nSKTCwoMxwBniuChSmyhlagQGKSnRY9+znOWq0v7hgmJRwp6FqclTbubmr+K6lzPy\nhs86mEp68O5TcOTYWUlPZDqfKwfNTbtCl5YDRr8Gxb5buHmkp6gUSgDkRsXiZ5VV\nb3GBmXRqbnwo5ZRNAzQeM6ylXCn4jKs310lQGUrFbrJqlyxUdfxzqdlaIRn2X+HY\nxRSYbHox3LVNPpJxYSBRvpQVFSy9xbX8d1v6OM8+xluB31cbLBtm08KqPFuqx+cO\nI2H5F0CYqYzhyOSKJsiOEJT6/uH4ewryskZzncx9ae62SC+bB5n3aJLmOSTkKLFY\nYS5IsmDT2m3iMgzsJNUKVoCx2zihAzgBanFFBsG+Xmoq0aKseZUI6vd2qpd5tUST\n/wS1sNk0Ph7teWB2ACgbFE6etnJ6stwjHFZOj/iTYhlnR2zDRU8akunFdGb6CB4/\nhMxGJxaqXSJeGtHm7FpadlUTf+2ESbYcVW+ui/F8sdBJseQdKZf3VdZZMgM0bcaX\nNE47cauDTy72WdU9YJX/YXKYMLDE0iFHTnGpfVGsuWGPYhlwZ3dFIO07mWnCRM6X\nu5JXRB1oy5n5HRluMsmpSN/R92MeBxKFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wHQYDVR0OBBYEFNtH0F0xfijSLHEyIkRGD9gW6NazMA4GA1UdDwEB/wQEAwIB\nhjANBgkqhkiG9w0BAQwFAAOCAgEACo+5jFeY3ygxoDDzL3xpfe5M0U1WxdKk+az4\n/OfjZvkoma7WfChi3IIMtwtKLYC2/seKWA4KjlB3rlTsCVNPnK6D+gAnybcfTKk/\nIRSPk92zagwQkSUWtAk80HpVfWJzpkSU16ejiajhedzOBRtg6BwsbSqLCDXb8hXr\neXWC1S9ZceGc+LcKRHewGWPu31JDhHE9bNcl9BFSAS0lYVZqxIRWxivZ+45j5uQv\nwPrC8ggqsdU3K8quV6dblUQzzA8gKbXJpCzXZihkPrYpQHTH0szvXvgebh+CNUAG\nrUxm8+yTS0NFI3U+RLbcLFVzSvjMOnEwCX0SPj5XZRYYXs5ajtQCoZhTUkkwpDV8\nRxXk8qGKiXwUxDO8GRvmvM82IOiXz5w2jy/h7b7soyIgdYiUydMq4Ja4ogB/xPZa\ngf4y0o+bremO15HFf1MkaU2UxPK5FFVUds05pKvpSIaQWbF5lw4LHHj4ZtVup7zF\nCLjPWs4Hs/oUkxLMqQDw0FBwlqa4uot8ItT8uq5BFpz196ZZ+4WXw5PVzfSxZibI\nC/nwcj0AS6qharXOs8yPnPFLPSZ7BbmWzFDgo3tpglRqo3LbSPsiZR+sLeivqydr\n0w4RK1btRda5Ws88uZMmW7+2aufposMKcbAdrApDEAVzHijbB/nolS5nsnFPHZoA\nKDPtFEk=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICtzCCAj2gAwIBAgIQVZ5Y/KqjR4XLou8MCD5pOjAKBggqhkjOPQQDAzCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIGFwLXNvdXRoZWFzdC00IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIyMDUyNTE2NTgzM1oYDzIxMjIwNTI1MTc1ODMzWjCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIGFwLXNvdXRoZWFzdC00IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbo473OmpD5vkckdJajXg\nbrhmNFyoSa0WCY1njuZC2zMFp3zP6rX4I1r3imrYnJd9pFH/aSiV/r6L5ACE5RPx\n4qdg5SQ7JJUaZc3DWsTOiOed7BCZSzM+KTYK/2QzDMApo0IwQDAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBTmogc06+1knsej1ltKUOdWFvwgsjAOBgNVHQ8BAf8E\nBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAIs7TlLMbGTWNXpGiKf9DxaM07d/iDHe\nF/Vv/wyWSTGdobxBL6iArQNVXz0Gr4dvPAIwd0rsoa6R0x5mtvhdRPtM37FYrbHJ\npbV+OMusQqcSLseunLBoCHenvJW0QOCQ8EDY\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGBTCCA+2gAwIBAgIRAO9dVdiLTEGO8kjUFExJmgowDQYJKoZIhvcNAQEMBQAw\ngZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq\nQW1hem9uIFJEUyBpbC1jZW50cmFsLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYD\nVQQHDAdTZWF0dGxlMCAXDTIyMTIwMjIwMjYwOFoYDzIxMjIxMjAyMjEyNjA4WjCB\nmjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB\nbWF6b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV\nBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDkVHmJ\nbUc8CNDGBcgPmXHSHj5dS1PDnnpk3doCu6pahyYXW8tqAOmOqsDuNz48exY7YVy4\nu9I9OPBeTYB9ZUKwxq+1ZNLsr1cwVz5DdOyDREVFOjlU4rvw0eTgzhP5yw/d+Ai/\n+WmPebZG0irwPKN2f60W/KJ45UNtR+30MT8ugfnPuSHWjjV+dqCOCp/mj8nOCckn\nk8GoREwjuTFJMKInpQUC0BaVVX6LiIdgtoLY4wdx00EqNBuROoRTAvrked0jvm7J\nUI39CSYxhNZJ9F6LdESZXjI4u2apfNQeSoy6WptxFHr+kh2yss1B2KT6lbwGjwWm\nl9HODk9kbBNSy2NeewAms36q+p8wSLPavL28IRfK0UaBAiN1hr2a/2RDGCwOJmw6\n5erRC5IIX5kCStyXPEGhVPp18EvMuBd37eLIxjZBBO8AIDf4Ue8QmxSeZH0cT204\n3/Bd6XR6+Up9iMTxkHr1URcL1AR8Zd62lg/lbEfxePNMK9mQGxKP8eTMG5AjtW9G\nTatEoRclgE0wZQalXHmKpBNshyYdGqQZhzL1MxCxWzfHNgZkTKIsdzxrjnP7RiBR\njdRH0YhXn6Y906QfLwMCaufwfQ5J8+nj/tu7nG138kSxsu6VUkhnQJhUcUsxuHD/\nNnBx0KGVEldtZiZf7ccgtRVp1lA0OrVtq3ZLMQIDAQABo0IwQDAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBQ2WC3p8rWeE2N0S4Om01KsNLpk/jAOBgNVHQ8BAf8E\nBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAFFEVDt45Obr6Ax9E4RMgsKjj4QjMFB9\nwHev1jL7hezl/ULrHuWxjIusaIZEIcKfn+v2aWtqOq13P3ht7jV5KsV29CmFuCdQ\nq3PWiAXVs+hnMskTOmGMDnptqd6/UuSIha8mlOKKAvnmRQJvfX9hIfb/b/mVyKWD\nuvTTmcy3cOTJY5ZIWGyzuvmcqA0YNcb7rkJt/iaLq4RX3/ofq4y4w36hefbcvj++\npXHOmXk3dAej3y6SMBOUcGMyCJcCluRPNYKDTLn+fitcPxPC3JG7fI5bxQ0D6Hpa\nqbyGBQu96sfahQyMc+//H8EYlo4b0vPeS5RFFXJS/VBf0AyNT4vVc7H17Q6KjeNp\nwEARqsIa7UalHx9MnxrQ/LSTTxiC8qmDkIFuQtw8iQMN0SoL5S0eCZNRD31awgaY\ny1PvY8JMN549ugIUjOXnown/OxharLW1evWUraU5rArq3JfeFpPXl4K/u10T5SCL\niJRoxFilGPMFE3hvnmbi5rEy8wRUn7TpLb4I4s/CB/lT2qZTPqvQHwxKCnMm9BKF\nNHb4rLL5dCvUi5NJ6fQ/exOoGdOVSfT7jqFeq2TtNunERSz9vpriweliB6iIe1Al\nThj8aEs1GqA764rLVGA+vUe18NhjJm9EemrdIzjSQFy/NdbN/DMaHqEzJogWloAI\nizQWYnCS19TJ\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICvTCCAkOgAwIBAgIQCIY7E/bFvFN2lK9Kckb0dTAKBggqhkjOPQQDAzCBnjEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTcwNQYDVQQDDC5BbWF6\nb24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYD\nVQQHDAdTZWF0dGxlMCAXDTIxMDUxODIxMDUxMFoYDzIxMjEwNTE4MjIwNTEwWjCB\nnjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTcwNQYDVQQDDC5B\nbWF6b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMI0hzf1JCEOI\nEue4+DmcNnSs2i2UaJxHMrNGGfU7b42a7vwP53F7045ffHPBGP4jb9q02/bStZzd\nVHqfcgqkSRI7beBKjD2mfz82hF/wJSITTgCLs+NRpS6zKMFOFHUNo0IwQDAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS8uF/6hk5mPLH4qaWv9NVZaMmyTjAOBgNV\nHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAO7Pu9wzLyM0X7Q08uLIL+vL\nqaxe3UFuzFTWjM16MLJHbzLf1i9IDFKz+Q4hXCSiJwIwClMBsqT49BPUxVsJnjGr\nEbyEk6aOOVfY1p2yQL649zh3M4h8okLnwf+bYIb1YpeU\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEADCCAuigAwIBAgIQY+JhwFEQTe36qyRlUlF8ozANBgkqhkiG9w0BAQsFADCB\nmDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB\nbWF6b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUxOTE5MjQxNloYDzIwNjEwNTE5MjAyNDE2WjCBmDEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6\nb24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIye77j6ev40\n8wRPyN2OdKFSUfI9jB20Or2RLO+RDoL43+USXdrze0Wv4HMRLqaen9BcmCfaKMp0\nE4SFo47bXK/O17r6G8eyq1sqnHE+v288mWtYH9lAlSamNFRF6YwA7zncmE/iKL8J\n0vePHMHP/B6svw8LULZCk+nZk3tgxQn2+r0B4FOz+RmpkoVddfqqUPMbKUxhM2wf\nfO7F6bJaUXDNMBPhCn/3ayKCjYr49ErmnpYV2ZVs1i34S+LFq39J7kyv6zAgbHv9\n+/MtRMoRB1CjpqW0jIOZkHBdYcd1o9p1zFn591Do1wPkmMsWdjIYj+6e7UXcHvOB\n2+ScIRAcnwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGtq2W\nYSyMMxpdQ3IZvcGE+nyZqTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD\nggEBAEgoP3ixJsKSD5FN8dQ01RNHERl/IFbA7TRXfwC+L1yFocKnQh4Mp/msPRSV\n+OeHIvemPW/wtZDJzLTOFJ6eTolGekHK1GRTQ6ZqsWiU2fmiOP8ks4oSpI+tQ9Lw\nVrfZqTiEcS5wEIqyfUAZZfKDo7W1xp+dQWzfczSBuZJZwI5iaha7+ILM0r8Ckden\nTVTapc5pLSoO15v0ziRuQ2bT3V3nwu/U0MRK44z+VWOJdSiKxdnOYDs8hFNnKhfe\nklbTZF7kW7WbiNYB43OaAQBJ6BALZsIskEaqfeZT8FD71uN928TcEQyBDXdZpRN+\niGQZDGhht0r0URGMDSs9waJtTfA=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIF/jCCA+agAwIBAgIQXY/dmS+72lZPranO2JM9jjANBgkqhkiG9w0BAQwFADCB\nlzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB\nbWF6b24gUkRTIGFwLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjEwNTI1MjEzNDUxWhgPMjEyMTA1MjUyMjM0NTFaMIGXMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv\nbiBSRFMgYXAtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMyW9kBJjD/hx8e8\nb5E1sF42bp8TXsz1htSYE3Tl3T1Aq379DfEhB+xa/ASDZxt7/vwa81BkNo4M6HYq\nokYIXeE7cu5SnSgjWXqcERhgPevtAwgmhdE3yREe8oz2DyOi2qKKZqah+1gpPaIQ\nfK0uAqoeQlyHosye3KZZKkDHBatjBsQ5kf8lhuf7wVulEZVRHY2bP2X7N98PfbpL\nQdH7mWXzDtJJ0LiwFwds47BrkgK1pkHx2p1mTo+HMkfX0P6Fq1atkVC2RHHtbB/X\niYyH7paaHBzviFrhr679zNqwXIOKlbf74w3mS11P76rFn9rS1BAH2Qm6eY5S/Fxe\nHEKXm4kjPN63Zy0p3yE5EjPt54yPkvumOnT+RqDGJ2HCI9k8Ehcbve0ogfdRKNqQ\nVHWYTy8V33ndQRHZlx/CuU1yN61TH4WSoMly1+q1ihTX9sApmlQ14B2pJi/9DnKW\ncwECrPy1jAowC2UJ45RtC8UC05CbP9yrIy/7Noj8gQDiDOepm+6w1g6aNlWoiuQS\nkyI6nzz1983GcnOHya73ga7otXo0Qfg9jPghlYiMomrgshlSLDHZG0Ib/3hb8cnR\n1OcN9FpzNmVK2Ll1SmTMLrIhuCkyNYX9O/bOknbcf706XeESxGduSkHEjIw/k1+2\nAtteoq5dT6cwjnJ9hyhiueVlVkiDAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w\nHQYDVR0OBBYEFLUI+DD7RJs+0nRnjcwIVWzzYSsFMA4GA1UdDwEB/wQEAwIBhjAN\nBgkqhkiG9w0BAQwFAAOCAgEAb1mcCHv4qMQetLGTBH9IxsB2YUUhr5dda0D2BcHr\nUtDbfd0VQs4tux6h/6iKwHPx0Ew8fuuYj99WknG0ffgJfNc5/fMspxR/pc1jpdyU\n5zMQ+B9wi0lOZPO9uH7/pr+d2odcNEy8zAwqdv/ihsTwLmGP54is9fVbsgzNW1cm\nHKAVL2t/Ope+3QnRiRilKCN1lzhav4HHdLlN401TcWRWKbEuxF/FgxSO2Hmx86pj\ne726lweCTMmnq/cTsPOVY0WMjs0or3eHDVlyLgVeV5ldyN+ptg3Oit60T05SRa58\nAJPTaVKIcGQ/gKkKZConpu7GDofT67P/ox0YNY57LRbhsx9r5UY4ROgz7WMQ1yoS\nY+19xizm+mBm2PyjMUbfwZUyCxsdKMwVdOq5/UmTmdms+TR8+m1uBHPOTQ2vKR0s\nPd/THSzPuu+d3dbzRyDSLQbHFFneG760CUlD/ZmzFlQjJ89/HmAmz8IyENq+Sjhx\nJgzy+FjVZb8aRUoYLlnffpUpej1n87Ynlr1GrvC4GsRpNpOHlwuf6WD4W0qUTsC/\nC9JO+fBzUj/aWlJzNcLEW6pte1SB+EdkR2sZvWH+F88TxemeDrV0jKJw5R89CDf8\nZQNfkxJYjhns+YeV0moYjqQdc7tq4i04uggEQEtVzEhRLU5PE83nlh/K2NZZm8Kj\ndIA=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID/zCCAuegAwIBAgIRAPVSMfFitmM5PhmbaOFoGfUwDQYJKoZIhvcNAQELBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyB1cy1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyNTIyMzQ1N1oYDzIwNjEwNTI1MjMzNDU3WjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIHVzLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDu9H7TBeGoDzMr\ndxN6H8COntJX4IR6dbyhnj5qMD4xl/IWvp50lt0VpmMd+z2PNZzx8RazeGC5IniV\n5nrLg0AKWRQ2A/lGGXbUrGXCSe09brMQCxWBSIYe1WZZ1iU1IJ/6Bp4D2YEHpXrW\nbPkOq5x3YPcsoitgm1Xh8ygz6vb7PsvJvPbvRMnkDg5IqEThapPjmKb8ZJWyEFEE\nQRrkCIRueB1EqQtJw0fvP4PKDlCJAKBEs/y049FoOqYpT3pRy0WKqPhWve+hScMd\n6obq8kxTFy1IHACjHc51nrGII5Bt76/MpTWhnJIJrCnq1/Uc3Qs8IVeb+sLaFC8K\nDI69Sw6bAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE7PCopt\nlyOgtXX0Y1lObBUxuKaCMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC\nAQEAFj+bX8gLmMNefr5jRJfHjrL3iuZCjf7YEZgn89pS4z8408mjj9z6Q5D1H7yS\njNETVV8QaJip1qyhh5gRzRaArgGAYvi2/r0zPsy+Tgf7v1KGL5Lh8NT8iCEGGXwF\ng3Ir+Nl3e+9XUp0eyyzBIjHtjLBm6yy8rGk9p6OtFDQnKF5OxwbAgip42CD75r/q\np421maEDDvvRFR4D+99JZxgAYDBGqRRceUoe16qDzbMvlz0A9paCZFclxeftAxv6\nQlR5rItMz/XdzpBJUpYhdzM0gCzAzdQuVO5tjJxmXhkSMcDP+8Q+Uv6FA9k2VpUV\nE/O5jgpqUJJ2Hc/5rs9VkAPXeA==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrzCCAjWgAwIBAgIQW0yuFCle3uj4vWiGU0SaGzAKBggqhkjOPQQDAzCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjEwNTE5MTkzNTE2WhgPMjEyMTA1MTkyMDM1MTZaMIGXMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS\nRFMgYWYtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs\nZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDPiKNZSaXs3Un/J/v+LTsFDANHpi7en\noL2qh0u0DoqNzEBTbBjvO23bLN3k599zh6CY3HKW0r2k1yaIdbWqt4upMCRCcUFi\nI4iedAmubgzh56wJdoMZztjXZRwDthTkJKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd\nBgNVHQ4EFgQUWbYkcrvVSnAWPR5PJhIzppcAnZIwDgYDVR0PAQH/BAQDAgGGMAoG\nCCqGSM49BAMDA2gAMGUCMCESGqpat93CjrSEjE7z+Hbvz0psZTHwqaxuiH64GKUm\nmYynIiwpKHyBrzjKBmeDoQIxANGrjIo6/b8Jl6sdIZQI18V0pAyLfLiZjlHVOnhM\nMOTVgr82ZuPoEHTX78MxeMnYlw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECTCCAvGgAwIBAgIRAIbsx8XOl0sgTNiCN4O+18QwDQYJKoZIhvcNAQELBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI1MjE1NDU4WhgPMjA2MTA1MjUyMjU0NTha\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\ntROxwXWCgn5R9gI/2Ivjzaxc0g95ysBjoJsnhPdJEHQb7w3y2kWrVWU3Y9fOitgb\nCEsnEC3PrhRnzNVW0fPsK6kbvOeCmjvY30rdbxbc8h+bjXfGmIOgAkmoULEr6Hc7\nG1Q/+tvv4lEwIs7bEaf+abSZxRJbZ0MBxhbHn7UHHDiMZYvzK+SV1MGCxx7JVhrm\nxWu3GC1zZCsGDhB9YqY9eR6PmjbqA5wy8vqbC57dZZa1QVtWIQn3JaRXn+faIzHx\nnLMN5CEWihsdmHBXhnRboXprE/OS4MFv1UrQF/XM/h5RBeCywpHePpC+Oe1T3LNC\niP8KzRFrjC1MX/WXJnmOVQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBS33XbXAUMs1znyZo4B0+B3D68WFTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI\nhvcNAQELBQADggEBADuadd2EmlpueY2VlrIIPC30QkoA1EOSoCmZgN6124apkoY1\nHiV4r+QNPljN4WP8gmcARnNkS7ZeR4fvWi8xPh5AxQCpiaBMw4gcbTMCuKDV68Pw\nP2dZCTMspvR3CDfM35oXCufdtFnxyU6PAyINUqF/wyTHguO3owRFPz64+sk3r2pT\nWHmJjG9E7V+KOh0s6REgD17Gqn6C5ijLchSrPUHB0wOIkeLJZndHxN/76h7+zhMt\nfFeNxPWHY2MfpcaLjz4UREzZPSB2U9k+y3pW1omCIcl6MQU9itGx/LpQE+H3ZeX2\nM2bdYd5L+ow+bdbGtsVKOuN+R9Dm17YpswF+vyQ=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGATCCA+mgAwIBAgIRAKlQ+3JX9yHXyjP/Ja6kZhkwDQYJKoZIhvcNAQEMBQAw\ngZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo\nQW1hem9uIFJEUyBhcC1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTAgFw0yMTA1MTkxNzQ1MjBaGA8yMTIxMDUxOTE4NDUyMFowgZgx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h\nem9uIFJEUyBhcC1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH\nU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKtahBrpUjQ6\nH2mni05BAKU6Z5USPZeSKmBBJN3YgD17rJ93ikJxSgzJ+CupGy5rvYQ0xznJyiV0\n91QeQN4P+G2MjGQR0RGeUuZcfcZitJro7iAg3UBvw8WIGkcDUg+MGVpRv/B7ry88\n7E4OxKb8CPNoa+a9j6ABjOaaxaI22Bb7j3OJ+JyMICs6CU2bgkJaj3VUV9FCNUOc\nh9PxD4jzT9yyGYm/sK9BAT1WOTPG8XQUkpcFqy/IerZDfiQkf1koiSd4s5VhBkUn\naQHOdri/stldT7a+HJFVyz2AXDGPDj+UBMOuLq0K6GAT6ThpkXCb2RIf4mdTy7ox\nN5BaJ+ih+Ro3ZwPkok60egnt/RN98jgbm+WstgjJWuLqSNInnMUgkuqjyBWwePqX\nKib+wdpyx/LOzhKPEFpeMIvHQ3A0sjlulIjnh+j+itezD+dp0UNxMERlW4Bn/IlS\nsYQVNfYutWkRPRLErXOZXtlxxkI98JWQtLjvGzQr+jywxTiw644FSLWdhKa6DtfU\n2JWBHqQPJicMElfZpmfaHZjtXuCZNdZQXWg7onZYohe281ZrdFPOqC4rUq7gYamL\nT+ZB+2P+YCPOLJ60bj/XSvcB7mesAdg8P0DNddPhHUFWx2dFqOs1HxIVB4FZVA9U\nPpbv4a484yxjTgG7zFZNqXHKTqze6rBBAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wHQYDVR0OBBYEFCEAqjighncv/UnWzBjqu1Ka2Yb4MA4GA1UdDwEB/wQEAwIB\nhjANBgkqhkiG9w0BAQwFAAOCAgEAYyvumblckIXlohzi3QiShkZhqFzZultbFIu9\nGhA5CDar1IFMhJ9vJpO9nUK/camKs1VQRs8ZsBbXa0GFUM2p8y2cgUfLwFULAiC/\nsWETyW5lcX/xc4Pyf6dONhqFJt/ovVBxNZtcmMEWv/1D6Tf0nLeEb0P2i/pnSRR4\nOq99LVFjossXtyvtaq06OSiUUZ1zLPvV6AQINg8dWeBOWRcQYhYcEcC2wQ06KShZ\n0ahuu7ar5Gym3vuLK6nH+eQrkUievVomN/LpASrYhK32joQ5ypIJej3sICIgJUEP\nUoeswJ+Z16f3ECoL1OSnq4A0riiLj1ZGmVHNhM6m/gotKaHNMxsK9zsbqmuU6IT/\nP6cR0S+vdigQG8ZNFf5vEyVNXhl8KcaJn6lMD/gMB2rY0qpaeTg4gPfU5wcg8S4Y\nC9V//tw3hv0f2n+8kGNmqZrylOQDQWSSo8j8M2SRSXiwOHDoTASd1fyBEIqBAwzn\nLvXVg8wQd1WlmM3b0Vrsbzltyh6y4SuKSkmgufYYvC07NknQO5vqvZcNoYbLNea3\n76NkFaMHUekSbwVejZgG5HGwbaYBgNdJEdpbWlA3X4yGRVxknQSUyt4dZRnw/HrX\nk8x6/wvtw7wht0/DOqz1li7baSsMazqxx+jDdSr1h9xML416Q4loFCLgqQhil8Jq\nEm4Hy3A=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBDCCAuygAwIBAgIQFn6AJ+uxaPDpNVx7174CpjANBgkqhkiG9w0BAQsFADCB\nmjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB\nbWF6b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV\nBAcMB1NlYXR0bGUwIBcNMjIxMjAyMjAxNDA4WhgPMjA2MjEyMDIyMTE0MDhaMIGa\nMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j\nLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt\nYXpvbiBSRFMgaWwtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2xGTSJ\nfXorki/dkkTqdLyv4U1neeFYEyUCPN/HJ7ZloNwhj8RBrHYhZ4qtvUAvN+rs8fUm\nL0wmaL69ye61S+CSfDzNwBDGwOzUm/cc1NEJOHCm8XA0unBNBvpJTjsFk2LQ+rz8\noU0lVV4mjnfGektrTDeADonO1adJvUTYmF6v1wMnykSkp8AnW9EG/6nwcAJuAJ7d\nBfaLThm6lfxPdsBNG81DLKi2me2TLQ4yl+vgRKJi2fJWwA77NaDqQuD5upRIcQwt\n5noJt2kFFmeiro98ZMMRaDTHAHhJfWkwkw5f2QNIww7T4r85IwbQCgJVRo4m4ZTC\nW/1eiEccU2407mECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU\nDNhVvGHzKXv0Yh6asK0apP9jJlUwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB\nCwUAA4IBAQCoEVTUY/rF9Zrlpb1Y1hptEguw0i2pCLakcmv3YNj6thsubbGeGx8Z\nRjUA/gPKirpoae2HU1y64WEu7akwr6pdTRtXXjbe9NReT6OW/0xAwceSXCOiStqS\ncMsWWTGg6BA3uHqad5clqITjDZr1baQ8X8en4SXRBxXyhJXbOkB60HOQeFR9CNeh\npJdrWLeNYXwU0Z59juqdVMGwvDAYdugWUhW2rhafVUXszfRA5c8Izc+E31kq90aY\nLmxFXUHUfG0eQOmxmg+Z/nG7yLUdHIFA3id8MRh22hye3KvRdQ7ZVGFni0hG2vQQ\nQ01AvD/rhzyjg0czzJKLK9U/RttwdMaV\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGBTCCA+2gAwIBAgIRAJfKe4Zh4aWNt3bv6ZjQwogwDQYJKoZIhvcNAQEMBQAw\ngZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq\nQW1hem9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYD\nVQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMDg1M1oYDzIxMjEwNTIxMjMwODUzWjCB\nmjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB\nbWF6b24gUkRTIGNhLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV\nBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpgUH6\nCrzd8cOw9prAh2rkQqAOx2vtuI7xX4tmBG4I/um28eBjyVmgwQ1fpq0Zg2nCKS54\nNn0pCmT7f3h6Bvopxn0J45AzXEtajFqXf92NQ3iPth95GVfAJSD7gk2LWMhpmID9\nJGQyoGuDPg+hYyr292X6d0madzEktVVGO4mKTF989qEg+tY8+oN0U2fRTrqa2tZp\niYsmg350ynNopvntsJAfpCO/srwpsqHHLNFZ9jvhTU8uW90wgaKO9i31j/mHggCE\n+CAOaJCM3g+L8DPl/2QKsb6UkBgaaIwKyRgKSj1IlgrK+OdCBCOgM9jjId4Tqo2j\nZIrrPBGl6fbn1+etZX+2/tf6tegz+yV0HHQRAcKCpaH8AXF44bny9andslBoNjGx\nH6R/3ib4FhPrnBMElzZ5i4+eM/cuPC2huZMBXb/jKgRC/QN1Wm3/nah5FWq+yn+N\ntiAF10Ga0BYzVhHDEwZzN7gn38bcY5yi/CjDUNpY0OzEe2+dpaBKPlXTaFfn9Nba\nCBmXPRF0lLGGtPeTAgjcju+NEcVa82Ht1pqxyu2sDtbu3J5bxp4RKtj+ShwN8nut\nTkf5Ea9rSmHEY13fzgibZlQhXaiFSKA2ASUwgJP19Putm0XKlBCNSGCoECemewxL\n+7Y8FszS4Uu4eaIwvXVqUEE2yf+4ex0hqQ1acQIDAQABo0IwQDAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBSeUnXIRxNbYsZLtKomIz4Y1nOZEzAOBgNVHQ8BAf8E\nBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAIpRvxVS0dzoosBh/qw65ghPUGSbP2D4\ndm6oYCv5g/zJr4fR7NzEbHOXX5aOQnHbQL4M/7veuOCLNPOW1uXwywMg6gY+dbKe\nYtPVA1as8G9sUyadeXyGh2uXGsziMFXyaESwiAXZyiYyKChS3+g26/7jwECFo5vC\nXGhWpIO7Hp35Yglp8AnwnEAo/PnuXgyt2nvyTSrxlEYa0jus6GZEZd77pa82U1JH\nqFhIgmKPWWdvELA3+ra1nKnvpWM/xX0pnMznMej5B3RT3Y+k61+kWghJE81Ix78T\n+tG4jSotgbaL53BhtQWBD1yzbbilqsGE1/DXPXzHVf9yD73fwh2tGWSaVInKYinr\na4tcrB3KDN/PFq0/w5/21lpZjVFyu/eiPj6DmWDuHW73XnRwZpHo/2OFkei5R7cT\nrn/YdDD6c1dYtSw5YNnS6hdCQ3sOiB/xbPRN9VWJa6se79uZ9NLz6RMOr73DNnb2\nbhIR9Gf7XAA5lYKqQk+A+stoKbIT0F65RnkxrXi/6vSiXfCh/bV6B41cf7MY/6YW\nehserSdjhQamv35rTFdM+foJwUKz1QN9n9KZhPxeRmwqPitAV79PloksOnX25ElN\nSlyxdndIoA1wia1HRd26EFm2pqfZ2vtD2EjU3wD42CXX4H8fKVDna30nNFSYF0yn\njGKc3k6UNxpg\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIF/jCCA+agAwIBAgIQaRHaEqqacXN20e8zZJtmDDANBgkqhkiG9w0BAQwFADCB\nlzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB\nbWF6b24gUkRTIHVzLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjEwNTI1MjIzODM1WhgPMjEyMTA1MjUyMzM4MzVaMIGXMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv\nbiBSRFMgdXMtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAInfBCaHuvj6Rb5c\nL5Wmn1jv2PHtEGMHm+7Z8dYosdwouG8VG2A+BCYCZfij9lIGszrTXkY4O7vnXgru\nJUNdxh0Q3M83p4X+bg+gODUs3jf+Z3Oeq7nTOk/2UYvQLcxP4FEXILxDInbQFcIx\nyen1ESHggGrjEodgn6nbKQNRfIhjhW+TKYaewfsVWH7EF2pfj+cjbJ6njjgZ0/M9\nVZifJFBgat6XUTOf3jwHwkCBh7T6rDpgy19A61laImJCQhdTnHKvzTpxcxiLRh69\nZObypR7W04OAUmFS88V7IotlPmCL8xf7kwxG+gQfvx31+A9IDMsiTqJ1Cc4fYEKg\nbL+Vo+2Ii4W2esCTGVYmHm73drznfeKwL+kmIC/Bq+DrZ+veTqKFYwSkpHRyJCEe\nU4Zym6POqQ/4LBSKwDUhWLJIlq99bjKX+hNTJykB+Lbcx0ScOP4IAZQoxmDxGWxN\nS+lQj+Cx2pwU3S/7+OxlRndZAX/FKgk7xSMkg88HykUZaZ/ozIiqJqSnGpgXCtED\noQ4OJw5ozAr+/wudOawaMwUWQl5asD8fuy/hl5S1nv9XxIc842QJOtJFxhyeMIXt\nLVECVw/dPekhMjS3Zo3wwRgYbnKG7YXXT5WMxJEnHu8+cYpMiRClzq2BEP6/MtI2\nAZQQUFu2yFjRGL2OZA6IYjxnXYiRAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w\nHQYDVR0OBBYEFADCcQCPX2HmkqQcmuHfiQ2jjqnrMA4GA1UdDwEB/wQEAwIBhjAN\nBgkqhkiG9w0BAQwFAAOCAgEASXkGQ2eUmudIKPeOIF7RBryCoPmMOsqP0+1qxF8l\npGkwmrgNDGpmd9s0ArfIVBTc1jmpgB3oiRW9c6n2OmwBKL4UPuQ8O3KwSP0iD2sZ\nKMXoMEyphCEzW1I2GRvYDugL3Z9MWrnHkoaoH2l8YyTYvszTvdgxBPpM2x4pSkp+\n76d4/eRpJ5mVuQ93nC+YG0wXCxSq63hX4kyZgPxgCdAA+qgFfKIGyNqUIqWgeyTP\nn5OgKaboYk2141Rf2hGMD3/hsGm0rrJh7g3C0ZirPws3eeJfulvAOIy2IZzqHUSY\njkFzraz6LEH3IlArT3jUPvWKqvh2lJWnnp56aqxBR7qHH5voD49UpJWY1K0BjGnS\nOHcurpp0Yt/BIs4VZeWdCZwI7JaSeDcPMaMDBvND3Ia5Fga0thgYQTG6dE+N5fgF\nz+hRaujXO2nb0LmddVyvE8prYlWRMuYFv+Co8hcMdJ0lEZlfVNu0jbm9/GmwAZ+l\n9umeYO9yz/uC7edC8XJBglMAKUmVK9wNtOckUWAcCfnPWYLbYa/PqtXBYcxrso5j\niaS/A7iEW51uteHBGrViCy1afGG+hiUWwFlesli+Rq4dNstX3h6h2baWABaAxEVJ\ny1RnTQSz6mROT1VmZSgSVO37rgIyY0Hf0872ogcTS+FfvXgBxCxsNWEbiQ/XXva4\n0Ws=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICtDCCAjqgAwIBAgIRAMyaTlVLN0ndGp4ffwKAfoMwCgYIKoZIzj0EAwMwgZkx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h\nem9uIFJEUyBtZS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjIwNTA3MDA0NDM3WhgPMjEyMjA1MDcwMTQ0MzdaMIGZMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv\nbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE19nCV1nsI6CohSor13+B25cr\nzg+IHdi9Y3L7ziQnHWI6yjBazvnKD+oC71aRRlR8b5YXsYGUQxWzPLHN7EGPcSGv\nbzA9SLG1KQYCJaQ0m9Eg/iGrwKWOgylbhVw0bCxoo0IwQDAPBgNVHRMBAf8EBTAD\nAQH/MB0GA1UdDgQWBBS4KsknsJXM9+QPEkBdZxUPaLr11zAOBgNVHQ8BAf8EBAMC\nAYYwCgYIKoZIzj0EAwMDaAAwZQIxAJaRgrYIEfXQMZQQDxMTYS0azpyWSseQooXo\nL3nYq4OHGBgYyQ9gVjvRYWU85PXbfgIwdi82DtANQFkCu+j+BU0JBY/uRKPEeYzo\nJG92igKIcXPqCoxIJ7lJbbzmuf73gQu5\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGATCCA+mgAwIBAgIRAJwCobx0Os8F7ihbJngxrR8wDQYJKoZIhvcNAQEMBQAw\ngZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo\nQW1hem9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTAgFw0yMTA1MjAxNzE1MzNaGA8yMTIxMDUyMDE4MTUzM1owgZgx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h\nem9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH\nU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANukKwlm+ZaI\nY5MkWGbEVLApEyLmlrHLEg8PfiiEa9ts7jssQcin3bzEPdTqGr5jo91ONoZ3ccWq\nxJgg1W3bLu5CAO2CqIOXTXHRyCO/u0Ch1FGgWB8xETPSi3UHt/Vn1ltdO6DYdbDU\nmYgwzYrvLBdRCwxsb9o+BuYQHVFzUYonqk/y9ujz3gotzFq7r55UwDTA1ita3vb4\neDKjIb4b1M4Wr81M23WHonpje+9qkkrAkdQcHrkgvSCV046xsq/6NctzwCUUNsgF\n7Q1a8ut5qJEYpz5ta8vI1rqFqAMBqCbFjRYlmAoTTpFPOmzAVxV+YoqTrW5A16su\n/2SXlMYfJ/n/ad/QfBNPPAAQMpyOr2RCL/YiL/PFZPs7NxYjnZHNWxMLSPgFyI+/\nt2klnn5jR76KJK2qimmaXedB90EtFsMRUU1e4NxH9gDuyrihKPJ3aVnZ35mSipvR\n/1KB8t8gtFXp/VQaz2sg8+uxPMKB81O37fL4zz6Mg5K8+aq3ejBiyHucpFGnsnVB\n3kQWeD36ONkybngmgWoyPceuSWm1hQ0Z7VRAQX+KlxxSaHmSaIk1XxZu9h9riQHx\nfMuev6KXjRn/CjCoUTn+7eFrt0dT5GryQEIZP+nA0oq0LKxogigHNZlwAT4flrqb\nJUfZJrqgoce5HjZSXl10APbtPjJi0fW9AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wHQYDVR0OBBYEFEfV+LztI29OVDRm0tqClP3NrmEWMA4GA1UdDwEB/wQEAwIB\nhjANBgkqhkiG9w0BAQwFAAOCAgEAvSNe+0wuk53KhWlRlRf2x/97H2Q76X3anzF0\n5fOSVm022ldALzXMzqOfdnoKIhAu2oVKiHHKs7mMas+T6TL+Mkphx0CYEVxFE3PG\n061q3CqJU+wMm9W9xsB79oB2XG47r1fIEywZZ3GaRsatAbjcNOT8uBaATPQAfJFN\nzjFe4XyN+rA4cFrYNvfHTeu5ftrYmvks7JlRaJgEGWsz+qXux7uvaEEVPqEumd2H\nuYeaRNOZ2V23R009X5lbgBFx9tq5VDTnKhQiTQ2SeT0rc1W3Dz5ik6SbQQNP3nSR\n0Ywy7r/sZ3fcDyfFiqnrVY4Ympfvb4YW2PZ6OsQJbzH6xjdnTG2HtzEU30ngxdp1\nWUEF4zt6rjJCp7QBUqXgdlHvJqYu6949qtWjEPiFN9uSsRV2i1YDjJqN52dLjAPn\nAipJKo8x1PHTwUzuITqnB9BdP+5TlTl8biJfkEf/+08eWDTLlDHr2VrZLOLompTh\nbS5OrhDmqA2Q+O+EWrTIhMflwwlCpR9QYM/Xwvlbad9H0FUHbJsCVNaru3wGOgWo\ntt3dNSK9Lqnv/Ej9K9v6CRr36in4ylJKivhJ5B9E7ABHg7EpBJ1xi7O5eNDkNoJG\n+pFyphJq3AkBR2U4ni2tUaTAtSW2tks7IaiDV+UMtqZyGabT5ISQfWLLtLHSWn2F\nTspdjbg=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIECTCCAvGgAwIBAgIRAJZFh4s9aZGzKaTMLrSb4acwDQYJKoZIhvcNAQELBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBCZXRhIHVzLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjEyODQxWhgPMjA2MTA1MTgyMjI4NDFa\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgQmV0YSB1cy1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n17i2yoU6diep+WrqxIn2CrDEO2NdJVwWTSckx4WMZlLpkQDoymSmkNHjq9ADIApD\nA31Cx+843apL7wub8QkFZD0Tk7/ThdHWJOzcAM3ov98QBPQfOC1W5zYIIRP2F+vQ\nTRETHQnLcW3rLv0NMk5oQvIKpJoC9ett6aeVrzu+4cU4DZVWYlJUoC/ljWzCluau\n8blfW0Vwin6OB7s0HCG5/wijQWJBU5SrP/KAIPeQi1GqG5efbqAXDr/ple0Ipwyo\nXjjl73LenGUgqpANlC9EAT4i7FkJcllLPeK3NcOHjuUG0AccLv1lGsHAxZLgjk/x\nz9ZcnVV9UFWZiyJTKxeKPwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\nDgQWBBRWyMuZUo4gxCR3Luf9/bd2AqZ7CjAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI\nhvcNAQELBQADggEBAIqN2DlIKlvDFPO0QUZQVFbsi/tLdYM98/vvzBpttlTGVMyD\ngJuQeHVz+MnhGIwoCGOlGU3OOUoIlLAut0+WG74qYczn43oA2gbMd7HoD7oL/IGg\nnjorBwJVcuuLv2G//SqM3nxGcLRtkRnQ+lvqPxMz9+0fKFUn6QcIDuF0QSfthLs2\nWSiGEPKO9c9RSXdRQ4pXA7c3hXng8P4A2ZmdciPne5Nu4I4qLDGZYRrRLRkNTrOi\nTyS6r2HNGUfgF7eOSeKt3NWL+mNChcYj71/Vycf5edeczpUgfnWy9WbPrK1svKyl\naAs2xg+X6O8qB+Mnj2dNBzm+lZIS3sIlm+nO9sg=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrjCCAjSgAwIBAgIRAPAlEk8VJPmEzVRRaWvTh2AwCgYIKoZIzj0EAwMwgZYx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h\nem9uIFJEUyB1cy1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjEwNTI1MjI0MTU1WhgPMjEyMTA1MjUyMzQxNTVaMIGWMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS\nRFMgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx5xjrup8II4HOJw15NTnS3H5yMrQGlbj\nEDA5MMGnE9DmHp5dACIxmPXPMe/99nO7wNdl7G71OYPCgEvWm0FhdvVUeTb3LVnV\nBnaXt32Ek7/oxGk1T+Df03C+W0vmuJ+wo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G\nA1UdDgQWBBTGXmqBWN/1tkSea4pNw0oHrjk2UDAOBgNVHQ8BAf8EBAMCAYYwCgYI\nKoZIzj0EAwMDaAAwZQIxAIqqZWCSrIkZ7zsv/FygtAusW6yvlL935YAWYPVXU30m\njkMFLM+/RJ9GMvnO8jHfCgIwB+whlkcItzE9CRQ6CsMo/d5cEHDUu/QW6jSIh9BR\nOGh9pTYPVkUbBiKPA7lVVhre\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIF/zCCA+egAwIBAgIRAJGY9kZITwfSRaAS/bSBOw8wDQYJKoZIhvcNAQEMBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyBzYS1lYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUxOTE4MTEyMFoYDzIxMjEwNTE5MTkxMTIwWjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIHNhLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDe2vlDp6Eo4WQi\nWi32YJOgdXHhxTFrLjB9SRy22DYoMaWfginJIwJcSR8yse8ZDQuoNhERB9LRggAE\neng23mhrfvtL1yQkMlZfBu4vG1nOb22XiPFzk7X2wqz/WigdYNBCqa1kK3jrLqPx\nYUy7jk2oZle4GLVRTNGuMfcid6S2hs3UCdXfkJuM2z2wc3WUlvHoVNk37v2/jzR/\nhSCHZv5YHAtzL/kLb/e64QkqxKll5QmKhyI6d7vt6Lr1C0zb+DmwxUoJhseAS0hI\ndRk5DklMb4Aqpj6KN0ss0HAYqYERGRIQM7KKA4+hxDMUkJmt8KqWKZkAlCZgflzl\nm8NZ31o2cvBzf6g+VFHx+6iVrSkohVQydkCxx7NJ743iPKsh8BytSM4qU7xx4OnD\nH2yNXcypu+D5bZnVZr4Pywq0w0WqbTM2bpYthG9IC4JeVUvZ2mDc01lqOlbMeyfT\nog5BRPLDXdZK8lapo7se2teh64cIfXtCmM2lDSwm1wnH2iSK+AWZVIM3iE45WSGc\nvZ+drHfVgjJJ5u1YrMCWNL5C2utFbyF9Obw9ZAwm61MSbPQL9JwznhNlCh7F2ANW\nZHWQPNcOAJqzE4uVcJB1ZeVl28ORYY1668lx+s9yYeMXk3QQdj4xmdnvoBFggqRB\nZR6Z0D7ZohADXe024RzEo1TukrQgKQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/\nMB0GA1UdDgQWBBT7Vs4Y5uG/9aXnYGNMEs6ycPUT3jAOBgNVHQ8BAf8EBAMCAYYw\nDQYJKoZIhvcNAQEMBQADggIBACN4Htp2PvGcQA0/sAS+qUVWWJoAXSsu8Pgc6Gar\n7tKVlNJ/4W/a6pUV2Xo/Tz3msg4yiE8sMESp2k+USosD5n9Alai5s5qpWDQjrqrh\n76AGyF2nzve4kIN19GArYhm4Mz/EKEG1QHYvBDGgXi3kNvL/a2Zbybp+3LevG+q7\nxtx4Sz9yIyMzuT/6Y7ijtiMZ9XbuxGf5wab8UtwT3Xq1UradJy0KCkzRJAz/Wy/X\nHbTkEvKSaYKExH6sLo0jqdIjV/d2Io31gt4e0Ly1ER2wPyFa+pc/swu7HCzrN+iz\nA2ZM4+KX9nBvFyfkHLix4rALg+WTYJa/dIsObXkdZ3z8qPf5A9PXlULiaa1mcP4+\nrokw74IyLEYooQ8iSOjxumXhnkTS69MAdGzXYE5gnHokABtGD+BB5qLhtLt4fqAp\n8AyHpQWMyV42M9SJLzQ+iOz7kAgJOBOaVtJI3FV/iAg/eqWVm3yLuUTWDxSHrKuL\nN19+pSjF6TNvUSFXwEa2LJkfDqIOCE32iOuy85QY//3NsgrSQF6UkSPa95eJrSGI\n3hTRYYh3Up2GhBGl1KUy7/o0k3KRZTk4s38fylY8bZ3TakUOH5iIGoHyFVVcp361\nPyy25SzFSmNalWoQd9wZVc/Cps2ldxhcttM+WLkFNzprd0VJa8qTz8vYtHP0ouDN\nnWS0\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICtDCCAjmgAwIBAgIQKKqVZvk6NsLET+uYv5myCzAKBggqhkjOPQQDAzCBmTEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTIwMAYDVQQDDClBbWF6\nb24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwH\nU2VhdHRsZTAgFw0yMjEyMDIyMDMyMjBaGA8yMTIyMTIwMjIxMzIyMFowgZkxCzAJ\nBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMw\nEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1hem9u\nIFJEUyBpbC1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASYwfvj8BmvLAP6UkNQ4X4dXBB/\nwebBO7swW+8HnFN2DAu+Cn/lpcDpu+dys1JmkVX435lrCH3oZjol0kCDIM1lF4Cv\n+78yoY1Jr/YMat22E4iz4AZd9q0NToS7+ZA0r2yjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wHQYDVR0OBBYEFO/8Py16qPr7J2GWpvxlTMB+op7XMA4GA1UdDwEB/wQEAwIB\nhjAKBggqhkjOPQQDAwNpADBmAjEAwk+rg788+u8JL6sdix7l57WTo8E/M+o3TO5x\nuRuPdShrBFm4ArGR2PPs4zCQuKgqAjEAi0TA3PVqAxKpoz+Ps8/054p9WTgDfBFZ\ni/lm2yTaPs0xjY6FNWoy7fsVw5oEKxOn\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGCTCCA/GgAwIBAgIRAOY7gfcBZgR2tqfBzMbFQCUwDQYJKoZIhvcNAQEMBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNCBSb290IENBIFJTQTQwOTYgRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjIwNTI1MTY1NDU5WhgPMjEyMjA1MjUxNzU0NTla\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTQgUm9vdCBDQSBSU0E0MDk2IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\nlfxER43FuLRdL08bddF0YhbCP+XXKj1A/TFMXmd2My8XDei8rPXFYyyjMig9+xZw\nuAsIxLwz8uiA26CKA8bCZKg5VG2kTeOJAfvBJaLv1CZefs3Z4Uf1Sjvm6MF2yqEj\nGoORfyfL9HiZFTDuF/hcjWoKYCfMuG6M/wO8IbdICrX3n+BiYQJu/pFO660Mg3h/\n8YBBWYDbHoCiH/vkqqJugQ5BM3OI5nsElW51P1icEEqti4AZ7JmtSv9t7fIFBVyR\noaEyOgpp0sm193F/cDJQdssvjoOnaubsSYm1ep3awZAUyGN/X8MBrPY95d0hLhfH\nEhc5Icyg+hsosBljlAyksmt4hFQ9iBnWIz/ZTfGMck+6p3HVL9RDgvluez+rWv59\n8q7omUGsiPApy5PDdwI/Wt/KtC34/2sjslIJfvgifdAtkRPkhff1WEwER00ADrN9\neGGInaCpJfb1Rq8cV2n00jxg7DcEd65VR3dmIRb0bL+jWK62ni/WdEyomAOMfmGj\naWf78S/4rasHllWJ+QwnaUYY3u6N8Cgio0/ep4i34FxMXqMV3V0/qXdfhyabi/LM\nwCxNo1Dwt+s6OtPJbwO92JL+829QAxydfmaMTeHBsgMPkG7RwAekeuatKGHNsc2Z\nx2Q4C2wVvOGAhcHwxfM8JfZs3nDSZJndtVVnFlUY0UECAwEAAaNCMEAwDwYDVR0T\nAQH/BAUwAwEB/zAdBgNVHQ4EFgQUpnG7mWazy6k97/tb5iduRB3RXgQwDgYDVR0P\nAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCDLqq1Wwa9Tkuv7vxBnIeVvvFF\necTn+P+wJxl9Qa2ortzqTHZsBDyJO62d04AgBwiDXkJ9a+bthgG0H1J7Xee8xqv1\nxyX2yKj24ygHjspLotKP4eDMdDi5TYq+gdkbPmm9Q69B1+W6e049JVGXvWG8/7kU\nigxeuCYwtCCdUPRLf6D8y+1XMGgVv3/DSOHWvTg3MJ1wJ3n3+eve3rjGdRYWZeJu\nk21HLSZYzVrCtUsh2YAeLnUbSxVuT2Xr4JehYe9zW5HEQ8Je/OUfnCy9vzoN/ITw\nosAH+EBJQey7RxEDqMwCaRefH0yeHFcnOll0OXg/urnQmwbEYzQ1uutJaBPsjU0J\nQf06sMxI7GiB5nPE+CnI2sM6A9AW9kvwexGXpNJiLxF8dvPQthpOKGcYu6BFvRmt\n6ctfXd9b7JJoVqMWuf5cCY6ihpk1e9JTlAqu4Eb/7JNyGiGCR40iSLvV28un9wiE\nplrdYxwcNYq851BEu3r3AyYWw/UW1AKJ5tM+/Gtok+AphMC9ywT66o/Kfu44mOWm\nL3nSLSWEcgfUVgrikpnyGbUnGtgCmHiMlUtNVexcE7OtCIZoVAlCGKNu7tyuJf10\nQlk8oIIzfSIlcbHpOYoN79FkLoDNc2er4Gd+7w1oPQmdAB0jBJnA6t0OUBPKdDdE\nUfff2jrbfbzECn1ELg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGCDCCA/CgAwIBAgIQIuO1A8LOnmc7zZ/vMm3TrDANBgkqhkiG9w0BAQwFADCB\nnDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB\nbWF6b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G\nA1UEBwwHU2VhdHRsZTAgFw0yMTA1MjQyMDQ2MThaGA8yMTIxMDUyNDIxNDYxOFow\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAO\nBgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDq\nqRHKbG8ZK6/GkGm2cenznEF06yHwI1gD5sdsHjTgekDZ2Dl9RwtDmUH2zFuIQwGj\nSeC7E2iKwrJRA5wYzL9/Vk8NOILEKQOP8OIKUHbc7q8rEtjs401KcU6pFBBEdO9G\nCTiRhogq+8mhC13AM/UriZJbKhwgM2UaDOzAneGMhQAGjH8z83NsNcPxpYVE7tqM\nsch5yLtIJLkJRusrmQQTeHUev16YNqyUa+LuFclFL0FzFCimkcxUhXlbfEKXbssS\nyPzjiv8wokGyo7+gA0SueceMO2UjfGfute3HlXZDcNvBbkSY+ver41jPydyRD6Qq\noEkh0tyIbPoa3oU74kwipJtz6KBEA3u3iq61OUR0ENhR2NeP7CSKrC24SnQJZ/92\nqxusrbyV/0w+U4m62ug/o4hWNK1lUcc2AqiBOvCSJ7qpdteTFxcEIzDwYfERDx6a\nd9+3IPvzMb0ZCxBIIUFMxLTF7yAxI9s6KZBBXSZ6tDcCCYIgEysEPRWMRAcG+ye/\nfZVn9Vnzsj4/2wchC2eQrYpb1QvG4eMXA4M5tFHKi+/8cOPiUzJRgwS222J8YuDj\nyEBval874OzXk8H8Mj0JXJ/jH66WuxcBbh5K7Rp5oJn7yju9yqX6qubY8gVeMZ1i\nu4oXCopefDqa35JplQNUXbWwSebi0qJ4EK0V8F9Q+QIDAQABo0IwQDAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBT4ysqCxaPe7y+g1KUIAenqu8PAgzAOBgNVHQ8B\nAf8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBALU8WN35KAjPZEX65tobtCDQFkIO\nuJjv0alD7qLB0i9eY80C+kD87HKqdMDJv50a5fZdqOta8BrHutgFtDm+xo5F/1M3\nu5/Vva5lV4xy5DqPajcF4Mw52czYBmeiLRTnyPJsU93EQIC2Bp4Egvb6LI4cMOgm\n4pY2hL8DojOC5PXt4B1/7c1DNcJX3CMzHDm4SMwiv2MAxSuC/cbHXcWMk+qXdrVx\n+ayLUSh8acaAOy3KLs1MVExJ6j9iFIGsDVsO4vr4ZNsYQiyHjp+L8ops6YVBO5AT\nk/pI+axHIVsO5qiD4cFWvkGqmZ0gsVtgGUchZaacboyFsVmo6QPrl28l6LwxkIEv\nGGJYvIBW8sfqtGRspjfX5TlNy5IgW/VOwGBdHHsvg/xpRo31PR3HOFw7uPBi7cAr\nFiZRLJut7af98EB2UvovZnOh7uIEGPeecQWeOTQfJeWet2FqTzFYd0NUMgqPuJx1\nvLKferP+ajAZLJvVnW1J7Vccx/pm0rMiUJEf0LRb/6XFxx7T2RGjJTi0EzXODTYI\ngnLfBBjnolQqw+emf4pJ4pAtly0Gq1KoxTG2QN+wTd4lsCMjnelklFDjejwnl7Uy\nvtxzRBAu/hi/AqDkDFf94m6j+edIrjbi9/JDFtQ9EDlyeqPgw0qwi2fwtJyMD45V\nfejbXelUSJSzDIdY\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGCTCCA/GgAwIBAgIRAN7Y9G9i4I+ZaslPobE7VL4wDQYJKoZIhvcNAQEMBQAw\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAO\nBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIwMTYzMzIzWhgPMjEyMTA1MjAxNzMzMjNa\nMIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg\nSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM\nLEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAw\nDgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\n4BEPCiIfiK66Q/qa8k+eqf1Q3qsa6Xuu/fPkpuStXVBShhtXd3eqrM0iT4Xxs420\nVa0vSB3oZ7l86P9zYfa60n6PzRxdYFckYX330aI7L/oFIdaodB/C9szvROI0oLG+\n6RwmIF2zcprH0cTby8MiM7G3v9ykpq27g4WhDC1if2j8giOQL3oHpUaByekZNIHF\ndIllsI3RkXmR3xmmxoOxJM1B9MZi7e1CvuVtTGOnSGpNCQiqofehTGwxCN2wFSK8\nxysaWlw48G0VzZs7cbxoXMH9QbMpb4tpk0d+T8JfAPu6uWO9UwCLWWydf0CkmA/+\nD50/xd1t33X9P4FEaPSg5lYbHXzSLWn7oLbrN2UqMLaQrkoEBg/VGvzmfN0mbflw\n+T87bJ/VEOVNlG+gepyCTf89qIQVWOjuYMox4sK0PjzZGsYEuYiq1+OUT3vk/e5K\nag1fCcq2Isy4/iwB2xcXrsQ6ljwdk1fc+EmOnjGKrhuOHJY3S+RFv4ToQBsVyYhC\nXGaC3EkqIX0xaCpDimxYhFjWhpDXAjG/zJ+hRLDAMCMhl/LPGRk/D1kzSbPmdjpl\nlEMK5695PeBvEBTQdBQdOiYgOU3vWU6tzwwHfiM2/wgvess/q0FDAHfJhppbgbb9\n3vgsIUcsvoC5o29JvMsUxsDRvsAfEmMSDGkJoA/X6GECAwEAAaNCMEAwDwYDVR0T\nAQH/BAUwAwEB/zAdBgNVHQ4EFgQUgEWm1mZCbGD6ytbwk2UU1aLaOUUwDgYDVR0P\nAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBb4+ABTGBGwxK1U/q4g8JDqTQM\n1Wh8Oz8yAk4XtPJMAmCctxbd81cRnSnePWw/hxViLVtkZ/GsemvXfqAQyOn1coN7\nQeYSw+ZOlu0j2jEJVynmgsR7nIRqE7QkCyZAU+d2FTJUfmee+IiBiGyFGgxz9n7A\nJhBZ/eahBbiuoOik/APW2JWLh0xp0W0GznfJ8lAlaQTyDa8iDXmVtbJg9P9qzkvl\nFgPXQttzEOyooF8Pb2LCZO4kUz+1sbU7tHdr2YE+SXxt6D3SBv+Yf0FlvyWLiqVk\nGDEOlPPTDSjAWgKnqST8UJ0RDcZK/v1ixs7ayqQJU0GUQm1I7LGTErWXHMnCuHKe\nUKYuiSZwmTcJ06NgdhcCnGZgPq13ryMDqxPeltQc3n5eO7f1cL9ERYLDLOzm6A9P\noQ3MfcVOsbHgGHZWaPSeNrQRN9xefqBXH0ZPasgcH9WJdsLlEjVUXoultaHOKx3b\nUCCb+d3EfqF6pRT488ippOL6bk7zNubwhRa/+y4wjZtwe3kAX78ACJVcjPobH9jZ\nErySads5zdQeaoee5wRKdp3TOfvuCe4bwLRdhOLCHWzEcXzY3g/6+ppLvNom8o+h\nBh5X26G6KSfr9tqhQ3O9IcbARjnuPbvtJnoPY0gz3EHHGPhy0RNW8i2gl3nUp0ah\nPtjwbKW0hYAhIttT0Q==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICtzCCAj2gAwIBAgIQQRBQTs6Y3H1DDbpHGta3lzAKBggqhkjOPQQDAzCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDYxMTAwMTI0M1oYDzIxMjEwNjExMDExMjQzWjCBmzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6\nb24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEs0942Xj4m/gKA+WA6F5h\nAHYuek9eGpzTRoLJddM4rEV1T3eSueytMVKOSlS3Ub9IhyQrH2D8EHsLYk9ktnGR\npATk0kCYTqFbB7onNo070lmMJmGT/Q7NgwC8cySChFxbo0IwQDAPBgNVHRMBAf8E\nBTADAQH/MB0GA1UdDgQWBBQ20iKBKiNkcbIZRu0y1uoF1yJTEzAOBgNVHQ8BAf8E\nBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwYv0wTSrpQTaPaarfLN8Xcqrqu3hzl07n\nFrESIoRw6Cx77ZscFi2/MV6AFyjCV/TlAjEAhpwJ3tpzPXpThRML8DMJYZ3YgMh3\nCMuLqhPpla3cL0PhybrD27hJWl29C4el6aMO\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrDCCAjOgAwIBAgIQGcztRyV40pyMKbNeSN+vXTAKBggqhkjOPQQDAzCBljEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6\nb24gUkRTIHVzLWVhc3QtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTAgFw0yMTA1MjEyMzE1NTZaGA8yMTIxMDUyMjAwMTU1NlowgZYxCzAJBgNV\nBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD\nVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE\nUyB1cy1lYXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw\ndjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQfDcv+GGRESD9wT+I5YIPRsD3L+/jsiIis\nTr7t9RSbFl+gYpO7ZbDXvNbV5UGOC5lMJo/SnqFRTC6vL06NF7qOHfig3XO8QnQz\n6T5uhhrhnX2RSY3/10d2kTyHq3ZZg3+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD\nVR0OBBYEFLDyD3PRyNXpvKHPYYxjHXWOgfPnMA4GA1UdDwEB/wQEAwIBhjAKBggq\nhkjOPQQDAwNnADBkAjB20HQp6YL7CqYD82KaLGzgw305aUKw2aMrdkBR29J183jY\n6Ocj9+Wcif9xnRMS+7oCMAvrt03rbh4SU9BohpRUcQ2Pjkh7RoY0jDR4Xq4qzjNr\n5UFr3BXpFvACxXF51BksGQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrjCCAjWgAwIBAgIQeKbS5zvtqDvRtwr5H48cAjAKBggqhkjOPQQDAzCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjEwNTIwMTcxOTU1WhgPMjEyMTA1MjAxODE5NTVaMIGXMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS\nRFMgbWUtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs\nZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABEKjgUaAPmUlRMEQdBC7BScAGosJ1zRV\nLDd38qTBjzgmwBfQJ5ZfGIvyEK5unB09MB4e/3qqK5I/L6Qn5Px/n5g4dq0c7MQZ\nu7G9GBYm90U3WRJBf7lQrPStXaRnS4A/O6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAd\nBgNVHQ4EFgQUNKcAbGEIn03/vkwd8g6jNyiRdD4wDgYDVR0PAQH/BAQDAgGGMAoG\nCCqGSM49BAMDA2cAMGQCMHIeTrjenCSYuGC6txuBt/0ZwnM/ciO9kHGWVCoK8QLs\njGghb5/YSFGZbmQ6qpGlSAIwVOQgdFfTpEfe5i+Vs9frLJ4QKAfc27cTNYzRIM0I\nE+AJgK4C4+DiyyMzOpiCfmvq\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGCDCCA/CgAwIBAgIQSFkEUzu9FYgC5dW+5lnTgjANBgkqhkiG9w0BAQwFADCB\nnDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB\nbWF6b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G\nA1UEBwwHU2VhdHRsZTAgFw0yMTA2MTEwMDA4MzZaGA8yMTIxMDYxMTAxMDgzNlow\ngZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws\nQW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAO\nBgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDx\nmy5Qmd8zdwaI/KOKV9Xar9oNbhJP5ED0JCiigkuvCkg5qM36klszE8JhsUj40xpp\nvQw9wkYW4y+C8twBpzKGBvakqMnoaVUV7lOCKx0RofrnNwkZCboTBB4X/GCZ3fIl\nYTybS7Ehi1UuiaZspIT5A2jidoA8HiBPk+mTg1UUkoWS9h+MEAPa8L4DY6fGf4pO\nJ1Gk2cdePuNzzIrpm2yPto+I8MRROwZ3ha7ooyymOXKtz2c7jEHHJ314boCXAv9G\ncdo27WiebewZkHHH7Zx9iTIVuuk2abyVSzvLVeGv7Nuy4lmSqa5clWYqWsGXxvZ2\n0fZC5Gd+BDUMW1eSpW7QDTk3top6x/coNoWuLSfXiC5ZrJkIKimSp9iguULgpK7G\nabMMN4PR+O+vhcB8E879hcwmS2yd3IwcPTl3QXxufqeSV58/h2ibkqb/W4Bvggf6\n5JMHQPlPHOqMCVFIHP1IffIo+Of7clb30g9FD2j3F4qgV3OLwEDNg/zuO1DiAvH1\nL+OnmGHkfbtYz+AVApkAZrxMWwoYrwpauyBusvSzwRE24vLTd2i80ZDH422QBLXG\nrN7Zas8rwIiBKacJLYtBYETw8mfsNt8gb72aIQX6cZOsphqp6hUtKaiMTVgGazl7\ntBXqbB+sIv3S9X6bM4cZJKkMJOXbnyCCLZFYv8TurwIDAQABo0IwQDAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBTOVtaS1b/lz6yJDvNk65vEastbQTAOBgNVHQ8B\nAf8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBABEONg+TmMZM/PrYGNAfB4S41zp1\n3CVjslZswh/pC4kgXSf8cPJiUOzMwUevuFQj7tCqxQtJEygJM2IFg4ViInIah2kh\nxlRakEGGw2dEVlxZAmmLWxlL1s1lN1565t5kgVwM0GVfwYM2xEvUaby6KDVJIkD3\naM6sFDBshvVA70qOggM6kU6mwTbivOROzfoIQDnVaT+LQjHqY/T+ok6IN0YXXCWl\nFavai8RDjzLDFwXSRvgIK+1c49vlFFY4W9Efp7Z9tPSZU1TvWUcKdAtV8P2fPHAS\nvAZ+g9JuNfeawhEibjXkwg6Z/yFUueQCQOs9TRXYogzp5CMMkfdNJF8byKYqHscs\nUosIcETnHwqwban99u35sWcoDZPr6aBIrz7LGKTJrL8Nis8qHqnqQBXu/fsQEN8u\nzJ2LBi8sievnzd0qI0kaWmg8GzZmYH1JCt1GXSqOFkI8FMy2bahP7TUQR1LBUKQ3\nhrOSqldkhN+cSAOnvbQcFzLr+iEYEk34+NhcMIFVE+51KJ1n6+zISOinr6mI3ckX\n6p2tmiCD4Shk2Xx/VTY/KGvQWKFcQApWezBSvDNlGe0yV71LtLf3dr1pr4ofo7cE\nrYucCJ40bfxEU/fmzYdBF32xP7AOD9U0FbOR3Mcthc6Z6w20WFC+zru8FGY08gPf\nWT1QcNdw7ntUJP/w\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrzCCAjWgAwIBAgIQARky6+5PNFRkFVOp3Ob1CTAKBggqhkjOPQQDAzCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjIwNTIzMTg0MTI4WhgPMjEyMjA1MjMxOTQxMjdaMIGXMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS\nRFMgZXUtc291dGgtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs\nZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNVGL5oF7cfIBxKyWd2PVK/S5yQfaJY3\nQFHWvEdt6951n9JhiiPrHzfVHsxZp1CBjILRMzjgRbYWmc8qRoLkgGE7htGdwudJ\nFa/WuKzO574Prv4iZXUnVGTboC7JdvKbh6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAd\nBgNVHQ4EFgQUgDeIIEKynwUbNXApdIPnmRWieZwwDgYDVR0PAQH/BAQDAgGGMAoG\nCCqGSM49BAMDA2gAMGUCMEOOJfucrST+FxuqJkMZyCM3gWGZaB+/w6+XUAJC6hFM\nuSTY0F44/bERkA4XhH+YGAIxAIpJQBakCA1/mXjsTnQ+0El9ty+LODp8ibkn031c\n8DKDS7pR9UK7ZYdR6zFg3ZCjQw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrjCCAjOgAwIBAgIQJvkWUcYLbnxtuwnyjMmntDAKBggqhkjOPQQDAzCBljEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6\nb24gUkRTIGV1LXdlc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTAgFw0yMTA1MjUyMjI2MTJaGA8yMTIxMDUyNTIzMjYxMlowgZYxCzAJBgNV\nBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD\nVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE\nUyBldS13ZXN0LTMgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw\ndjAQBgcqhkjOPQIBBgUrgQQAIgNiAARENn8uHCyjn1dFax4OeXxvbV861qsXFD9G\nDshumTmFzWWHN/69WN/AOsxy9XN5S7Cgad4gQgeYYYgZ5taw+tFo/jQvCLY//uR5\nuihcLuLJ78opvRPvD9kbWZ6oXfBtFkWjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD\nVR0OBBYEFKiK3LpoF+gDnqPldGSwChBPCYciMA4GA1UdDwEB/wQEAwIBhjAKBggq\nhkjOPQQDAwNpADBmAjEA+7qfvRlnvF1Aosyp9HzxxCbN7VKu+QXXPhLEBWa5oeWW\nUOcifunf/IVLC4/FGCsLAjEAte1AYp+iJyOHDB8UYkhBE/1sxnFaTiEPbvQBU0wZ\nSuwWVLhu2wWDuSW+K7tTuL8p\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID/zCCAuegAwIBAgIRAKeDpqX5WFCGNo94M4v69sUwDQYJKoZIhvcNAQELBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyBldS13ZXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyNTIyMTgzM1oYDzIwNjEwNTI1MjMxODMzWjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGV1LXdlc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcKOTEMTfzvs4H\nWtJR8gI7GXN6xesulWtZPv21oT+fLGwJ+9Bv8ADCGDDrDxfeH/HxJmzG9hgVAzVn\n4g97Bn7q07tGZM5pVi96/aNp11velZT7spOJKfJDZTlGns6DPdHmx48whpdO+dOb\n6+eR0VwCIv+Vl1fWXgoACXYCoKjhxJs+R+fwY//0JJ1YG8yjZ+ghLCJmvlkOJmE1\nTCPUyIENaEONd6T+FHGLVYRRxC2cPO65Jc4yQjsXvvQypoGgx7FwD5voNJnFMdyY\n754JGPOOe/SZdepN7Tz7UEq8kn7NQSbhmCsgA/Hkjkchz96qN/YJ+H/okiQUTNB0\neG9ogiVFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFjayw9Y\nMjbxfF14XAhMM2VPl0PfMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC\nAQEAAtmx6d9+9CWlMoU0JCirtp4dSS41bBfb9Oor6GQ8WIr2LdfZLL6uES/ubJPE\n1Sh5Vu/Zon5/MbqLMVrfniv3UpQIof37jKXsjZJFE1JVD/qQfRzG8AlBkYgHNEiS\nVtD4lFxERmaCkY1tjKB4Dbd5hfhdrDy29618ZjbSP7NwAfnwb96jobCmMKgxVGiH\nUqsLSiEBZ33b2hI7PJ6iTJnYBWGuiDnsWzKRmheA4nxwbmcQSfjbrNwa93w3caL2\nv/4u54Kcasvcu3yFsUwJygt8z43jsGAemNZsS7GWESxVVlW93MJRn6M+MMakkl9L\ntWaXdHZ+KUV7LhfYLb0ajvb40w==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBDCCAuygAwIBAgIQJ5oxPEjefCsaESSwrxk68DANBgkqhkiG9w0BAQsFADCB\nmjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB\nbWF6b24gUkRTIGV1LWNlbnRyYWwtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNV\nBAcMB1NlYXR0bGUwIBcNMjIwNjA2MjExNzA1WhgPMjA2MjA2MDYyMjE3MDVaMIGa\nMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j\nLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt\nYXpvbiBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALTQt5eX\ng+VP3BjO9VBkWJhE0GfLrU/QIk32I6WvrnejayTrlup9H1z4QWlXF7GNJrqScRMY\nKhJHlcP05aPsx1lYco6pdFOf42ybXyWHHJdShj4A5glU81GTT+VrXGzHSarLmtua\neozkQgPpDsSlPt0RefyTyel7r3Cq+5K/4vyjCTcIqbfgaGwTU36ffjM1LaPCuE4O\nnINMeD6YuImt2hU/mFl20FZ+IZQUIFZZU7pxGLqTRz/PWcH8tDDxnkYg7tNuXOeN\nJbTpXrw7St50/E9ZQ0llGS+MxJD8jGRAa/oL4G/cwnV8P2OEPVVkgN9xDDQeieo0\n3xkzolkDkmeKOnUCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU\nbwu8635iQGQMRanekesORM8Hkm4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB\nCwUAA4IBAQAgN6LE9mUgjsj6xGCX1afYE69fnmCjjb0rC6eEe1mb/QZNcyw4XBIW\n6+zTXo4mjZ4ffoxb//R0/+vdTE7IvaLgfAZgFsLKJCtYDDstXZj8ujQnGR9Pig3R\nW+LpNacvOOSJSawNQq0Xrlcu55AU4buyD5VjcICnfF1dqBMnGTnh27m/scd/ZMx/\nkapHZ/fMoK2mAgSX/NvUKF3UkhT85vSSM2BTtET33DzCPDQTZQYxFBa4rFRmFi4c\nBLlmIReiCGyh3eJhuUUuYAbK6wLaRyPsyEcIOLMQmZe1+gAFm1+1/q5Ke9ugBmjf\nPbTWjsi/lfZ5CdVAhc5lmZj/l5aKqwaS\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrjCCAjSgAwIBAgIRAKKPTYKln9L4NTx9dpZGUjowCgYIKoZIzj0EAwMwgZYx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h\nem9uIFJEUyBldS13ZXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjEwNTIxMjI1NTIxWhgPMjEyMTA1MjEyMzU1MjFaMIGWMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS\nRFMgZXUtd2VzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE/owTReDvaRqdmbtTzXbyRmEpKCETNj6O\nhZMKH0F8oU9Tmn8RU7kQQj6xUKEyjLPrFBN7c+26TvrVO1KmJAvbc8bVliiJZMbc\nC0yV5PtJTalvlMZA1NnciZuhxaxrzlK1o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G\nA1UdDgQWBBT4i5HaoHtrs7Mi8auLhMbKM1XevDAOBgNVHQ8BAf8EBAMCAYYwCgYI\nKoZIzj0EAwMDaAAwZQIxAK9A+8/lFdX4XJKgfP+ZLy5ySXC2E0Spoy12Gv2GdUEZ\np1G7c1KbWVlyb1d6subzkQIwKyH0Naf/3usWfftkmq8SzagicKz5cGcEUaULq4tO\nGzA/AMpr63IDBAqkZbMDTCmH\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrzCCAjWgAwIBAgIQTgIvwTDuNWQo0Oe1sOPQEzAKBggqhkjOPQQDAzCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjEwNTI0MjEwNjM4WhgPMjEyMTA1MjQyMjA2MzhaMIGXMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS\nRFMgZXUtbm9ydGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs\nZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJuzXLU8q6WwSKXBvx8BbdIi3mPhb7Xo\nrNJBfuMW1XRj5BcKH1ZoGaDGw+BIIwyBJg8qNmCK8kqIb4cH8/Hbo3Y+xBJyoXq/\ncuk8aPrxiNoRsKWwiDHCsVxaK9L7GhHHAqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd\nBgNVHQ4EFgQUYgcsdU4fm5xtuqLNppkfTHM2QMYwDgYDVR0PAQH/BAQDAgGGMAoG\nCCqGSM49BAMDA2gAMGUCMQDz/Rm89+QJOWJecYAmYcBWCcETASyoK1kbr4vw7Hsg\n7Ew3LpLeq4IRmTyuiTMl0gMCMAa0QSjfAnxBKGhAnYxcNJSntUyyMpaXzur43ec0\n3D8npJghwC4DuICtKEkQiI5cSg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGATCCA+mgAwIBAgIRAORIGqQXLTcbbYT2upIsSnQwDQYJKoZIhvcNAQEMBQAw\ngZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo\nQW1hem9uIFJEUyBldS1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTAgFw0yMjA1MjMxODM0MjJaGA8yMTIyMDUyMzE5MzQyMlowgZgx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h\nem9uIFJEUyBldS1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH\nU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPKukwsW2s/h\n1k+Hf65pOP0knVBnOnMQyT1mopp2XHGdXznj9xS49S30jYoUnWccyXgD983A1bzu\nw4fuJRHg4MFdz/NWTgXvy+zy0Roe83OPIJjUmXnnzwUHQcBa9vl6XUO65iQ3pbSi\nfQfNDFXD8cvuXbkezeADoy+iFAlzhXTzV9MD44GTuo9Z3qAXNGHQCrgRSCL7uRYt\nt1nfwboCbsVRnElopn2cTigyVXE62HzBUmAw1GTbAZeFAqCn5giBWYAfHwTUldRL\n6eEa6atfsS2oPNus4ZENa1iQxXq7ft+pMdNt0qKXTCZiiCZjmLkY0V9kWwHTRRF8\nr+75oSL//3di43QnuSCgjwMRIeWNtMud5jf3eQzSBci+9njb6DrrSUbx7blP0srg\n94/C/fYOp/0/EHH34w99Th14VVuGWgDgKahT9/COychLOubXUT6vD1As47S9KxTv\nyYleVKwJnF9cVjepODN72fNlEf74BwzgSIhUmhksmZSeJBabrjSUj3pdyo/iRZN/\nCiYz9YPQ29eXHPQjBZVIUqWbOVfdwsx0/Xu5T1e7yyXByQ3/oDulahtcoKPAFQ3J\nee6NJK655MdS7pM9hJnU2Rzu3qZ/GkM6YK7xTlMXVouPUZov/VbiaCKbqYDs8Dg+\nUKdeNXAT6+BMleGQzly1X7vjhgeA8ugVAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wHQYDVR0OBBYEFJdaPwpCf78UolFTEn6GO85/QwUIMA4GA1UdDwEB/wQEAwIB\nhjANBgkqhkiG9w0BAQwFAAOCAgEAWkxHIT3mers5YnZRSVjmpxCLivGj1jMB9VYC\niKqTAeIvD0940L0YaZgivQll5pue8UUcQ6M2uCdVVAsNJdmQ5XHIYiGOknYPtxzO\naO+bnZp7VIZw/vJ49hvH6RreA2bbxYMZO/ossYdcWsWbOKHFrRmAw0AhtK/my51g\nobV7eQg+WmlE5Iqc75ycUsoZdc3NimkjBi7LQoNP1HMvlLHlF71UZhQDdq+/WdV7\n0zmg+epkki1LjgMmuPyb+xWuYkFKT1/faX+Xs62hIm5BY+aI4if4RuQ+J//0pOSs\nUajrjTo+jLGB8A96jAe8HaFQenbwMjlaHRDAF0wvbkYrMr5a6EbneAB37V05QD0Y\nRh4L4RrSs9DX2hbSmS6iLDuPEjanHKzglF5ePEvnItbRvGGkynqDVlwF+Bqfnw8l\n0i8Hr1f1/LP1c075UjkvsHlUnGgPbLqA0rDdcxF8Fdlv1BunUjX0pVlz10Ha5M6P\nAdyWUOneOfaA5G7jjv7i9qg3r99JNs1/Lmyg/tV++gnWTAsSPFSSEte81kmPhlK3\n2UtAO47nOdTtk+q4VIRAwY1MaOR7wTFZPfer1mWs4RhKNu/odp8urEY87iIzbMWT\nQYO/4I6BGj9rEWNGncvR5XTowwIthMCj2KWKM3Z/JxvjVFylSf+s+FFfO1bNIm6h\nu3UBpZI=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICtDCCAjmgAwIBAgIQenQbcP/Zbj9JxvZ+jXbRnTAKBggqhkjOPQQDAzCBmTEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTIwMAYDVQQDDClBbWF6\nb24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwH\nU2VhdHRsZTAgFw0yMTA1MjEyMjMzMjRaGA8yMTIxMDUyMTIzMzMyNFowgZkxCzAJ\nBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMw\nEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1hem9u\nIFJEUyBldS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATlBHiEM9LoEb1Hdnd5j2VpCDOU\n5nGuFoBD8ROUCkFLFh5mHrHfPXwBc63heW9WrP3qnDEm+UZEUvW7ROvtWCTPZdLz\nZ4XaqgAlSqeE2VfUyZOZzBSgUUJk7OlznXfkCMOjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wHQYDVR0OBBYEFDT/ThjQZl42Nv/4Z/7JYaPNMly2MA4GA1UdDwEB/wQEAwIB\nhjAKBggqhkjOPQQDAwNpADBmAjEAnZWmSgpEbmq+oiCa13l5aGmxSlfp9h12Orvw\nDq/W5cENJz891QD0ufOsic5oGq1JAjEAp5kSJj0MxJBTHQze1Aa9gG4sjHBxXn98\n4MP1VGsQuhfndNHQb4V0Au7OWnOeiobq\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID/zCCAuegAwIBAgIRAMgnyikWz46xY6yRgiYwZ3swDQYJKoZIhvcNAQELBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyMDE2NDkxMloYDzIwNjEwNTIwMTc0OTEyWjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGV1LXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCi8JYOc9cYSgZH\ngYPxLk6Xcc7HqzamvsnjYU98Dcb98y6iDqS46Ra2Ne02MITtU5MDL+qjxb8WGDZV\nRUA9ZS69tkTO3gldW8QdiSh3J6hVNJQW81F0M7ZWgV0gB3n76WCmfT4IWos0AXHM\n5v7M/M4tqVmCPViQnZb2kdVlM3/Xc9GInfSMCgNfwHPTXl+PXX+xCdNBePaP/A5C\n5S0oK3HiXaKGQAy3K7VnaQaYdiv32XUatlM4K2WS4AMKt+2cw3hTCjlmqKRHvYFQ\nveWCXAuc+U5PQDJ9SuxB1buFJZhT4VP3JagOuZbh5NWpIbOTxlAJOb5pGEDuJTKi\n1gQQQVEFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNXm+N87\nOFxK9Af/bjSxDCiulGUzMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC\nAQEAkqIbkgZ45spvrgRQ6n9VKzDLvNg+WciLtmVrqyohwwJbj4pYvWwnKQCkVc7c\nhUOSBmlSBa5REAPbH5o8bdt00FPRrD6BdXLXhaECKgjsHe1WW08nsequRKD8xVmc\n8bEX6sw/utBeBV3mB+3Zv7ejYAbDFM4vnRsWtO+XqgReOgrl+cwdA6SNQT9oW3e5\nrSQ+VaXgJtl9NhkiIysq9BeYigxqS/A13pHQp0COMwS8nz+kBPHhJTsajHCDc8F4\nHfLi6cgs9G0gaRhT8FCH66OdGSqn196sE7Y3bPFFFs/3U+vxvmQgoZC6jegQXAg5\nPrxd+VNXtNI/azitTysQPumH7A==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEBTCCAu2gAwIBAgIRAO8bekN7rUReuNPG8pSTKtEwDQYJKoZIhvcNAQELBQAw\ngZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq\nQW1hem9uIFJEUyBldS1jZW50cmFsLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYD\nVQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMjM0N1oYDzIwNjEwNTIxMjMyMzQ3WjCB\nmjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB\nbWF6b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV\nBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTTYds\nTray+Q9VA5j5jTh5TunHKFQzn68ZbOzdqaoi/Rq4ohfC0xdLrxCpfqn2TGDHN6Zi\n2qGK1tWJZEd1H0trhzd9d1CtGK+3cjabUmz/TjSW/qBar7e9MA67/iJ74Gc+Ww43\nA0xPNIWcL4aLrHaLm7sHgAO2UCKsrBUpxErOAACERScVYwPAfu79xeFcX7DmcX+e\nlIqY16pQAvK2RIzrekSYfLFxwFq2hnlgKHaVgZ3keKP+nmXcXmRSHQYUUr72oYNZ\nHcNYl2+gxCc9ccPEHM7xncVEKmb5cWEWvVoaysgQ+osi5f5aQdzgC2X2g2daKbyA\nXL/z5FM9GHpS5BJjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE\nFBDAiJ7Py9/A9etNa/ebOnx5l5MGMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B\nAQsFAAOCAQEALMh/+81fFPdJV/RrJUeoUvFCGMp8iaANu97NpeJyKitNOv7RoeVP\nWjivS0KcCqZaDBs+p6IZ0sLI5ZH098LDzzytcfZg0PsGqUAb8a0MiU/LfgDCI9Ee\njsOiwaFB8k0tfUJK32NPcIoQYApTMT2e26lPzYORSkfuntme2PTHUnuC7ikiQrZk\nP+SZjWgRuMcp09JfRXyAYWIuix4Gy0eZ4rpRuaTK6mjAb1/LYoNK/iZ/gTeIqrNt\nl70OWRsWW8jEmSyNTIubGK/gGGyfuZGSyqoRX6OKHESkP6SSulbIZHyJ5VZkgtXo\n2XvyRyJ7w5pFyoofrL3Wv0UF8yt/GDszmg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIF/zCCA+egAwIBAgIRAMDk/F+rrhdn42SfE+ghPC8wDQYJKoZIhvcNAQEMBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyBldS13ZXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyMTIyNTEyMloYDzIxMjEwNTIxMjM1MTIyWjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGV1LXdlc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2twMALVg9vRVu\nVNqsr6N8thmp3Dy8jEGTsm3GCQ+C5P2YcGlD/T/5icfWW84uF7Sx3ezcGlvsqFMf\nUkj9sQyqtz7qfFFugyy7pa/eH9f48kWFHLbQYm9GEgbYBIrWMp1cy3vyxuMCwQN4\nDCncqU+yNpy0CprQJEha3PzY+3yJOjDQtc3zr99lyECCFJTDUucxHzyQvX89eL74\nuh8la0lKH3v9wPpnEoftbrwmm5jHNFdzj7uXUHUJ41N7af7z7QUfghIRhlBDiKtx\n5lYZemPCXajTc3ryDKUZC/b+B6ViXZmAeMdmQoPE0jwyEp/uaUcdp+FlUQwCfsBk\nayPFEApTWgPiku2isjdeTVmEgL8bJTDUZ6FYFR7ZHcYAsDzcwHgIu3GGEMVRS3Uf\nILmioiyly9vcK4Sa01ondARmsi/I0s7pWpKflaekyv5boJKD/xqwz9lGejmJHelf\n8Od2TyqJScMpB7Q8c2ROxBwqwB72jMCEvYigB+Wnbb8RipliqNflIGx938FRCzKL\nUQUBmNAznR/yRRL0wHf9UAE/8v9a09uZABeiznzOFAl/frHpgdAbC00LkFlnwwgX\ng8YfEFlkp4fLx5B7LtoO6uVNFVimLxtwirpyKoj3G4M/kvSTux8bTw0heBCmWmKR\n57MS6k7ODzbv+Kpeht2hqVZCNFMxoQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/\nMB0GA1UdDgQWBBRuMnDhJjoj7DcKALj+HbxEqj3r6jAOBgNVHQ8BAf8EBAMCAYYw\nDQYJKoZIhvcNAQEMBQADggIBALSnXfx72C3ldhBP5kY4Mo2DDaGQ8FGpTOOiD95d\n0rf7I9LrsBGVqu/Nir+kqqP80PB70+Jy9fHFFigXwcPBX3MpKGxK8Cel7kVf8t1B\n4YD6A6bqlzP+OUL0uGWfZpdpDxwMDI2Flt4NEldHgXWPjvN1VblEKs0+kPnKowyg\njhRMgBbD/y+8yg0fIcjXUDTAw/+INcp21gWaMukKQr/8HswqC1yoqW9in2ijQkpK\n2RB9vcQ0/gXR0oJUbZQx0jn0OH8Agt7yfMAnJAdnHO4M3gjvlJLzIC5/4aGrRXZl\nJoZKfJ2fZRnrFMi0nhAYDeInoS+Rwx+QzaBk6fX5VPyCj8foZ0nmqvuYoydzD8W5\nmMlycgxFqS+DUmO+liWllQC4/MnVBlHGB1Cu3wTj5kgOvNs/k+FW3GXGzD3+rpv0\nQTLuwSbMr+MbEThxrSZRSXTCQzKfehyC+WZejgLb+8ylLJUA10e62o7H9PvCrwj+\nZDVmN7qj6amzvndCP98sZfX7CFZPLfcBd4wVIjHsFjSNEwWHOiFyLPPG7cdolGKA\nlOFvonvo4A1uRc13/zFeP0Xi5n5OZ2go8aOOeGYdI2vB2sgH9R2IASH/jHmr0gvY\n0dfBCcfXNgrS0toq0LX/y+5KkKOxh52vEYsJLdhqrveuZhQnsFEm/mFwjRXkyO7c\n2jpC\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGADCCA+igAwIBAgIQYe0HgSuFFP9ivYM2vONTrTANBgkqhkiG9w0BAQwFADCB\nmDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB\nbWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUxOTE4MzMyMVoYDzIxMjEwNTE5MTkzMzIxWjCBmDEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6\nb24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuO7QPKfPMTo2\nPOQWvzDLwi5f++X98hGjORI1zkN9kotCYH5pAzSBwBPoMNaIfedgmsIxGHj2fq5G\n4oXagNhNuGP79Zl6uKW5H7S74W7aWM8C0s8zuxMOI4GZy5h2IfQk3m/3AzZEX5w8\nUtNPkzo2feDVOkerHT+j+vjXgAxZ4wHnuMDcRT+K4r9EXlAH6X9b/RO0JlfEwmNz\nxlqqGxocq9qRC66N6W0HF2fNEAKP84n8H80xcZBOBthQORRi8HSmKcPdmrvwCuPz\nM+L+j18q6RAVaA0ABbD0jMWcTf0UvjUfBStn5mvu/wGlLjmmRkZsppUTRukfwqXK\nyltUsTq0tOIgCIpne5zA4v+MebbR5JBnsvd4gdh5BI01QH470yB7BkUefZ9bobOm\nOseAAVXcYFJKe4DAA6uLDrqOfFSxV+CzVvEp3IhLRaik4G5MwI/h2c/jEYDqkg2J\nHMflxc2gcSMdk7E5ByLz5f6QrFfSDFk02ZJTs4ssbbUEYohht9znPMQEaWVqATWE\n3n0VspqZyoBNkH/agE5GiGZ/k/QyeqzMNj+c9kr43Upu8DpLrz8v2uAp5xNj3YVg\nihaeD6GW8+PQoEjZ3mrCmH7uGLmHxh7Am59LfEyNrDn+8Rq95WvkmbyHSVxZnBmo\nh/6O3Jk+0/QhIXZ2hryMflPcYWeRGH0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB\n/zAdBgNVHQ4EFgQU2eFK7+R3x/me8roIBNxBrplkM6EwDgYDVR0PAQH/BAQDAgGG\nMA0GCSqGSIb3DQEBDAUAA4ICAQB5gWFe5s7ObQFj1fTO9L6gYgtFhnwdmxU0q8Ke\nHWCrdFmyXdC39qdAFOwM5/7fa9zKmiMrZvy9HNvCXEp4Z7z9mHhBmuqPZQx0qPgU\nuLdP8wGRuWryzp3g2oqkX9t31Z0JnkbIdp7kfRT6ME4I4VQsaY5Y3mh+hIHOUvcy\np+98i3UuEIcwJnVAV9wTTzrWusZl9iaQ1nSYbmkX9bBssJ2GmtW+T+VS/1hJ/Q4f\nAlE3dOQkLFoPPb3YRWBHr2n1LPIqMVwDNAuWavRA2dSfaLl+kzbn/dua7HTQU5D4\nb2Fu2vLhGirwRJe+V7zdef+tI7sngXqjgObyOeG5O2BY3s+um6D4fS0Th3QchMO7\n0+GwcIgSgcjIjlrt6/xJwJLE8cRkUUieYKq1C4McpZWTF30WnzOPUzRzLHkcNzNA\n0A7sKMK6QoYWo5Rmo8zewUxUqzc9oQSrYADP7PEwGncLtFe+dlRFx+PA1a+lcIgo\n1ZGfXigYtQ3VKkcknyYlJ+hN4eCMBHtD81xDy9iP2MLE41JhLnoB2rVEtewO5diF\n7o95Mwl84VMkLhhHPeGKSKzEbBtYYBifHNct+Bst8dru8UumTltgfX6urH3DN+/8\nJF+5h3U8oR2LL5y76cyeb+GWDXXy9zoQe2QvTyTy88LwZq1JzujYi2k8QiLLhFIf\nFEv9Bg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICsDCCAjagAwIBAgIRAMgApnfGYPpK/fD0dbN2U4YwCgYIKoZIzj0EAwMwgZcx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwnQW1h\nem9uIFJEUyBldS1zb3V0aC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMCAXDTIxMDUxOTE4MzgxMVoYDzIxMjEwNTE5MTkzODExWjCBlzELMAkG\nA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzAR\nBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6b24g\nUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0\nbGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQfEWl6d4qSuIoECdZPp+39LaKsfsX7\nTHs3/RrtT0+h/jl3bjZ7Qc68k16x+HGcHbaayHfqD0LPdzH/kKtNSfQKqemdxDQh\nZ4pwkixJu8T1VpXZ5zzCvBXCl75UqgEFS92jQjBAMA8GA1UdEwEB/wQFMAMBAf8w\nHQYDVR0OBBYEFFPrSNtWS5JU+Tvi6ABV231XbjbEMA4GA1UdDwEB/wQEAwIBhjAK\nBggqhkjOPQQDAwNoADBlAjEA+a7hF1IrNkBd2N/l7IQYAQw8chnRZDzh4wiGsZsC\n6A83maaKFWUKIb3qZYXFSi02AjAbp3wxH3myAmF8WekDHhKcC2zDvyOiKLkg9Y6v\nZVmyMR043dscQbcsVoacOYv198c=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICtDCCAjqgAwIBAgIRAPhVkIsQ51JFhD2kjFK5uAkwCgYIKoZIzj0EAwMwgZkx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h\nem9uIFJEUyBldS1jZW50cmFsLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjIwNjA2MjEyOTE3WhgPMjEyMjA2MDYyMjI5MTdaMIGZMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv\nbiBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEA5xnIEBtG5b2nmbj49UEwQza\nyX0844fXjccYzZ8xCDUe9dS2XOUi0aZlGblgSe/3lwjg8fMcKXLObGGQfgIx1+5h\nAIBjORis/dlyN5q/yH4U5sjS8tcR0GDGVHrsRUZCo0IwQDAPBgNVHRMBAf8EBTAD\nAQH/MB0GA1UdDgQWBBRK+lSGutXf4DkTjR3WNfv4+KeNFTAOBgNVHQ8BAf8EBAMC\nAYYwCgYIKoZIzj0EAwMDaAAwZQIxAJ4NxQ1Gerqr70ZrnUqc62Vl8NNqTzInamCG\nKce3FTsMWbS9qkgrjZkO9QqOcGIw/gIwSLrwUT+PKr9+H9eHyGvpq9/3AIYSnFkb\nCf3dyWPiLKoAtLFwjzB/CkJlsAS1c8dS\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIF/jCCA+agAwIBAgIQGZH12Q7x41qIh9vDu9ikTjANBgkqhkiG9w0BAQwFADCB\nlzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB\nbWF6b24gUkRTIGV1LXdlc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjEwNTI1MjIyMjMzWhgPMjEyMTA1MjUyMzIyMzNaMIGXMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv\nbiBSRFMgZXUtd2VzdC0zIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqE47sHXWzdpuqj\nJHb+6jM9tDbQLDFnYjDWpq4VpLPZhb7xPNh9gnYYTPKG4avG421EblAHqzy9D2pN\n1z90yKbIfUb/Sy2MhQbmZomsObhONEra06fJ0Dydyjswf1iYRp2kwpx5AgkVoNo7\n3dlws73zFjD7ImKvUx2C7B75bhnw2pJWkFnGcswl8fZt9B5Yt95sFOKEz2MSJE91\nkZlHtya19OUxZ/cSGci4MlOySzqzbGwUqGxEIDlY8I39VMwXaYQ8uXUN4G780VcL\nu46FeyRGxZGz2n3hMc805WAA1V5uir87vuirTvoSVREET97HVRGVVNJJ/FM6GXr1\nVKtptybbo81nefYJg9KBysxAa2Ao2x2ry/2ZxwhS6VZ6v1+90bpZA1BIYFEDXXn/\ndW07HSCFnYSlgPtSc+Muh15mdr94LspYeDqNIierK9i4tB6ep7llJAnq0BU91fM2\nJPeqyoTtc3m06QhLf68ccSxO4l8Hmq9kLSHO7UXgtdjfRVaffngopTNk8qK7bIb7\nLrgkqhiQw/PRCZjUdyXL153/fUcsj9nFNe25gM4vcFYwH6c5trd2tUl31NTi1MfG\nMgp3d2dqxQBIYANkEjtBDMy3SqQLIo9EymqmVP8xx2A/gCBgaxvMAsI6FSWRoC7+\nhqJ8XH4mFnXSHKtYMe6WPY+/XZgtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w\nHQYDVR0OBBYEFIkXqTnllT/VJnI2NqipA4XV8rh1MA4GA1UdDwEB/wQEAwIBhjAN\nBgkqhkiG9w0BAQwFAAOCAgEAKjSle8eenGeHgT8pltWCw/HzWyQruVKhfYIBfKJd\nMhV4EnH5BK7LxBIvpXGsFUrb0ThzSw0fn0zoA9jBs3i/Sj6KyeZ9qUF6b8ycDXd+\nwHonmJiQ7nk7UuMefaYAfs06vosgl1rI7eBHC0itexIQmKh0aX+821l4GEgEoSMf\nloMFTLXv2w36fPHHCsZ67ODldgcZbKNnpCTX0YrCwEYO3Pz/L398btiRcWGrewrK\njdxAAyietra8DRno1Zl87685tfqc6HsL9v8rVw58clAo9XAQvT+fmSOFw/PogRZ7\nOMHUat3gu/uQ1M5S64nkLLFsKu7jzudBuoNmcJysPlzIbqJ7vYc82OUGe9ucF3wi\n3tbKQ983hdJiTExVRBLX/fYjPsGbG3JtPTv89eg2tjWHlPhCDMMxyRKl6isu2RTq\n6VT489Z2zQrC33MYF8ZqO1NKjtyMAMIZwxVu4cGLkVsqFmEV2ScDHa5RadDyD3Ok\nm+mqybhvEVm5tPgY6p0ILPMN3yvJsMSPSvuBXhO/X5ppNnpw9gnxpwbjQKNhkFaG\nM5pkADZ14uRguOLM4VthSwUSEAr5VQYCFZhEwK+UOyJAGiB/nJz6IxL5XBNUXmRM\nHl8Xvz4riq48LMQbjcVQj0XvH941yPh+P8xOi00SGaQRaWp55Vyr4YKGbV0mEDz1\nr1o=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIF/zCCA+egAwIBAgIRAKwYju1QWxUZpn6D1gOtwgQwDQYJKoZIhvcNAQEMBQAw\ngZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn\nQW1hem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyMDE2NTM1NFoYDzIxMjEwNTIwMTc1MzU0WjCBlzEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6\nb24gUkRTIGV1LXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCKdBP1U4lqWWkc\nCb25/BKRTsvNVnISiKocva8GAzJyKfcGRa85gmgu41U+Hz6+39K+XkRfM0YS4BvQ\nF1XxWT0bNyypuvwCvmYShSTjN1TY0ltncDddahTajE/4MdSOZb/c98u0yt03cH+G\nhVwRyT50h0v/UEol50VfwcVAEZEgcQQYhf1IFUFlIvKpmDOqLuFakOnc7c9akK+i\nivST+JO1tgowbnNkn2iLlSSgUWgb1gjaOsNfysagv1RXdlyPw3EyfwkFifAQvF2P\nQ0ayYZfYS640cccv7efM1MSVyFHR9PrrDsF/zr2S2sGPbeHr7R/HwLl+S5J/l9N9\ny0rk6IHAWV4dEkOvgpnuJKURwA48iu1Hhi9e4moNS6eqoK2KmY3VFpuiyWcA73nH\nGSmyaH+YuMrF7Fnuu7GEHZL/o6+F5cL3mj2SJJhL7sz0ryf5Cs5R4yN9BIEj/f49\nwh84pM6nexoI0Q4wiSFCxWiBpjSmOK6h7z6+2utaB5p20XDZHhxAlmlx4vMuWtjh\nXckgRFxc+ZpVMU3cAHUpVEoO49e/+qKEpPzp8Xg4cToKw2+AfTk3cmyyXQfGwXMQ\nZUHNZ3w9ILMWihGCM2aGUsLcGDRennvNmnmin/SENsOQ8Ku0/a3teEzwV9cmmdYz\n5iYs1YtgPvKFobY6+T2RXXh+A5kprwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/\nMB0GA1UdDgQWBBSyUrsQVnKmA8z6/2Ech0rCvqpNmTAOBgNVHQ8BAf8EBAMCAYYw\nDQYJKoZIhvcNAQEMBQADggIBAFlj3IFmgiFz5lvTzFTRizhVofhTJsGr14Yfkuc7\nUrXPuXOwJomd4uot2d/VIeGJpfnuS84qGdmQyGewGTJ9inatHsGZgHl9NHNWRwKZ\nlTKTbBiq7aqgtUSFa06v202wpzU+1kadxJJePrbABxiXVfOmIW/a1a4hPNcT3syH\nFIEg1+CGsp71UNjBuwg3JTKWna0sLSKcxLOSOvX1fzxK5djzVpEsvQMB4PSAzXca\nvENgg2ErTwgTA+4s6rRtiBF9pAusN1QVuBahYP3ftrY6f3ycS4K65GnqscyfvKt5\nYgjtEKO3ZeeX8NpubMbzC+0Z6tVKfPFk/9TXuJtwvVeqow0YMrLLyRiYvK7EzJ97\nrrkxoKnHYQSZ+rH2tZ5SE392/rfk1PJL0cdHnkpDkUDO+8cKsFjjYKAQSNC52sKX\n74AVh6wMwxYwVZZJf2/2XxkjMWWhKNejsZhUkTISSmiLs+qPe3L67IM7GyKm9/m6\nR3r8x6NGjhTsKH64iYJg7AeKeax4b2e4hBb6GXFftyOs7unpEOIVkJJgM6gh3mwn\nR7v4gwFbLKADKt1vHuerSZMiTuNTGhSfCeDM53XI/mjZl2HeuCKP1mCDLlaO+gZR\nQ/G+E0sBKgEX4xTkAc3kgkuQGfExdGtnN2U2ehF80lBHB8+2y2E+xWWXih/ZyIcW\nwOx+\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGBDCCA+ygAwIBAgIQM4C8g5iFRucSWdC8EdqHeDANBgkqhkiG9w0BAQwFADCB\nmjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB\nbWF6b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV\nBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjIyODI2WhgPMjEyMTA1MjEyMzI4MjZaMIGa\nMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j\nLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt\nYXpvbiBSRFMgZXUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANeTsD/u\n6saPiY4Sg0GlJlMXMBltnrcGAEkwq34OKQ0bCXqcoNJ2rcAMmuFC5x9Ho1Y3YzB7\nNO2GpIh6bZaO76GzSv4cnimcv9n/sQSYXsGbPD+bAtnN/RvNW1avt4C0q0/ghgF1\nVFS8JihIrgPYIArAmDtGNEdl5PUrdi9y6QGggbRfidMDdxlRdZBe1C18ZdgERSEv\nUgSTPRlVczONG5qcQkUGCH83MMqL5MKQiby/Br5ZyPq6rxQMwRnQ7tROuElzyYzL\n7d6kke+PNzG1mYy4cbYdjebwANCtZ2qYRSUHAQsOgybRcSoarv2xqcjO9cEsDiRU\nl97ToadGYa4VVERuTaNZxQwrld4mvzpyKuirqZltOqg0eoy8VUsaRPL3dc5aChR0\ndSrBgRYmSAClcR2/2ZCWpXemikwgt031Dsc0A/+TmVurrsqszwbr0e5xqMow9LzO\nMI/JtLd0VFtoOkL/7GG2tN8a+7gnLFxpv+AQ0DH5n4k/BY/IyS+H1erqSJhOTQ11\nvDOFTM5YplB9hWV9fp5PRs54ILlHTlZLpWGs3I2BrJwzRtg/rOlvsosqcge9ryai\nAKm2j+JBg5wJ19R8oxRy8cfrNTftZePpISaLTyV2B16w/GsSjqixjTQe9LRN2DHk\ncC+HPqYyzW2a3pUVyTGHhW6a7YsPBs9yzt6hAgMBAAGjQjBAMA8GA1UdEwEB/wQF\nMAMBAf8wHQYDVR0OBBYEFIqA8QkOs2cSirOpCuKuOh9VDfJfMA4GA1UdDwEB/wQE\nAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAOUI90mEIsa+vNJku0iUwdBMnHiO4gm7E\n5JloP7JG0xUr7d0hypDorMM3zVDAL+aZRHsq8n934Cywj7qEp1304UF6538ByGdz\ntkfacJsUSYfdlNJE9KbA4T+U+7SNhj9jvePpVjdQbhgzxITE9f8CxY/eM40yluJJ\nPhbaWvOiRagzo74wttlcDerzLT6Y/JrVpWhnB7IY8HvzK+BwAdaCsBUPC3HF+kth\nCIqLq7J3YArTToejWZAp5OOI6DLPM1MEudyoejL02w0jq0CChmZ5i55ElEMnapRX\n7GQTARHmjgAOqa95FjbHEZzRPqZ72AtZAWKFcYFNk+grXSeWiDgPFOsq6mDg8DDB\n0kfbYwKLFFCC9YFmYzR2YrWw2NxAScccUc2chOWAoSNHiqBbHR8ofrlJSWrtmKqd\nYRCXzn8wqXnTS3NNHNccqJ6dN+iMr9NGnytw8zwwSchiev53Fpc1mGrJ7BKTWH0t\nZrA6m32wzpMymtKozlOPYoE5mtZEzrzHEXfa44Rns7XIHxVQSXVWyBHLtIsZOrvW\nU5F41rQaFEpEeUQ7sQvqUoISfTUVRNDn6GK6YaccEhCji14APLFIvhRQUDyYMIiM\n4vll0F/xgVRHTgDVQ8b8sxdhSYlqB4Wc2Ym41YRz+X2yPqk3typEZBpc4P5Tt1/N\n89cEIGdbjsA=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEADCCAuigAwIBAgIQYjbPSg4+RNRD3zNxO1fuKDANBgkqhkiG9w0BAQsFADCB\nmDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB\nbWF6b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUyNDIwNTkyMVoYDzIwNjEwNTI0MjE1OTIxWjCBmDEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6\nb24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA179eQHxcV0YL\nXMkqEmhSBazHhnRVd8yICbMq82PitE3BZcnv1Z5Zs/oOgNmMkOKae4tCXO/41JCX\nwAgbs/eWWi+nnCfpQ/FqbLPg0h3dqzAgeszQyNl9IzTzX4Nd7JFRBVJXPIIKzlRf\n+GmFsAhi3rYgDgO27pz3ciahVSN+CuACIRYnA0K0s9lhYdddmrW/SYeWyoB7jPa2\nLmWpAs7bDOgS4LlP2H3eFepBPgNufRytSQUVA8f58lsE5w25vNiUSnrdlvDrIU5n\nQwzc7NIZCx4qJpRbSKWrUtbyJriWfAkGU7i0IoainHLn0eHp9bWkwb9D+C/tMk1X\nERZw2PDGkwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSFmR7s\ndAblusFN+xhf1ae0KUqhWTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD\nggEBAHsXOpjPMyH9lDhPM61zYdja1ebcMVgfUvsDvt+w0xKMKPhBzYDMs/cFOi1N\nQ8LV79VNNfI2NuvFmGygcvTIR+4h0pqqZ+wjWl3Kk5jVxCrbHg3RBX02QLumKd/i\nkwGcEtTUvTssn3SM8bgM0/1BDXgImZPC567ciLvWDo0s/Fe9dJJC3E0G7d/4s09n\nOMdextcxFuWBZrBm/KK3QF0ByA8MG3//VXaGO9OIeeOJCpWn1G1PjT1UklYhkg61\nEbsTiZVA2DLd1BGzfU4o4M5mo68l0msse/ndR1nEY6IywwpgIFue7+rEleDh6b9d\nPYkG1rHVw2I0XDG4o17aOn5E94I=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEADCCAuigAwIBAgIQC6W4HFghUkkgyQw14a6JljANBgkqhkiG9w0BAQsFADCB\nmDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB\nbWF6b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIyMDUyMzE4MTYzMloYDzIwNjIwNTIzMTkxNjMyWjCBmDEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6\nb24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiM/t4FV2R9Nx\nUQG203UY83jInTa/6TMq0SPyg617FqYZxvz2kkx09x3dmxepUg9ttGMlPgjsRZM5\nLCFEi1FWk+hxHzt7vAdhHES5tdjwds3aIkgNEillmRDVrUsbrDwufLaa+MMDO2E1\nwQ/JYFXw16WBCCi2g1EtyQ2Xp+tZDX5IWOTnvhZpW8vVDptZ2AcJ5rMhfOYO3OsK\n5EF0GGA5ldzuezP+BkrBYGJ4wVKGxeaq9+5AT8iVZrypjwRkD7Y5CurywK3+aBwm\ns9Q5Nd8t45JCOUzYp92rFKsCriD86n/JnEvgDfdP6Hvtm0/DkwXK40Wz2q0Zrd0k\nmjP054NRPwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRR7yqd\nSfKcX2Q8GzhcVucReIpewTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD\nggEBAEszBRDwXcZyNm07VcFwI1Im94oKwKccuKYeJEsizTBsVon8VpEiMwDs+yGu\n3p8kBhvkLwWybkD/vv6McH7T5b9jDX2DoOudqYnnaYeypsPH/00Vh3LvKagqzQza\norWLx+0tLo8xW4BtU+Wrn3JId8LvAhxyYXTn9bm+EwPcStp8xGLwu53OPD1RXYuy\nuu+3ps/2piP7GVfou7H6PRaqbFHNfiGg6Y+WA0HGHiJzn8uLmrRJ5YRdIOOG9/xi\nqTmAZloUNM7VNuurcMM2hWF494tQpsQ6ysg2qPjbBqzlGoOt3GfBTOZmqmwmqtam\nK7juWM/mdMQAJ3SMlE5wI8nVdx4=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIICrjCCAjSgAwIBAgIRAL9SdzVPcpq7GOpvdGoM80IwCgYIKoZIzj0EAwMwgZYx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h\nem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl\nYXR0bGUwIBcNMjEwNTIwMTY1ODA3WhgPMjEyMTA1MjAxNzU4MDdaMIGWMQswCQYD\nVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG\nA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS\nRFMgZXUtd2VzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEJWDgXebvwjR+Ce+hxKOLbnsfN5W5dOlP\nZn8kwWnD+SLkU81Eac/BDJsXGrMk6jFD1vg16PEkoSevsuYWlC8xR6FmT6F6pmeh\nfsMGOyJpfK4fyoEPhKeQoT23lFIc5Orjo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G\nA1UdDgQWBBSVNAN1CHAz0eZ77qz2adeqjm31TzAOBgNVHQ8BAf8EBAMCAYYwCgYI\nKoZIzj0EAwMDaAAwZQIxAMlQeHbcjor49jqmcJ9gRLWdEWpXG8thIf6zfYQ/OEAg\nd7GDh4fR/OUk0VfjsBUN/gIwZB0bGdXvK38s6AAE/9IT051cz/wMe9GIrX1MnL1T\n1F5OqnXJdiwfZRRTHsRQ/L00\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGBDCCA+ygAwIBAgIQalr16vDfX4Rsr+gfQ4iVFDANBgkqhkiG9w0BAQwFADCB\nmjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB\nbWF6b24gUkRTIGV1LWNlbnRyYWwtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV\nBAcMB1NlYXR0bGUwIBcNMjIwNjA2MjEyNTIzWhgPMjEyMjA2MDYyMjI1MjNaMIGa\nMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j\nLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt\nYXpvbiBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANbHbFg7\n2VhZor1YNtez0VlNFaobS3PwOMcEn45BE3y7HONnElIIWXGQa0811M8V2FnyqnE8\nZ5aO1EuvijvWf/3D8DPZkdmAkIfh5hlZYY6Aatr65kEOckwIAm7ZZzrwFogYuaFC\nz/q0CW+8gxNK+98H/zeFx+IxiVoPPPX6UlrLvn+R6XYNERyHMLNgoZbbS5gGHk43\nKhENVv3AWCCcCc85O4rVd+DGb2vMVt6IzXdTQt6Kih28+RGph+WDwYmf+3txTYr8\nxMcCBt1+whyCPlMbC+Yn/ivtCO4LRf0MPZDRQrqTTrFf0h/V0BGEUmMGwuKgmzf5\nKl9ILdWv6S956ioZin2WgAxhcn7+z//sN++zkqLreSf90Vgv+A7xPRqIpTdJ/nWG\nJaAOUofBfsDsk4X4SUFE7xJa1FZAiu2lqB/E+y7jnWOvFRalzxVJ2Y+D/ZfUfrnK\n4pfKtyD1C6ni1celrZrAwLrJ3PoXPSg4aJKh8+CHex477SRsGj8KP19FG8r0P5AG\n8lS1V+enFCNvT5KqEBpDZ/Y5SQAhAYFUX+zH4/n4ql0l/emS+x23kSRrF+yMkB9q\nlhC/fMk6Pi3tICBjrDQ8XAxv56hfud9w6+/ljYB2uQ1iUYtlE3JdIiuE+3ws26O8\ni7PLMD9zQmo+sVi12pLHfBHQ6RRHtdVRXbXRAgMBAAGjQjBAMA8GA1UdEwEB/wQF\nMAMBAf8wHQYDVR0OBBYEFBFot08ipEL9ZUXCG4lagmF53C0/MA4GA1UdDwEB/wQE\nAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAi2mcZi6cpaeqJ10xzMY0F3L2eOKYnlEQ\nh6QyhmNKCUF05q5u+cok5KtznzqMwy7TFOZtbVHl8uUX+xvgq/MQCxqFAnuStBXm\ngr2dg1h509ZwvTdk7TDxGdftvPCfnPNJBFbMSq4CZtNcOFBg9Rj8c3Yj+Qvwd56V\nzWs65BUkDNJrXmxdvhJZjUkMa9vi/oFN+M84xXeZTaC5YDYNZZeW9706QqDbAVES\n5ulvKLavB8waLI/lhRBK5/k0YykCMl0A8Togt8D1QsQ0eWWbIM8/HYJMPVFhJ8Wj\nvT1p/YVeDA3Bo1iKDOttgC5vILf5Rw1ZEeDxjf/r8A7VS13D3OLjBmc31zxRTs3n\nXvHKP9MieQHn9GE44tEYPjK3/yC6BDFzCBlvccYHmqGb+jvDEXEBXKzimdC9mcDl\nf4BBQWGJBH5jkbU9p6iti19L/zHhz7qU6UJWbxY40w92L9jS9Utljh4A0LCTjlnR\nNQUgjnGC6K+jkw8hj0LTC5Ip87oqoT9w7Av5EJ3VJ4hcnmNMXJJ1DkWYdnytcGpO\nDMVITQzzDZRwhbitCVPHagTN2wdi9TEuYE33J0VmFeTc6FSI50wP2aOAZ0Q1/8Aj\nbxeM5jS25eaHc2CQAuhrc/7GLnxOcPwdWQb2XWT8eHudhMnoRikVv/KSK3mf6om4\n1YfpdH2jp30=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID/jCCAuagAwIBAgIQTDc+UgTRtYO7ZGTQ8UWKDDANBgkqhkiG9w0BAQsFADCB\nlzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB\nbWF6b24gUkRTIGV1LXdlc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM\nB1NlYXR0bGUwIBcNMjEwNTIxMjI0NjI0WhgPMjA2MTA1MjEyMzQ2MjRaMIGXMQsw\nCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET\nMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv\nbiBSRFMgZXUtd2VzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh\ndHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM1oGtthQ1YiVIC2\ni4u4swMAGxAjc/BZp0yq0eP5ZQFaxnxs7zFAPabEWsrjeDzrRhdVO0h7zskrertP\ngblGhfD20JfjvCHdP1RUhy/nzG+T+hn6Takan/GIgs8grlBMRHMgBYHW7tklhjaH\n3F7LujhceAHhhgp6IOrpb6YTaTTaJbF3GTmkqxSJ3l1LtEoWz8Al/nL/Ftzxrtez\nVs6ebpvd7sw37sxmXBWX2OlvUrPCTmladw9OrllGXtCFw4YyLe3zozBlZ3cHzQ0q\nlINhpRcajTMfZrsiGCkQtoJT+AqVJPS2sHjqsEH8yiySW9Jbq4zyMbM1yqQ2vnnx\nMJgoYMcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUaQG88UnV\nJPTI+Pcti1P+q3H7pGYwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB\nAQBAkgr75V0sEJimC6QRiTVWEuj2Khy7unjSfudbM6zumhXEU2/sUaVLiYy6cA/x\n3v0laDle6T07x9g64j5YastE/4jbzrGgIINFlY0JnaYmR3KZEjgi1s1fkRRf3llL\nPJm9u4Q1mbwAMQK/ZjLuuRcL3uRIHJek18nRqT5h43GB26qXyvJqeYYpYfIjL9+/\nYiZAbSRRZG+Li23cmPWrbA1CJY121SB+WybCbysbOXzhD3Sl2KSZRwSw4p2HrFtV\n1Prk0dOBtZxCG9luf87ultuDZpfS0w6oNBAMXocgswk24ylcADkkFxBWW+7BETn1\nEpK+t1Lm37mU4sxtuha00XAi\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEADCCAuigAwIBAgIQcY44/8NUvBwr6LlHfRy7KjANBgkqhkiG9w0BAQsFADCB\nmDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu\nYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB\nbWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH\nDAdTZWF0dGxlMCAXDTIxMDUxOTE4MjcxOFoYDzIwNjEwNTE5MTkyNzE4WjCBmDEL\nMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x\nEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6\nb24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT\nZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0UaBeC+Usalu\nEtXnV7+PnH+gi7/71tI/jkKVGKuhD2JDVvqLVoqbMHRh3+wGMvqKCjbHPcC2XMWv\n566fpAj4UZ9CLB5fVzss+QVNTl+FH2XhEzigopp+872ajsNzcZxrMkifxGb4i0U+\nt0Zi+UrbL5tsfP2JonKR1crOrbS6/DlzHBjIiJazGOQcMsJjNuTOItLbMohLpraA\n/nApa3kOvI7Ufool1/34MG0+wL3UUA4YkZ6oBJVxjZvvs6tI7Lzz/SnhK2widGdc\nsnbLqBpHNIZQSorVoiwcFaRBGYX/uzYkiw44Yfa4cK2V/B5zgu1Fbr0gbI2am4eh\nyVYyg4jPawIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS9gM1m\nIIjyh9O5H/7Vj0R/akI7UzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD\nggEBAF0Sm9HC2AUyedBVnwgkVXMibnYChOzz7T+0Y+fOLXYAEXex2s8oqGeZdGYX\nJHkjBn7JXu7LM+TpTbPbFFDoc1sgMguD/ls+8XsqAl1CssW+amryIL+jfcfbgQ+P\nICwEUD9hGdjBgJ5WcuS+qqxHsEIlFNci3HxcxfBa9VsWs5TjI7Vsl4meL5lf7ZyL\nwDV7dHRuU+cImqG1MIvPRIlvPnT7EghrCYi2VCPhP2pM/UvShuwVnkz4MJ29ebIk\nWR9kpblFxFdE92D5UUvMCjC2kmtgzNiErvTcwIvOO9YCbBHzRB1fFiWrXUHhJWq9\nIkaxR5icb/IpAV0A1lYZEWMVsfQ=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIGATCCA+mgAwIBAgIRAMa0TPL+QgbWfUPpYXQkf8wwDQYJKoZIhvcNAQEMBQAw\ngZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ\nbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo\nQW1hem9uIFJEUyBldS1ub3J0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE\nBwwHU2VhdHRsZTAgFw0yMTA1MjQyMTAzMjBaGA8yMTIxMDUyNDIyMDMyMFowgZgx\nCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu\nMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h\nem9uIFJEUyBldS1ub3J0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH\nU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANhS9LJVJyWp\n6Rudy9t47y6kzvgnFYDrvJVtgEK0vFn5ifdlHE7xqMz4LZqWBFTnS+3oidwVRqo7\ntqsuuElsouStO8m315/YUzKZEPmkw8h5ufWt/lg3NTCoUZNkB4p4skr7TspyMUwE\nVdlKQuWTCOLtofwmWT+BnFF3To6xTh3XPlT3ssancw27Gob8kJegD7E0TSMVsecP\nB8je65+3b8CGwcD3QB3kCTGLy87tXuS2+07pncHvjMRMBdDQQQqhXWsRSeUNg0IP\nxdHTWcuwMldYPWK5zus9M4dCNBDlmZjKdcZZVUOKeBBAm7Uo7CbJCk8r/Fvfr6mw\nnXXDtuWhqn/WhJiI/y0QU27M+Hy5CQMxBwFsfAjJkByBpdXmyYxUgTmMpLf43p7H\noWfH1xN0cT0OQEVmAQjMakauow4AQLNkilV+X6uAAu3STQVFRSrpvMen9Xx3EPC3\nG9flHueTa71bU65Xe8ZmEmFhGeFYHY0GrNPAFhq9RThPRY0IPyCZe0Th8uGejkek\njQjm0FHPOqs5jc8CD8eJs4jSEFt9lasFLVDcAhx0FkacLKQjGHvKAnnbRwhN/dF3\nxt4oL8Z4JGPCLau056gKnYaEyviN7PgO+IFIVOVIdKEBu2ASGE8/+QJB5bcHefNj\n04hEkDW0UYJbSfPpVbGAR0gFI/QpycKnAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wHQYDVR0OBBYEFFMXvvjoaGGUcul8GA3FT05DLbZcMA4GA1UdDwEB/wQEAwIB\nhjANBgkqhkiG9w0BAQwFAAOCAgEAQLwFhd2JKn4K/6salLyIA4mP58qbA/9BTB/r\nD9l0bEwDlVPSdY7R3gZCe6v7SWLfA9RjE5tdWDrQMi5IU6W2OVrVsZS/yGJfwnwe\na/9iUAYprA5QYKDg37h12XhVsDKlYCekHdC+qa5WwB1SL3YUprDLPWeaIQdg+Uh2\n+LxvpZGoxoEbca0fc7flwq9ke/3sXt/3V4wJDyY6AL2YNdjFzC+FtYjHHx8rYxHs\naesP7yunuN17KcfOZBBnSFRrx96k+Xm95VReTEEpwiBqAECqEpMbd+R0mFAayMb1\ncE77GaK5yeC2f67NLYGpkpIoPbO9p9rzoXLE5GpSizMjimnz6QCbXPFAFBDfSzim\nu6azp40kEUO6kWd7rBhqRwLc43D3TtNWQYxMve5mTRG4Od+eMKwYZmQz89BQCeqm\naZiJP9y9uwJw4p/A5V3lYHTDQqzmbOyhGUk6OdpdE8HXs/1ep1xTT20QDYOx3Ekt\nr4mmNYfH/8v9nHNRlYJOqFhmoh1i85IUl5IHhg6OT5ZTTwsGTSxvgQQXrmmHVrgZ\nrZIqyBKllCgVeB9sMEsntn4bGLig7CS/N1y2mYdW/745yCLZv2gj0NXhPqgEIdVV\nf9DhFD4ohE1C63XP0kOQee+LYg/MY5vH8swpCSWxQgX5icv5jVDz8YTdCKgUc5u8\nrM2p0kk=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "redash/query_runner/files/redshift-ca-bundle.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDeDCCAuGgAwIBAgIJALPHPDcjk979MA0GCSqGSIb3DQEBBQUAMIGFMQswCQYD\nVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTET\nMBEGA1UEChMKQW1hem9uLmNvbTELMAkGA1UECxMCQ00xLTArBgkqhkiG9w0BCQEW\nHmNvb2tpZS1tb25zdGVyLWNvcmVAYW1hem9uLmNvbTAeFw0xMjExMDIyMzI0NDda\nFw0xODExMDEyMzI0NDdaMIGFMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu\nZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTETMBEGA1UEChMKQW1hem9uLmNvbTELMAkG\nA1UECxMCQ00xLTArBgkqhkiG9w0BCQEWHmNvb2tpZS1tb25zdGVyLWNvcmVAYW1h\nem9uLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAw949t4UZ+9n1K8vj\nPVkyehoV2kWepDmJ8YKl358nkmNwrSAGkslVttdpZS+FrgIcb44UbfVbB4bOSq0J\nqd39GYVRzSazCwr2tpibFvH87PyAX4VVUBDlCizJToEYsXkAKecs+IRqCDWG2ht/\npibO2+T5Wp8jaxUBvDmoHY3BSgkCAwEAAaOB7TCB6jAdBgNVHQ4EFgQUE5KUaWSM\nUml+6MZQia7DjmfjvLgwgboGA1UdIwSBsjCBr4AUE5KUaWSMUml+6MZQia7Djmfj\nvLihgYukgYgwgYUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw\nDgYDVQQHEwdTZWF0dGxlMRMwEQYDVQQKEwpBbWF6b24uY29tMQswCQYDVQQLEwJD\nTTEtMCsGCSqGSIb3DQEJARYeY29va2llLW1vbnN0ZXItY29yZUBhbWF6b24uY29t\nggkAs8c8NyOT3v0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQC9l5+L\n7PaPiF9tsZ20CkyBNEdcM3dWrGT2KR0UBQLWYgPDoBKKkqV56c361kWInOtZ2ucf\nJHjJpT1Np8j673LRbTrZiFiITMg7CcScq5u2ntMa3BNVCeVYlqVLH3RZ7RiQIBXR\nM5hUZ03/aJqN3fQKamd3MfGHft42AXFOwvh9xg==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl\nMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp\nU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw\nNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE\nChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp\nZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3\nDQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf\n8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN\n+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0\nX9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa\nK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA\n1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G\nA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR\nzt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0\nYXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD\nbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w\nDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3\nL7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D\neruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl\nxy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp\nVSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY\nWQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx\nEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT\nHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs\nZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5\nMDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD\nVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy\nZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy\ndmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p\nOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2\n8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K\nTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe\nhRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk\n6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw\nDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q\nAdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI\nbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB\nve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z\nqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd\niEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn\n0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN\nsSi6\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\nADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\nb24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\nMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\nb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\nca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\nIFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\nVOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\njgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\nAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\nA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\nU5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\nN+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\no/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\nrqXRfboQnoZsG4q5WTP468SQvvG5\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF\nADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\nb24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL\nMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\nb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK\ngXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ\nW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg\n1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K\n8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r\n2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me\nz/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR\n8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj\nmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz\n7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6\n+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI\n0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\nAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm\nUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2\nLIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY\n+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS\nk5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl\n7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm\nbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl\nurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+\nfUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63\nn749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE\n76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H\n9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT\n4PsJYGw=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5\nMQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\nUm9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\nA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\nQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl\nui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j\nQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr\nttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr\nBqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM\nYyRIHN8wfdVoOw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5\nMQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\nUm9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\nA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\nQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi\n9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk\nM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB\n/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB\nMAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw\nCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW\n1KyLa2tJElMzrdfkviT8tQp21KW8EA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "redash/query_runner/google_analytics.py",
    "content": "import logging\nfrom base64 import b64decode\nfrom datetime import datetime\nfrom urllib.parse import parse_qs, urlparse\n\nfrom redash.query_runner import (\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    register,\n)\nfrom redash.utils import json_loads\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import google.auth\n    from apiclient.discovery import build\n    from apiclient.errors import HttpError\n    from google.oauth2.service_account import Credentials\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\ntypes_conv = dict(\n    STRING=TYPE_STRING,\n    INTEGER=TYPE_INTEGER,\n    FLOAT=TYPE_FLOAT,\n    DATE=TYPE_DATE,\n    DATETIME=TYPE_DATETIME,\n)\n\n\ndef parse_ga_response(response):\n    columns = []\n    for h in response[\"columnHeaders\"]:\n        if h[\"name\"] in (\"ga:date\", \"mcf:conversionDate\"):\n            h[\"dataType\"] = \"DATE\"\n        elif h[\"name\"] == \"ga:dateHour\":\n            h[\"dataType\"] = \"DATETIME\"\n        columns.append(\n            {\n                \"name\": h[\"name\"],\n                \"friendly_name\": h[\"name\"].split(\":\", 1)[1],\n                \"type\": types_conv.get(h[\"dataType\"], \"string\"),\n            }\n        )\n\n    rows = []\n    for r in response.get(\"rows\", []):\n        d = {}\n        for c, value in enumerate(r):\n            column_name = response[\"columnHeaders\"][c][\"name\"]\n            column_type = [col for col in columns if col[\"name\"] == column_name][0][\"type\"]\n\n            # mcf results come a bit different than ga results:\n            if isinstance(value, dict):\n                if \"primitiveValue\" in value:\n                    value = value[\"primitiveValue\"]\n                elif \"conversionPathValue\" in value:\n                    steps = []\n                    for step in value[\"conversionPathValue\"]:\n                        steps.append(\"{}:{}\".format(step[\"interactionType\"], step[\"nodeValue\"]))\n                    value = \", \".join(steps)\n                else:\n                    raise Exception(\"Results format not supported\")\n\n            if column_type == TYPE_DATE:\n                value = datetime.strptime(value, \"%Y%m%d\")\n            elif column_type == TYPE_DATETIME:\n                if len(value) == 10:\n                    value = datetime.strptime(value, \"%Y%m%d%H\")\n                elif len(value) == 12:\n                    value = datetime.strptime(value, \"%Y%m%d%H%M\")\n                else:\n                    raise Exception(\"Unknown date/time format in results: '{}'\".format(value))\n\n            d[column_name] = value\n        rows.append(d)\n\n    return {\"columns\": columns, \"rows\": rows}\n\n\nclass GoogleAnalytics(BaseSQLQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def type(cls):\n        return \"google_analytics\"\n\n    @classmethod\n    def name(cls):\n        return \"Google Analytics\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\"jsonKeyFile\": {\"type\": \"string\", \"title\": \"JSON Key File (ADC is used if omitted)\"}},\n            \"required\": [],\n            \"secret\": [\"jsonKeyFile\"],\n        }\n\n    def __init__(self, configuration):\n        super(GoogleAnalytics, self).__init__(configuration)\n        self.syntax = \"json\"\n\n    def _get_analytics_service(self):\n        scopes = [\"https://www.googleapis.com/auth/analytics.readonly\"]\n\n        try:\n            key = json_loads(b64decode(self.configuration[\"jsonKeyFile\"]))\n            creds = Credentials.from_service_account_info(key, scopes=scopes)\n        except KeyError:\n            creds = google.auth.default(scopes=scopes)[0]\n\n        return build(\"analytics\", \"v3\", credentials=creds)\n\n    def _get_tables(self, schema):\n        accounts = self._get_analytics_service().management().accounts().list().execute().get(\"items\")\n        if accounts is None:\n            raise Exception(\"Failed getting accounts.\")\n        else:\n            for account in accounts:\n                schema[account[\"name\"]] = {\"name\": account[\"name\"], \"columns\": []}\n                properties = (\n                    self._get_analytics_service()\n                    .management()\n                    .webproperties()\n                    .list(accountId=account[\"id\"])\n                    .execute()\n                    .get(\"items\", [])\n                )\n                for property_ in properties:\n                    if \"defaultProfileId\" in property_ and \"name\" in property_:\n                        schema[account[\"name\"]][\"columns\"].append(\n                            \"{0} (ga:{1})\".format(property_[\"name\"], property_[\"defaultProfileId\"])\n                        )\n\n        return list(schema.values())\n\n    def test_connection(self):\n        try:\n            service = self._get_analytics_service()\n            service.management().accounts().list().execute()\n        except HttpError as e:\n            # Make sure we return a more readable error to the end user\n            raise Exception(e._get_reason())\n\n    def run_query(self, query, user):\n        logger.debug(\"Analytics is about to execute query: %s\", query)\n        try:\n            params = json_loads(query)\n        except Exception:\n            query_string = parse_qs(urlparse(query).query, keep_blank_values=True)\n            params = {k.replace(\"-\", \"_\"): \",\".join(v) for k, v in query_string.items()}\n\n        if \"mcf:\" in params[\"metrics\"] and \"ga:\" in params[\"metrics\"]:\n            raise Exception(\"Can't mix mcf: and ga: metrics.\")\n\n        if \"mcf:\" in params.get(\"dimensions\", \"\") and \"ga:\" in params.get(\"dimensions\", \"\"):\n            raise Exception(\"Can't mix mcf: and ga: dimensions.\")\n\n        if \"mcf:\" in params[\"metrics\"]:\n            api = self._get_analytics_service().data().mcf()\n        else:\n            api = self._get_analytics_service().data().ga()\n\n        if len(params) > 0:\n            try:\n                response = api.get(**params).execute()\n                data = parse_ga_response(response)\n                error = None\n            except HttpError as e:\n                # Make sure we return a more readable error to the end user\n                error = e._get_reason()\n                data = None\n        else:\n            error = \"Wrong query format.\"\n            data = None\n        return data, error\n\n\nregister(GoogleAnalytics)\n"
  },
  {
    "path": "redash/query_runner/google_analytics4.py",
    "content": "import datetime\nimport logging\nfrom base64 import b64decode\n\nimport requests\n\nfrom redash.query_runner import (\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\nfrom redash.utils import json_loads\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import google.auth\n    import google.auth.transport.requests\n    from google.oauth2.service_account import Credentials\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\ntypes_conv = dict(\n    STRING=TYPE_STRING,\n    INTEGER=TYPE_INTEGER,\n    FLOAT=TYPE_FLOAT,\n    DATE=TYPE_DATE,\n    DATETIME=TYPE_DATETIME,\n)\n\nga_report_endpoint = \"https://analyticsdata.googleapis.com/v1beta/properties/{propertyId}:runReport\"\nga_metadata_endpoint = \"https://analyticsdata.googleapis.com/v1beta/properties/{propertyId}/metadata\"\n\n\ndef format_column_value(column_name, value, columns):\n    column_type = [col for col in columns if col[\"name\"] == column_name][0][\"type\"]\n\n    if column_type == TYPE_DATE:\n        value = datetime.datetime.strptime(value, \"%Y%m%d\")\n    elif column_type == TYPE_DATETIME:\n        if len(value) == 10:\n            value = datetime.datetime.strptime(value, \"%Y%m%d%H\")\n        elif len(value) == 12:\n            value = datetime.datetime.strptime(value, \"%Y%m%d%H%M\")\n        else:\n            raise Exception(\"Unknown date/time format in results: '{}'\".format(value))\n\n    return value\n\n\ndef get_formatted_column_json(column_name):\n    data_type = None\n\n    if column_name == \"date\":\n        data_type = \"DATE\"\n    elif column_name == \"dateHour\":\n        data_type = \"DATETIME\"\n\n    result = {\n        \"name\": column_name,\n        \"friendly_name\": column_name,\n        \"type\": types_conv.get(data_type, \"string\"),\n    }\n\n    return result\n\n\ndef parse_ga_response(response):\n    columns = []\n\n    for dim_header in response[\"dimensionHeaders\"]:\n        columns.append(get_formatted_column_json(dim_header[\"name\"]))\n\n    for met_header in response[\"metricHeaders\"]:\n        columns.append(get_formatted_column_json(met_header[\"name\"]))\n\n    rows = []\n    for r in response[\"rows\"]:\n        counter = 0\n        d = {}\n        for item in r[\"dimensionValues\"]:\n            column_name = columns[counter][\"name\"]\n            value = item[\"value\"]\n\n            d[column_name] = format_column_value(column_name, value, columns)\n            counter = counter + 1\n\n        for item in r[\"metricValues\"]:\n            column_name = columns[counter][\"name\"]\n            value = item[\"value\"]\n\n            d[column_name] = format_column_value(column_name, value, columns)\n            counter = counter + 1\n\n        rows.append(d)\n\n    return {\"columns\": columns, \"rows\": rows}\n\n\nclass GoogleAnalytics4(BaseQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def type(cls):\n        return \"google_analytics4\"\n\n    @classmethod\n    def name(cls):\n        return \"Google Analytics 4\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"propertyId\": {\"type\": \"number\", \"title\": \"Property Id\"},\n                \"jsonKeyFile\": {\"type\": \"string\", \"title\": \"JSON Key File (ADC is used if omitted)\"},\n            },\n            \"required\": [\"propertyId\"],\n            \"secret\": [\"jsonKeyFile\"],\n        }\n\n    def _get_access_token(self):\n        scopes = [\"https://www.googleapis.com/auth/analytics.readonly\"]\n\n        try:\n            key = json_loads(b64decode(self.configuration[\"jsonKeyFile\"]))\n            creds = Credentials.from_service_account_info(key, scopes=scopes)\n        except KeyError:\n            creds = google.auth.default(scopes=scopes)[0]\n\n        creds.refresh(google.auth.transport.requests.Request())\n\n        return creds.token\n\n    def run_query(self, query, user):\n        access_token = self._get_access_token()\n        params = json_loads(query)\n\n        property_id = self.configuration[\"propertyId\"]\n\n        headers = {\"Content-Type\": \"application/json\", \"Authorization\": f\"Bearer {access_token}\"}\n\n        url = ga_report_endpoint.replace(\"{propertyId}\", str(property_id))\n        r = requests.post(url, json=params, headers=headers)\n        r.raise_for_status()\n\n        raw_result = r.json()\n\n        data = parse_ga_response(raw_result)\n\n        error = None\n\n        return data, error\n\n    def test_connection(self):\n        try:\n            access_token = self._get_access_token()\n            property_id = self.configuration[\"propertyId\"]\n\n            url = ga_metadata_endpoint.replace(\"{propertyId}\", str(property_id))\n\n            headers = {\"Content-Type\": \"application/json\", \"Authorization\": f\"Bearer {access_token}\"}\n\n            r = requests.get(url, headers=headers)\n            r.raise_for_status()\n        except Exception as e:\n            raise Exception(e)\n\n\nregister(GoogleAnalytics4)\n"
  },
  {
    "path": "redash/query_runner/google_search_console.py",
    "content": "import logging\nfrom base64 import b64decode\nfrom datetime import datetime\n\nfrom redash.query_runner import (\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    register,\n)\nfrom redash.utils import json_loads\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import google.auth\n    from apiclient.discovery import build\n    from apiclient.errors import HttpError\n    from google.oauth2.service_account import Credentials\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\ntypes_conv = dict(\n    STRING=TYPE_STRING,\n    INTEGER=TYPE_INTEGER,\n    FLOAT=TYPE_FLOAT,\n    DATE=TYPE_DATE,\n    DATETIME=TYPE_DATETIME,\n)\n\n\ndef parse_ga_response(response, dimensions):\n    columns = []\n\n    for item in dimensions:\n        if item == \"date\":\n            data_type = \"date\"\n        else:\n            data_type = \"string\"\n        columns.append(\n            {\n                \"name\": item,\n                \"friendly_name\": item,\n                \"type\": data_type,\n            }\n        )\n\n    default_items = [\"clicks\", \"impressions\", \"ctr\", \"position\"]\n    for item in default_items:\n        columns.append({\"name\": item, \"friendly_name\": item, \"type\": \"number\"})\n\n    rows = []\n    for r in response.get(\"rows\", []):\n        d = {}\n        for k, value in r.items():\n            if k == \"keys\":\n                for index, val in enumerate(value):\n                    column_name = columns[index][\"name\"]\n                    column_type = columns[index][\"type\"]\n                    val = get_formatted_value(column_type, val)\n                    d[column_name] = val\n            else:\n                column_name = k\n                column_type = [col for col in columns if col[\"name\"] == column_name][0][\"type\"]\n                value = get_formatted_value(column_type, value)\n                d[column_name] = value\n        rows.append(d)\n\n    return {\"columns\": columns, \"rows\": rows}\n\n\ndef get_formatted_value(column_type, value):\n    if column_type == \"number\":\n        value = round(value, 2)\n    elif column_type == TYPE_DATE:\n        value = datetime.strptime(value, \"%Y-%m-%d\")\n    elif column_type == TYPE_DATETIME:\n        if len(value) == 10:\n            value = datetime.strptime(value, \"%Y%m%d%H\")\n        elif len(value) == 12:\n            value = datetime.strptime(value, \"%Y%m%d%H%M\")\n        else:\n            raise Exception(\"Unknown date/time format in results: '{}'\".format(value))\n    return value\n\n\nclass GoogleSearchConsole(BaseSQLQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def type(cls):\n        return \"google_search_console\"\n\n    @classmethod\n    def name(cls):\n        return \"Google Search Console\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"siteURL\": {\"type\": \"string\", \"title\": \"Site URL\"},\n                \"jsonKeyFile\": {\"type\": \"string\", \"title\": \"JSON Key File (ADC is used if omitted)\"},\n            },\n            \"required\": [],\n            \"secret\": [\"jsonKeyFile\"],\n        }\n\n    def __init__(self, configuration):\n        super(GoogleSearchConsole, self).__init__(configuration)\n        self.syntax = \"json\"\n\n    def _get_search_service(self):\n        scopes = [\"https://www.googleapis.com/auth/webmasters.readonly\"]\n\n        try:\n            key = json_loads(b64decode(self.configuration[\"jsonKeyFile\"]))\n            creds = Credentials.from_service_account_info(key, scopes=scopes)\n        except KeyError:\n            creds = google.auth.default(scopes=scopes)[0]\n\n        return build(\"searchconsole\", \"v1\", credentials=creds)\n\n    def test_connection(self):\n        try:\n            service = self._get_search_service()\n            service.sites().list().execute()\n        except HttpError as e:\n            # Make sure we return a more readable error to the end user\n            raise Exception(e._get_reason())\n\n    def run_query(self, query, user):\n        logger.debug(\"Search Analytics is about to execute query: %s\", query)\n        params = json_loads(query)\n        site_url = self.configuration[\"siteURL\"]\n        api = self._get_search_service()\n\n        if len(params) > 0:\n            try:\n                response = api.searchanalytics().query(siteUrl=site_url, body=params).execute()\n                data = parse_ga_response(response, params[\"dimensions\"])\n                error = None\n            except HttpError as e:\n                # Make sure we return a more readable error to the end user\n                error = e._get_reason()\n                data = None\n        else:\n            error = \"Wrong query format.\"\n            data = None\n        return data, error\n\n\nregister(GoogleSearchConsole)\n"
  },
  {
    "path": "redash/query_runner/google_spanner.py",
    "content": ""
  },
  {
    "path": "redash/query_runner/google_spreadsheets.py",
    "content": "import logging\nimport re\nfrom base64 import b64decode\n\nfrom dateutil import parser\nfrom requests import Session\nfrom xlsxwriter.utility import xl_col_to_name\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    guess_type,\n    register,\n)\nfrom redash.utils import json_loads\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import google.auth\n    import gspread\n    from google.auth.exceptions import GoogleAuthError\n    from google.oauth2.service_account import Credentials\n    from gspread.exceptions import APIError\n    from gspread.exceptions import WorksheetNotFound as GSWorksheetNotFound\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\ndef _load_key(filename):\n    with open(filename, \"rb\") as f:\n        return json_loads(f.read())\n\n\ndef _get_columns_and_column_names(row):\n    column_names = []\n    columns = []\n    duplicate_counter = 1\n\n    for i, column_name in enumerate(row):\n        if not column_name:\n            column_name = \"column_{}\".format(xl_col_to_name(i))\n\n        if column_name in column_names:\n            column_name = \"{}{}\".format(column_name, duplicate_counter)\n            duplicate_counter += 1\n\n        column_names.append(column_name)\n        columns.append({\"name\": column_name, \"friendly_name\": column_name, \"type\": TYPE_STRING})\n\n    return columns, column_names\n\n\ndef _value_eval_list(row_values, col_types):\n    value_list = []\n    raw_values = zip(col_types, row_values)\n    for typ, rval in raw_values:\n        try:\n            if rval is None or rval == \"\":\n                val = None\n            elif typ == TYPE_BOOLEAN:\n                val = True if str(rval).lower() == \"true\" else False\n            elif typ == TYPE_DATETIME:\n                val = parser.parse(rval)\n            elif typ == TYPE_FLOAT:\n                val = float(rval)\n            elif typ == TYPE_INTEGER:\n                val = int(rval)\n            else:\n                # for TYPE_STRING and default\n                val = str(rval)\n            value_list.append(val)\n        except (ValueError, OverflowError):\n            value_list.append(rval)\n    return value_list\n\n\nHEADER_INDEX = 0\n\n\nclass WorksheetNotFoundError(Exception):\n    def __init__(self, worksheet_num, worksheet_count):\n        message = \"Worksheet number {} not found. Spreadsheet has {} worksheets. Note that the worksheet count is zero based.\".format(\n            worksheet_num, worksheet_count\n        )\n        super(WorksheetNotFoundError, self).__init__(message)\n\n\nclass WorksheetNotFoundByTitleError(Exception):\n    def __init__(self, worksheet_title):\n        message = \"Worksheet title '{}' not found.\".format(worksheet_title)\n        super(WorksheetNotFoundByTitleError, self).__init__(message)\n\n\ndef parse_query(query):\n    values = query.split(\"|\")\n    key = values[0]  # key of the spreadsheet\n    worksheet_num_or_title = 0  # A default value for when a number of inputs is invalid\n    if len(values) == 2:\n        s = values[1].strip()\n        if len(s) > 0:\n            if re.match(r\"^\\\"(.*?)\\\"$\", s):\n                # A string quoted by \" means a title of worksheet\n                worksheet_num_or_title = s[1:-1]\n            else:\n                # if spreadsheet contains more than one worksheet - this is the number of it\n                worksheet_num_or_title = int(s)\n\n    return key, worksheet_num_or_title\n\n\ndef parse_worksheet(worksheet):\n    if not worksheet:\n        return {\"columns\": [], \"rows\": []}\n\n    columns, column_names = _get_columns_and_column_names(worksheet[HEADER_INDEX])\n\n    if len(worksheet) > 1:\n        for j, value in enumerate(worksheet[HEADER_INDEX + 1]):\n            columns[j][\"type\"] = guess_type(value)\n\n    column_types = [c[\"type\"] for c in columns]\n    rows = [dict(zip(column_names, _value_eval_list(row, column_types))) for row in worksheet[HEADER_INDEX + 1 :]]\n    data = {\"columns\": columns, \"rows\": rows}\n\n    return data\n\n\ndef parse_spreadsheet(spreadsheet, worksheet_num_or_title):\n    worksheet = None\n    if isinstance(worksheet_num_or_title, int):\n        worksheet = spreadsheet.get_worksheet_by_index(worksheet_num_or_title)\n        if worksheet is None:\n            worksheet_count = len(spreadsheet.worksheets())\n            raise WorksheetNotFoundError(worksheet_num_or_title, worksheet_count)\n    elif isinstance(worksheet_num_or_title, str):\n        worksheet = spreadsheet.get_worksheet_by_title(worksheet_num_or_title)\n        if worksheet is None:\n            raise WorksheetNotFoundByTitleError(worksheet_num_or_title)\n\n    worksheet_values = worksheet.get_all_values()\n\n    return parse_worksheet(worksheet_values)\n\n\ndef is_url_key(key):\n    return key.startswith(\"https://\")\n\n\ndef parse_api_error(error):\n    error_data = error.response.json()\n\n    if \"error\" in error_data and \"message\" in error_data[\"error\"]:\n        message = error_data[\"error\"][\"message\"]\n    else:\n        message = str(error)\n\n    return message\n\n\nclass SpreadsheetWrapper:\n    def __init__(self, spreadsheet):\n        self.spreadsheet = spreadsheet\n\n    def worksheets(self):\n        return self.spreadsheet.worksheets()\n\n    def get_worksheet_by_index(self, index):\n        return self.spreadsheet.get_worksheet(index)\n\n    def get_worksheet_by_title(self, title):\n        try:\n            return self.spreadsheet.worksheet(title)\n        except GSWorksheetNotFound:\n            return None\n\n\nclass TimeoutSession(Session):\n    def request(self, *args, **kwargs):\n        kwargs.setdefault(\"timeout\", 300)\n        return super(TimeoutSession, self).request(*args, **kwargs)\n\n\nclass GoogleSpreadsheet(BaseQueryRunner):\n    should_annotate_query = False\n\n    def __init__(self, configuration):\n        super(GoogleSpreadsheet, self).__init__(configuration)\n        self.syntax = \"custom\"\n\n    @classmethod\n    def name(cls):\n        return \"Google Sheets\"\n\n    @classmethod\n    def type(cls):\n        return \"google_spreadsheets\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\"jsonKeyFile\": {\"type\": \"string\", \"title\": \"JSON Key File (ADC is used if omitted)\"}},\n            \"required\": [],\n            \"secret\": [\"jsonKeyFile\"],\n        }\n\n    def _get_spreadsheet_service(self):\n        scopes = [\"https://spreadsheets.google.com/feeds\"]\n\n        try:\n            key = json_loads(b64decode(self.configuration[\"jsonKeyFile\"]))\n            creds = Credentials.from_service_account_info(key, scopes=scopes)\n        except KeyError:\n            creds = google.auth.default(scopes=scopes)[0]\n\n        timeout_session = Session()\n        timeout_session.requests_session = TimeoutSession()\n        spreadsheetservice = gspread.Client(auth=creds, session=timeout_session)\n        spreadsheetservice.login()\n        return spreadsheetservice\n\n    def test_connection(self):\n        test_spreadsheet_key = \"1S0mld7LMbUad8LYlo13Os9f7eNjw57MqVC0YiCd1Jis\"\n        try:\n            service = self._get_spreadsheet_service()\n            service.open_by_key(test_spreadsheet_key).worksheets()\n        except APIError as e:\n            logger.exception(e)\n            message = parse_api_error(e)\n            raise Exception(message)\n        except GoogleAuthError as e:\n            logger.exception(e)\n            raise Exception(str(e))\n\n    def run_query(self, query, user):\n        logger.debug(\"Spreadsheet is about to execute query: %s\", query)\n        key, worksheet_num_or_title = parse_query(query)\n\n        try:\n            spreadsheet_service = self._get_spreadsheet_service()\n\n            if is_url_key(key):\n                spreadsheet = spreadsheet_service.open_by_url(key)\n            else:\n                spreadsheet = spreadsheet_service.open_by_key(key)\n\n            data = parse_spreadsheet(SpreadsheetWrapper(spreadsheet), worksheet_num_or_title)\n\n            return data, None\n        except gspread.SpreadsheetNotFound:\n            return (\n                None,\n                \"Spreadsheet ({}) not found. Make sure you used correct id.\".format(key),\n            )\n        except APIError as e:\n            return None, parse_api_error(e)\n\n\nregister(GoogleSpreadsheet)\n"
  },
  {
    "path": "redash/query_runner/graphite.py",
    "content": "import datetime\nimport logging\n\nimport requests\n\nfrom redash.query_runner import (\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\n\ndef _transform_result(response):\n    columns = (\n        {\"name\": \"Time::x\", \"type\": TYPE_DATETIME},\n        {\"name\": \"value::y\", \"type\": TYPE_FLOAT},\n        {\"name\": \"name::series\", \"type\": TYPE_STRING},\n    )\n\n    rows = []\n\n    for series in response.json():\n        for values in series[\"datapoints\"]:\n            timestamp = datetime.datetime.fromtimestamp(int(values[1]))\n            rows.append(\n                {\n                    \"Time::x\": timestamp,\n                    \"name::series\": series[\"target\"],\n                    \"value::y\": values[0],\n                }\n            )\n\n    return {\"columns\": columns, \"rows\": rows}\n\n\nclass Graphite(BaseQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\"},\n                \"username\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"verify\": {\"type\": \"boolean\", \"title\": \"Verify SSL certificate\"},\n            },\n            \"required\": [\"url\"],\n            \"secret\": [\"password\"],\n        }\n\n    def __init__(self, configuration):\n        super(Graphite, self).__init__(configuration)\n        self.syntax = \"custom\"\n\n        if \"username\" in self.configuration and self.configuration[\"username\"]:\n            self.auth = (self.configuration[\"username\"], self.configuration[\"password\"])\n        else:\n            self.auth = None\n\n        self.verify = self.configuration.get(\"verify\", True)\n        self.base_url = \"%s/render?format=json&\" % self.configuration[\"url\"]\n\n    def test_connection(self):\n        r = requests.get(\n            \"{}/render\".format(self.configuration[\"url\"]),\n            auth=self.auth,\n            verify=self.verify,\n        )\n        if r.status_code != 200:\n            raise Exception(\"Got invalid response from Graphite (http status code: {0}).\".format(r.status_code))\n\n    def run_query(self, query, user):\n        url = \"%s%s\" % (self.base_url, \"&\".join(query.split(\"\\n\")))\n        error = None\n        data = None\n\n        try:\n            response = requests.get(url, auth=self.auth, verify=self.verify)\n\n            if response.status_code == 200:\n                data = _transform_result(response)\n            else:\n                error = \"Failed getting results (%d)\" % response.status_code\n        except Exception as ex:\n            data = None\n            error = str(ex)\n\n        return data, error\n\n\nregister(Graphite)\n"
  },
  {
    "path": "redash/query_runner/hive_ds.py",
    "content": "import base64\nimport logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    JobTimeoutException,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    from pyhive import hive\n    from pyhive.exc import DatabaseError\n    from thrift.transport import THttpClient\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nCOLUMN_NAME = 0\nCOLUMN_TYPE = 1\n\ntypes_map = {\n    \"BIGINT_TYPE\": TYPE_INTEGER,\n    \"TINYINT_TYPE\": TYPE_INTEGER,\n    \"SMALLINT_TYPE\": TYPE_INTEGER,\n    \"INT_TYPE\": TYPE_INTEGER,\n    \"DOUBLE_TYPE\": TYPE_FLOAT,\n    \"DECIMAL_TYPE\": TYPE_FLOAT,\n    \"FLOAT_TYPE\": TYPE_FLOAT,\n    \"REAL_TYPE\": TYPE_FLOAT,\n    \"BOOLEAN_TYPE\": TYPE_BOOLEAN,\n    \"TIMESTAMP_TYPE\": TYPE_DATETIME,\n    \"DATE_TYPE\": TYPE_DATE,\n    \"CHAR_TYPE\": TYPE_STRING,\n    \"STRING_TYPE\": TYPE_STRING,\n    \"VARCHAR_TYPE\": TYPE_STRING,\n}\n\n\nclass Hive(BaseSQLQueryRunner):\n    should_annotate_query = False\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\"},\n                \"database\": {\"type\": \"string\"},\n                \"username\": {\"type\": \"string\"},\n            },\n            \"order\": [\"host\", \"port\", \"database\", \"username\"],\n            \"required\": [\"host\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"hive\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    def _get_tables(self, schema):\n        schemas_query = \"show schemas\"\n\n        tables_query = \"show tables in %s\"\n\n        columns_query = \"show columns in %s.%s\"\n\n        for schema_name in [\n            a for a in [str(a[\"database_name\"]) for a in self._run_query_internal(schemas_query)] if len(a) > 0\n        ]:\n            for table_name in [\n                a\n                for a in [str(a[\"tab_name\"]) for a in self._run_query_internal(tables_query % schema_name)]\n                if len(a) > 0\n            ]:\n                columns = [\n                    a\n                    for a in [\n                        str(a[\"field\"]) for a in self._run_query_internal(columns_query % (schema_name, table_name))\n                    ]\n                    if len(a) > 0\n                ]\n\n                if schema_name != \"default\":\n                    table_name = \"{}.{}\".format(schema_name, table_name)\n\n                schema[table_name] = {\"name\": table_name, \"columns\": columns}\n        return list(schema.values())\n\n    def _get_connection(self):\n        host = self.configuration[\"host\"]\n\n        connection = hive.connect(\n            host=host,\n            port=self.configuration.get(\"port\", None),\n            database=self.configuration.get(\"database\", \"default\"),\n            username=self.configuration.get(\"username\", None),\n        )\n\n        return connection\n\n    def run_query(self, query, user):\n        connection = None\n        try:\n            connection = self._get_connection()\n            cursor = connection.cursor()\n\n            cursor.execute(query)\n\n            column_names = []\n            columns = []\n\n            for column in cursor.description:\n                column_name = column[COLUMN_NAME]\n                column_names.append(column_name)\n\n                columns.append(\n                    {\n                        \"name\": column_name,\n                        \"friendly_name\": column_name,\n                        \"type\": types_map.get(column[COLUMN_TYPE], None),\n                    }\n                )\n\n            rows = [dict(zip(column_names, row)) for row in cursor]\n\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n        except (KeyboardInterrupt, JobTimeoutException):\n            if connection:\n                connection.cancel()\n            raise\n        except DatabaseError as e:\n            try:\n                error = e.args[0].status.errorMessage\n            except AttributeError:\n                error = str(e)\n            data = None\n        finally:\n            if connection:\n                connection.close()\n\n        return data, error\n\n\nclass HiveHttp(Hive):\n    @classmethod\n    def name(cls):\n        return \"Hive (HTTP)\"\n\n    @classmethod\n    def type(cls):\n        return \"hive_http\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\"},\n                \"database\": {\"type\": \"string\"},\n                \"username\": {\"type\": \"string\"},\n                \"http_scheme\": {\n                    \"type\": \"string\",\n                    \"title\": \"HTTP Scheme (http or https)\",\n                    \"default\": \"https\",\n                },\n                \"http_path\": {\"type\": \"string\", \"title\": \"HTTP Path\"},\n                \"http_password\": {\"type\": \"string\", \"title\": \"Password\"},\n            },\n            \"order\": [\n                \"host\",\n                \"port\",\n                \"http_path\",\n                \"username\",\n                \"http_password\",\n                \"database\",\n                \"http_scheme\",\n            ],\n            \"secret\": [\"http_password\"],\n            \"required\": [\"host\", \"http_path\"],\n        }\n\n    def _get_connection(self):\n        host = self.configuration[\"host\"]\n\n        scheme = self.configuration.get(\"http_scheme\", \"https\")\n\n        # if path is set but is missing initial slash, append it\n        path = self.configuration.get(\"http_path\", \"\")\n        if path and path[0] != \"/\":\n            path = \"/\" + path\n\n        # if port is set prepend colon\n        port = self.configuration.get(\"port\", \"\")\n        if port:\n            port = \":\" + str(port)\n\n        http_uri = \"{}://{}{}{}\".format(scheme, host, port, path)\n\n        # create transport\n        transport = THttpClient.THttpClient(http_uri)\n\n        # if username or password is set, add Authorization header\n        username = self.configuration.get(\"username\", \"\")\n        password = self.configuration.get(\"http_password\", \"\")\n        if username or password:\n            auth = base64.b64encode(username.encode(\"ascii\") + b\":\" + password.encode(\"ascii\"))\n            transport.setCustomHeaders({\"Authorization\": \"Basic \" + auth.decode()})\n\n        # create connection\n        connection = hive.connect(thrift_transport=transport)\n\n        return connection\n\n\nregister(Hive)\nregister(HiveHttp)\n"
  },
  {
    "path": "redash/query_runner/ignite.py",
    "content": "import datetime\nimport importlib.util\nimport logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    JobTimeoutException,\n    register,\n)\n\nignite_available = importlib.util.find_spec(\"pyignite\") is not None\ngridgain_available = importlib.util.find_spec(\"pygridgain\") is not None\n\n\nlogger = logging.getLogger(__name__)\n\ntypes_map = {\n    \"java.lang.String\": TYPE_STRING,\n    \"java.lang.Float\": TYPE_FLOAT,\n    \"java.lang.Double\": TYPE_FLOAT,\n    \"java.sql.Date\": TYPE_DATETIME,\n    \"java.sql.Timestamp\": TYPE_DATETIME,\n    \"java.lang.Long\": TYPE_INTEGER,\n    \"java.lang.Integer\": TYPE_INTEGER,\n    \"java.lang.Short\": TYPE_INTEGER,\n    \"java.lang.Boolean\": TYPE_BOOLEAN,\n    \"java.lang.Decimal\": TYPE_FLOAT,\n}\n\n\nclass Ignite(BaseSQLQueryRunner):\n    should_annotate_query = False\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"server\": {\"type\": \"string\", \"default\": \"127.0.0.1:10800\"},\n                \"tls\": {\"type\": \"boolean\", \"default\": False, \"title\": \"Use SSL/TLS connection\"},\n                \"schema\": {\"type\": \"string\", \"title\": \"Schema Name\", \"default\": \"PUBLIC\"},\n                \"distributed_joins\": {\"type\": \"boolean\", \"title\": \"Allow distributed joins\", \"default\": False},\n                \"enforce_join_order\": {\"type\": \"boolean\", \"title\": \"Enforce join order\", \"default\": False},\n                \"lazy\": {\"type\": \"boolean\", \"title\": \"Lazy query execution\", \"default\": True},\n                \"gridgain\": {\"type\": \"boolean\", \"title\": \"Use GridGain libraries\", \"default\": gridgain_available},\n            },\n            \"required\": [\"server\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def name(cls):\n        return \"Apache Ignite\"\n\n    @classmethod\n    def type(cls):\n        return \"ignite\"\n\n    @classmethod\n    def enabled(cls):\n        return ignite_available or gridgain_available\n\n    def _get_tables(self, schema):\n        query = \"\"\"\n        SELECT schema_name, table_name, column_name, type\n        FROM SYS.TABLE_COLUMNS\n        WHERE schema_name NOT IN ('SYS') and column_name not in ('_KEY','_VAL');\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            raise Exception(\"Failed getting schema.\")\n\n        for row in results[\"rows\"]:\n            if row[\"SCHEMA_NAME\"] != self.configuration.get(\"schema\", \"PUBLIC\"):\n                table_name = \"{}.{}\".format(row[\"SCHEMA_NAME\"], row[\"TABLE_NAME\"])\n            else:\n                table_name = row[\"TABLE_NAME\"]\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            col_type = TYPE_STRING\n            if row[\"TYPE\"] in types_map:\n                col_type = types_map[row[\"TYPE\"]]\n\n            schema[table_name][\"columns\"].append({\"name\": row[\"COLUMN_NAME\"], \"type\": col_type})\n\n        return list(schema.values())\n\n    def normalise_column(self, col):\n        # if it's a datetime, just return the milliseconds\n        if type(col) is tuple and len(col) == 2 and type(col[0]) is datetime.datetime and isinstance(col[1], int):\n            return col[0]\n        else:\n            return col\n\n    def normalise_row(self, row):\n        return [self.normalise_column(col) for col in row]\n\n    def server_to_connection(self, s):\n        st = s.split(\":\")\n        if len(st) == 1:\n            server = s\n            port = 10800\n        elif len(st) == 2:\n            server = st[0]\n            port = int(st[1])\n        else:\n            server = \"unknown\"\n            port = 10800\n        return (server, port)\n\n    def _parse_results(self, c):\n        column_names = next(c)\n        columns = [{\"name\": col, \"friendly_name\": col.lower()} for col in column_names]\n        rows = [dict(zip(column_names, self.normalise_row(row))) for row in c]\n\n        return (columns, rows)\n\n    def run_query(self, query, user):\n        connection = None\n\n        try:\n            server = self.configuration.get(\"server\", \"127.0.0.1:10800\")\n            user = self.configuration.get(\"user\", None)\n            password = self.configuration.get(\"password\", None)\n            tls = self.configuration.get(\"tls\", False)\n            distributed_joins = self.configuration.get(\"distributed_joins\", False)\n            enforce_join_order = self.configuration.get(\"enforce_join_order\", False)\n            lazy = self.configuration.get(\"lazy\", True)\n            gridgain = self.configuration.get(\"gridgain\", False)\n\n            if gridgain:\n                from pygridgain import Client\n            else:\n                from pyignite import Client\n\n            connection = Client(username=user, password=password, use_ssl=tls)\n            connection.connect([self.server_to_connection(s) for s in server.split(\",\")])\n\n            cursor = connection.sql(\n                query,\n                include_field_names=True,\n                distributed_joins=distributed_joins,\n                enforce_join_order=enforce_join_order,\n                lazy=lazy,\n            )\n            logger.debug(\"Ignite running query: %s\", query)\n\n            result = self._parse_results(cursor)\n            data = {\"columns\": result[0], \"rows\": result[1]}\n            error = None\n\n        except (KeyboardInterrupt, JobTimeoutException):\n            connection.cancel()\n            raise\n        finally:\n            if connection:\n                connection.close()\n\n        return data, error\n\n\nregister(Ignite)\n"
  },
  {
    "path": "redash/query_runner/impala_ds.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    JobTimeoutException,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    from impala.dbapi import connect\n    from impala.error import DatabaseError, RPCError\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nCOLUMN_NAME = 0\nCOLUMN_TYPE = 1\n\ntypes_map = {\n    \"BIGINT\": TYPE_INTEGER,\n    \"TINYINT\": TYPE_INTEGER,\n    \"SMALLINT\": TYPE_INTEGER,\n    \"INT\": TYPE_INTEGER,\n    \"DOUBLE\": TYPE_FLOAT,\n    \"DECIMAL\": TYPE_FLOAT,\n    \"FLOAT\": TYPE_FLOAT,\n    \"REAL\": TYPE_FLOAT,\n    \"BOOLEAN\": TYPE_BOOLEAN,\n    \"TIMESTAMP\": TYPE_DATETIME,\n    \"CHAR\": TYPE_STRING,\n    \"STRING\": TYPE_STRING,\n    \"VARCHAR\": TYPE_STRING,\n}\n\n\nclass Impala(BaseSQLQueryRunner):\n    noop_query = \"show schemas\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\"},\n                \"protocol\": {\n                    \"type\": \"string\",\n                    \"extendedEnum\": [\n                        {\"value\": \"beeswax\", \"name\": \"Beeswax\"},\n                        {\"value\": \"hiveserver2\", \"name\": \"Hive Server 2\"},\n                    ],\n                    \"title\": \"Protocol\",\n                },\n                \"database\": {\"type\": \"string\"},\n                \"use_ldap\": {\"type\": \"boolean\"},\n                \"use_ssl\": {\"type\": \"boolean\"},\n                \"ldap_user\": {\"type\": \"string\"},\n                \"ldap_password\": {\"type\": \"string\"},\n                \"timeout\": {\"type\": \"number\"},\n            },\n            \"required\": [\"host\"],\n            \"secret\": [\"ldap_password\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"impala\"\n\n    def _get_tables(self, schema_dict):\n        schemas_query = \"show schemas;\"\n        tables_query = \"show tables in `%s`;\"\n        columns_query = \"show column stats `%s`.`%s`;\"\n\n        for schema_name in [str(a[\"name\"]) for a in self._run_query_internal(schemas_query)]:\n            for table_name in [str(a[\"name\"]) for a in self._run_query_internal(tables_query % schema_name)]:\n                columns = [\n                    str(a[\"Column\"]) for a in self._run_query_internal(columns_query % (schema_name, table_name))\n                ]\n\n                if schema_name != \"default\":\n                    table_name = \"{}.{}\".format(schema_name, table_name)\n\n                schema_dict[table_name] = {\"name\": table_name, \"columns\": columns}\n\n        return list(schema_dict.values())\n\n    def run_query(self, query, user):\n        connection = None\n        try:\n            connection = connect(**self.configuration.to_dict())\n\n            cursor = connection.cursor()\n\n            cursor.execute(query)\n\n            column_names = []\n            columns = []\n\n            for column in cursor.description:\n                column_name = column[COLUMN_NAME]\n                column_names.append(column_name)\n\n                columns.append(\n                    {\n                        \"name\": column_name,\n                        \"friendly_name\": column_name,\n                        \"type\": types_map.get(column[COLUMN_TYPE], None),\n                    }\n                )\n\n            rows = [dict(zip(column_names, row)) for row in cursor]\n\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n            cursor.close()\n        except DatabaseError as e:\n            data = None\n            error = str(e)\n        except RPCError as e:\n            data = None\n            error = \"Metastore Error [%s]\" % str(e)\n        except (KeyboardInterrupt, JobTimeoutException):\n            connection.cancel()\n            raise\n        finally:\n            if connection:\n                connection.close()\n\n        return data, error\n\n\nregister(Impala)\n"
  },
  {
    "path": "redash/query_runner/influx_db.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    from influxdb import InfluxDBClient\n\n    enabled = True\n\nexcept ImportError:\n    enabled = False\n\n\nTYPES_MAP = {\n    str: TYPE_STRING,\n    int: TYPE_INTEGER,\n    float: TYPE_FLOAT,\n}\n\n\ndef _get_type(value):\n    return TYPES_MAP.get(type(value), TYPE_STRING)\n\n\ndef _transform_result(results):\n    column_names = []\n    result_rows = []\n\n    for result in results:\n        for series in result.raw.get(\"series\", []):\n            for column in series[\"columns\"]:\n                if column not in column_names:\n                    column_names.append(column)\n            tags = series.get(\"tags\", {})\n            for key in tags.keys():\n                if key not in column_names:\n                    column_names.append(key)\n\n    for result in results:\n        for series in result.raw.get(\"series\", []):\n            for point in series[\"values\"]:\n                result_row = {}\n                for column in column_names:\n                    tags = series.get(\"tags\", {})\n                    if column in tags:\n                        result_row[column] = tags[column]\n                    elif column in series[\"columns\"]:\n                        index = series[\"columns\"].index(column)\n                        value = point[index]\n                        result_row[column] = value\n                result_rows.append(result_row)\n\n    if len(result_rows) > 0:\n        result_columns = [{\"name\": c, \"type\": _get_type(result_rows[0][c])} for c in result_rows[0].keys()]\n    else:\n        result_columns = [{\"name\": c, \"type\": TYPE_STRING} for c in column_names]\n\n    return {\"columns\": result_columns, \"rows\": result_rows}\n\n\nclass InfluxDB(BaseQueryRunner):\n    should_annotate_query = False\n    noop_query = \"show measurements limit 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\"url\": {\"type\": \"string\"}},\n            \"required\": [\"url\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def type(cls):\n        return \"influxdb\"\n\n    def run_query(self, query, user):\n        client = InfluxDBClient.from_dsn(self.configuration[\"url\"])\n\n        logger.debug(\"influxdb url: %s\", self.configuration[\"url\"])\n        logger.debug(\"influxdb got query: %s\", query)\n\n        try:\n            results = client.query(query)\n            if not isinstance(results, list):\n                results = [results]\n\n            json_data = _transform_result(results)\n            error = None\n        except Exception as ex:\n            json_data = None\n            error = str(ex)\n\n        return json_data, error\n\n\nregister(InfluxDB)\n"
  },
  {
    "path": "redash/query_runner/influx_db_v2.py",
    "content": "import logging\nimport os\nfrom base64 import b64decode\nfrom tempfile import NamedTemporaryFile\nfrom typing import Any, Dict, Optional, Tuple, Type, TypeVar\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\ntry:\n    from influxdb_client import InfluxDBClient\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nlogger = logging.getLogger(__name__)\n\nT = TypeVar(\"T\")\n\nTYPES_MAP = {\n    \"integer\": TYPE_INTEGER,\n    \"long\": TYPE_INTEGER,\n    \"float\": TYPE_FLOAT,\n    \"double\": TYPE_FLOAT,\n    \"boolean\": TYPE_BOOLEAN,\n    \"string\": TYPE_STRING,\n    \"datetime:RFC3339\": TYPE_DATETIME,\n}\n\n\nclass InfluxDBv2(BaseQueryRunner):\n    \"\"\"\n    Query runner for influxdb version 2.\n    \"\"\"\n\n    should_annotate_query = False\n\n    def _get_influx_kwargs(self) -> Dict:\n        \"\"\"\n        Determines additional arguments for influxdb client connection.\n        :return: An object with additional arguments for influxdb client.\n        \"\"\"\n        return {\n            \"verify_ssl\": self.configuration.get(\"verify_ssl\", None),\n            \"cert_file\": self._create_cert_file(\"cert_File\"),\n            \"cert_key_file\": self._create_cert_file(\"cert_key_File\"),\n            \"cert_key_password\": self.configuration.get(\"cert_key_password\", None),\n            \"ssl_ca_cert\": self._create_cert_file(\"ssl_ca_cert_File\"),\n        }\n\n    def _create_cert_file(self, key: str) -> str:\n        \"\"\"\n        Creates a temporary file from base64 encoded content from stored\n        configuration in filesystem.\n        :param key: The key to get the content from configuration object.\n        :return: The name of temporary file.\n        \"\"\"\n        cert_file_name = None\n\n        if self.configuration.get(key, None) is not None:\n            with NamedTemporaryFile(mode=\"w\", delete=False) as cert_file:\n                cert_bytes = b64decode(self.configuration[key])\n                cert_file.write(cert_bytes.decode(\"utf-8\"))\n                cert_file_name = cert_file.name\n\n        return cert_file_name\n\n    def _cleanup_cert_files(self, influx_kwargs: Dict) -> None:\n        \"\"\"\n        Deletes temporary stored files in filesystem.\n        \"\"\"\n        for key in [\"cert_file\", \"cert_key_file\", \"ssl_ca_cert\"]:\n            cert_path = influx_kwargs.get(key, None)\n            if cert_path is not None and os.path.exists(cert_path):\n                os.remove(cert_path)\n\n    @classmethod\n    def configuration_schema(cls: Type[T]) -> Dict:\n        \"\"\"\n        Defines a configuration schema for this query runner.\n        :param cls: Object of this class.\n        :return: The defined configuration schema.\n        \"\"\"\n        # files has to end with \"File\" in name\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\", \"title\": \"URL\"},\n                \"org\": {\"type\": \"string\", \"title\": \"Organization\"},\n                \"token\": {\"type\": \"string\", \"title\": \"Token\"},\n                \"verify_ssl\": {\"type\": \"boolean\", \"title\": \"Verify SSL\", \"default\": False},\n                \"cert_File\": {\"type\": \"string\", \"title\": \"SSL Client Certificate\", \"default\": None},\n                \"cert_key_File\": {\"type\": \"string\", \"title\": \"SSL Client Key\", \"default\": None},\n                \"cert_key_password\": {\"type\": \"string\", \"title\": \"Password for SSL Client Key\", \"default\": None},\n                \"ssl_ca_cert_File\": {\"type\": \"string\", \"title\": \"SSL Root Certificate\", \"default\": None},\n            },\n            \"order\": [\"url\", \"org\", \"token\", \"cert_File\", \"cert_key_File\", \"cert_key_password\", \"ssl_ca_cert_File\"],\n            \"required\": [\"url\", \"org\", \"token\"],\n            \"secret\": [\"token\", \"cert_File\", \"cert_key_File\", \"cert_key_password\", \"ssl_ca_cert_File\"],\n            \"extra_options\": [\"verify_ssl\", \"cert_File\", \"cert_key_File\", \"cert_key_password\", \"ssl_ca_cert_File\"],\n        }\n\n    @classmethod\n    def enabled(cls: Type[T]) -> bool:\n        \"\"\"\n        Determines, if this query runner is enabled or not.\n        :param cls: Object of this class.\n        :return: True, if this query runner is enabled; otherwise False.\n        \"\"\"\n        return enabled\n\n    def test_connection(self) -> None:\n        \"\"\"\n        Tests the healthiness of the influxdb instance. If it is not healthy,\n        it logs an error message and raises an exception with an appropriate\n        message.\n        :raises Exception: If the remote influxdb instance is not healthy.\n        \"\"\"\n        try:\n            influx_kwargs = self._get_influx_kwargs()\n            with InfluxDBClient(\n                url=self.configuration[\"url\"],\n                token=self.configuration[\"token\"],\n                org=self.configuration[\"org\"],\n                **influx_kwargs,\n            ) as client:\n                healthy = client.health()\n                if healthy.status == \"fail\":\n                    logger.error(\"Connection test failed, due to: \" f\"{healthy.message!r}.\")\n                    raise Exception(\"InfluxDB is not healthy. Check logs for more \" \"information.\")\n        except Exception:\n            raise\n        finally:\n            self._cleanup_cert_files(influx_kwargs)\n\n    def _get_type(self, type_: str) -> str:\n        \"\"\"\n        Determines the internal type of a passed data type which the database\n        uses.\n        :param type_: The type from the database to map to internal datatype.\n        :return: The name of the internal datatype.\n        \"\"\"\n        return TYPES_MAP.get(type_, \"string\")\n\n    def _get_data_from_tables(self, tables: Any) -> Dict:\n        \"\"\"\n        Determines the data of the given tables in an appropriate schema for\n        redash ui to render it. It retrieves all available columns and records\n        from the tables.\n        :param tables: A list of FluxTable instances.\n        :return: An object with columns and rows list.\n        \"\"\"\n        columns = []\n        rows = []\n\n        for table in tables:\n            for column in table.columns:\n                column_entry = {\n                    \"name\": column.label,\n                    \"type\": self._get_type(column.data_type),\n                    \"friendly_name\": column.label.title(),\n                }\n                if column_entry not in columns:\n                    columns.append(column_entry)\n\n            rows.extend([row.values for row in [record for record in table.records]])\n\n        return {\"columns\": columns, \"rows\": rows}\n\n    def run_query(self, query: str, user: str) -> Tuple[Optional[str], Optional[str]]:\n        \"\"\"\n        Runs a given query against the influxdb instance and returns its\n        result.\n        :param query: The query, this runner is executed.\n        :param user: The user who runs the query.\n        :return: A 2-tuple:\n            1. element: The queried result in an appropriate format for redash\n                ui. If an error occurred, it returns None.\n            2. element: An error message, if an error occured. None, if no\n                error occurred.\n        \"\"\"\n        data = None\n        error = None\n\n        try:\n            influx_kwargs = self._get_influx_kwargs()\n            with InfluxDBClient(\n                url=self.configuration[\"url\"],\n                token=self.configuration[\"token\"],\n                org=self.configuration[\"org\"],\n                **influx_kwargs,\n            ) as client:\n                logger.debug(f\"InfluxDB got query: {query!r}\")\n\n                tables = client.query_api().query(query)\n\n                data = self._get_data_from_tables(tables)\n        except Exception as ex:\n            error = str(ex)\n        finally:\n            self._cleanup_cert_files(influx_kwargs)\n\n        return data, error\n\n\nregister(InfluxDBv2)\n"
  },
  {
    "path": "redash/query_runner/jql.py",
    "content": "import re\nfrom collections import OrderedDict\n\nfrom redash.query_runner import TYPE_STRING, BaseHTTPQueryRunner, register\nfrom redash.utils import json_loads\n\n\n# TODO: make this more general and move into __init__.py\nclass ResultSet:\n    def __init__(self):\n        self.columns = OrderedDict()\n        self.rows = []\n\n    def add_row(self, row):\n        for key in row.keys():\n            self.add_column(key)\n\n        self.rows.append(row)\n\n    def add_column(self, column, column_type=TYPE_STRING):\n        if column not in self.columns:\n            self.columns[column] = {\n                \"name\": column,\n                \"type\": column_type,\n                \"friendly_name\": column,\n            }\n\n    def to_json(self):\n        return {\"rows\": self.rows, \"columns\": list(self.columns.values())}\n\n    def merge(self, set):\n        self.rows = self.rows + set.rows\n\n\ndef parse_issue(issue, field_mapping):  # noqa: C901\n    result = OrderedDict()\n\n    # Handle API v3 response format: key field may be missing, use id as fallback\n    result[\"key\"] = issue.get(\"key\", issue.get(\"id\", \"unknown\"))\n\n    # Handle API v3 response format: fields may be missing\n    fields = issue.get(\"fields\", {})\n    for k, v in fields.items():  #\n        output_name = field_mapping.get_output_field_name(k)\n        member_names = field_mapping.get_dict_members(k)\n\n        if isinstance(v, dict):\n            if len(member_names) > 0:\n                # if field mapping with dict member mappings defined get value of each member\n                for member_name in member_names:\n                    if member_name in v:\n                        result[field_mapping.get_dict_output_field_name(k, member_name)] = v[member_name]\n\n            else:\n                # these special mapping rules are kept for backwards compatibility\n                if \"key\" in v:\n                    result[\"{}_key\".format(output_name)] = v[\"key\"]\n                if \"name\" in v:\n                    result[\"{}_name\".format(output_name)] = v[\"name\"]\n\n                if k in v:\n                    result[output_name] = v[k]\n\n                if \"watchCount\" in v:\n                    result[output_name] = v[\"watchCount\"]\n\n        elif isinstance(v, list):\n            if len(member_names) > 0:\n                # if field mapping with dict member mappings defined get value of each member\n                for member_name in member_names:\n                    listValues = []\n                    for listItem in v:\n                        if isinstance(listItem, dict):\n                            if member_name in listItem:\n                                listValues.append(listItem[member_name])\n                    if len(listValues) > 0:\n                        result[field_mapping.get_dict_output_field_name(k, member_name)] = \",\".join(listValues)\n\n            else:\n                # otherwise support list values only for non-dict items\n                listValues = []\n                for listItem in v:\n                    if not isinstance(listItem, dict):\n                        listValues.append(listItem)\n                if len(listValues) > 0:\n                    result[output_name] = \",\".join(listValues)\n\n        else:\n            result[output_name] = v\n\n    return result\n\n\ndef parse_issues(data, field_mapping):\n    results = ResultSet()\n\n    for issue in data[\"issues\"]:\n        results.add_row(parse_issue(issue, field_mapping))\n\n    return results\n\n\ndef parse_count(data):\n    results = ResultSet()\n    # API v3 may not return 'total' field, fallback to counting issues\n    count = data.get(\"total\", len(data.get(\"issues\", [])))\n    results.add_row({\"count\": count})\n    return results\n\n\nclass FieldMapping:\n    def __init__(cls, query_field_mapping):\n        cls.mapping = []\n        for k, v in query_field_mapping.items():\n            field_name = k\n            member_name = None\n\n            # check for member name contained in field name\n            member_parser = re.search(r\"(\\w+)\\.(\\w+)\", k)\n            if member_parser:\n                field_name = member_parser.group(1)\n                member_name = member_parser.group(2)\n\n            cls.mapping.append(\n                {\n                    \"field_name\": field_name,\n                    \"member_name\": member_name,\n                    \"output_field_name\": v,\n                }\n            )\n\n    def get_output_field_name(cls, field_name):\n        for item in cls.mapping:\n            if item[\"field_name\"] == field_name and not item[\"member_name\"]:\n                return item[\"output_field_name\"]\n        return field_name\n\n    def get_dict_members(cls, field_name):\n        member_names = []\n        for item in cls.mapping:\n            if item[\"field_name\"] == field_name and item[\"member_name\"]:\n                member_names.append(item[\"member_name\"])\n        return member_names\n\n    def get_dict_output_field_name(cls, field_name, member_name):\n        for item in cls.mapping:\n            if item[\"field_name\"] == field_name and item[\"member_name\"] == member_name:\n                return item[\"output_field_name\"]\n        return None\n\n\nclass JiraJQL(BaseHTTPQueryRunner):\n    noop_query = '{\"queryType\": \"count\"}'\n    response_error = \"JIRA returned unexpected status code\"\n    requires_authentication = True\n    url_title = \"JIRA URL\"\n    username_title = \"Username\"\n    password_title = \"API Token\"\n\n    @classmethod\n    def name(cls):\n        return \"JIRA (JQL)\"\n\n    def __init__(self, configuration):\n        super(JiraJQL, self).__init__(configuration)\n        self.syntax = \"json\"\n\n    def run_query(self, query, user):\n        # Updated to API v3 endpoint, fix double slash issue\n        jql_url = \"{}/rest/api/3/search/jql\".format(self.configuration[\"url\"].rstrip(\"/\"))\n\n        query = json_loads(query)\n        query_type = query.pop(\"queryType\", \"select\")\n        field_mapping = FieldMapping(query.pop(\"fieldMapping\", {}))\n\n        # API v3 requires mandatory jql parameter with restrictions\n        if \"jql\" not in query or not query[\"jql\"]:\n            query[\"jql\"] = \"created >= -30d order by created DESC\"\n\n        if query_type == \"count\":\n            query[\"maxResults\"] = 1\n            query[\"fields\"] = \"\"\n        else:\n            query[\"maxResults\"] = query.get(\"maxResults\", 1000)\n\n        if \"fields\" not in query:\n            query[\"fields\"] = \"*all\"\n\n        response, error = self.get_response(jql_url, params=query)\n        if error is not None:\n            return None, error\n\n        data = response.json()\n\n        if query_type == \"count\":\n            results = parse_count(data)\n        else:\n            results = parse_issues(data, field_mapping)\n\n            # API v3 uses token-based pagination instead of startAt/total\n            while not data.get(\"isLast\", True) and \"nextPageToken\" in data:\n                query[\"nextPageToken\"] = data[\"nextPageToken\"]\n                response, error = self.get_response(jql_url, params=query)\n                if error is not None:\n                    return None, error\n\n                data = response.json()\n                addl_results = parse_issues(data, field_mapping)\n                results.merge(addl_results)\n\n        return results.to_json(), None\n\n\nregister(JiraJQL)\n"
  },
  {
    "path": "redash/query_runner/json_ds.py",
    "content": "import datetime\nimport logging\nfrom urllib.parse import urljoin\n\nimport yaml\nfrom funcy import compact, project\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseHTTPQueryRunner,\n    register,\n)\n\n\nclass QueryParseError(Exception):\n    pass\n\n\ndef parse_query(query):\n    # TODO: copy paste from Metrica query runner, we should extract this into a utility\n    query = query.strip()\n    if query == \"\":\n        raise QueryParseError(\"Query is empty.\")\n    try:\n        params = yaml.safe_load(query)\n        return params\n    except ValueError as e:\n        logging.exception(e)\n        error = str(e)\n        raise QueryParseError(error)\n\n\nTYPES_MAP = {\n    str: TYPE_STRING,\n    bytes: TYPE_STRING,\n    int: TYPE_INTEGER,\n    float: TYPE_FLOAT,\n    bool: TYPE_BOOLEAN,\n    datetime.datetime: TYPE_DATETIME,\n}\n\n\ndef _get_column_by_name(columns, column_name):\n    for c in columns:\n        if \"name\" in c and c[\"name\"] == column_name:\n            return c\n\n    return None\n\n\ndef _get_type(value):\n    return TYPES_MAP.get(type(value), TYPE_STRING)\n\n\ndef add_column(columns, column_name, column_type):\n    if _get_column_by_name(columns, column_name) is None:\n        columns.append({\"name\": column_name, \"friendly_name\": column_name, \"type\": column_type})\n\n\ndef _apply_path_search(response, path, default=None):\n    if path is None:\n        return response\n\n    path_parts = path.split(\".\")\n    path_parts.reverse()\n    while len(path_parts) > 0:\n        current_path = path_parts.pop()\n        if current_path in response:\n            response = response[current_path]\n        elif default is not None:\n            return default\n        else:\n            raise Exception(\"Couldn't find path {} in response.\".format(path))\n\n    return response\n\n\ndef _normalize_json(data, path):\n    if not data:\n        return None\n    data = _apply_path_search(data, path)\n\n    if isinstance(data, dict):\n        data = [data]\n\n    return data\n\n\ndef _sort_columns_with_fields(columns, fields):\n    if fields:\n        columns = compact([_get_column_by_name(columns, field) for field in fields])\n\n    return columns\n\n\n# TODO: merge the logic here with the one in MongoDB's queyr runner\ndef parse_json(data, fields):\n    rows = []\n    columns = []\n\n    for row in data:\n        parsed_row = {}\n\n        for key in row:\n            if isinstance(row[key], dict):\n                for inner_key in row[key]:\n                    column_name = \"{}.{}\".format(key, inner_key)\n                    if fields and key not in fields and column_name not in fields:\n                        continue\n\n                    value = row[key][inner_key]\n                    add_column(columns, column_name, _get_type(value))\n                    parsed_row[column_name] = value\n            else:\n                if fields and key not in fields:\n                    continue\n\n                value = row[key]\n                add_column(columns, key, _get_type(value))\n                parsed_row[key] = row[key]\n\n        rows.append(parsed_row)\n\n    columns = _sort_columns_with_fields(columns, fields)\n\n    return {\"rows\": rows, \"columns\": columns}\n\n\nclass JSON(BaseHTTPQueryRunner):\n    requires_url = False\n    base_url_title = \"Base URL\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"base_url\": {\"type\": \"string\", \"title\": cls.base_url_title},\n                \"username\": {\"type\": \"string\", \"title\": cls.username_title},\n                \"password\": {\"type\": \"string\", \"title\": cls.password_title},\n            },\n            \"secret\": [\"password\"],\n            \"order\": [\"base_url\", \"username\", \"password\"],\n        }\n\n    def __init__(self, configuration):\n        super(JSON, self).__init__(configuration)\n        self.syntax = \"yaml\"\n\n    def test_connection(self):\n        pass\n\n    def run_query(self, query, user):\n        query = parse_query(query)\n\n        data, error = self._run_json_query(query)\n        if error is not None:\n            return None, error\n\n        if data:\n            return data, None\n        return None, \"Got empty response from '{}'.\".format(query[\"url\"])\n\n    def _run_json_query(self, query):\n        if not isinstance(query, dict):\n            raise QueryParseError(\"Query should be a YAML object describing the URL to query.\")\n\n        if \"url\" not in query:\n            raise QueryParseError(\"Query must include 'url' option.\")\n\n        method = query.get(\"method\", \"get\")\n        request_options = project(query, (\"params\", \"headers\", \"data\", \"auth\", \"json\", \"verify\"))\n\n        fields = query.get(\"fields\")\n        path = query.get(\"path\")\n\n        if \"pagination\" in query:\n            pagination = RequestPagination.from_config(self.configuration, query[\"pagination\"])\n        else:\n            pagination = None\n\n        if isinstance(request_options.get(\"auth\", None), list):\n            request_options[\"auth\"] = tuple(request_options[\"auth\"])\n        elif self.configuration.get(\"username\") or self.configuration.get(\"password\"):\n            request_options[\"auth\"] = (self.configuration.get(\"username\"), self.configuration.get(\"password\"))\n\n        if method not in (\"get\", \"post\"):\n            raise QueryParseError(\"Only GET or POST methods are allowed.\")\n\n        if fields and not isinstance(fields, list):\n            raise QueryParseError(\"'fields' needs to be a list.\")\n\n        results, error = self._get_all_results(query[\"url\"], method, path, pagination, **request_options)\n        return parse_json(results, fields), error\n\n    def _get_all_results(self, url, method, result_path, pagination, **request_options):\n        \"\"\"Get all results from a paginated endpoint.\"\"\"\n        base_url = self.configuration.get(\"base_url\")\n        url = urljoin(base_url, url)\n\n        results = []\n        has_more = True\n        while has_more:\n            response, error = self._get_json_response(url, method, **request_options)\n            has_more = False\n\n            result = _normalize_json(response, result_path)\n            if result:\n                results.extend(result)\n                if pagination:\n                    has_more, url, request_options = pagination.next(url, request_options, response)\n\n        return results, error\n\n    def _get_json_response(self, url, method, **request_options):\n        response, error = self.get_response(url, http_method=method, **request_options)\n        result = response.json() if error is None else {}\n        return result, error\n\n\nclass RequestPagination:\n    def next(self, url, request_options, response):\n        \"\"\"Checks the response for another page.\n\n        Returns:\n            has_more, next_url, next_request_options\n        \"\"\"\n        return False, None, request_options\n\n    @staticmethod\n    def from_config(configuration, pagination):\n        if not isinstance(pagination, dict) or not isinstance(pagination.get(\"type\"), str):\n            raise QueryParseError(\"'pagination' should be an object with a `type` property\")\n\n        if pagination[\"type\"] == \"url\":\n            return UrlPagination(pagination)\n        elif pagination[\"type\"] == \"token\":\n            return TokenPagination(pagination)\n\n        raise QueryParseError(\"Unknown 'pagination.type' {}\".format(pagination[\"type\"]))\n\n\nclass UrlPagination(RequestPagination):\n    def __init__(self, pagination):\n        self.path = pagination.get(\"path\", \"_links.next.href\")\n        if not isinstance(self.path, str):\n            raise QueryParseError(\"'pagination.path' should be a string\")\n\n    def next(self, url, request_options, response):\n        next_url = _apply_path_search(response, self.path, \"\")\n        if not next_url:\n            return False, None, request_options\n\n        next_url = urljoin(url, next_url)\n        return True, next_url, request_options\n\n\nclass TokenPagination(RequestPagination):\n    def __init__(self, pagination):\n        self.fields = pagination.get(\"fields\", [\"next_page_token\", \"page_token\"])\n        if not isinstance(self.fields, list) or len(self.fields) != 2:\n            raise QueryParseError(\"'pagination.fields' should be a list of 2 field names\")\n\n    def next(self, url, request_options, response):\n        next_token = _apply_path_search(response, self.fields[0], \"\")\n        if not next_token:\n            return False, None, request_options\n\n        params = request_options.get(\"params\", {})\n\n        # prevent infinite loop that can happen if self.fields[1] is wrong\n        if next_token == params.get(self.fields[1]):\n            raise Exception(\"{} did not change; possible misconfiguration\".format(self.fields[0]))\n\n        params[self.fields[1]] = next_token\n        request_options[\"params\"] = params\n        return True, url, request_options\n\n\nregister(JSON)\n"
  },
  {
    "path": "redash/query_runner/kylin.py",
    "content": "import logging\nimport os\n\nimport requests\nfrom requests.auth import HTTPBasicAuth\n\nfrom redash import settings\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntypes_map = {\n    \"tinyint\": TYPE_INTEGER,\n    \"smallint\": TYPE_INTEGER,\n    \"integer\": TYPE_INTEGER,\n    \"bigint\": TYPE_INTEGER,\n    \"int4\": TYPE_INTEGER,\n    \"long8\": TYPE_INTEGER,\n    \"int\": TYPE_INTEGER,\n    \"short\": TYPE_INTEGER,\n    \"long\": TYPE_INTEGER,\n    \"byte\": TYPE_INTEGER,\n    \"hllc10\": TYPE_INTEGER,\n    \"hllc12\": TYPE_INTEGER,\n    \"hllc14\": TYPE_INTEGER,\n    \"hllc15\": TYPE_INTEGER,\n    \"hllc16\": TYPE_INTEGER,\n    \"hllc(10)\": TYPE_INTEGER,\n    \"hllc(12)\": TYPE_INTEGER,\n    \"hllc(14)\": TYPE_INTEGER,\n    \"hllc(15)\": TYPE_INTEGER,\n    \"hllc(16)\": TYPE_INTEGER,\n    \"float\": TYPE_FLOAT,\n    \"double\": TYPE_FLOAT,\n    \"decimal\": TYPE_FLOAT,\n    \"real\": TYPE_FLOAT,\n    \"numeric\": TYPE_FLOAT,\n    \"boolean\": TYPE_BOOLEAN,\n    \"bool\": TYPE_BOOLEAN,\n    \"date\": TYPE_DATE,\n    \"datetime\": TYPE_DATETIME,\n    \"timestamp\": TYPE_DATETIME,\n    \"time\": TYPE_DATETIME,\n    \"varchar\": TYPE_STRING,\n    \"char\": TYPE_STRING,\n    \"string\": TYPE_STRING,\n}\n\n\nclass Kylin(BaseQueryRunner):\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\", \"title\": \"Kylin Username\"},\n                \"password\": {\"type\": \"string\", \"title\": \"Kylin Password\"},\n                \"url\": {\n                    \"type\": \"string\",\n                    \"title\": \"Kylin API URL\",\n                    \"default\": \"http://kylin.example.com/kylin/\",\n                },\n                \"project\": {\"type\": \"string\", \"title\": \"Kylin Project\"},\n            },\n            \"order\": [\"url\", \"project\", \"user\", \"password\"],\n            \"required\": [\"url\", \"project\", \"user\", \"password\"],\n            \"secret\": [\"password\"],\n        }\n\n    def run_query(self, query, user):\n        url = self.configuration[\"url\"]\n        kylinuser = self.configuration[\"user\"]\n        kylinpass = self.configuration[\"password\"]\n        kylinproject = self.configuration[\"project\"]\n\n        resp = requests.post(\n            os.path.join(url, \"api/query\"),\n            auth=HTTPBasicAuth(kylinuser, kylinpass),\n            json={\n                \"sql\": query,\n                \"offset\": settings.KYLIN_OFFSET,\n                \"limit\": settings.KYLIN_LIMIT,\n                \"acceptPartial\": settings.KYLIN_ACCEPT_PARTIAL,\n                \"project\": kylinproject,\n            },\n        )\n\n        if not resp.ok:\n            return {}, resp.text or str(resp.reason)\n\n        data = resp.json()\n        columns = self.get_columns(data[\"columnMetas\"])\n        rows = self.get_rows(columns, data[\"results\"])\n\n        return {\"columns\": columns, \"rows\": rows}, None\n\n    def get_schema(self, get_stats=False):\n        url = self.configuration[\"url\"]\n        kylinuser = self.configuration[\"user\"]\n        kylinpass = self.configuration[\"password\"]\n        kylinproject = self.configuration[\"project\"]\n\n        resp = requests.get(\n            os.path.join(url, \"api/tables_and_columns\"),\n            params={\"project\": kylinproject},\n            auth=HTTPBasicAuth(kylinuser, kylinpass),\n        )\n\n        resp.raise_for_status()\n\n        data = resp.json()\n        return [self.get_table_schema(table) for table in data]\n\n    def test_connection(self):\n        url = self.configuration[\"url\"]\n        requests.get(url).raise_for_status()\n\n    def get_columns(self, colmetas):\n        return self.fetch_columns(\n            [\n                (\n                    meta[\"name\"],\n                    types_map.get(meta[\"columnTypeName\"].lower(), TYPE_STRING),\n                )\n                for meta in colmetas\n            ]\n        )\n\n    def get_rows(self, columns, results):\n        return [dict(zip((column[\"name\"] for column in columns), row)) for row in results]\n\n    def get_table_schema(self, table):\n        name = table[\"table_NAME\"]\n        columns = [col[\"column_NAME\"].lower() for col in table[\"columns\"]]\n        return {\"name\": name, \"columns\": columns}\n\n\nregister(Kylin)\n"
  },
  {
    "path": "redash/query_runner/memsql_ds.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    JobTimeoutException,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    from memsql.common import database\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nCOLUMN_NAME = 0\nCOLUMN_TYPE = 1\n\ntypes_map = {\n    \"BIGINT\": TYPE_INTEGER,\n    \"TINYINT\": TYPE_INTEGER,\n    \"SMALLINT\": TYPE_INTEGER,\n    \"MEDIUMINT\": TYPE_INTEGER,\n    \"INT\": TYPE_INTEGER,\n    \"DOUBLE\": TYPE_FLOAT,\n    \"DECIMAL\": TYPE_FLOAT,\n    \"FLOAT\": TYPE_FLOAT,\n    \"REAL\": TYPE_FLOAT,\n    \"BOOL\": TYPE_BOOLEAN,\n    \"BOOLEAN\": TYPE_BOOLEAN,\n    \"TIMESTAMP\": TYPE_DATETIME,\n    \"DATETIME\": TYPE_DATETIME,\n    \"DATE\": TYPE_DATETIME,\n    \"JSON\": TYPE_STRING,\n    \"CHAR\": TYPE_STRING,\n    \"VARCHAR\": TYPE_STRING,\n}\n\n\nclass MemSQL(BaseSQLQueryRunner):\n    should_annotate_query = False\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\"},\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n            },\n            \"required\": [\"host\", \"port\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"memsql\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    def _get_tables(self, schema):\n        schemas_query = \"show schemas\"\n\n        tables_query = \"show tables in %s\"\n\n        columns_query = \"show columns in %s\"\n\n        for schema_name in [\n            a for a in [str(a[\"Database\"]) for a in self._run_query_internal(schemas_query)] if len(a) > 0\n        ]:\n            for table_name in [\n                a\n                for a in [\n                    str(a[\"Tables_in_%s\" % schema_name]) for a in self._run_query_internal(tables_query % schema_name)\n                ]\n                if len(a) > 0\n            ]:\n                table_name = \".\".join((schema_name, table_name))\n                columns = [\n                    a\n                    for a in [str(a[\"Field\"]) for a in self._run_query_internal(columns_query % table_name)]\n                    if len(a) > 0\n                ]\n\n                schema[table_name] = {\"name\": table_name, \"columns\": columns}\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        cursor = None\n        try:\n            cursor = database.connect(**self.configuration.to_dict())\n\n            res = cursor.query(query)\n            # column_names = []\n            # columns = []\n            #\n            # for column in cursor.description:\n            #     column_name = column[COLUMN_NAME]\n            #     column_names.append(column_name)\n            #\n            #     columns.append({\n            #         'name': column_name,\n            #         'friendly_name': column_name,\n            #         'type': types_map.get(column[COLUMN_TYPE], None)\n            #     })\n\n            rows = [dict(zip(row.keys(), row.values())) for row in res]\n\n            # ====================================================================================================\n            # temporary - until https://github.com/memsql/memsql-python/pull/8 gets merged\n            # ====================================================================================================\n            columns = []\n            column_names = rows[0].keys() if rows else None\n\n            if column_names:\n                for column in column_names:\n                    columns.append({\"name\": column, \"friendly_name\": column, \"type\": TYPE_STRING})\n\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n        except (KeyboardInterrupt, JobTimeoutException):\n            cursor.close()\n            raise\n        finally:\n            if cursor:\n                cursor.close()\n\n        return data, error\n\n\nregister(MemSQL)\n"
  },
  {
    "path": "redash/query_runner/mongodb.py",
    "content": "import datetime\nimport logging\nimport re\n\nfrom dateutil.parser import parse\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\nfrom redash.utils import json_loads, parse_human_time\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import pymongo\n    from bson.decimal128 import Decimal128\n    from bson.json_util import JSONOptions\n    from bson.json_util import object_hook as bson_object_hook\n    from bson.objectid import ObjectId\n    from bson.son import SON\n    from bson.timestamp import Timestamp\n\n    enabled = True\n\nexcept ImportError:\n    enabled = False\n\n\nTYPES_MAP = {\n    str: TYPE_STRING,\n    bytes: TYPE_STRING,\n    int: TYPE_INTEGER,\n    float: TYPE_FLOAT,\n    bool: TYPE_BOOLEAN,\n    datetime.datetime: TYPE_DATETIME,\n}\n\n\ndate_regex = re.compile(r'ISODate\\(\"(.*)\"\\)', re.IGNORECASE)\n\n\ndef parse_oids(oids):\n    if not isinstance(oids, list):\n        raise Exception(\"$oids takes an array as input.\")\n\n    return [bson_object_hook({\"$oid\": oid}) for oid in oids]\n\n\ndef datetime_parser(dct):\n    for k, v in dct.items():\n        if isinstance(v, str):\n            m = date_regex.findall(v)\n            if len(m) > 0:\n                dct[k] = parse(m[0], yearfirst=True)\n\n    if \"$humanTime\" in dct:\n        return parse_human_time(dct[\"$humanTime\"])\n\n    if \"$oids\" in dct:\n        return parse_oids(dct[\"$oids\"])\n\n    opts = JSONOptions(tz_aware=True)\n    return bson_object_hook(dct, json_options=opts)\n\n\ndef parse_query_json(query: str):\n    query_data = json_loads(query, object_hook=datetime_parser)\n    return query_data\n\n\ndef _get_column_by_name(columns, column_name):\n    for c in columns:\n        if \"name\" in c and c[\"name\"] == column_name:\n            return c\n\n    return None\n\n\ndef _parse_dict(dic: dict, flatten: bool = False) -> dict:\n    res = {}\n\n    def _flatten(x, name=\"\"):\n        if isinstance(x, dict):\n            for k, v in x.items():\n                _flatten(v, \"{}.{}\".format(name, k))\n        elif isinstance(x, list):\n            for idx, item in enumerate(x):\n                _flatten(item, \"{}.{}\".format(name, idx))\n        else:\n            res[name[1:]] = x\n\n    if flatten:\n        _flatten(dic)\n    else:\n        for key, value in dic.items():\n            if isinstance(value, dict):\n                for tmp_key, tmp_value in _parse_dict(value).items():\n                    new_key = \"{}.{}\".format(key, tmp_key)\n                    res[new_key] = tmp_value\n            else:\n                res[key] = value\n    return res\n\n\ndef parse_results(results: list, flatten: bool = False) -> list:\n    rows = []\n    columns = []\n\n    for row in results:\n        parsed_row = {}\n\n        parsed_row = _parse_dict(row, flatten)\n        for column_name, value in parsed_row.items():\n            if _get_column_by_name(columns, column_name) is None:\n                columns.append(\n                    {\n                        \"name\": column_name,\n                        \"friendly_name\": column_name,\n                        \"type\": TYPES_MAP.get(type(value), TYPE_STRING),\n                    }\n                )\n\n        rows.append(parsed_row)\n\n    return rows, columns\n\n\ndef _sorted_fields(fields):\n    ord = {}\n    for k, v in fields.items():\n        if isinstance(v, int):\n            ord[k] = v\n        else:\n            ord[k] = len(fields)\n\n    return sorted(ord, key=ord.get)\n\n\nclass MongoDB(BaseQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"connectionString\": {\"type\": \"string\", \"title\": \"Connection String\"},\n                \"username\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"dbName\": {\"type\": \"string\", \"title\": \"Database Name\"},\n                \"replicaSetName\": {\"type\": \"string\", \"title\": \"Replica Set Name\"},\n                \"readPreference\": {\n                    \"type\": \"string\",\n                    \"extendedEnum\": [\n                        {\"value\": \"primaryPreferred\", \"name\": \"Primary Preferred\"},\n                        {\"value\": \"primary\", \"name\": \"Primary\"},\n                        {\"value\": \"secondary\", \"name\": \"Secondary\"},\n                        {\"value\": \"secondaryPreferred\", \"name\": \"Secondary Preferred\"},\n                        {\"value\": \"nearest\", \"name\": \"Nearest\"},\n                    ],\n                    \"title\": \"Replica Set Read Preference\",\n                },\n                \"flatten\": {\n                    \"type\": \"string\",\n                    \"extendedEnum\": [\n                        {\"value\": \"False\", \"name\": \"False\"},\n                        {\"value\": \"True\", \"name\": \"True\"},\n                    ],\n                    \"title\": \"Flatten Results\",\n                },\n            },\n            \"secret\": [\"password\"],\n            \"required\": [\"connectionString\", \"dbName\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    def __init__(self, configuration):\n        super(MongoDB, self).__init__(configuration)\n\n        self.syntax = \"json\"\n\n        self.db_name = self.configuration.get(\"dbName\", \"\")\n\n        self.is_replica_set = (\n            True if \"replicaSetName\" in self.configuration and self.configuration[\"replicaSetName\"] else False\n        )\n\n        self.flatten = self.configuration.get(\"flatten\", \"False\").upper() in [\"TRUE\", \"YES\", \"ON\", \"1\", \"Y\", \"T\"]\n        logger.debug(\"flatten: {}\".format(self.flatten))\n\n    @classmethod\n    def custom_json_encoder(cls, dec, o):\n        if isinstance(o, ObjectId):\n            return str(o)\n        elif isinstance(o, Timestamp):\n            return dec.default(o.as_datetime())\n        elif isinstance(o, Decimal128):\n            return o.to_decimal()\n        return None\n\n    def _get_db(self):\n        kwargs = {}\n        if self.is_replica_set:\n            kwargs[\"replicaSet\"] = self.configuration[\"replicaSetName\"]\n            readPreference = self.configuration.get(\"readPreference\")\n            if readPreference:\n                kwargs[\"readPreference\"] = readPreference\n\n        if self.configuration.get(\"username\"):\n            kwargs[\"username\"] = self.configuration[\"username\"]\n\n        if self.configuration.get(\"password\"):\n            kwargs[\"password\"] = self.configuration[\"password\"]\n\n        db_connection = pymongo.MongoClient(self.configuration[\"connectionString\"], **kwargs)\n\n        return db_connection[self.db_name]\n\n    def test_connection(self):\n        db = self._get_db()\n        if not db.command(\"connectionStatus\")[\"ok\"]:\n            raise Exception(\"MongoDB connection error\")\n\n        return db\n\n    def _merge_property_names(self, columns, document):\n        for property in document:\n            if property not in columns:\n                columns.append(property)\n\n    def _is_collection_a_view(self, db, collection_name):\n        if \"viewOn\" in db[collection_name].options():\n            return True\n        else:\n            return False\n\n    def _get_collection_fields(self, db, collection_name):\n        # Since MongoDB is a document based database and each document doesn't have\n        # to have the same fields as another documet in the collection its a bit hard to\n        # show these attributes as fields in the schema.\n        #\n        # For now, the logic is to take the first and last documents (last is determined\n        # by the Natural Order (http://www.mongodb.org/display/DOCS/Sorting+and+Natural+Order)\n        # as we don't know the correct order. In most single server installations it would be\n        # fine. In replicaset when reading from non master it might not return the really last\n        # document written.\n        collection_is_a_view = self._is_collection_a_view(db, collection_name)\n        documents_sample = []\n        try:\n            if collection_is_a_view:\n                for d in db[collection_name].find().limit(2):\n                    documents_sample.append(d)\n            else:\n                for d in db[collection_name].find().sort([(\"$natural\", 1)]).limit(1):\n                    documents_sample.append(d)\n\n                for d in db[collection_name].find().sort([(\"$natural\", -1)]).limit(1):\n                    documents_sample.append(d)\n        except Exception as ex:\n            template = \"An exception of type {0} occurred. Arguments:\\n{1!r}\"\n            message = template.format(type(ex).__name__, ex.args)\n            logger.error(message)\n            return []\n        columns = []\n        for d in documents_sample:\n            self._merge_property_names(columns, d)\n        return columns\n\n    def get_schema(self, get_stats=False):\n        schema = {}\n        db = self._get_db()\n        for collection_name in db.list_collection_names():\n            if collection_name.startswith(\"system.\"):\n                continue\n            columns = self._get_collection_fields(db, collection_name)\n            if columns:\n                schema[collection_name] = {\n                    \"name\": collection_name,\n                    \"columns\": sorted(columns),\n                }\n\n        return list(schema.values())\n\n    def run_query(self, query, user):  # noqa: C901\n        db = self._get_db()\n\n        logger.debug(\"mongodb connection string: %s\", self.configuration[\"connectionString\"])\n        logger.debug(\"mongodb got query: %s\", query)\n\n        try:\n            query_data = parse_query_json(query)\n        except ValueError as error:\n            return None, f\"Invalid JSON format. {error.__str__()}\"\n\n        if \"collection\" not in query_data:\n            return None, \"'collection' must have a value to run a query\"\n        else:\n            collection = query_data[\"collection\"]\n\n        q = query_data.get(\"query\", None)\n        f = None\n\n        aggregate = query_data.get(\"aggregate\", None)\n        if aggregate:\n            for step in aggregate:\n                if \"$sort\" in step:\n                    sort_list = []\n                    for sort_item in step[\"$sort\"]:\n                        if isinstance(sort_item, dict):\n                            sort_list.append((sort_item[\"name\"], sort_item.get(\"direction\", 1)))\n                        elif isinstance(sort_item, list):\n                            sort_list.append(tuple(sort_item))\n                    step[\"$sort\"] = SON(sort_list)\n\n        if \"fields\" in query_data:\n            f = query_data[\"fields\"]\n\n        s = None\n        if \"sort\" in query_data and query_data[\"sort\"]:\n            s = []\n            for field_data in query_data[\"sort\"]:\n                if isinstance(field_data, dict):\n                    s.append((field_data[\"name\"], field_data.get(\"direction\", 1)))\n                elif isinstance(field_data, list):\n                    s.append(tuple(field_data))\n\n        columns = []\n        rows = []\n\n        cursor = None\n        if q or (not q and not aggregate):\n            if \"count\" in query_data:\n                options = {opt: query_data[opt] for opt in (\"skip\", \"limit\") if opt in query_data}\n                cursor = db[collection].count_documents(q, **options)\n            else:\n                if s:\n                    cursor = db[collection].find(q, f).sort(s)\n                else:\n                    cursor = db[collection].find(q, f)\n\n                if \"skip\" in query_data:\n                    cursor = cursor.skip(query_data[\"skip\"])\n\n                if \"limit\" in query_data:\n                    cursor = cursor.limit(query_data[\"limit\"])\n\n        elif aggregate:\n            allow_disk_use = query_data.get(\"allowDiskUse\", False)\n            r = db[collection].aggregate(aggregate, allowDiskUse=allow_disk_use)\n\n            # Backwards compatibility with older pymongo versions.\n            #\n            # Older pymongo version would return a dictionary from an aggregate command.\n            # The dict would contain a \"result\" key which would hold the cursor.\n            # Newer ones return pymongo.command_cursor.CommandCursor.\n            if isinstance(r, dict):\n                cursor = r[\"result\"]\n            else:\n                cursor = r\n\n        if \"count\" in query_data:\n            columns.append({\"name\": \"count\", \"friendly_name\": \"count\", \"type\": TYPE_INTEGER})\n\n            rows.append({\"count\": cursor})\n        else:\n            rows, columns = parse_results(cursor, flatten=self.flatten)\n\n        if f:\n            ordered_columns = []\n            for k in _sorted_fields(f):\n                column = _get_column_by_name(columns, k)\n                if column:\n                    ordered_columns.append(column)\n\n            columns = ordered_columns\n            logger.debug(\"columns: {}\".format(columns))\n\n        if query_data.get(\"sortColumns\"):\n            reverse = query_data[\"sortColumns\"] == \"desc\"\n            columns = sorted(columns, key=lambda col: col[\"name\"], reverse=reverse)\n\n        data = {\"columns\": columns, \"rows\": rows}\n        error = None\n\n        return data, error\n\n\nregister(MongoDB)\n"
  },
  {
    "path": "redash/query_runner/mssql.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    JobTimeoutException,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import pymssql\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n# from _mssql.pyx ## DB-API type definitions & http://www.freetds.org/tds.html#types ##\ntypes_map = {\n    1: TYPE_STRING,\n    2: TYPE_STRING,\n    # Type #3 supposed to be an integer, but in some cases decimals are returned\n    # with this type. To be on safe side, marking it as float.\n    3: TYPE_FLOAT,\n    4: TYPE_DATETIME,\n    5: TYPE_FLOAT,\n}\n\n\nclass SqlServer(BaseSQLQueryRunner):\n    should_annotate_query = False\n    noop_query = \"SELECT 1\"\n\n    limit_query = \" TOP 1000\"\n    limit_keywords = [\"TOP\"]\n    limit_after_select = True\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"server\": {\"type\": \"string\", \"default\": \"127.0.0.1\"},\n                \"port\": {\"type\": \"number\", \"default\": 1433},\n                \"tds_version\": {\n                    \"type\": \"string\",\n                    \"default\": \"7.0\",\n                    \"title\": \"TDS Version\",\n                },\n                \"charset\": {\n                    \"type\": \"string\",\n                    \"default\": \"UTF-8\",\n                    \"title\": \"Character Set\",\n                },\n                \"db\": {\"type\": \"string\", \"title\": \"Database Name\"},\n            },\n            \"required\": [\"db\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def name(cls):\n        return \"Microsoft SQL Server\"\n\n    @classmethod\n    def type(cls):\n        return \"mssql\"\n\n    def _get_tables(self, schema):\n        query = \"\"\"\n        SELECT table_schema, table_name, column_name\n        FROM INFORMATION_SCHEMA.COLUMNS\n        WHERE table_schema NOT IN ('guest','INFORMATION_SCHEMA','sys','db_owner','db_accessadmin'\n                                  ,'db_securityadmin','db_ddladmin','db_backupoperator','db_datareader'\n                                  ,'db_datawriter','db_denydatareader','db_denydatawriter'\n                                  );\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            if row[\"table_schema\"] != self.configuration[\"db\"]:\n                table_name = \"{}.{}\".format(row[\"table_schema\"], row[\"table_name\"])\n            else:\n                table_name = row[\"table_name\"]\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"column_name\"])\n\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        connection = None\n\n        try:\n            server = self.configuration.get(\"server\", \"\")\n            user = self.configuration.get(\"user\", \"\")\n            password = self.configuration.get(\"password\", \"\")\n            db = self.configuration[\"db\"]\n            port = self.configuration.get(\"port\", 1433)\n            tds_version = self.configuration.get(\"tds_version\", \"7.0\")\n            charset = self.configuration.get(\"charset\", \"UTF-8\")\n\n            if port != 1433:\n                server = server + \":\" + str(port)\n\n            connection = pymssql.connect(\n                server=server,\n                user=user,\n                password=password,\n                database=db,\n                tds_version=tds_version,\n                charset=charset,\n            )\n\n            if isinstance(query, str):\n                query = query.encode(charset)\n\n            cursor = connection.cursor()\n            logger.debug(\"SqlServer running query: %s\", query)\n\n            cursor.execute(query)\n            data = cursor.fetchall()\n\n            if cursor.description is not None:\n                columns = self.fetch_columns([(i[0], types_map.get(i[1], None)) for i in cursor.description])\n                rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in data]\n\n                data = {\"columns\": columns, \"rows\": rows}\n                error = None\n            else:\n                error = \"No data was returned.\"\n                data = None\n\n            cursor.close()\n            connection.commit()\n        except pymssql.Error as e:\n            try:\n                # Query errors are at `args[1]`\n                error = e.args[1]\n            except IndexError:\n                # Connection errors are `args[0][1]`\n                error = e.args[0][1]\n            data = None\n        except (KeyboardInterrupt, JobTimeoutException):\n            connection.cancel()\n            raise\n        finally:\n            if connection:\n                connection.close()\n\n        return data, error\n\n\nregister(SqlServer)\n"
  },
  {
    "path": "redash/query_runner/mssql_odbc.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    BaseSQLQueryRunner,\n    JobTimeoutException,\n    register,\n)\nfrom redash.query_runner.mssql import types_map\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import pyodbc\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\nclass SQLServerODBC(BaseSQLQueryRunner):\n    should_annotate_query = False\n    noop_query = \"SELECT 1\"\n\n    limit_query = \" TOP 1000\"\n    limit_keywords = [\"TOP\"]\n    limit_after_select = True\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"server\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\", \"default\": 1433},\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"db\": {\"type\": \"string\", \"title\": \"Database Name\"},\n                \"charset\": {\n                    \"type\": \"string\",\n                    \"default\": \"UTF-8\",\n                    \"title\": \"Character Set\",\n                },\n                \"use_ssl\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Use SSL\",\n                    \"default\": False,\n                },\n                \"verify_ssl\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Verify SSL certificate\",\n                    \"default\": False,\n                },\n            },\n            \"order\": [\n                \"server\",\n                \"port\",\n                \"user\",\n                \"password\",\n                \"db\",\n                \"charset\",\n                \"use_ssl\",\n                \"verify_ssl\",\n            ],\n            \"required\": [\"server\", \"user\", \"password\", \"db\"],\n            \"secret\": [\"password\"],\n            \"extra_options\": [\"verify_ssl\", \"use_ssl\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def name(cls):\n        return \"Microsoft SQL Server (ODBC)\"\n\n    @classmethod\n    def type(cls):\n        return \"mssql_odbc\"\n\n    @property\n    def supports_auto_limit(self):\n        return False\n\n    def _get_tables(self, schema):\n        query = \"\"\"\n        SELECT table_schema, table_name, column_name\n        FROM INFORMATION_SCHEMA.COLUMNS\n        WHERE table_schema NOT IN ('guest','INFORMATION_SCHEMA','sys','db_owner','db_accessadmin'\n                                  ,'db_securityadmin','db_ddladmin','db_backupoperator','db_datareader'\n                                  ,'db_datawriter','db_denydatareader','db_denydatawriter'\n                                  );\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            if row[\"table_schema\"] != self.configuration[\"db\"]:\n                table_name = \"{}.{}\".format(row[\"table_schema\"], row[\"table_name\"])\n            else:\n                table_name = row[\"table_name\"]\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"column_name\"])\n\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        connection = None\n\n        try:\n            server = self.configuration.get(\"server\")\n            user = self.configuration.get(\"user\", \"\")\n            password = self.configuration.get(\"password\", \"\")\n            db = self.configuration[\"db\"]\n            port = self.configuration.get(\"port\", 1433)\n\n            connection_params = {\n                \"Driver\": \"{ODBC Driver 18 for SQL Server}\",\n                \"Server\": server,\n                \"Port\": port,\n                \"Database\": db,\n                \"Uid\": user,\n                \"Pwd\": password,\n            }\n\n            if self.configuration.get(\"use_ssl\", False):\n                connection_params[\"Encrypt\"] = \"YES\"\n\n                if not self.configuration.get(\"verify_ssl\"):\n                    connection_params[\"TrustServerCertificate\"] = \"YES\"\n                else:\n                    connection_params[\"TrustServerCertificate\"] = \"NO\"\n            else:\n                connection_params[\"Encrypt\"] = \"NO\"\n\n            def fn(k):\n                return \"{}={}\".format(k, connection_params[k])\n\n            connection_string = \";\".join(list(map(fn, connection_params)))\n\n            connection = pyodbc.connect(connection_string)\n            cursor = connection.cursor()\n            logger.debug(\"SQLServerODBC running query: %s\", query)\n            cursor.execute(query)\n            data = cursor.fetchall()\n\n            if cursor.description is not None:\n                columns = self.fetch_columns([(i[0], types_map.get(i[1], None)) for i in cursor.description])\n                rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in data]\n\n                data = {\"columns\": columns, \"rows\": rows}\n                error = None\n            else:\n                error = \"No data was returned.\"\n                data = None\n\n            cursor.close()\n        except pyodbc.Error as e:\n            try:\n                # Query errors are at `args[1]`\n                error = e.args[1]\n            except IndexError:\n                # Connection errors are `args[0][1]`\n                error = e.args[0][1]\n            data = None\n        except (KeyboardInterrupt, JobTimeoutException):\n            connection.cancel()\n            raise\n        finally:\n            if connection:\n                connection.close()\n\n        return data, error\n\n\nregister(SQLServerODBC)\n"
  },
  {
    "path": "redash/query_runner/mysql.py",
    "content": "import logging\nimport os\nimport threading\n\nfrom redash.query_runner import (\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    InterruptException,\n    JobTimeoutException,\n    register,\n)\nfrom redash.settings import parse_boolean\n\ntry:\n    import MySQLdb\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nlogger = logging.getLogger(__name__)\ntypes_map = {\n    0: TYPE_FLOAT,\n    1: TYPE_INTEGER,\n    2: TYPE_INTEGER,\n    3: TYPE_INTEGER,\n    4: TYPE_FLOAT,\n    5: TYPE_FLOAT,\n    7: TYPE_DATETIME,\n    8: TYPE_INTEGER,\n    9: TYPE_INTEGER,\n    10: TYPE_DATE,\n    12: TYPE_DATETIME,\n    15: TYPE_STRING,\n    16: TYPE_INTEGER,\n    246: TYPE_FLOAT,\n    253: TYPE_STRING,\n    254: TYPE_STRING,\n}\n\n\nclass Result:\n    def __init__(self):\n        pass\n\n\nclass Mysql(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        show_ssl_settings = parse_boolean(os.environ.get(\"MYSQL_SHOW_SSL_SETTINGS\", \"true\"))\n\n        schema = {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\", \"default\": \"127.0.0.1\"},\n                \"user\": {\"type\": \"string\"},\n                \"passwd\": {\"type\": \"string\", \"title\": \"Password\"},\n                \"db\": {\"type\": \"string\", \"title\": \"Database name\"},\n                \"port\": {\"type\": \"number\", \"default\": 3306},\n                \"connect_timeout\": {\"type\": \"number\", \"default\": 60, \"title\": \"Connection Timeout\"},\n                \"charset\": {\"type\": \"string\", \"default\": \"utf8mb4\"},\n                \"use_unicode\": {\"type\": \"boolean\", \"default\": True},\n                \"autocommit\": {\"type\": \"boolean\", \"default\": False},\n            },\n            \"order\": [\n                \"host\",\n                \"port\",\n                \"user\",\n                \"passwd\",\n                \"db\",\n                \"connect_timeout\",\n                \"charset\",\n                \"use_unicode\",\n                \"autocommit\",\n            ],\n            \"required\": [\"db\"],\n            \"secret\": [\"passwd\"],\n        }\n\n        if show_ssl_settings:\n            schema[\"properties\"].update(\n                {\n                    \"ssl_mode\": {\n                        \"type\": \"string\",\n                        \"title\": \"SSL Mode\",\n                        \"default\": \"preferred\",\n                        \"extendedEnum\": [\n                            {\"value\": \"disabled\", \"name\": \"Disabled\"},\n                            {\"value\": \"preferred\", \"name\": \"Preferred\"},\n                            {\"value\": \"required\", \"name\": \"Required\"},\n                            {\"value\": \"verify-ca\", \"name\": \"Verify CA\"},\n                            {\"value\": \"verify-identity\", \"name\": \"Verify Identity\"},\n                        ],\n                    },\n                    \"use_ssl\": {\"type\": \"boolean\", \"title\": \"Use SSL\"},\n                    \"ssl_cacert\": {\n                        \"type\": \"string\",\n                        \"title\": \"Path to CA certificate file to verify peer against (SSL)\",\n                    },\n                    \"ssl_cert\": {\n                        \"type\": \"string\",\n                        \"title\": \"Path to client certificate file (SSL)\",\n                    },\n                    \"ssl_key\": {\n                        \"type\": \"string\",\n                        \"title\": \"Path to private key file (SSL)\",\n                    },\n                }\n            )\n\n        return schema\n\n    @classmethod\n    def name(cls):\n        return \"MySQL\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    def _connection(self):\n        params = dict(\n            host=self.configuration.get(\"host\", \"\"),\n            user=self.configuration.get(\"user\", \"\"),\n            passwd=self.configuration.get(\"passwd\", \"\"),\n            db=self.configuration[\"db\"],\n            port=self.configuration.get(\"port\", 3306),\n            charset=self.configuration.get(\"charset\", \"utf8mb4\"),\n            use_unicode=self.configuration.get(\"use_unicode\", True),\n            connect_timeout=self.configuration.get(\"connect_timeout\", 60),\n            autocommit=self.configuration.get(\"autocommit\", True),\n        )\n\n        ssl_options = self._get_ssl_parameters()\n\n        if ssl_options:\n            params[\"ssl\"] = ssl_options\n\n        connection = MySQLdb.connect(**params)\n\n        return connection\n\n    def _get_tables(self, schema):\n        query = \"\"\"\n        SELECT col.table_schema as table_schema,\n               col.table_name as table_name,\n               col.column_name as column_name,\n               col.data_type as data_type,\n               col.column_comment as column_comment\n        FROM `information_schema`.`columns` col\n        WHERE LOWER(col.table_schema) NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys');\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            if row[\"table_schema\"] != self.configuration[\"db\"]:\n                table_name = \"{}.{}\".format(row[\"table_schema\"], row[\"table_name\"])\n            else:\n                table_name = row[\"table_name\"]\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(\n                {\n                    \"name\": row[\"column_name\"],\n                    \"type\": row[\"data_type\"],\n                    \"description\": row[\"column_comment\"],\n                }\n            )\n\n        table_query = \"\"\"\n                      SELECT col.table_schema as table_schema,\n                             col.table_name as table_name,\n                             col.table_comment as table_comment\n                      FROM `information_schema`.`tables` col\n                      WHERE LOWER(col.table_schema) NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys'); \\\n                      \"\"\"\n\n        results, error = self.run_query(table_query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            if row[\"table_schema\"] != self.configuration[\"db\"]:\n                table_name = \"{}.{}\".format(row[\"table_schema\"], row[\"table_name\"])\n            else:\n                table_name = row[\"table_name\"]\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            if \"table_comment\" in row and row[\"table_comment\"]:\n                schema[table_name][\"description\"] = row[\"table_comment\"]\n\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        ev = threading.Event()\n        thread_id = \"\"\n        r = Result()\n        t = None\n\n        try:\n            connection = self._connection()\n            thread_id = connection.thread_id()\n            t = threading.Thread(target=self._run_query, args=(query, user, connection, r, ev))\n            t.start()\n            while not ev.wait(1):\n                pass\n        except (KeyboardInterrupt, InterruptException, JobTimeoutException):\n            self._cancel(thread_id)\n            t.join()\n            raise\n\n        return r.data, r.error\n\n    def _run_query(self, query, user, connection, r, ev):\n        try:\n            cursor = connection.cursor()\n            logger.debug(\"MySQL running query: %s\", query)\n            cursor.execute(query)\n\n            data = cursor.fetchall()\n            desc = cursor.description\n\n            while cursor.nextset():\n                if cursor.description is not None:\n                    data = cursor.fetchall()\n                    desc = cursor.description\n\n            # TODO - very similar to pg.py\n            if desc is not None:\n                columns = self.fetch_columns([(i[0], types_map.get(i[1], None)) for i in desc])\n                rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in data]\n\n                data = {\"columns\": columns, \"rows\": rows}\n                r.data = data\n                r.error = None\n            else:\n                r.data = None\n                r.error = \"No data was returned.\"\n\n            cursor.close()\n        except MySQLdb.Error as e:\n            if cursor:\n                cursor.close()\n            r.data = None\n            r.error = e.args[1]\n        finally:\n            ev.set()\n            if connection:\n                connection.close()\n\n    def _get_ssl_parameters(self):\n        if not self.configuration.get(\"use_ssl\"):\n            return None\n\n        ssl_params = {}\n\n        if self.configuration.get(\"use_ssl\"):\n            config_map = {\"ssl_mode\": \"preferred\", \"ssl_cacert\": \"ca\", \"ssl_cert\": \"cert\", \"ssl_key\": \"key\"}\n            for key, cfg in config_map.items():\n                val = self.configuration.get(key)\n                if val:\n                    ssl_params[cfg] = val\n\n        return ssl_params\n\n    def _cancel(self, thread_id):\n        connection = None\n        cursor = None\n        error = None\n\n        try:\n            connection = self._connection()\n            cursor = connection.cursor()\n            query = \"KILL %d\" % (thread_id)\n            logging.debug(query)\n            cursor.execute(query)\n        except MySQLdb.Error as e:\n            if cursor:\n                cursor.close()\n            error = e.args[1]\n        finally:\n            if connection:\n                connection.close()\n\n        return error\n\n\nclass RDSMySQL(Mysql):\n    @classmethod\n    def name(cls):\n        return \"MySQL (Amazon RDS)\"\n\n    @classmethod\n    def type(cls):\n        return \"rds_mysql\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\"},\n                \"user\": {\"type\": \"string\"},\n                \"passwd\": {\"type\": \"string\", \"title\": \"Password\"},\n                \"db\": {\"type\": \"string\", \"title\": \"Database name\"},\n                \"port\": {\"type\": \"number\", \"default\": 3306},\n                \"use_ssl\": {\"type\": \"boolean\", \"title\": \"Use SSL\"},\n                \"charset\": {\"type\": \"string\", \"default\": \"utf8mb4\"},\n            },\n            \"order\": [\"host\", \"port\", \"user\", \"passwd\", \"db\"],\n            \"required\": [\"db\", \"user\", \"passwd\", \"host\"],\n            \"secret\": [\"passwd\"],\n        }\n\n    def _get_ssl_parameters(self):\n        if self.configuration.get(\"use_ssl\"):\n            ca_path = os.path.join(os.path.dirname(__file__), \"./files/rds-combined-ca-bundle.pem\")\n            return {\"ca\": ca_path}\n\n        return None\n\n\nregister(Mysql)\nregister(RDSMySQL)\n"
  },
  {
    "path": "redash/query_runner/nz.py",
    "content": "import logging\nimport traceback\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import nzpy\n    import nzpy.core\n\n    _enabled = True\n    _nztypes = {\n        nzpy.core.NzTypeInt1: TYPE_INTEGER,\n        nzpy.core.NzTypeInt2: TYPE_INTEGER,\n        nzpy.core.NzTypeInt: TYPE_INTEGER,\n        nzpy.core.NzTypeInt8: TYPE_INTEGER,\n        nzpy.core.NzTypeBool: TYPE_BOOLEAN,\n        nzpy.core.NzTypeDate: TYPE_DATE,\n        nzpy.core.NzTypeTimestamp: TYPE_DATETIME,\n        nzpy.core.NzTypeDouble: TYPE_FLOAT,\n        nzpy.core.NzTypeFloat: TYPE_FLOAT,\n        nzpy.core.NzTypeChar: TYPE_STRING,\n        nzpy.core.NzTypeNChar: TYPE_STRING,\n        nzpy.core.NzTypeNVarChar: TYPE_STRING,\n        nzpy.core.NzTypeVarChar: TYPE_STRING,\n        nzpy.core.NzTypeVarFixedChar: TYPE_STRING,\n        nzpy.core.NzTypeNumeric: TYPE_FLOAT,\n    }\n\n    _cat_types = {\n        16: TYPE_BOOLEAN,  # boolean\n        17: TYPE_STRING,  # bytea\n        19: TYPE_STRING,  # name type\n        20: TYPE_INTEGER,  # int8\n        21: TYPE_INTEGER,  # int2\n        23: TYPE_INTEGER,  # int4\n        25: TYPE_STRING,  # TEXT type\n        26: TYPE_INTEGER,  # oid\n        28: TYPE_INTEGER,  # xid\n        700: TYPE_FLOAT,  # float4\n        701: TYPE_FLOAT,  # float8\n        705: TYPE_STRING,  # unknown\n        829: TYPE_STRING,  # MACADDR type\n        1042: TYPE_STRING,  # CHAR type\n        1043: TYPE_STRING,  # VARCHAR type\n        1082: TYPE_DATE,  # date\n        1083: TYPE_DATETIME,\n        1114: TYPE_DATETIME,  # timestamp w/ tz\n        1184: TYPE_DATETIME,\n        1700: TYPE_FLOAT,  # NUMERIC\n        2275: TYPE_STRING,  # cstring\n        2950: TYPE_STRING,  # uuid\n    }\nexcept ImportError:\n    _enabled = False\n    _nztypes = {}\n    _cat_types = {}\n\n\nclass Netezza(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"host\": {\"type\": \"string\", \"default\": \"127.0.0.1\"},\n                \"port\": {\"type\": \"number\", \"default\": 5480},\n                \"database\": {\"type\": \"string\", \"title\": \"Database Name\", \"default\": \"system\"},\n            },\n            \"order\": [\"host\", \"port\", \"user\", \"password\", \"database\"],\n            \"required\": [\"user\", \"password\", \"database\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"nz\"\n\n    def __init__(self, configuration):\n        super().__init__(configuration)\n        self._conn = None\n\n    @property\n    def connection(self):\n        if self._conn is None:\n            self._conn = nzpy.connect(\n                host=self.configuration.get(\"host\"),\n                user=self.configuration.get(\"user\"),\n                password=self.configuration.get(\"password\"),\n                port=self.configuration.get(\"port\"),\n                database=self.configuration.get(\"database\"),\n            )\n        return self._conn\n\n    def get_schema(self, get_stats=False):\n        qry = \"\"\"\n        select\n            table_schema || '.' || table_name as table_name,\n            column_name,\n            data_type\n        from\n            columns\n        where\n            table_schema not in (^information_schema^, ^definition_schema^) and\n            table_catalog = current_catalog;\n        \"\"\"\n        schema = {}\n        with self.connection.cursor() as cursor:\n            cursor.execute(qry)\n            for table_name, column_name, data_type in cursor:\n                if table_name not in schema:\n                    schema[table_name] = {\"name\": table_name, \"columns\": []}\n                schema[table_name][\"columns\"].append({\"name\": column_name, \"type\": data_type})\n            return list(schema.values())\n\n    @classmethod\n    def enabled(cls):\n        global _enabled\n        return _enabled\n\n    def type_map(self, typid, func):\n        global _nztypes, _cat_types\n        typ = _nztypes.get(typid)\n        if typ is None:\n            return _cat_types.get(typid)\n        # check for conflicts\n        if typid == nzpy.core.NzTypeVarChar:\n            return TYPE_BOOLEAN if \"bool\" in func.__name__ else typ\n\n        if typid == nzpy.core.NzTypeInt2:\n            return TYPE_STRING if \"text\" in func.__name__ else typ\n\n        if typid in (nzpy.core.NzTypeVarFixedChar, nzpy.core.NzTypeVarBinary, nzpy.core.NzTypeNVarChar):\n            return TYPE_INTEGER if \"int\" in func.__name__ else typ\n        return typ\n\n    def run_query(self, query, user):\n        data, error = None, None\n        try:\n            with self.connection.cursor() as cursor:\n                cursor.execute(query)\n                if cursor.description is None:\n                    columns = {\"columns\": [], \"rows\": []}\n                else:\n                    columns = self.fetch_columns(\n                        [\n                            (val[0], self.type_map(val[1], cursor.ps[\"row_desc\"][i][\"func\"]))\n                            for i, val in enumerate(cursor.description)\n                        ]\n                    )\n                rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in cursor]\n\n                data = {\"columns\": columns, \"rows\": rows}\n        except Exception:\n            error = traceback.format_exc()\n        return data, error\n\n\nregister(Netezza)\n"
  },
  {
    "path": "redash/query_runner/oracle.py",
    "content": "import logging\nimport os\n\nfrom redash.query_runner import (\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    JobTimeoutException,\n    register,\n)\n\ntry:\n    import oracledb\n\n    TYPES_MAP = {\n        oracledb.DATETIME: TYPE_DATETIME,\n        oracledb.CLOB: TYPE_STRING,\n        oracledb.LOB: TYPE_STRING,\n        oracledb.FIXED_CHAR: TYPE_STRING,\n        oracledb.FIXED_NCHAR: TYPE_STRING,\n        oracledb.INTERVAL: TYPE_DATETIME,\n        oracledb.LONG_STRING: TYPE_STRING,\n        oracledb.NATIVE_FLOAT: TYPE_FLOAT,\n        oracledb.NCHAR: TYPE_STRING,\n        oracledb.NUMBER: TYPE_FLOAT,\n        oracledb.ROWID: TYPE_INTEGER,\n        oracledb.STRING: TYPE_STRING,\n        oracledb.TIMESTAMP: TYPE_DATETIME,\n    }\n\n    ENABLED = True\nexcept ImportError:\n    ENABLED = False\n\nlogger = logging.getLogger(__name__)\n\n\nclass Oracle(BaseSQLQueryRunner):\n    should_annotate_query = False\n    noop_query = \"SELECT 1 FROM dual\"\n    limit_query = \" FETCH NEXT 1000 ROWS ONLY\"\n    limit_keywords = [\"ROW\", \"ROWS\", \"ONLY\", \"TIES\"]\n\n    @classmethod\n    def get_col_type(cls, col_type, scale):\n        if col_type == oracledb.NUMBER:\n            if scale is None:\n                return TYPE_INTEGER\n            if scale > 0:\n                return TYPE_FLOAT\n            return TYPE_INTEGER\n        else:\n            return TYPES_MAP.get(col_type, None)\n\n    @classmethod\n    def enabled(cls):\n        return ENABLED\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"host\": {\n                    \"type\": \"string\",\n                    \"title\": \"Host: To use a DSN Service Name instead, use the text string `_useservicename` in the host name field.\",\n                },\n                \"port\": {\"type\": \"number\"},\n                \"servicename\": {\"type\": \"string\", \"title\": \"DSN Service Name\"},\n                \"encoding\": {\"type\": \"string\"},\n            },\n            \"required\": [\"servicename\", \"user\", \"password\", \"host\", \"port\"],\n            \"extra_options\": [\"encoding\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"oracle\"\n\n    def _get_tables(self, schema):\n        query = \"\"\"\n        SELECT\n            all_tab_cols.OWNER,\n            all_tab_cols.TABLE_NAME,\n            all_tab_cols.COLUMN_NAME\n        FROM all_tab_cols\n        WHERE all_tab_cols.OWNER NOT IN('SYS','SYSTEM','ORDSYS','CTXSYS','WMSYS','MDSYS','ORDDATA','XDB','OUTLN','DMSYS','DSSYS','EXFSYS','LBACSYS','TSMSYS')\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            if row[\"OWNER\"] is not None:\n                table_name = \"{}.{}\".format(row[\"OWNER\"], row[\"TABLE_NAME\"])\n            else:\n                table_name = row[\"TABLE_NAME\"]\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"COLUMN_NAME\"])\n\n        return list(schema.values())\n\n    @classmethod\n    def _convert_number(cls, value):\n        try:\n            return int(value)\n        except BaseException:\n            return value\n\n    @classmethod\n    def output_handler(cls, cursor, name, default_type, length, precision, scale):\n        if default_type in (oracledb.CLOB, oracledb.LOB):\n            return cursor.var(oracledb.LONG_STRING, 80000, cursor.arraysize)\n\n        if default_type in (oracledb.STRING, oracledb.FIXED_CHAR):\n            return cursor.var(str, length, cursor.arraysize)\n\n        if default_type == oracledb.NUMBER:\n            if scale <= 0:\n                return cursor.var(\n                    oracledb.STRING,\n                    255,\n                    outconverter=Oracle._convert_number,\n                    arraysize=cursor.arraysize,\n                )\n\n    def run_query(self, query, user):\n        if self.configuration.get(\"encoding\"):\n            os.environ[\"NLS_LANG\"] = self.configuration[\"encoding\"]\n\n        # To use a DSN Service Name instead, use the text string `_useservicename` in the host name field.\n        if self.configuration[\"host\"].lower() == \"_useservicename\":\n            dsn = self.configuration[\"servicename\"]\n        else:\n            dsn = oracledb.makedsn(\n                self.configuration[\"host\"],\n                self.configuration[\"port\"],\n                service_name=self.configuration[\"servicename\"],\n            )\n\n        connection = oracledb.connect(\n            user=self.configuration[\"user\"],\n            password=self.configuration[\"password\"],\n            dsn=dsn,\n        )\n        connection.outputtypehandler = Oracle.output_handler\n\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(query)\n            rows_count = cursor.rowcount\n            if cursor.description is not None:\n                columns = self.fetch_columns([(i[0], Oracle.get_col_type(i[1], i[5])) for i in cursor.description])\n                rows = [dict(zip((c[\"name\"] for c in columns), row)) for row in cursor]\n                data = {\"columns\": columns, \"rows\": rows}\n                error = None\n            else:\n                columns = [{\"name\": \"Row(s) Affected\", \"type\": \"TYPE_INTEGER\"}]\n                rows = [{\"Row(s) Affected\": rows_count}]\n                data = {\"columns\": columns, \"rows\": rows}\n                connection.commit()\n        except oracledb.DatabaseError as err:\n            (err_args,) = err.args\n            line_number = query.count(\"\\n\", 0, err_args.offset) + 1\n            column_number = err_args.offset - query.rfind(\"\\n\", 0, err_args.offset) - 1\n            error = \"Query failed at line {}, column {}: {}\".format(str(line_number), str(column_number), str(err))\n            data = None\n        except (KeyboardInterrupt, JobTimeoutException):\n            connection.cancel()\n            raise\n        finally:\n            os.environ.pop(\"NLS_LANG\", None)\n            connection.close()\n\n        return data, error\n\n\nregister(Oracle)\n"
  },
  {
    "path": "redash/query_runner/pg.py",
    "content": "import logging\nimport os\nimport select\nfrom base64 import b64decode\nfrom tempfile import NamedTemporaryFile\nfrom uuid import uuid4\n\nimport psycopg2\nfrom psycopg2.extras import Range\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    InterruptException,\n    JobTimeoutException,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import boto3\n\n    IAM_ENABLED = True\nexcept ImportError:\n    IAM_ENABLED = False\n\ntypes_map = {\n    20: TYPE_INTEGER,\n    21: TYPE_INTEGER,\n    23: TYPE_INTEGER,\n    700: TYPE_FLOAT,\n    1700: TYPE_FLOAT,\n    701: TYPE_FLOAT,\n    16: TYPE_BOOLEAN,\n    1082: TYPE_DATE,\n    1182: TYPE_DATE,\n    1114: TYPE_DATETIME,\n    1184: TYPE_DATETIME,\n    1115: TYPE_DATETIME,\n    1185: TYPE_DATETIME,\n    1014: TYPE_STRING,\n    1015: TYPE_STRING,\n    1008: TYPE_STRING,\n    1009: TYPE_STRING,\n    2951: TYPE_STRING,\n    1043: TYPE_STRING,\n    1002: TYPE_STRING,\n    1003: TYPE_STRING,\n}\n\n\ndef _wait(conn, timeout=None):\n    while 1:\n        try:\n            state = conn.poll()\n            if state == psycopg2.extensions.POLL_OK:\n                break\n            elif state == psycopg2.extensions.POLL_WRITE:\n                select.select([], [conn.fileno()], [], timeout)\n            elif state == psycopg2.extensions.POLL_READ:\n                select.select([conn.fileno()], [], [], timeout)\n            else:\n                raise psycopg2.OperationalError(\"poll() returned %s\" % state)\n        except select.error:\n            raise psycopg2.OperationalError(\"select.error received\")\n\n\ndef full_table_name(schema, name):\n    if \".\" in name:\n        name = '\"{}\"'.format(name)\n\n    return \"{}.{}\".format(schema, name)\n\n\ndef build_schema(query_result, schema):\n    # By default we omit the public schema name from the table name. But there are\n    # edge cases, where this might cause conflicts. For example:\n    # * We have a schema named \"main\" with table \"users\".\n    # * We have a table named \"main.users\" in the public schema.\n    # (while this feels unlikely, this actually happened)\n    # In this case if we omit the schema name for the public table, we will have\n    # a conflict.\n    table_names = set(\n        map(\n            lambda r: full_table_name(r[\"table_schema\"], r[\"table_name\"]),\n            query_result[\"rows\"],\n        )\n    )\n\n    for row in query_result[\"rows\"]:\n        if row[\"table_schema\"] != \"public\":\n            table_name = full_table_name(row[\"table_schema\"], row[\"table_name\"])\n        else:\n            if row[\"table_name\"] in table_names:\n                table_name = full_table_name(row[\"table_schema\"], row[\"table_name\"])\n            else:\n                table_name = row[\"table_name\"]\n\n        if table_name not in schema:\n            schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n        column = row[\"column_name\"]\n        if row.get(\"data_type\") is not None:\n            column = {\"name\": row[\"column_name\"], \"type\": row[\"data_type\"]}\n\n        schema[table_name][\"columns\"].append(column)\n\n\ndef _create_cert_file(configuration, key, ssl_config):\n    file_key = key + \"File\"\n    if file_key in configuration:\n        with NamedTemporaryFile(mode=\"w\", delete=False) as cert_file:\n            cert_bytes = b64decode(configuration[file_key])\n            cert_file.write(cert_bytes.decode(\"utf-8\"))\n\n        ssl_config[key] = cert_file.name\n\n\ndef _cleanup_ssl_certs(ssl_config):\n    for k, v in ssl_config.items():\n        if k != \"sslmode\":\n            os.remove(v)\n\n\ndef _get_ssl_config(configuration):\n    ssl_config = {\"sslmode\": configuration.get(\"sslmode\", \"prefer\")}\n\n    _create_cert_file(configuration, \"sslrootcert\", ssl_config)\n    _create_cert_file(configuration, \"sslcert\", ssl_config)\n    _create_cert_file(configuration, \"sslkey\", ssl_config)\n\n    return ssl_config\n\n\ndef _parse_dsn(configuration):\n    standard_params = {\"user\", \"password\", \"host\", \"port\", \"dbname\"}\n    params = psycopg2.extensions.parse_dsn(configuration.get(\"dsn\", \"\"))\n    overlap = standard_params.intersection(params.keys())\n    if overlap:\n        raise ValueError(\"Extra parameters may not contain {}\".format(overlap))\n    return params\n\n\nclass PostgreSQL(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"host\": {\"type\": \"string\", \"default\": \"127.0.0.1\"},\n                \"port\": {\"type\": \"number\", \"default\": 5432},\n                \"dbname\": {\"type\": \"string\", \"title\": \"Database Name\"},\n                \"dsn\": {\"type\": \"string\", \"default\": \"application_name=redash\", \"title\": \"Parameters\"},\n                \"sslmode\": {\n                    \"type\": \"string\",\n                    \"title\": \"SSL Mode\",\n                    \"default\": \"prefer\",\n                    \"extendedEnum\": [\n                        {\"value\": \"disable\", \"name\": \"Disable\"},\n                        {\"value\": \"allow\", \"name\": \"Allow\"},\n                        {\"value\": \"prefer\", \"name\": \"Prefer\"},\n                        {\"value\": \"require\", \"name\": \"Require\"},\n                        {\"value\": \"verify-ca\", \"name\": \"Verify CA\"},\n                        {\"value\": \"verify-full\", \"name\": \"Verify Full\"},\n                    ],\n                },\n                \"sslrootcertFile\": {\"type\": \"string\", \"title\": \"SSL Root Certificate\"},\n                \"sslcertFile\": {\"type\": \"string\", \"title\": \"SSL Client Certificate\"},\n                \"sslkeyFile\": {\"type\": \"string\", \"title\": \"SSL Client Key\"},\n            },\n            \"order\": [\"host\", \"port\", \"user\", \"password\"],\n            \"required\": [\"dbname\"],\n            \"secret\": [\"password\", \"sslrootcertFile\", \"sslcertFile\", \"sslkeyFile\"],\n            \"extra_options\": [\n                \"sslmode\",\n                \"sslrootcertFile\",\n                \"sslcertFile\",\n                \"sslkeyFile\",\n            ],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"pg\"\n\n    @classmethod\n    def custom_json_encoder(cls, dec, o):\n        if isinstance(o, Range):\n            # From: https://github.com/psycopg/psycopg2/pull/779\n            if o._bounds is None:\n                return \"\"\n\n            items = [o._bounds[0], str(o._lower), \", \", str(o._upper), o._bounds[1]]\n\n            return \"\".join(items)\n        return None\n\n    def _get_definitions(self, schema, query):\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        build_schema(results, schema)\n\n    def _get_tables(self, schema):\n        \"\"\"\n        relkind constants from https://www.postgresql.org/docs/current/catalog-pg-class.html\n        m = materialized view\n        \"\"\"\n\n        query = \"\"\"\n        SELECT s.nspname AS table_schema,\n               c.relname AS table_name,\n               a.attname AS column_name,\n               NULL AS data_type\n        FROM pg_class c\n        JOIN pg_namespace s\n        ON c.relnamespace = s.oid\n        AND s.nspname NOT IN ('pg_catalog', 'information_schema')\n        JOIN pg_attribute a\n        ON a.attrelid = c.oid\n        AND a.attnum > 0\n        AND NOT a.attisdropped\n        WHERE c.relkind = 'm'\n        AND has_table_privilege(quote_ident(s.nspname) || '.' || quote_ident(c.relname), 'select')\n        AND has_schema_privilege(s.nspname, 'usage')\n\n        UNION\n\n        SELECT table_schema,\n               table_name,\n               column_name,\n               data_type\n        FROM information_schema.columns\n        WHERE table_schema NOT IN ('pg_catalog', 'information_schema')\n        AND has_table_privilege(quote_ident(table_schema) || '.' || quote_ident(table_name), 'select')\n        AND has_schema_privilege(table_schema, 'usage')\n        \"\"\"\n\n        self._get_definitions(schema, query)\n\n        return list(schema.values())\n\n    def _get_connection(self):\n        self.ssl_config = _get_ssl_config(self.configuration)\n        self.dsn = _parse_dsn(self.configuration)\n        connection = psycopg2.connect(\n            user=self.configuration.get(\"user\"),\n            password=self.configuration.get(\"password\"),\n            host=self.configuration.get(\"host\"),\n            port=self.configuration.get(\"port\"),\n            dbname=self.configuration.get(\"dbname\"),\n            async_=True,\n            **self.ssl_config,\n            **self.dsn,\n        )\n\n        return connection\n\n    def run_query(self, query, user):\n        connection = self._get_connection()\n        _wait(connection, timeout=10)\n\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(query)\n            _wait(connection)\n\n            if cursor.description is not None:\n                columns = self.fetch_columns([(i[0], types_map.get(i[1], None)) for i in cursor.description])\n                rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in cursor]\n\n                data = {\"columns\": columns, \"rows\": rows}\n                error = None\n            else:\n                error = \"Query completed but it returned no data.\"\n                data = None\n        except (select.error, OSError):\n            error = \"Query interrupted. Please retry.\"\n            data = None\n        except psycopg2.DatabaseError as e:\n            error = str(e)\n            data = None\n        except (KeyboardInterrupt, InterruptException, JobTimeoutException):\n            connection.cancel()\n            raise\n        finally:\n            connection.close()\n            _cleanup_ssl_certs(self.ssl_config)\n\n        return data, error\n\n\nclass Redshift(PostgreSQL):\n    @classmethod\n    def type(cls):\n        return \"redshift\"\n\n    @classmethod\n    def name(cls):\n        return \"Redshift\"\n\n    def _get_connection(self):\n        self.ssl_config = {}\n\n        sslrootcert_path = os.path.join(os.path.dirname(__file__), \"./files/redshift-ca-bundle.crt\")\n\n        connection = psycopg2.connect(\n            user=self.configuration.get(\"user\"),\n            password=self.configuration.get(\"password\"),\n            host=self.configuration.get(\"host\"),\n            port=self.configuration.get(\"port\"),\n            dbname=self.configuration.get(\"dbname\"),\n            sslmode=self.configuration.get(\"sslmode\", \"prefer\"),\n            sslrootcert=sslrootcert_path,\n            async_=True,\n        )\n\n        return connection\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\"},\n                \"dbname\": {\"type\": \"string\", \"title\": \"Database Name\"},\n                \"sslmode\": {\"type\": \"string\", \"title\": \"SSL Mode\", \"default\": \"prefer\"},\n                \"adhoc_query_group\": {\n                    \"type\": \"string\",\n                    \"title\": \"Query Group for Adhoc Queries\",\n                    \"default\": \"default\",\n                },\n                \"scheduled_query_group\": {\n                    \"type\": \"string\",\n                    \"title\": \"Query Group for Scheduled Queries\",\n                    \"default\": \"default\",\n                },\n            },\n            \"order\": [\n                \"host\",\n                \"port\",\n                \"user\",\n                \"password\",\n                \"dbname\",\n                \"sslmode\",\n                \"adhoc_query_group\",\n                \"scheduled_query_group\",\n            ],\n            \"required\": [\"dbname\", \"user\", \"password\", \"host\", \"port\"],\n            \"secret\": [\"password\"],\n        }\n\n    def annotate_query(self, query, metadata):\n        annotated = super(Redshift, self).annotate_query(query, metadata)\n\n        if metadata.get(\"Scheduled\", False):\n            query_group = self.configuration.get(\"scheduled_query_group\")\n        else:\n            query_group = self.configuration.get(\"adhoc_query_group\")\n\n        if query_group:\n            set_query_group = \"set query_group to {};\".format(query_group)\n            annotated = \"{}\\n{}\".format(set_query_group, annotated)\n\n        return annotated\n\n    def _get_tables(self, schema):\n        # Use svv_columns to include internal & external (Spectrum) tables and views data for Redshift\n        # https://docs.aws.amazon.com/redshift/latest/dg/r_SVV_COLUMNS.html\n        # Use HAS_SCHEMA_PRIVILEGE(), SVV_EXTERNAL_SCHEMAS and HAS_TABLE_PRIVILEGE() to filter\n        # out tables the current user cannot access.\n        # https://docs.aws.amazon.com/redshift/latest/dg/r_HAS_SCHEMA_PRIVILEGE.html\n        # https://docs.aws.amazon.com/redshift/latest/dg/r_SVV_EXTERNAL_SCHEMAS.html\n        # https://docs.aws.amazon.com/redshift/latest/dg/r_HAS_TABLE_PRIVILEGE.html\n        query = \"\"\"\n        WITH tables AS (\n            SELECT DISTINCT table_name,\n                            table_schema,\n                            column_name,\n                            data_type,\n                            ordinal_position AS pos\n            FROM svv_columns\n            WHERE table_schema NOT IN ('pg_internal','pg_catalog','information_schema')\n            AND table_schema NOT LIKE 'pg_temp_%'\n        )\n        SELECT table_name, table_schema, column_name, data_type\n        FROM tables\n        WHERE\n            HAS_SCHEMA_PRIVILEGE(table_schema, 'USAGE') AND\n            (\n                table_schema IN (SELECT schemaname FROM SVV_EXTERNAL_SCHEMAS) OR\n                HAS_TABLE_PRIVILEGE('\"' || table_schema || '\".\"' || table_name || '\"', 'SELECT')\n            )\n        ORDER BY table_name, pos\n        \"\"\"\n\n        self._get_definitions(schema, query)\n\n        return list(schema.values())\n\n\nclass RedshiftIAM(Redshift):\n    @classmethod\n    def type(cls):\n        return \"redshift_iam\"\n\n    @classmethod\n    def name(cls):\n        return \"Redshift (with IAM User/Role)\"\n\n    @classmethod\n    def enabled(cls):\n        return IAM_ENABLED\n\n    def _login_method_selection(self):\n        if self.configuration.get(\"rolename\"):\n            if not self.configuration.get(\"aws_access_key_id\") or not self.configuration.get(\"aws_secret_access_key\"):\n                return \"ASSUME_ROLE_NO_KEYS\"\n            else:\n                return \"ASSUME_ROLE_KEYS\"\n        elif self.configuration.get(\"aws_access_key_id\") and self.configuration.get(\"aws_secret_access_key\"):\n            return \"KEYS\"\n        elif not self.configuration.get(\"password\"):\n            return \"ROLE\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"rolename\": {\"type\": \"string\", \"title\": \"IAM Role Name\"},\n                \"aws_region\": {\"type\": \"string\", \"title\": \"AWS Region\"},\n                \"aws_access_key_id\": {\"type\": \"string\", \"title\": \"AWS Access Key ID\"},\n                \"aws_secret_access_key\": {\n                    \"type\": \"string\",\n                    \"title\": \"AWS Secret Access Key\",\n                },\n                \"clusterid\": {\"type\": \"string\", \"title\": \"Redshift Cluster ID\"},\n                \"user\": {\"type\": \"string\"},\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\"},\n                \"dbname\": {\"type\": \"string\", \"title\": \"Database Name\"},\n                \"sslmode\": {\"type\": \"string\", \"title\": \"SSL Mode\", \"default\": \"prefer\"},\n                \"adhoc_query_group\": {\n                    \"type\": \"string\",\n                    \"title\": \"Query Group for Adhoc Queries\",\n                    \"default\": \"default\",\n                },\n                \"scheduled_query_group\": {\n                    \"type\": \"string\",\n                    \"title\": \"Query Group for Scheduled Queries\",\n                    \"default\": \"default\",\n                },\n            },\n            \"order\": [\n                \"rolename\",\n                \"aws_region\",\n                \"aws_access_key_id\",\n                \"aws_secret_access_key\",\n                \"clusterid\",\n                \"host\",\n                \"port\",\n                \"user\",\n                \"dbname\",\n                \"sslmode\",\n                \"adhoc_query_group\",\n                \"scheduled_query_group\",\n            ],\n            \"required\": [\"dbname\", \"user\", \"host\", \"port\", \"aws_region\"],\n            \"secret\": [\"aws_secret_access_key\"],\n        }\n\n    def _get_connection(self):\n        self.ssl_config = {}\n\n        sslrootcert_path = os.path.join(os.path.dirname(__file__), \"./files/redshift-ca-bundle.crt\")\n\n        login_method = self._login_method_selection()\n\n        if login_method == \"KEYS\":\n            client = boto3.client(\n                \"redshift\",\n                region_name=self.configuration.get(\"aws_region\"),\n                aws_access_key_id=self.configuration.get(\"aws_access_key_id\"),\n                aws_secret_access_key=self.configuration.get(\"aws_secret_access_key\"),\n            )\n        elif login_method == \"ROLE\":\n            client = boto3.client(\"redshift\", region_name=self.configuration.get(\"aws_region\"))\n        else:\n            if login_method == \"ASSUME_ROLE_KEYS\":\n                assume_client = client = boto3.client(\n                    \"sts\",\n                    region_name=self.configuration.get(\"aws_region\"),\n                    aws_access_key_id=self.configuration.get(\"aws_access_key_id\"),\n                    aws_secret_access_key=self.configuration.get(\"aws_secret_access_key\"),\n                )\n            else:\n                assume_client = client = boto3.client(\"sts\", region_name=self.configuration.get(\"aws_region\"))\n            role_session = f\"redash_{uuid4().hex}\"\n            session_keys = assume_client.assume_role(\n                RoleArn=self.configuration.get(\"rolename\"), RoleSessionName=role_session\n            )[\"Credentials\"]\n            client = boto3.client(\n                \"redshift\",\n                region_name=self.configuration.get(\"aws_region\"),\n                aws_access_key_id=session_keys[\"AccessKeyId\"],\n                aws_secret_access_key=session_keys[\"SecretAccessKey\"],\n                aws_session_token=session_keys[\"SessionToken\"],\n            )\n        credentials = client.get_cluster_credentials(\n            DbUser=self.configuration.get(\"user\"),\n            DbName=self.configuration.get(\"dbname\"),\n            ClusterIdentifier=self.configuration.get(\"clusterid\"),\n        )\n        db_user = credentials[\"DbUser\"]\n        db_password = credentials[\"DbPassword\"]\n        connection = psycopg2.connect(\n            user=db_user,\n            password=db_password,\n            host=self.configuration.get(\"host\"),\n            port=self.configuration.get(\"port\"),\n            dbname=self.configuration.get(\"dbname\"),\n            sslmode=self.configuration.get(\"sslmode\", \"prefer\"),\n            sslrootcert=sslrootcert_path,\n            async_=True,\n        )\n\n        return connection\n\n\nclass CockroachDB(PostgreSQL):\n    @classmethod\n    def type(cls):\n        return \"cockroach\"\n\n\nregister(PostgreSQL)\nregister(Redshift)\nregister(RedshiftIAM)\nregister(CockroachDB)\n"
  },
  {
    "path": "redash/query_runner/phoenix.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import phoenixdb\n    from phoenixdb.errors import Error\n\n    enabled = True\n\nexcept ImportError:\n    enabled = False\n\nTYPES_MAPPING = {\n    \"VARCHAR\": TYPE_STRING,\n    \"CHAR\": TYPE_STRING,\n    \"BINARY\": TYPE_STRING,\n    \"VARBINARY\": TYPE_STRING,\n    \"BOOLEAN\": TYPE_BOOLEAN,\n    \"TIME\": TYPE_DATETIME,\n    \"DATE\": TYPE_DATETIME,\n    \"TIMESTAMP\": TYPE_DATETIME,\n    \"UNSIGNED_TIME\": TYPE_DATETIME,\n    \"UNSIGNED_DATE\": TYPE_DATETIME,\n    \"UNSIGNED_TIMESTAMP\": TYPE_DATETIME,\n    \"INTEGER\": TYPE_INTEGER,\n    \"UNSIGNED_INT\": TYPE_INTEGER,\n    \"BIGINT\": TYPE_INTEGER,\n    \"UNSIGNED_LONG\": TYPE_INTEGER,\n    \"TINYINT\": TYPE_INTEGER,\n    \"UNSIGNED_TINYINT\": TYPE_INTEGER,\n    \"SMALLINT\": TYPE_INTEGER,\n    \"UNSIGNED_SMALLINT\": TYPE_INTEGER,\n    \"FLOAT\": TYPE_FLOAT,\n    \"UNSIGNED_FLOAT\": TYPE_FLOAT,\n    \"DOUBLE\": TYPE_FLOAT,\n    \"UNSIGNED_DOUBLE\": TYPE_FLOAT,\n    \"DECIMAL\": TYPE_FLOAT,\n}\n\n\nclass Phoenix(BaseQueryRunner):\n    noop_query = \"select 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\"url\": {\"type\": \"string\"}},\n            \"required\": [\"url\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def type(cls):\n        return \"phoenix\"\n\n    def get_schema(self, get_stats=False):\n        schema = {}\n        query = \"\"\"\n        SELECT TABLE_SCHEM, TABLE_NAME, COLUMN_NAME\n        FROM SYSTEM.CATALOG\n        WHERE TABLE_SCHEM IS NULL OR TABLE_SCHEM != 'SYSTEM' AND COLUMN_NAME IS NOT NULL\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            table_name = \"{}.{}\".format(row[\"TABLE_SCHEM\"], row[\"TABLE_NAME\"])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"COLUMN_NAME\"])\n\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        connection = phoenixdb.connect(url=self.configuration.get(\"url\", \"\"), autocommit=True)\n\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(query)\n            column_tuples = [(i[0], TYPES_MAPPING.get(i[1], None)) for i in cursor.description]\n            columns = self.fetch_columns(column_tuples)\n            rows = [dict(zip(([column[\"name\"] for column in columns]), r)) for i, r in enumerate(cursor.fetchall())]\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n            cursor.close()\n        except Error as e:\n            data = None\n            error = \"code: {}, sql state:{}, message: {}\".format(e.code, e.sqlstate, str(e))\n        finally:\n            if connection:\n                connection.close()\n\n        return data, error\n\n\nregister(Phoenix)\n"
  },
  {
    "path": "redash/query_runner/pinot.py",
    "content": "try:\n    import pinotdb\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nimport logging\n\nimport requests\nfrom requests.auth import HTTPBasicAuth\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\nPINOT_TYPES_MAPPING = {\n    \"BOOLEAN\": TYPE_BOOLEAN,\n    \"INT\": TYPE_INTEGER,\n    \"LONG\": TYPE_INTEGER,\n    \"FLOAT\": TYPE_FLOAT,\n    \"DOUBLE\": TYPE_FLOAT,\n    \"STRING\": TYPE_STRING,\n    \"BYTES\": TYPE_STRING,\n    \"JSON\": TYPE_STRING,\n    \"TIMESTAMP\": TYPE_DATETIME,\n}\n\n\nclass Pinot(BaseQueryRunner):\n    noop_query = \"SELECT 1\"\n    username = None\n    password = None\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"brokerHost\": {\"type\": \"string\", \"default\": \"\"},\n                \"brokerPort\": {\"type\": \"number\", \"default\": 8099},\n                \"brokerScheme\": {\"type\": \"string\", \"default\": \"http\"},\n                \"controllerURI\": {\"type\": \"string\", \"default\": \"\"},\n                \"username\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n            },\n            \"order\": [\"brokerScheme\", \"brokerHost\", \"brokerPort\", \"controllerURI\", \"username\", \"password\"],\n            \"required\": [\"brokerHost\", \"controllerURI\"],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    def __init__(self, configuration):\n        super(Pinot, self).__init__(configuration)\n        self.controller_uri = self.configuration.get(\"controllerURI\")\n        self.username = self.configuration.get(\"username\") or None\n        self.password = self.configuration.get(\"password\") or None\n\n    def run_query(self, query, user):\n        logger.debug(\"Running query %s with username: %s\", query, self.username)\n        connection = pinotdb.connect(\n            host=self.configuration[\"brokerHost\"],\n            port=self.configuration[\"brokerPort\"],\n            path=\"/query/sql\",\n            scheme=(self.configuration.get(\"brokerScheme\") or \"http\"),\n            verify_ssl=False,\n            username=self.username,\n            password=self.password,\n        )\n\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(query)\n            logger.debug(\"cursor.schema = %s\", cursor.schema)\n            columns = self.fetch_columns(\n                [(i[\"name\"], PINOT_TYPES_MAPPING.get(i[\"type\"], None)) for i in cursor.schema]\n            )\n            rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in cursor]\n\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n            logger.debug(\"Pinot execute query [%s]\", query)\n        finally:\n            connection.close()\n\n        return data, error\n\n    def get_schema(self, get_stats=False):\n        schema = {}\n        for schema_name in self.get_schema_names():\n            for table_name in self.get_table_names():\n                schema_table_name = \"{}.{}\".format(schema_name, table_name)\n                if table_name not in schema:\n                    schema[schema_table_name] = {\"name\": schema_table_name, \"columns\": []}\n                table_schema = self.get_pinot_table_schema(table_name)\n\n                for column in (\n                    table_schema.get(\"dimensionFieldSpecs\", [])\n                    + table_schema.get(\"metricFieldSpecs\", [])\n                    + table_schema.get(\"dateTimeFieldSpecs\", [])\n                ):\n                    c = {\n                        \"name\": column[\"name\"],\n                        \"type\": PINOT_TYPES_MAPPING[column[\"dataType\"]],\n                    }\n                    schema[schema_table_name][\"columns\"].append(c)\n        return list(schema.values())\n\n    def get_schema_names(self):\n        return [\"default\"]\n\n    def get_pinot_table_schema(self, pinot_table_name):\n        return self.get_metadata_from_controller(\"/tables/\" + pinot_table_name + \"/schema\")\n\n    def get_table_names(self):\n        return self.get_metadata_from_controller(\"/tables\")[\"tables\"]\n\n    def get_metadata_from_controller(self, path):\n        url = self.controller_uri + path\n        r = requests.get(url, headers={\"Accept\": \"application/json\"}, auth=HTTPBasicAuth(self.username, self.password))\n        try:\n            result = r.json()\n            logger.debug(\"get_metadata_from_controller from path %s\", path)\n        except ValueError as e:\n            raise pinotdb.exceptions.DatabaseError(\n                f\"Got invalid json response from {self.controller_uri}:{path}: {r.text}\"\n            ) from e\n        return result\n\n\nregister(Pinot)\n"
  },
  {
    "path": "redash/query_runner/presto.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    InterruptException,\n    JobTimeoutException,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\n\ntry:\n    from pyhive import presto\n    from pyhive.exc import DatabaseError\n\n    enabled = True\n\nexcept ImportError:\n    enabled = False\n\nPRESTO_TYPES_MAPPING = {\n    \"integer\": TYPE_INTEGER,\n    \"tinyint\": TYPE_INTEGER,\n    \"smallint\": TYPE_INTEGER,\n    \"long\": TYPE_INTEGER,\n    \"bigint\": TYPE_INTEGER,\n    \"float\": TYPE_FLOAT,\n    \"double\": TYPE_FLOAT,\n    \"boolean\": TYPE_BOOLEAN,\n    \"string\": TYPE_STRING,\n    \"varchar\": TYPE_STRING,\n    \"date\": TYPE_DATE,\n}\n\n\nclass Presto(BaseQueryRunner):\n    noop_query = \"SHOW TABLES\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\"},\n                \"protocol\": {\"type\": \"string\", \"default\": \"http\"},\n                \"port\": {\"type\": \"number\"},\n                \"schema\": {\"type\": \"string\"},\n                \"catalog\": {\"type\": \"string\"},\n                \"username\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n            },\n            \"order\": [\n                \"host\",\n                \"protocol\",\n                \"port\",\n                \"username\",\n                \"password\",\n                \"schema\",\n                \"catalog\",\n            ],\n            \"required\": [\"host\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def type(cls):\n        return \"presto\"\n\n    def get_schema(self, get_stats=False):\n        schema = {}\n        query = \"\"\"\n        SELECT table_schema, table_name, column_name\n        FROM information_schema.columns\n        WHERE table_schema NOT IN ('pg_catalog', 'information_schema')\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            table_name = \"{}.{}\".format(row[\"table_schema\"], row[\"table_name\"])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"column_name\"])\n\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        connection = presto.connect(\n            host=self.configuration.get(\"host\", \"\"),\n            port=self.configuration.get(\"port\", 8080),\n            protocol=self.configuration.get(\"protocol\", \"http\"),\n            username=self.configuration.get(\"username\", \"redash\"),\n            password=(self.configuration.get(\"password\") or None),\n            catalog=self.configuration.get(\"catalog\", \"hive\"),\n            schema=self.configuration.get(\"schema\", \"default\"),\n        )\n\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(query)\n            column_tuples = [(i[0], PRESTO_TYPES_MAPPING.get(i[1], None)) for i in cursor.description]\n            columns = self.fetch_columns(column_tuples)\n            rows = [dict(zip(([column[\"name\"] for column in columns]), r)) for i, r in enumerate(cursor.fetchall())]\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n        except DatabaseError as db:\n            data = None\n            default_message = \"Unspecified DatabaseError: {0}\".format(str(db))\n            if isinstance(db.args[0], dict):\n                message = db.args[0].get(\"failureInfo\", {\"message\", None}).get(\"message\")\n            else:\n                message = None\n            error = default_message if message is None else message\n        except (KeyboardInterrupt, InterruptException, JobTimeoutException):\n            cursor.cancel()\n            raise\n\n        return data, error\n\n\nregister(Presto)\n"
  },
  {
    "path": "redash/query_runner/prometheus.py",
    "content": "import os\nimport time\nfrom base64 import b64decode\nfrom datetime import datetime\nfrom tempfile import NamedTemporaryFile\nfrom urllib.parse import parse_qs\n\nimport requests\nfrom dateutil import parser\n\nfrom redash.query_runner import (\n    TYPE_DATETIME,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\n\ndef get_instant_rows(metrics_data):\n    rows = []\n\n    for metric in metrics_data:\n        row_data = metric[\"metric\"]\n\n        timestamp, value = metric[\"value\"]\n        date_time = datetime.fromtimestamp(timestamp)\n\n        row_data.update({\"timestamp\": date_time, \"value\": value})\n        rows.append(row_data)\n    return rows\n\n\ndef get_range_rows(metrics_data):\n    rows = []\n\n    for metric in metrics_data:\n        ts_values = metric[\"values\"]\n        metric_labels = metric[\"metric\"]\n\n        for values in ts_values:\n            row_data = metric_labels.copy()\n\n            timestamp, value = values\n            date_time = datetime.fromtimestamp(timestamp)\n\n            row_data.update({\"timestamp\": date_time, \"value\": value})\n            rows.append(row_data)\n    return rows\n\n\n# Convert datetime string to timestamp\ndef convert_query_range(payload):\n    query_range = {}\n\n    for key in [\"start\", \"end\"]:\n        if key not in payload.keys():\n            continue\n        value = payload[key][0]\n\n        if isinstance(value, str):\n            # Don't convert timestamp string\n            try:\n                int(value)\n                continue\n            except ValueError:\n                pass\n            value = parser.parse(value)\n\n        if type(value) is datetime:\n            query_range[key] = [int(time.mktime(value.timetuple()))]\n\n    payload.update(query_range)\n\n\nclass Prometheus(BaseQueryRunner):\n    should_annotate_query = False\n\n    def _get_datetime_now(self):\n        return datetime.now()\n\n    def _get_prometheus_kwargs(self):\n        ca_cert_file = self._create_cert_file(\"ca_cert_File\")\n        if ca_cert_file is not None:\n            verify = ca_cert_file\n        else:\n            verify = self.configuration.get(\"verify_ssl\", True)\n\n        cert_file = self._create_cert_file(\"cert_File\")\n        cert_key_file = self._create_cert_file(\"cert_key_File\")\n        if cert_file is not None and cert_key_file is not None:\n            cert = (cert_file, cert_key_file)\n        else:\n            cert = ()\n\n        return {\n            \"verify\": verify,\n            \"cert\": cert,\n        }\n\n    def _create_cert_file(self, key):\n        cert_file_name = None\n\n        if self.configuration.get(key, None) is not None:\n            with NamedTemporaryFile(mode=\"w\", delete=False) as cert_file:\n                cert_bytes = b64decode(self.configuration[key])\n                cert_file.write(cert_bytes.decode(\"utf-8\"))\n                cert_file_name = cert_file.name\n\n        return cert_file_name\n\n    def _cleanup_cert_files(self, promehteus_kwargs):\n        verify = promehteus_kwargs.get(\"verify\", True)\n        if isinstance(verify, str) and os.path.exists(verify):\n            os.remove(verify)\n\n        cert = promehteus_kwargs.get(\"cert\", ())\n        for cert_file in cert:\n            if os.path.exists(cert_file):\n                os.remove(cert_file)\n\n    @classmethod\n    def configuration_schema(cls):\n        # files has to end with \"File\" in name\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\", \"title\": \"Prometheus API URL\"},\n                \"verify_ssl\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Verify SSL (Ignored, if SSL Root Certificate is given)\",\n                    \"default\": True,\n                },\n                \"cert_File\": {\"type\": \"string\", \"title\": \"SSL Client Certificate\", \"default\": None},\n                \"cert_key_File\": {\"type\": \"string\", \"title\": \"SSL Client Key\", \"default\": None},\n                \"ca_cert_File\": {\"type\": \"string\", \"title\": \"SSL Root Certificate\", \"default\": None},\n            },\n            \"required\": [\"url\"],\n            \"secret\": [\"cert_File\", \"cert_key_File\", \"ca_cert_File\"],\n            \"extra_options\": [\"verify_ssl\", \"cert_File\", \"cert_key_File\", \"ca_cert_File\"],\n        }\n\n    def test_connection(self):\n        result = False\n        promehteus_kwargs = {}\n        try:\n            promehteus_kwargs = self._get_prometheus_kwargs()\n            resp = requests.get(self.configuration.get(\"url\", None), **promehteus_kwargs)\n            result = resp.ok\n        except Exception:\n            raise\n        finally:\n            self._cleanup_cert_files(promehteus_kwargs)\n\n        return result\n\n    def get_schema(self, get_stats=False):\n        schema = []\n        promehteus_kwargs = {}\n        try:\n            base_url = self.configuration[\"url\"]\n            metrics_path = \"/api/v1/label/__name__/values\"\n            promehteus_kwargs = self._get_prometheus_kwargs()\n\n            response = requests.get(base_url + metrics_path, **promehteus_kwargs)\n\n            response.raise_for_status()\n            data = response.json()[\"data\"]\n\n            schema = {}\n            for name in data:\n                schema[name] = {\"name\": name, \"columns\": []}\n            schema = list(schema.values())\n        except Exception:\n            raise\n        finally:\n            self._cleanup_cert_files(promehteus_kwargs)\n\n        return schema\n\n    def run_query(self, query, user):\n        \"\"\"\n        Query Syntax, actually it is the URL query string.\n        Check the Prometheus HTTP API for the details of the supported query string.\n\n        https://prometheus.io/docs/prometheus/latest/querying/api/\n\n        example: instant query\n            query=http_requests_total\n\n        example: range query\n            query=http_requests_total&start=2018-01-20T00:00:00.000Z&end=2018-01-25T00:00:00.000Z&step=60s\n\n        example: until now range query\n            query=http_requests_total&start=2018-01-20T00:00:00.000Z&step=60s\n            query=http_requests_total&start=2018-01-20T00:00:00.000Z&end=now&step=60s\n        \"\"\"\n\n        base_url = self.configuration[\"url\"]\n        columns = [\n            {\"friendly_name\": \"timestamp\", \"type\": TYPE_DATETIME, \"name\": \"timestamp\"},\n            {\"friendly_name\": \"value\", \"type\": TYPE_STRING, \"name\": \"value\"},\n        ]\n        promehteus_kwargs = {}\n\n        try:\n            error = None\n            query = query.strip()\n            # for backward compatibility\n            query = \"query={}\".format(query) if not query.startswith(\"query=\") else query\n\n            payload = parse_qs(query)\n            query_type = \"query_range\" if \"step\" in payload.keys() else \"query\"\n\n            # for the range of until now\n            if query_type == \"query_range\" and (\"end\" not in payload.keys() or \"now\" in payload[\"end\"]):\n                date_now = self._get_datetime_now()\n                payload.update({\"end\": [date_now]})\n\n            convert_query_range(payload)\n\n            api_endpoint = base_url + \"/api/v1/{}\".format(query_type)\n\n            promehteus_kwargs = self._get_prometheus_kwargs()\n\n            response = requests.get(api_endpoint, params=payload, **promehteus_kwargs)\n            response.raise_for_status()\n\n            metrics = response.json()[\"data\"][\"result\"]\n\n            if len(metrics) == 0:\n                return None, \"query result is empty.\"\n\n            metric_labels = metrics[0][\"metric\"].keys()\n\n            for label_name in metric_labels:\n                columns.append(\n                    {\n                        \"friendly_name\": label_name,\n                        \"type\": TYPE_STRING,\n                        \"name\": label_name,\n                    }\n                )\n\n            if query_type == \"query_range\":\n                rows = get_range_rows(metrics)\n            else:\n                rows = get_instant_rows(metrics)\n\n            data = {\"rows\": rows, \"columns\": columns}\n\n        except requests.RequestException as e:\n            return None, str(e)\n        except Exception:\n            raise\n        finally:\n            self._cleanup_cert_files(promehteus_kwargs)\n\n        return data, error\n\n\nregister(Prometheus)\n"
  },
  {
    "path": "redash/query_runner/python.py",
    "content": "import datetime\nimport importlib\nimport logging\nimport sys\n\nfrom RestrictedPython import compile_restricted\nfrom RestrictedPython.Guards import (\n    guarded_iter_unpack_sequence,\n    guarded_unpack_sequence,\n    safe_builtins,\n)\nfrom RestrictedPython.transformer import IOPERATOR_TO_STR\n\nfrom redash import models\nfrom redash.query_runner import (\n    SUPPORTED_COLUMN_TYPES,\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\nfrom redash.utils.pandas import pandas_installed\n\nif pandas_installed:\n    import pandas as pd\n\n    from redash.utils.pandas import pandas_to_result\n\n    enabled = True\nelse:\n    enabled = False\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass CustomPrint:\n    \"\"\"CustomPrint redirect \"print\" calls to be sent as \"log\" on the result object.\"\"\"\n\n    def __init__(self):\n        self.enabled = True\n        self.lines = []\n\n    def write(self, text):\n        if self.enabled:\n            if text and text.strip():\n                log_line = \"[{0}] {1}\".format(datetime.datetime.utcnow().isoformat(), text)\n                self.lines.append(log_line)\n\n    def enable(self):\n        self.enabled = True\n\n    def disable(self):\n        self.enabled = False\n\n    def __call__(self, *args):\n        return self\n\n    def _call_print(self, *objects, **kwargs):\n        print(*objects, file=self)\n\n\nclass Python(BaseQueryRunner):\n    should_annotate_query = False\n\n    safe_builtins = (\n        \"abs\",\n        \"all\",\n        \"any\",\n        \"bool\",\n        \"complex\",\n        \"dict\",\n        \"divmod\",\n        \"enumerate\",\n        \"filter\",\n        \"float\",\n        \"int\",\n        \"len\",\n        \"list\",\n        \"map\",\n        \"max\",\n        \"min\",\n        \"next\",\n        \"reversed\",\n        \"round\",\n        \"set\",\n        \"slice\",\n        \"sorted\",\n        \"str\",\n        \"sum\",\n        \"tuple\",\n    )\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"allowedImportModules\": {\n                    \"type\": \"string\",\n                    \"title\": \"Modules to import prior to running the script\",\n                },\n                \"additionalModulesPaths\": {\"type\": \"string\"},\n                \"additionalBuiltins\": {\"type\": \"string\"},\n            },\n        }\n\n    @classmethod\n    def enabled(cls):\n        return True\n\n    def __init__(self, configuration):\n        super(Python, self).__init__(configuration)\n\n        self.syntax = \"python\"\n\n        self._allowed_modules = {}\n        self._script_locals = {\"result\": {\"rows\": [], \"columns\": [], \"log\": []}}\n        self._enable_print_log = True\n        self._custom_print = CustomPrint()\n\n        if self.configuration.get(\"allowedImportModules\", None):\n            for item in self.configuration[\"allowedImportModules\"].split(\",\"):\n                self._allowed_modules[item] = None\n\n        if self.configuration.get(\"additionalModulesPaths\", None):\n            for p in self.configuration[\"additionalModulesPaths\"].split(\",\"):\n                if p not in sys.path:\n                    sys.path.append(p)\n\n        if self.configuration.get(\"additionalBuiltins\", None):\n            for b in self.configuration[\"additionalBuiltins\"].split(\",\"):\n                if b not in self.safe_builtins:\n                    self.safe_builtins += (b,)\n\n    def custom_import(self, name, globals=None, locals=None, fromlist=(), level=0):\n        if name in self._allowed_modules:\n            m = None\n            if self._allowed_modules[name] is None:\n                m = importlib.import_module(name)\n                self._allowed_modules[name] = m\n            else:\n                m = self._allowed_modules[name]\n\n            return m\n\n        raise Exception(\"'{0}' is not configured as a supported import module\".format(name))\n\n    @staticmethod\n    def custom_write(obj):\n        \"\"\"\n        Custom hooks which controls the way objects/lists/tuples/dicts behave in\n        RestrictedPython\n        \"\"\"\n        return obj\n\n    @staticmethod\n    def custom_get_item(obj, key):\n        return obj[key]\n\n    @staticmethod\n    def custom_get_iter(obj):\n        return iter(obj)\n\n    @staticmethod\n    def custom_inplacevar(op, x, y):\n        if op not in IOPERATOR_TO_STR.values():\n            raise Exception(\"'{} is not supported inplace variable'\".format(op))\n        glb = {\"x\": x, \"y\": y}\n        exec(\"x\" + op + \"y\", glb)\n        return glb[\"x\"]\n\n    @staticmethod\n    def add_result_column(result, column_name, friendly_name, column_type):\n        \"\"\"Helper function to add columns inside a Python script running in Redash in an easier way\n\n        Parameters:\n        :result dict: The result dict\n        :column_name string: Name of the column, which should be consisted of lowercase latin letters or underscore.\n        :friendly_name string: Name of the column for display\n        :column_type string: Type of the column. Check supported data types for details.\n        \"\"\"\n        if column_type not in SUPPORTED_COLUMN_TYPES:\n            raise Exception(\"'{0}' is not a supported column type\".format(column_type))\n\n        if \"columns\" not in result:\n            result[\"columns\"] = []\n\n        result[\"columns\"].append({\"name\": column_name, \"friendly_name\": friendly_name, \"type\": column_type})\n\n    @staticmethod\n    def add_result_row(result, values):\n        \"\"\"Helper function to add one row to results set.\n\n        Parameters:\n        :result dict: The result dict\n        :values dict: One row of result in dict. The key should be one of the column names. The value is the value of the column in this row.\n        \"\"\"\n        if \"rows\" not in result:\n            result[\"rows\"] = []\n\n        result[\"rows\"].append(values)\n\n    @staticmethod\n    def execute_query(data_source_name_or_id, query, result_type=None):\n        \"\"\"Run query from specific data source.\n\n        Parameters:\n        :data_source_name_or_id string|integer: Name or ID of the data source\n        :query string: Query to run\n        \"\"\"\n        try:\n            if isinstance(data_source_name_or_id, int):\n                data_source = models.DataSource.get_by_id(data_source_name_or_id)\n            else:\n                data_source = models.DataSource.get_by_name(data_source_name_or_id)\n        except models.NoResultFound:\n            raise Exception(\"Wrong data source name/id: %s.\" % data_source_name_or_id)\n\n        # TODO: pass the user here...\n        data, error = data_source.query_runner.run_query(query, None)\n        if error is not None:\n            raise Exception(error)\n\n        # TODO: allow avoiding the JSON dumps/loads in same process\n        query_result = data\n\n        if result_type == \"dataframe\" and pandas_installed:\n            return pd.DataFrame(query_result[\"rows\"])\n\n        return query_result\n\n    @staticmethod\n    def get_source_schema(data_source_name_or_id):\n        \"\"\"Get schema from specific data source.\n\n        :param data_source_name_or_id: string|integer: Name or ID of the data source\n        :return:\n        \"\"\"\n        try:\n            if isinstance(data_source_name_or_id, int):\n                data_source = models.DataSource.get_by_id(data_source_name_or_id)\n            else:\n                data_source = models.DataSource.get_by_name(data_source_name_or_id)\n        except models.NoResultFound:\n            raise Exception(\"Wrong data source name/id: %s.\" % data_source_name_or_id)\n        schema = data_source.query_runner.get_schema()\n        return schema\n\n    @staticmethod\n    def get_query_result(query_id):\n        \"\"\"Get result of an existing query.\n\n        Parameters:\n        :query_id integer: ID of existing query\n        \"\"\"\n        try:\n            query = models.Query.get_by_id(query_id)\n        except models.NoResultFound:\n            raise Exception(\"Query id %s does not exist.\" % query_id)\n\n        if query.latest_query_data is None:\n            raise Exception(\"Query does not have results yet.\")\n\n        if query.latest_query_data.data is None:\n            raise Exception(\"Query does not have results yet.\")\n\n        return query.latest_query_data.data\n\n    def dataframe_to_result(self, result, df):\n        converted_result = pandas_to_result(df)\n\n        result[\"rows\"] = converted_result[\"rows\"]\n        for column in converted_result[\"columns\"]:\n            self.add_result_column(result, column[\"name\"], column[\"friendly_name\"], column[\"type\"])\n\n    def get_current_user(self):\n        return self._current_user.to_dict()\n\n    def test_connection(self):\n        pass\n\n    def validate_result(self, result):\n        \"\"\"Validate the result after executing the query.\n\n        Parameters:\n        :result dict: The result dict.\n        \"\"\"\n        if not result:\n            raise Exception(\"local variable `result` should not be empty.\")\n        if not isinstance(result, dict):\n            raise Exception(\"local variable `result` should be of type `dict`.\")\n        if \"rows\" not in result:\n            raise Exception(\"Missing `rows` field in `result` dict.\")\n        if \"columns\" not in result:\n            raise Exception(\"Missing `columns` field in `result` dict.\")\n        if not isinstance(result[\"rows\"], list):\n            raise Exception(\"`rows` field should be of type `list`.\")\n        if not isinstance(result[\"columns\"], list):\n            raise Exception(\"`columns` field should be of type `list`.\")\n\n    def run_query(self, query, user):\n        self._current_user = user\n\n        try:\n            error = None\n\n            code = compile_restricted(query, \"<string>\", \"exec\")\n\n            builtins = safe_builtins.copy()\n            builtins[\"_write_\"] = self.custom_write\n            builtins[\"__import__\"] = self.custom_import\n            builtins[\"_getattr_\"] = getattr\n            builtins[\"getattr\"] = getattr\n            builtins[\"_setattr_\"] = setattr\n            builtins[\"setattr\"] = setattr\n            builtins[\"_getitem_\"] = self.custom_get_item\n            builtins[\"_getiter_\"] = self.custom_get_iter\n            builtins[\"_print_\"] = self._custom_print\n            builtins[\"_unpack_sequence_\"] = guarded_unpack_sequence\n            builtins[\"_iter_unpack_sequence_\"] = guarded_iter_unpack_sequence\n            builtins[\"_inplacevar_\"] = self.custom_inplacevar\n\n            # Layer in our own additional set of builtins that we have\n            # considered safe.\n            for key in self.safe_builtins:\n                builtins[key] = __builtins__[key]\n\n            restricted_globals = dict(__builtins__=builtins)\n            restricted_globals[\"get_query_result\"] = self.get_query_result\n            restricted_globals[\"get_source_schema\"] = self.get_source_schema\n            restricted_globals[\"get_current_user\"] = self.get_current_user\n            restricted_globals[\"execute_query\"] = self.execute_query\n            restricted_globals[\"add_result_column\"] = self.add_result_column\n            if pandas_installed:\n                restricted_globals[\"dataframe_to_result\"] = self.dataframe_to_result\n            restricted_globals[\"add_result_row\"] = self.add_result_row\n            restricted_globals[\"disable_print_log\"] = self._custom_print.disable\n            restricted_globals[\"enable_print_log\"] = self._custom_print.enable\n\n            # Supported data types\n            restricted_globals[\"TYPE_DATETIME\"] = TYPE_DATETIME\n            restricted_globals[\"TYPE_BOOLEAN\"] = TYPE_BOOLEAN\n            restricted_globals[\"TYPE_INTEGER\"] = TYPE_INTEGER\n            restricted_globals[\"TYPE_STRING\"] = TYPE_STRING\n            restricted_globals[\"TYPE_DATE\"] = TYPE_DATE\n            restricted_globals[\"TYPE_FLOAT\"] = TYPE_FLOAT\n\n            # TODO: Figure out the best way to have a timeout on a script\n            #       One option is to use ETA with Celery + timeouts on workers\n            #       And replacement of worker process every X requests handled.\n\n            exec(code, restricted_globals, self._script_locals)\n\n            data = self._script_locals[\"result\"]\n            self.validate_result(data)\n            data[\"log\"] = self._custom_print.lines\n        except Exception as e:\n            error = str(type(e)) + \" \" + str(e)\n            data = None\n\n        return data, error\n\n\nregister(Python)\n"
  },
  {
    "path": "redash/query_runner/query_results.py",
    "content": "import datetime\nimport decimal\nimport hashlib\nimport logging\nimport re\nimport sqlite3\nfrom urllib.parse import parse_qs\n\nfrom redash import models\nfrom redash.permissions import has_access, view_only\nfrom redash.query_runner import (\n    TYPE_STRING,\n    BaseQueryRunner,\n    JobTimeoutException,\n    guess_type,\n    register,\n)\nfrom redash.utils import json_dumps\n\nlogger = logging.getLogger(__name__)\n\n\nclass PermissionError(Exception):\n    pass\n\n\nclass CreateTableError(Exception):\n    pass\n\n\ndef extract_query_params(query):\n    return re.findall(r\"(?:join|from)\\s+param_query_(\\d+)_{([^}]+)}\", query, re.IGNORECASE)\n\n\ndef extract_query_ids(query):\n    queries = re.findall(r\"(?:join|from)\\s+query_(\\d+)\", query, re.IGNORECASE)\n    return [int(q) for q in queries]\n\n\ndef extract_cached_query_ids(query):\n    queries = re.findall(r\"(?:join|from)\\s+cached_query_(\\d+)\", query, re.IGNORECASE)\n    return [int(q) for q in queries]\n\n\ndef _load_query(user, query_id):\n    query = models.Query.get_by_id(query_id)\n\n    if user.org_id != query.org_id:\n        raise PermissionError(\"Query id {} not found.\".format(query.id))\n\n    # TODO: this duplicates some of the logic we already have in the redash.handlers.query_results.\n    # We should merge it so it's consistent.\n    if not has_access(query.data_source, user, view_only):\n        raise PermissionError(\"You do not have access to query id {}.\".format(query.id))\n\n    return query\n\n\ndef replace_query_parameters(query_text, params):\n    qs = parse_qs(params)\n    for key, value in qs.items():\n        query_text = query_text.replace(\"{{{{{my_key}}}}}\".format(my_key=key), value[0])\n    return query_text\n\n\ndef get_query_results(user, query_id, bring_from_cache, params=None):\n    query = _load_query(user, query_id)\n    if bring_from_cache:\n        if query.latest_query_data_id is not None:\n            results = query.latest_query_data.data\n        else:\n            raise Exception(\"No cached result available for query {}.\".format(query.id))\n    else:\n        query_text = query.query_text\n        if params is not None:\n            query_text = replace_query_parameters(query_text, params)\n\n        results, error = query.data_source.query_runner.run_query(query_text, user)\n        if error:\n            raise Exception(\"Failed loading results for query id {}.\".format(query.id))\n\n    return results\n\n\ndef create_tables_from_query_ids(user, connection, query_ids, query_params, cached_query_ids=[]):\n    for query_id in set(cached_query_ids):\n        results = get_query_results(user, query_id, True)\n        table_name = \"cached_query_{query_id}\".format(query_id=query_id)\n        create_table(connection, table_name, results)\n\n    for query in set(query_params):\n        results = get_query_results(user, query[0], False, query[1])\n        table_hash = hashlib.md5(\n            \"query_{query}_{hash}\".format(query=query[0], hash=query[1]).encode(), usedforsecurity=False\n        ).hexdigest()\n        table_name = \"query_{query_id}_{param_hash}\".format(query_id=query[0], param_hash=table_hash)\n        create_table(connection, table_name, results)\n\n    for query_id in set(query_ids):\n        results = get_query_results(user, query_id, False)\n        table_name = \"query_{query_id}\".format(query_id=query_id)\n        create_table(connection, table_name, results)\n\n\ndef fix_column_name(name):\n    return '\"{}\"'.format(re.sub(r'[:.\"\\s]', \"_\", name, flags=re.UNICODE))\n\n\ndef flatten(value):\n    if isinstance(value, (list, dict)):\n        return json_dumps(value)\n    elif isinstance(value, decimal.Decimal):\n        return float(value)\n    elif isinstance(value, datetime.timedelta):\n        return str(value)\n    else:\n        return value\n\n\ndef create_table(connection, table_name, query_results):\n    try:\n        columns = [column[\"name\"] for column in query_results[\"columns\"]]\n        safe_columns = [fix_column_name(column) for column in columns]\n\n        column_list = \", \".join(safe_columns)\n        create_table = \"CREATE TABLE {table_name} ({column_list})\".format(\n            table_name=table_name, column_list=column_list\n        )\n        logger.debug(\"CREATE TABLE query: %s\", create_table)\n        connection.execute(create_table)\n    except sqlite3.OperationalError as exc:\n        raise CreateTableError(\"Error creating table {}: {}\".format(table_name, str(exc)))\n\n    insert_template = \"insert into {table_name} ({column_list}) values ({place_holders})\".format(\n        table_name=table_name,\n        column_list=column_list,\n        place_holders=\",\".join([\"?\"] * len(columns)),\n    )\n\n    for row in query_results[\"rows\"]:\n        values = [flatten(row.get(column)) for column in columns]\n        connection.execute(insert_template, values)\n\n\ndef prepare_parameterized_query(query, query_params):\n    for params in query_params:\n        table_hash = hashlib.md5(\n            \"query_{query}_{hash}\".format(query=params[0], hash=params[1]).encode(), usedforsecurity=False\n        ).hexdigest()\n        key = \"param_query_{query_id}_{{{param_string}}}\".format(query_id=params[0], param_string=params[1])\n        value = \"query_{query_id}_{param_hash}\".format(query_id=params[0], param_hash=table_hash)\n        query = query.replace(key, value)\n    return query\n\n\nclass Results(BaseQueryRunner):\n    should_annotate_query = False\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\"type\": \"object\", \"properties\": {}}\n\n    @classmethod\n    def name(cls):\n        return \"Query Results\"\n\n    def run_query(self, query, user):\n        connection = sqlite3.connect(\":memory:\")\n\n        query_ids = extract_query_ids(query)\n\n        query_params = extract_query_params(query)\n\n        cached_query_ids = extract_cached_query_ids(query)\n        create_tables_from_query_ids(user, connection, query_ids, query_params, cached_query_ids)\n\n        cursor = connection.cursor()\n\n        if query_params is not None:\n            query = prepare_parameterized_query(query, query_params)\n\n        try:\n            cursor.execute(query)\n\n            if cursor.description is not None:\n                columns = self.fetch_columns([(i[0], None) for i in cursor.description])\n\n                rows = []\n                column_names = [c[\"name\"] for c in columns]\n\n                for i, row in enumerate(cursor):\n                    for j, col in enumerate(row):\n                        guess = guess_type(col)\n\n                        if columns[j][\"type\"] is None:\n                            columns[j][\"type\"] = guess\n                        elif columns[j][\"type\"] != guess:\n                            columns[j][\"type\"] = TYPE_STRING\n\n                    rows.append(dict(zip(column_names, row)))\n\n                data = {\"columns\": columns, \"rows\": rows}\n                error = None\n            else:\n                error = \"Query completed but it returned no data.\"\n                data = None\n        except (KeyboardInterrupt, JobTimeoutException):\n            connection.cancel()\n            raise\n        finally:\n            connection.close()\n        return data, error\n\n\nregister(Results)\n"
  },
  {
    "path": "redash/query_runner/risingwave.py",
    "content": "from redash.query_runner import register\nfrom redash.query_runner.pg import PostgreSQL\n\n\nclass RisingWave(PostgreSQL):\n    @classmethod\n    def type(cls):\n        return \"risingwave\"\n\n    @classmethod\n    def name(cls):\n        return \"RisingWave\"\n\n    def _get_tables(self, schema):\n        query = \"\"\"\n        SELECT s.nspname as table_schema,\n               c.relname as table_name,\n               a.attname as column_name,\n               null as data_type\n        FROM pg_class c\n        JOIN pg_namespace s\n        ON c.relnamespace = s.oid\n        AND s.nspname NOT IN ('pg_catalog', 'information_schema', 'rw_catalog')\n        JOIN pg_attribute a\n        ON a.attrelid = c.oid\n        AND a.attnum > 0\n        AND NOT a.attisdropped\n        WHERE c.relkind IN ('m', 'f', 'p')\n\n        UNION\n\n        SELECT table_schema,\n               table_name,\n               column_name,\n               data_type\n        FROM information_schema.columns\n        WHERE table_schema NOT IN ('pg_catalog', 'information_schema', 'rw_catalog');\n        \"\"\"\n\n        self._get_definitions(schema, query)\n\n        return list(schema.values())\n\n\nregister(RisingWave)\n"
  },
  {
    "path": "redash/query_runner/rockset.py",
    "content": "import requests\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    register,\n)\n\n\ndef _get_type(value):\n    if isinstance(value, int):\n        return TYPE_INTEGER\n    elif isinstance(value, float):\n        return TYPE_FLOAT\n    elif isinstance(value, bool):\n        return TYPE_BOOLEAN\n    elif isinstance(value, str):\n        return TYPE_STRING\n    return TYPE_STRING\n\n\n# The following is here, because Rockset's PyPi package is Python 3 only.\n# Should be removed once we move to Python 3.\nclass RocksetAPI:\n    def __init__(self, api_key, api_server, vi_id):\n        self.api_key = api_key\n        self.api_server = api_server\n        self.vi_id = vi_id\n\n    def _request(self, endpoint, method=\"GET\", body=None):\n        headers = {\"Authorization\": \"ApiKey {}\".format(self.api_key), \"User-Agent\": \"rest:redash/1.0\"}\n        url = \"{}/v1/orgs/self/{}\".format(self.api_server, endpoint)\n\n        if method == \"GET\":\n            r = requests.get(url, headers=headers)\n            return r.json()\n        elif method == \"POST\":\n            r = requests.post(url, headers=headers, json=body)\n            return r.json()\n        else:\n            raise \"Unknown method: {}\".format(method)\n\n    def list_workspaces(self):\n        response = self._request(\"ws\")\n        return [x[\"name\"] for x in response[\"data\"] if x[\"collection_count\"] > 0]\n\n    def list_collections(self, workspace=\"commons\"):\n        response = self._request(\"ws/{}/collections\".format(workspace))\n        return [x[\"name\"] for x in response[\"data\"]]\n\n    def collection_columns(self, workspace, collection):\n        response = self.query('DESCRIBE \"{}\".\"{}\" OPTION(max_field_depth=1)'.format(workspace, collection))\n        return sorted(set([x[\"field\"][0] for x in response[\"results\"]]))\n\n    def query(self, sql):\n        query_path = \"queries\"\n        if self.vi_id is not None and self.vi_id != \"\":\n            query_path = f\"virtualinstances/{self.vi_id}/queries\"\n        return self._request(query_path, \"POST\", {\"sql\": {\"query\": sql}})\n\n\nclass Rockset(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"api_server\": {\n                    \"type\": \"string\",\n                    \"title\": \"API Server\",\n                    \"default\": \"https://api.rs2.usw2.rockset.com\",\n                },\n                \"api_key\": {\"title\": \"API Key\", \"type\": \"string\"},\n                \"vi_id\": {\"title\": \"Virtual Instance ID\", \"type\": \"string\"},\n            },\n            \"order\": [\"api_key\", \"api_server\", \"vi_id\"],\n            \"required\": [\"api_server\", \"api_key\"],\n            \"secret\": [\"api_key\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"rockset\"\n\n    def __init__(self, configuration):\n        super(Rockset, self).__init__(configuration)\n        self.api = RocksetAPI(\n            self.configuration.get(\"api_key\"),\n            self.configuration.get(\"api_server\", \"https://api.usw2a1.rockset.com\"),\n            self.configuration.get(\"vi_id\"),\n        )\n\n    def _get_tables(self, schema):\n        for workspace in self.api.list_workspaces():\n            for collection in self.api.list_collections(workspace):\n                table_name = collection if workspace == \"commons\" else \"{}.{}\".format(workspace, collection)\n                schema[table_name] = {\n                    \"name\": table_name,\n                    \"columns\": self.api.collection_columns(workspace, collection),\n                }\n        return sorted(schema.values(), key=lambda x: x[\"name\"])\n\n    def run_query(self, query, user):\n        results = self.api.query(query)\n        if \"code\" in results and results[\"code\"] != 200:\n            return None, \"{}: {}\".format(results[\"type\"], results[\"message\"])\n\n        if \"results\" not in results:\n            message = results.get(\"message\", \"Unknown response from Rockset.\")\n            return None, message\n\n        rows = results[\"results\"]\n        columns = []\n        if len(rows) > 0:\n            columns = []\n            for k in rows[0]:\n                columns.append({\"name\": k, \"friendly_name\": k, \"type\": _get_type(rows[0][k])})\n        data = {\"columns\": columns, \"rows\": rows}\n        return data, None\n\n\nregister(Rockset)\n"
  },
  {
    "path": "redash/query_runner/salesforce.py",
    "content": "import logging\nimport re\nfrom collections import OrderedDict\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    from simple_salesforce import Salesforce as SimpleSalesforce\n    from simple_salesforce import SalesforceError\n    from simple_salesforce.api import DEFAULT_API_VERSION\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n# See https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/field_types.htm\nTYPES_MAP = dict(\n    id=TYPE_STRING,\n    string=TYPE_STRING,\n    currency=TYPE_FLOAT,\n    reference=TYPE_STRING,\n    double=TYPE_FLOAT,\n    picklist=TYPE_STRING,\n    date=TYPE_DATE,\n    url=TYPE_STRING,\n    phone=TYPE_STRING,\n    textarea=TYPE_STRING,\n    int=TYPE_INTEGER,\n    datetime=TYPE_DATETIME,\n    boolean=TYPE_BOOLEAN,\n    percent=TYPE_FLOAT,\n    multipicklist=TYPE_STRING,\n    masterrecord=TYPE_STRING,\n    location=TYPE_STRING,\n    JunctionIdList=TYPE_STRING,\n    encryptedstring=TYPE_STRING,\n    email=TYPE_STRING,\n    DataCategoryGroupReference=TYPE_STRING,\n    combobox=TYPE_STRING,\n    calculated=TYPE_STRING,\n    anyType=TYPE_STRING,\n    address=TYPE_STRING,\n)\n\n# Query Runner for Salesforce SOQL Queries\n# For example queries, see:\n# https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_examples.htm\n\n\nclass Salesforce(BaseQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"username\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"token\": {\"type\": \"string\", \"title\": \"Security Token\"},\n                \"sandbox\": {\"type\": \"boolean\"},\n                \"api_version\": {\n                    \"type\": \"string\",\n                    \"title\": \"Salesforce API Version\",\n                    \"default\": DEFAULT_API_VERSION,\n                },\n            },\n            \"required\": [\"username\", \"password\"],\n            \"secret\": [\"password\", \"token\"],\n        }\n\n    def test_connection(self):\n        response = self._get_sf().describe()\n        if response is None:\n            raise Exception(\"Failed describing objects.\")\n        pass\n\n    def _get_sf(self):\n        sf = SimpleSalesforce(\n            username=self.configuration[\"username\"],\n            password=self.configuration[\"password\"],\n            security_token=self.configuration[\"token\"],\n            sandbox=self.configuration.get(\"sandbox\", False),\n            version=self.configuration.get(\"api_version\", DEFAULT_API_VERSION),\n            client_id=\"Redash\",\n        )\n        return sf\n\n    def _clean_value(self, value):\n        if isinstance(value, OrderedDict) and \"records\" in value:\n            value = value[\"records\"]\n            for row in value:\n                row.pop(\"attributes\", None)\n        return value\n\n    def _get_value(self, dct, dots):\n        for key in dots.split(\".\"):\n            if dct is not None and key in dct:\n                dct = dct.get(key)\n            else:\n                dct = None\n        return dct\n\n    def _get_column_name(self, key, parents=[]):\n        return \".\".join(parents + [key])\n\n    def _build_columns(self, sf, child, parents=[]):\n        child_type = child[\"attributes\"][\"type\"]\n        child_desc = sf.__getattr__(child_type).describe()\n        child_type_map = dict((f[\"name\"], f[\"type\"]) for f in child_desc[\"fields\"])\n        columns = []\n        for key in child.keys():\n            if key != \"attributes\":\n                if isinstance(child[key], OrderedDict) and \"attributes\" in child[key]:\n                    columns.extend(self._build_columns(sf, child[key], parents + [key]))\n                else:\n                    column_name = self._get_column_name(key, parents)\n                    key_type = child_type_map.get(key, \"string\")\n                    column_type = TYPES_MAP.get(key_type, TYPE_STRING)\n                    columns.append((column_name, column_type))\n        return columns\n\n    def _build_rows(self, columns, records):\n        rows = []\n        for record in records:\n            record.pop(\"attributes\", None)\n            row = dict()\n            for column in columns:\n                key = column[0]\n                value = self._get_value(record, key)\n                row[key] = self._clean_value(value)\n            rows.append(row)\n        return rows\n\n    def run_query(self, query, user):\n        logger.debug(\"Salesforce is about to execute query: %s\", query)\n        query = re.sub(r\"/\\*(.|\\n)*?\\*/\", \"\", query).strip()\n        try:\n            columns = []\n            rows = []\n            sf = self._get_sf()\n            response = sf.query_all(query)\n            records = response[\"records\"]\n            if response[\"totalSize\"] > 0 and len(records) == 0:\n                columns = self.fetch_columns([(\"Count\", TYPE_INTEGER)])\n                rows = [{\"Count\": response[\"totalSize\"]}]\n            elif len(records) > 0:\n                cols = self._build_columns(sf, records[0])\n                rows = self._build_rows(cols, records)\n                columns = self.fetch_columns(cols)\n            error = None\n            data = {\"columns\": columns, \"rows\": rows}\n        except SalesforceError as err:\n            error = err.content\n            data = None\n        return data, error\n\n    def get_schema(self, get_stats=False):\n        sf = self._get_sf()\n        response = sf.describe()\n        if response is None:\n            raise Exception(\"Failed describing objects.\")\n\n        schema = {}\n        for sobject in response[\"sobjects\"]:\n            table_name = sobject[\"name\"]\n            if sobject[\"queryable\"] is True and table_name not in schema:\n                desc = sf.__getattr__(sobject[\"name\"]).describe()\n                fields = desc[\"fields\"]\n                schema[table_name] = {\n                    \"name\": table_name,\n                    \"columns\": [f[\"name\"] for f in fields],\n                }\n        return list(schema.values())\n\n\nregister(Salesforce)\n"
  },
  {
    "path": "redash/query_runner/script.py",
    "content": "import os\nimport subprocess\n\nfrom redash.query_runner import BaseQueryRunner, register\n\n\ndef query_to_script_path(path, query):\n    if path != \"*\":\n        script = os.path.join(path, query.split(\" \")[0])\n        if not os.path.exists(script):\n            raise IOError(\"Script '{}' not found in script directory\".format(query))\n\n        return os.path.join(path, query).split(\" \")\n\n    return query\n\n\ndef run_script(script, shell):\n    output = subprocess.check_output(script, shell=shell)\n    if output is None:\n        return None, \"Error reading output\"\n\n    output = output.strip()\n    if not output:\n        return None, \"Empty output from script\"\n\n    return output, None\n\n\nclass Script(BaseQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def enabled(cls):\n        return \"check_output\" in subprocess.__dict__\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"path\": {\"type\": \"string\", \"title\": \"Scripts path\"},\n                \"shell\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Execute command through the shell\",\n                },\n            },\n            \"required\": [\"path\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"insecure_script\"\n\n    def __init__(self, configuration):\n        super(Script, self).__init__(configuration)\n\n        path = self.configuration.get(\"path\", \"\")\n        # If path is * allow any execution path\n        if path == \"*\":\n            return\n\n        # Poor man's protection against running scripts from outside the scripts directory\n        if path.find(\"../\") > -1:\n            raise ValueError(\"Scripts can only be run from the configured scripts directory\")\n\n    def test_connection(self):\n        pass\n\n    def run_query(self, query, user):\n        try:\n            script = query_to_script_path(self.configuration[\"path\"], query)\n            return run_script(script, self.configuration[\"shell\"])\n        except IOError as e:\n            return None, str(e)\n        except subprocess.CalledProcessError as e:\n            return None, str(e)\n\n\nregister(Script)\n"
  },
  {
    "path": "redash/query_runner/snowflake.py",
    "content": "try:\n    import snowflake.connector\n    from cryptography.hazmat.primitives.serialization import load_pem_private_key\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\nfrom base64 import b64decode\n\nfrom redash import __version__\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    register,\n)\n\nTYPES_MAP = {\n    0: TYPE_INTEGER,\n    1: TYPE_FLOAT,\n    2: TYPE_STRING,\n    3: TYPE_DATE,\n    4: TYPE_DATETIME,\n    5: TYPE_STRING,\n    6: TYPE_DATETIME,\n    7: TYPE_DATETIME,\n    8: TYPE_DATETIME,\n    13: TYPE_BOOLEAN,\n}\n\n\nclass Snowflake(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"account\": {\"type\": \"string\"},\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"private_key_File\": {\"type\": \"string\"},\n                \"private_key_pwd\": {\"type\": \"string\"},\n                \"warehouse\": {\"type\": \"string\"},\n                \"database\": {\"type\": \"string\"},\n                \"region\": {\"type\": \"string\", \"default\": \"us-west\"},\n                \"lower_case_columns\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Lower Case Column Names in Results\",\n                    \"default\": False,\n                },\n                \"host\": {\"type\": \"string\"},\n            },\n            \"order\": [\n                \"account\",\n                \"user\",\n                \"password\",\n                \"private_key_File\",\n                \"private_key_pwd\",\n                \"warehouse\",\n                \"database\",\n                \"region\",\n                \"host\",\n            ],\n            \"required\": [\"user\", \"account\", \"database\", \"warehouse\"],\n            \"secret\": [\"password\", \"private_key_File\", \"private_key_pwd\"],\n            \"extra_options\": [\n                \"host\",\n            ],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def determine_type(cls, data_type, scale):\n        t = TYPES_MAP.get(data_type, None)\n        if t == TYPE_INTEGER and scale > 0:\n            return TYPE_FLOAT\n        return t\n\n    def _get_connection(self):\n        region = self.configuration.get(\"region\")\n        account = self.configuration[\"account\"]\n\n        # for us-west we don't need to pass a region (and if we do, it fails to connect)\n        if region == \"us-west\":\n            region = None\n\n        if self.configuration.get(\"host\"):\n            host = self.configuration.get(\"host\")\n        else:\n            if region:\n                host = \"{}.{}.snowflakecomputing.com\".format(account, region)\n            else:\n                host = \"{}.snowflakecomputing.com\".format(account)\n\n        params = {\n            \"user\": self.configuration[\"user\"],\n            \"account\": account,\n            \"region\": region,\n            \"host\": host,\n            \"application\": \"Redash/{} (Snowflake)\".format(__version__.split(\"-\")[0]),\n        }\n\n        if self.configuration.get(\"password\"):\n            params[\"password\"] = self.configuration[\"password\"]\n        elif self.configuration.get(\"private_key_File\"):\n            private_key_b64 = self.configuration.get(\"private_key_File\")\n            private_key_bytes = b64decode(private_key_b64)\n            if self.configuration.get(\"private_key_pwd\"):\n                private_key_pwd = self.configuration.get(\"private_key_pwd\").encode()\n            else:\n                private_key_pwd = None\n            private_key_pem = load_pem_private_key(private_key_bytes, private_key_pwd)\n            params[\"private_key\"] = private_key_pem\n        else:\n            raise Exception(\"Neither password nor private_key_b64 is set.\")\n\n        connection = snowflake.connector.connect(**params)\n\n        return connection\n\n    def _column_name(self, column_name):\n        if self.configuration.get(\"lower_case_columns\", False):\n            return column_name.lower()\n\n        return column_name\n\n    def _parse_results(self, cursor):\n        columns = self.fetch_columns(\n            [(self._column_name(i[0]), self.determine_type(i[1], i[5])) for i in cursor.description]\n        )\n        rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in cursor]\n\n        data = {\"columns\": columns, \"rows\": rows}\n        return data\n\n    def run_query(self, query, user):\n        connection = self._get_connection()\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(\"USE WAREHOUSE {}\".format(self.configuration[\"warehouse\"]))\n            cursor.execute(\"USE {}\".format(self.configuration[\"database\"]))\n\n            cursor.execute(query)\n\n            data = self._parse_results(cursor)\n            error = None\n        finally:\n            cursor.close()\n            connection.close()\n\n        return data, error\n\n    def _run_query_without_warehouse(self, query):\n        connection = self._get_connection()\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(\"USE {}\".format(self.configuration[\"database\"]))\n\n            cursor.execute(query)\n\n            data = self._parse_results(cursor)\n            error = None\n        finally:\n            cursor.close()\n            connection.close()\n\n        return data, error\n\n    def _database_name_includes_schema(self):\n        return \".\" in self.configuration.get(\"database\")\n\n    def get_schema(self, get_stats=False):\n        if self._database_name_includes_schema():\n            query = \"SHOW COLUMNS\"\n        else:\n            query = \"SHOW COLUMNS IN DATABASE\"\n\n        results, error = self._run_query_without_warehouse(query)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        schema = {}\n        for row in results[\"rows\"]:\n            if row[\"kind\"] == \"COLUMN\":\n                table_name = \"{}.{}\".format(row[\"schema_name\"], row[\"table_name\"])\n\n                if table_name not in schema:\n                    schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n                schema[table_name][\"columns\"].append(row[\"column_name\"])\n\n        return list(schema.values())\n\n\nregister(Snowflake)\n"
  },
  {
    "path": "redash/query_runner/sparql_endpoint.py",
    "content": "\"\"\"Provide the query runner for SPARQL Endpoints.\n\nseeAlso: https://www.w3.org/TR/rdf-sparql-query/\n\"\"\"\n\nimport json\nimport logging\nfrom os import environ\n\nfrom redash.query_runner import BaseQueryRunner\n\nfrom . import register\n\ntry:\n    import requests\n    from cmem.cmempy.queries import SparqlQuery\n    from rdflib.plugins.sparql import prepareQuery  # noqa\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\nlogger = logging.getLogger(__name__)\n\n\nclass SPARQLEndpointQueryRunner(BaseQueryRunner):\n    \"\"\"Use SPARQL Endpoint as redash data source\"\"\"\n\n    # These environment keys are used by cmempy\n    KNOWN_CONFIG_KEYS = (\"SPARQL_BASE_URI\", \"SSL_VERIFY\")\n\n    # These variables hold secret data and should NOT be logged\n    KNOWN_SECRET_KEYS = ()\n\n    # This allows for an easy connection test\n    noop_query = \"SELECT ?noop WHERE {BIND('noop' as ?noop)}\"\n\n    def __init__(self, configuration):\n        \"\"\"init the class and configuration\"\"\"\n        super(SPARQLEndpointQueryRunner, self).__init__(configuration)\n\n        self.configuration = configuration\n\n    def _setup_environment(self):\n        \"\"\"provide environment for rdflib\n\n        rdflib environment variables need to match key in the properties\n        object of the configuration_schema\n        \"\"\"\n        for key in self.KNOWN_CONFIG_KEYS:\n            if key in environ:\n                environ.pop(key)\n            value = self.configuration.get(key, None)\n            if value is not None:\n                environ[key] = str(value)\n                if key in self.KNOWN_SECRET_KEYS:\n                    logger.info(\"{} set by config\".format(key))\n                else:\n                    logger.info(\"{} set by config to {}\".format(key, environ[key]))\n\n    @staticmethod\n    def _transform_sparql_results(results):\n        \"\"\"transforms a SPARQL query result to a redash query result\n\n        source structure: SPARQL 1.1 Query Results JSON Format\n            - seeAlso: https://www.w3.org/TR/sparql11-results-json/\n\n        target structure: redash result set\n            there is no good documentation available\n            so here an example result set as needed for redash:\n            data = {\n                \"columns\": [ {\"name\": \"name\", \"type\": \"string\", \"friendly_name\": \"friendly name\"}],\n                \"rows\": [\n                    {\"name\": \"value 1\"},\n                    {\"name\": \"value 2\"}\n                ]}\n\n        FEATURE?: During the sparql_row loop, we could check the data types of the\n            values and, in case they are all the same, choose something better than\n            just string.\n        \"\"\"\n        logger.info(\"results are: {}\".format(results))\n        # Not sure why we do not use the json package here but all other\n        # query runner do it the same way :-)\n        sparql_results = results\n        # transform all bindings to redash rows\n        rows = []\n        for sparql_row in sparql_results[\"results\"][\"bindings\"]:\n            row = {}\n            for var in sparql_results[\"head\"][\"vars\"]:\n                try:\n                    row[var] = sparql_row[var][\"value\"]\n                except KeyError:\n                    # not bound SPARQL variables are set as empty strings\n                    row[var] = \"\"\n            rows.append(row)\n        # transform all vars to redash columns\n        columns = []\n        for var in sparql_results[\"head\"][\"vars\"]:\n            columns.append({\"name\": var, \"friendly_name\": var, \"type\": \"string\"})\n        # Not sure why we do not use the json package here but all other\n        # query runner do it the same way :-)\n        return {\"columns\": columns, \"rows\": rows}\n\n    @classmethod\n    def name(cls):\n        return \"SPARQL Endpoint\"\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def type(cls):\n        return \"sparql_endpoint\"\n\n    def remove_comments(self, string):\n        return string[string.index(\"*/\") + 2 :].strip()\n\n    def run_query(self, query, user):\n        \"\"\"send a query to a sparql endpoint\"\"\"\n        logger.info(\"about to execute query (user='{}'): {}\".format(user, query))\n        query_text = self.remove_comments(query)\n        query = SparqlQuery(query_text)\n        query_type = query.get_query_type()\n        if query_type not in [\"SELECT\", None]:\n            raise ValueError(\"Queries of type {} can not be processed by redash.\".format(query_type))\n\n        self._setup_environment()\n        try:\n            endpoint = self.configuration.get(\"SPARQL_BASE_URI\")\n            r = requests.get(\n                endpoint,\n                params=dict(query=query_text),\n                headers=dict(Accept=\"application/json\"),\n            )\n            data = self._transform_sparql_results(r.text)\n        except Exception as error:\n            logger.info(\"Error: {}\".format(error))\n            try:\n                # try to load Problem Details for HTTP API JSON\n                details = json.loads(error.response.text)\n                error = \"\"\n                if \"title\" in details:\n                    error += details[\"title\"] + \": \"\n                if \"detail\" in details:\n                    error += details[\"detail\"]\n                    return None, error\n            except Exception:\n                pass\n\n            return None, error\n\n        error = None\n        return data, error\n\n    @classmethod\n    def configuration_schema(cls):\n        \"\"\"provide the configuration of the data source as json schema\"\"\"\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"SPARQL_BASE_URI\": {\"type\": \"string\", \"title\": \"Base URL\"},\n                \"SSL_VERIFY\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Verify SSL certificates for API requests\",\n                    \"default\": True,\n                },\n            },\n            \"required\": [\"SPARQL_BASE_URI\"],\n            \"secret\": [],\n            \"extra_options\": [\"SSL_VERIFY\"],\n        }\n\n    def get_schema(self, get_stats=False):\n        \"\"\"Get the schema structure (prefixes, graphs).\"\"\"\n        schema = dict()\n        schema[\"1\"] = {\n            \"name\": \"-> Common Prefixes <-\",\n            \"columns\": self._get_common_prefixes_schema(),\n        }\n        schema[\"2\"] = {\"name\": \"-> Graphs <-\", \"columns\": self._get_graphs_schema()}\n        # schema.update(self._get_query_schema())\n        logger.info(f\"Getting Schema Values: {schema.values()}\")\n        return schema.values()\n\n    def _get_graphs_schema(self):\n        \"\"\"Get a list of readable graph FROM clause strings.\"\"\"\n        self._setup_environment()\n        endpoint = self.configuration.get(\"SPARQL_BASE_URI\")\n        query_text = \"SELECT DISTINCT ?g WHERE {GRAPH ?g {?s ?p ?o}}\"\n        r = requests.get(\n            endpoint,\n            params=dict(query=query_text),\n            headers=dict(Accept=\"application/json\"),\n        ).json()\n        graph_iris = [g.get(\"g\").get(\"value\") for g in r.get(\"results\").get(\"bindings\")]\n        graphs = []\n        for graph in graph_iris:\n            graphs.append(\"FROM <{}>\".format(graph))\n        return graphs\n\n    @staticmethod\n    def _get_common_prefixes_schema():\n        \"\"\"Get a list of SPARQL prefix declarations.\"\"\"\n        common_prefixes = [\n            \"PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\",\n            \"PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\",\n            \"PREFIX owl: <http://www.w3.org/2002/07/owl#>\",\n            \"PREFIX schema: <http://schema.org/>\",\n            \"PREFIX dct: <http://purl.org/dc/terms/>\",\n            \"PREFIX skos: <http://www.w3.org/2004/02/skos/core#>\",\n        ]\n        return common_prefixes\n\n\nregister(SPARQLEndpointQueryRunner)\n"
  },
  {
    "path": "redash/query_runner/sqlite.py",
    "content": "import logging\nimport sqlite3\n\nfrom redash.query_runner import (\n    BaseSQLQueryRunner,\n    JobTimeoutException,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\n\nclass Sqlite(BaseSQLQueryRunner):\n    noop_query = \"pragma quick_check\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\"dbpath\": {\"type\": \"string\", \"title\": \"Database Path\"}},\n            \"required\": [\"dbpath\"],\n        }\n\n    @classmethod\n    def type(cls):\n        return \"sqlite\"\n\n    def __init__(self, configuration):\n        super(Sqlite, self).__init__(configuration)\n\n        self._dbpath = self.configuration.get(\"dbpath\", \"\")\n\n    def _get_tables(self, schema):\n        query_table = \"select tbl_name from sqlite_master where type='table'\"\n        query_columns = 'PRAGMA table_info(\"%s\")'\n\n        results, error = self.run_query(query_table, None)\n\n        if error is not None:\n            raise Exception(\"Failed getting schema.\")\n\n        for row in results[\"rows\"]:\n            table_name = row[\"tbl_name\"]\n            schema[table_name] = {\"name\": table_name, \"columns\": []}\n            results_table, error = self.run_query(query_columns % (table_name,), None)\n            if error is not None:\n                self._handle_run_query_error(error)\n\n            for row_column in results_table[\"rows\"]:\n                schema[table_name][\"columns\"].append(row_column[\"name\"])\n\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        connection = sqlite3.connect(self._dbpath)\n\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(query)\n\n            if cursor.description is not None:\n                columns = self.fetch_columns([(i[0], None) for i in cursor.description])\n                rows = [dict(zip((column[\"name\"] for column in columns), row)) for row in cursor]\n\n                data = {\"columns\": columns, \"rows\": rows}\n                error = None\n            else:\n                error = \"Query completed but it returned no data.\"\n                data = None\n        except (KeyboardInterrupt, JobTimeoutException):\n            connection.cancel()\n            raise\n        finally:\n            connection.close()\n        return data, error\n\n\nregister(Sqlite)\n"
  },
  {
    "path": "redash/query_runner/tinybird.py",
    "content": "import logging\n\nimport requests\n\nfrom redash.query_runner import register\nfrom redash.query_runner.clickhouse import ClickHouse\n\nlogger = logging.getLogger(__name__)\n\n\nclass Tinybird(ClickHouse):\n    noop_query = \"SELECT count() FROM tinybird.pipe_stats LIMIT 1\"\n\n    DEFAULT_URL = \"https://api.tinybird.co\"\n\n    SQL_ENDPOINT = \"/v0/sql\"\n    DATASOURCES_ENDPOINT = \"/v0/datasources\"\n    PIPES_ENDPOINT = \"/v0/pipes\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\", \"default\": cls.DEFAULT_URL},\n                \"token\": {\"type\": \"string\", \"title\": \"Auth Token\"},\n                \"timeout\": {\n                    \"type\": \"number\",\n                    \"title\": \"Request Timeout\",\n                    \"default\": 30,\n                },\n                \"verify\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Verify SSL certificate\",\n                    \"default\": True,\n                },\n            },\n            \"order\": [\"url\", \"token\"],\n            \"required\": [\"token\"],\n            \"extra_options\": [\"timeout\", \"verify\"],\n            \"secret\": [\"token\"],\n        }\n\n    def _get_tables(self, schema):\n        self._collect_tinybird_schema(\n            schema,\n            self.DATASOURCES_ENDPOINT,\n            \"datasources\",\n        )\n\n        self._collect_tinybird_schema(\n            schema,\n            self.PIPES_ENDPOINT,\n            \"pipes\",\n        )\n\n        return list(schema.values())\n\n    def _send_query(self, data, session_id=None, session_check=None):\n        return self._get_from_tinybird(\n            self.SQL_ENDPOINT,\n            params={\"q\": data.encode(\"utf-8\", \"ignore\")},\n        )\n\n    def _collect_tinybird_schema(self, schema, endpoint, resource_type):\n        response = self._get_from_tinybird(endpoint)\n        resources = response.get(resource_type, [])\n\n        for r in resources:\n            if r[\"name\"] not in schema:\n                schema[r[\"name\"]] = {\"name\": r[\"name\"], \"columns\": []}\n\n            if resource_type == \"pipes\" and not r.get(\"endpoint\"):\n                continue\n\n            query = f\"SELECT * FROM {r['name']} LIMIT 1 FORMAT JSON\"\n            try:\n                query_result = self._send_query(query)\n            except Exception:\n                logger.exception(f\"error in schema {r['name']}\")\n                continue\n\n            columns = [meta[\"name\"] for meta in query_result[\"meta\"]]\n            schema[r[\"name\"]][\"columns\"].extend(columns)\n\n        return schema\n\n    def _get_from_tinybird(self, endpoint, params=None):\n        url = f\"{self.configuration.get('url', self.DEFAULT_URL)}{endpoint}\"\n        authorization = f\"Bearer {self.configuration.get('token')}\"\n\n        try:\n            response = requests.get(\n                url,\n                timeout=self.configuration.get(\"timeout\", 30),\n                params=params,\n                headers={\"Authorization\": authorization},\n                verify=self.configuration.get(\"verify\", True),\n            )\n        except requests.RequestException as e:\n            if e.response:\n                details = f\"({e.__class__.__name__}, Status Code: {e.response.status_code})\"\n            else:\n                details = f\"({e.__class__.__name__})\"\n            raise Exception(f\"Connection error to: {url} {details}.\")\n\n        if response.status_code >= 400:\n            raise Exception(response.text)\n\n        return response.json()\n\n\nregister(Tinybird)\n"
  },
  {
    "path": "redash/query_runner/treasuredata.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntry:\n    import tdclient\n    from tdclient import errors\n\n    enabled = True\n\nexcept ImportError:\n    enabled = False\n\nTD_TYPES_MAPPING = {\n    \"bigint\": TYPE_INTEGER,\n    \"tinyint\": TYPE_INTEGER,\n    \"smallint\": TYPE_INTEGER,\n    \"int\": TYPE_INTEGER,\n    \"integer\": TYPE_INTEGER,\n    \"long\": TYPE_INTEGER,\n    \"double\": TYPE_FLOAT,\n    \"decimal\": TYPE_FLOAT,\n    \"float\": TYPE_FLOAT,\n    \"real\": TYPE_FLOAT,\n    \"boolean\": TYPE_BOOLEAN,\n    \"timestamp\": TYPE_DATETIME,\n    \"date\": TYPE_DATETIME,\n    \"char\": TYPE_STRING,\n    \"string\": TYPE_STRING,\n    \"varchar\": TYPE_STRING,\n}\n\n\nclass TreasureData(BaseQueryRunner):\n    should_annotate_query = False\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"endpoint\": {\"type\": \"string\"},\n                \"apikey\": {\"type\": \"string\"},\n                \"type\": {\"type\": \"string\"},\n                \"db\": {\"type\": \"string\", \"title\": \"Database Name\"},\n                \"get_schema\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Auto Schema Retrieval\",\n                    \"default\": False,\n                },\n            },\n            \"secret\": [\"apikey\"],\n            \"required\": [\"apikey\", \"db\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def type(cls):\n        return \"treasuredata\"\n\n    def get_schema(self, get_stats=False):\n        schema = {}\n        if self.configuration.get(\"get_schema\", False):\n            try:\n                with tdclient.Client(\n                    self.configuration.get(\"apikey\"), endpoint=self.configuration.get(\"endpoint\")\n                ) as client:\n                    for table in client.tables(self.configuration.get(\"db\")):\n                        table_name = \"{}.{}\".format(self.configuration.get(\"db\"), table.name)\n                        for table_schema in table.schema:\n                            schema[table_name] = {\n                                \"name\": table_name,\n                                \"columns\": [column[0] for column in table.schema],\n                            }\n            except Exception:\n                raise Exception(\"Failed getting schema\")\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        connection = tdclient.connect(\n            endpoint=self.configuration.get(\"endpoint\", \"https://api.treasuredata.com\"),\n            apikey=self.configuration.get(\"apikey\"),\n            type=self.configuration.get(\"type\", \"hive\").lower(),\n            db=self.configuration.get(\"db\"),\n        )\n\n        cursor = connection.cursor()\n        try:\n            cursor.execute(query)\n            columns_tuples = [\n                (i[0], TD_TYPES_MAPPING.get(i[1], None)) for i in cursor.show_job()[\"hive_result_schema\"]\n            ]\n            columns = self.fetch_columns(columns_tuples)\n\n            if cursor.rowcount == 0:\n                rows = []\n            else:\n                rows = [dict(zip(([column[\"name\"] for column in columns]), r)) for r in cursor.fetchall()]\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n        except errors.InternalError as e:\n            data = None\n            error = \"%s: %s\" % (\n                str(e),\n                cursor.show_job().get(\"debug\", {}).get(\"stderr\", \"No stderr message in the response\"),\n            )\n        return data, error\n\n\nregister(TreasureData)\n"
  },
  {
    "path": "redash/query_runner/trino.py",
    "content": "import logging\nimport os\n\nfrom redash.models.users import ApiUser, User\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseQueryRunner,\n    InterruptException,\n    JobTimeoutException,\n    register,\n)\nfrom redash.settings import parse_boolean\n\nlogger = logging.getLogger(__name__)\nANNOTATE_QUERY = parse_boolean(os.environ.get(\"TRINO_ANNOTATE_QUERY\", \"true\"))\n\ntry:\n    import trino\n    from trino.exceptions import DatabaseError\n    from trino.types import NamedRowTuple\n\n    enabled = True\nexcept ImportError:\n    enabled = False\n\n\ndef _convert_row_types(value):\n    \"\"\"Convert NamedRowTuple instances to dicts so ROW fields are serialized with their names.\"\"\"\n    if isinstance(value, NamedRowTuple):\n        names = value.__annotations__.get(\"names\", [])\n        return {\n            name if name is not None else f\"_field{i}\": _convert_row_types(v)\n            for i, (name, v) in enumerate(zip(names, value))\n        }\n    if isinstance(value, (list, tuple)):\n        return [_convert_row_types(v) for v in value]\n    return value\n\n\nTRINO_TYPES_MAPPING = {\n    \"boolean\": TYPE_BOOLEAN,\n    \"tinyint\": TYPE_INTEGER,\n    \"smallint\": TYPE_INTEGER,\n    \"integer\": TYPE_INTEGER,\n    \"long\": TYPE_INTEGER,\n    \"bigint\": TYPE_INTEGER,\n    \"float\": TYPE_FLOAT,\n    \"real\": TYPE_FLOAT,\n    \"double\": TYPE_FLOAT,\n    \"decimal\": TYPE_INTEGER,\n    \"varchar\": TYPE_STRING,\n    \"char\": TYPE_STRING,\n    \"string\": TYPE_STRING,\n    \"json\": TYPE_STRING,\n    \"date\": TYPE_DATE,\n    \"timestamp\": TYPE_DATETIME,\n}\n\n\nclass Trino(BaseQueryRunner):\n    noop_query = \"SELECT 1\"\n    should_annotate_query = ANNOTATE_QUERY\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"protocol\": {\"type\": \"string\", \"default\": \"http\"},\n                \"host\": {\"type\": \"string\"},\n                \"port\": {\"type\": \"number\"},\n                \"username\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\"},\n                \"source\": {\"type\": \"string\", \"default\": \"redash\"},\n                \"client_tags\": {\"type\": \"string\", \"title\": \"Client tags (comma separated)\"},\n                \"catalog\": {\"type\": \"string\"},\n                \"schema\": {\"type\": \"string\"},\n                \"impersonation\": {\"type\": \"boolean\", \"default\": False},\n                \"impersonationField\": {\n                    \"type\": \"string\",\n                    \"title\": \"Impersonation User Attribute\",\n                    \"default\": \"email\",\n                    \"extendedEnum\": [{\"value\": \"email\", \"name\": \"Email\"}, {\"value\": \"name\", \"name\": \"Name\"}],\n                },\n            },\n            \"order\": [\n                \"protocol\",\n                \"host\",\n                \"port\",\n                \"username\",\n                \"password\",\n                \"source\",\n                \"client_tags\",\n                \"catalog\",\n                \"schema\",\n                \"impersonation\",\n            ],\n            \"required\": [\"host\", \"username\"],\n            \"secret\": [\"password\"],\n            \"extra_options\": [\n                \"client_tags\",\n                \"impersonation\",\n                \"impersonationField\",\n            ],\n        }\n\n    @classmethod\n    def enabled(cls):\n        return enabled\n\n    @classmethod\n    def type(cls):\n        return \"trino\"\n\n    def get_schema(self, get_stats=False):\n        if self.configuration.get(\"catalog\"):\n            catalogs = [self.configuration.get(\"catalog\")]\n        else:\n            catalogs = self._get_catalogs()\n\n        schema = {}\n        for catalog in catalogs:\n            query = f\"\"\"\n                SELECT table_schema, table_name, column_name, data_type\n                FROM {catalog}.information_schema.columns\n                WHERE table_schema NOT IN ('pg_catalog', 'information_schema')\n            \"\"\"\n            results, error = self.run_query(query, None)\n\n            if error is not None:\n                self._handle_run_query_error(error)\n\n            for row in results[\"rows\"]:\n                table_name = f'{catalog}.{row[\"table_schema\"]}.{row[\"table_name\"]}'\n\n                if table_name not in schema:\n                    schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n                column = {\"name\": row[\"column_name\"], \"type\": row[\"data_type\"]}\n                schema[table_name][\"columns\"].append(column)\n\n        return list(schema.values())\n\n    def _get_catalogs(self):\n        query = \"\"\"\n            SHOW CATALOGS\n        \"\"\"\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        catalogs = []\n        for row in results[\"rows\"]:\n            catalog = row[\"Catalog\"]\n            if \".\" in catalog:\n                catalog = f'\"{catalog}\"'\n            catalogs.append(catalog)\n        return catalogs\n\n    def _get_trino_user(self, user):\n        \"\"\"Determine the Trino user based on impersonation settings.\"\"\"\n        default_user = self.configuration.get(\"username\")\n\n        if not self.configuration.get(\"impersonation\") or user is None:\n            return default_user\n\n        impersonation_field = self.configuration.get(\"impersonationField\", \"email\")\n\n        if isinstance(user, User):\n            if impersonation_field == \"email\":\n                return user.email or default_user\n            elif impersonation_field == \"name\":\n                return user.name or default_user\n        elif isinstance(user, ApiUser):\n            return user.name or default_user\n\n        return default_user\n\n    def _get_client_tags(self):\n        client_tags = self.configuration.get(\"client_tags\")\n        if not client_tags:\n            return None\n        tags = [tag.strip() for tag in client_tags.split(\",\") if tag.strip()]\n        return tags or None\n\n    def run_query(self, query, user):\n        if self.configuration.get(\"password\"):\n            auth = trino.auth.BasicAuthentication(\n                username=self.configuration.get(\"username\"), password=self.configuration.get(\"password\")\n            )\n        else:\n            auth = trino.constants.DEFAULT_AUTH\n\n        connection = trino.dbapi.connect(\n            http_scheme=self.configuration.get(\"protocol\", \"http\"),\n            host=self.configuration.get(\"host\", \"\"),\n            source=self.configuration.get(\"source\", \"redash\"),\n            port=self.configuration.get(\"port\", 8080),\n            catalog=self.configuration.get(\"catalog\", \"\"),\n            schema=self.configuration.get(\"schema\", \"\"),\n            user=self._get_trino_user(user),\n            client_tags=self._get_client_tags(),\n            auth=auth,\n        )\n\n        cursor = connection.cursor()\n\n        try:\n            cursor.execute(query)\n            results = cursor.fetchall()\n            description = cursor.description\n            columns = self.fetch_columns([(c[0], TRINO_TYPES_MAPPING.get(c[1], None)) for c in description])\n            column_names = [c[\"name\"] for c in columns]\n            rows = [dict(zip(column_names, [_convert_row_types(v) for v in r])) for r in results]\n            data = {\"columns\": columns, \"rows\": rows}\n            error = None\n        except DatabaseError as db:\n            data = None\n            default_message = \"Unspecified DatabaseError: {0}\".format(str(db))\n            if isinstance(db.args[0], dict):\n                message = db.args[0].get(\"failureInfo\", {\"message\", None}).get(\"message\")\n            else:\n                message = None\n            error = default_message if message is None else message\n        except (KeyboardInterrupt, InterruptException, JobTimeoutException):\n            cursor.cancel()\n            raise\n\n        return data, error\n\n\nregister(Trino)\n"
  },
  {
    "path": "redash/query_runner/uptycs.py",
    "content": "import datetime\nimport logging\n\nimport jwt\nimport requests\n\nfrom redash.query_runner import BaseSQLQueryRunner, register\nfrom redash.utils import json_loads\n\nlogger = logging.getLogger(__name__)\n\n\nclass Uptycs(BaseSQLQueryRunner):\n    should_annotate_query = False\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\"},\n                \"customer_id\": {\"type\": \"string\"},\n                \"key\": {\"type\": \"string\"},\n                \"verify_ssl\": {\n                    \"type\": \"boolean\",\n                    \"default\": True,\n                    \"title\": \"Verify SSL Certificates\",\n                },\n                \"secret\": {\"type\": \"string\"},\n            },\n            \"order\": [\"url\", \"customer_id\", \"key\", \"secret\"],\n            \"required\": [\"url\", \"customer_id\", \"key\", \"secret\"],\n            \"secret\": [\"secret\", \"key\"],\n        }\n\n    def generate_header(self, key, secret):\n        header = {}\n        utcnow = datetime.datetime.utcnow()\n        date = utcnow.strftime(\"%a, %d %b %Y %H:%M:%S GMT\")\n        auth_var = jwt.encode({\"iss\": key}, secret, algorithm=\"HS256\")\n        authorization = \"Bearer %s\" % (auth_var)\n        header[\"date\"] = date\n        header[\"Authorization\"] = authorization\n        return header\n\n    def transformed_to_redash_json(self, data):\n        transformed_columns = []\n        rows = []\n        # convert all type to JSON string\n        # In future we correct data type  mapping later\n        if \"columns\" in data:\n            for json_each in data[\"columns\"]:\n                name = json_each[\"name\"]\n                new_json = {\"name\": name, \"type\": \"string\", \"friendly_name\": name}\n                transformed_columns.append(new_json)\n        # Transfored items into rows.\n        if \"items\" in data:\n            rows = data[\"items\"]\n\n        return {\"columns\": transformed_columns, \"rows\": rows}\n\n    def api_call(self, sql):\n        # JWT encoded header\n        header = self.generate_header(self.configuration.get(\"key\"), self.configuration.get(\"secret\"))\n\n        # URL form using API key file based on GLOBAL\n        url = \"%s/public/api/customers/%s/query\" % (\n            self.configuration.get(\"url\"),\n            self.configuration.get(\"customer_id\"),\n        )\n\n        # post data base sql\n        post_data_json = {\"query\": sql}\n\n        response = requests.post(\n            url,\n            headers=header,\n            json=post_data_json,\n            verify=self.configuration.get(\"verify_ssl\", True),\n        )\n\n        if response.status_code == 200:\n            response_output = json_loads(response.content)\n        else:\n            error = \"status_code \" + str(response.status_code) + \"\\n\"\n            error = error + \"failed to connect\"\n            data = {}\n            return data, error\n        # if we get right status code then call transfored_to_redash\n        data = self.transformed_to_redash_json(response_output)\n        error = None\n        # if we got error from Uptycs include error information\n        if \"error\" in response_output:\n            error = response_output[\"error\"][\"message\"][\"brief\"]\n            error = error + \"\\n\" + response_output[\"error\"][\"message\"][\"detail\"]\n        return data, error\n\n    def run_query(self, query, user):\n        data, error = self.api_call(query)\n        logger.debug(\"%s\", data)\n        return data, error\n\n    def get_schema(self, get_stats=False):\n        header = self.generate_header(self.configuration.get(\"key\"), self.configuration.get(\"secret\"))\n        url = \"%s/public/api/customers/%s/schema/global\" % (\n            self.configuration.get(\"url\"),\n            self.configuration.get(\"customer_id\"),\n        )\n        response = requests.get(url, headers=header, verify=self.configuration.get(\"verify_ssl\", True))\n        redash_json = []\n        schema = json_loads(response.content)\n        for each_def in schema[\"tables\"]:\n            table_name = each_def[\"name\"]\n            columns = []\n            for col in each_def[\"columns\"]:\n                columns.append(col[\"name\"])\n            table_json = {\"name\": table_name, \"columns\": columns}\n            redash_json.append(table_json)\n\n        logger.debug(\"%s\", list(schema.values()))\n        return redash_json\n\n\nregister(Uptycs)\n"
  },
  {
    "path": "redash/query_runner/url.py",
    "content": "from redash.query_runner import BaseHTTPQueryRunner, register\nfrom redash.utils import deprecated\n\n\n@deprecated()\nclass Url(BaseHTTPQueryRunner):\n    requires_url = False\n\n    def test_connection(self):\n        pass\n\n    def run_query(self, query, user):\n        base_url = self.configuration.get(\"url\", None)\n\n        query = query.strip()\n\n        if base_url is not None and base_url != \"\":\n            if query.find(\"://\") > -1:\n                return None, \"Accepting only relative URLs to '%s'\" % base_url\n\n        if base_url is None:\n            base_url = \"\"\n\n        url = base_url + query\n\n        response, error = self.get_response(url)\n        if error is not None:\n            return None, error\n\n        json_data = response.content.strip()\n\n        if json_data:\n            return json_data, None\n        else:\n            return None, \"Got empty response from '{}'.\".format(url)\n\n\nregister(Url)\n"
  },
  {
    "path": "redash/query_runner/vertica.py",
    "content": "import logging\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\ntypes_map = {\n    5: TYPE_BOOLEAN,\n    6: TYPE_INTEGER,\n    7: TYPE_FLOAT,\n    8: TYPE_STRING,\n    9: TYPE_STRING,\n    10: TYPE_DATE,\n    11: TYPE_DATETIME,\n    12: TYPE_DATETIME,\n    13: TYPE_DATETIME,\n    14: TYPE_DATETIME,\n    15: TYPE_DATETIME,\n    16: TYPE_FLOAT,\n    17: TYPE_STRING,\n    114: TYPE_DATETIME,\n    115: TYPE_STRING,\n    116: TYPE_STRING,\n    117: TYPE_STRING,\n}\n\n\nclass Vertica(BaseSQLQueryRunner):\n    noop_query = \"SELECT 1\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"host\": {\"type\": \"string\"},\n                \"user\": {\"type\": \"string\"},\n                \"password\": {\"type\": \"string\", \"title\": \"Password\"},\n                \"database\": {\"type\": \"string\", \"title\": \"Database name\"},\n                \"port\": {\"type\": \"number\"},\n                \"read_timeout\": {\"type\": \"number\", \"title\": \"Read Timeout\"},\n                \"connection_timeout\": {\"type\": \"number\", \"title\": \"Connection Timeout\"},\n            },\n            \"required\": [\"database\"],\n            \"order\": [\n                \"host\",\n                \"port\",\n                \"user\",\n                \"password\",\n                \"database\",\n                \"read_timeout\",\n                \"connection_timeout\",\n            ],\n            \"secret\": [\"password\"],\n        }\n\n    @classmethod\n    def enabled(cls):\n        try:\n            import vertica_python  # noqa: F401\n        except ImportError:\n            return False\n\n        return True\n\n    def _get_tables(self, schema):\n        query = \"\"\"\n        Select table_schema, table_name, column_name from columns where is_system_table=false\n        union all\n        select table_schema, table_name, column_name from view_columns;\n        \"\"\"\n\n        results, error = self.run_query(query, None)\n\n        if error is not None:\n            self._handle_run_query_error(error)\n\n        for row in results[\"rows\"]:\n            table_name = \"{}.{}\".format(row[\"table_schema\"], row[\"table_name\"])\n\n            if table_name not in schema:\n                schema[table_name] = {\"name\": table_name, \"columns\": []}\n\n            schema[table_name][\"columns\"].append(row[\"column_name\"])\n\n        return list(schema.values())\n\n    def run_query(self, query, user):\n        import vertica_python\n\n        if query == \"\":\n            data = None\n            error = \"Query is empty\"\n            return data, error\n\n        connection = None\n        try:\n            conn_info = {\n                \"host\": self.configuration.get(\"host\", \"\"),\n                \"port\": self.configuration.get(\"port\", 5433),\n                \"user\": self.configuration.get(\"user\", \"\"),\n                \"password\": self.configuration.get(\"password\", \"\"),\n                \"database\": self.configuration.get(\"database\", \"\"),\n                \"read_timeout\": self.configuration.get(\"read_timeout\", 600),\n            }\n\n            if self.configuration.get(\"connection_timeout\"):\n                conn_info[\"connection_timeout\"] = self.configuration.get(\"connection_timeout\")\n\n            connection = vertica_python.connect(**conn_info)\n            cursor = connection.cursor()\n            logger.debug(\"Vertica running query: %s\", query)\n            cursor.execute(query)\n\n            if cursor.description is not None:\n                columns_data = [(i[0], types_map.get(i[1], None)) for i in cursor.description]\n\n                columns = self.fetch_columns(columns_data)\n                rows = [dict(zip(([c[\"name\"] for c in columns]), r)) for r in cursor.fetchall()]\n\n                data = {\"columns\": columns, \"rows\": rows}\n                error = None\n            else:\n                data = None\n                error = \"No data was returned.\"\n\n            cursor.close()\n        finally:\n            if connection:\n                connection.close()\n\n        return data, error\n\n\nregister(Vertica)\n"
  },
  {
    "path": "redash/query_runner/yandex_disk.py",
    "content": "import logging\nfrom importlib.util import find_spec\n\nimport requests\nimport yaml\n\nfrom redash.query_runner import BaseSQLQueryRunner, register\nfrom redash.utils.pandas import pandas_installed\n\nopenpyxl_installed = find_spec(\"openpyxl\")\n\nif pandas_installed and openpyxl_installed:\n    import openpyxl  # noqa: F401\n    import pandas as pd\n\n    from redash.utils.pandas import pandas_to_result\n\n    enabled = True\n\n    EXTENSIONS_READERS = {\n        \"csv\": pd.read_csv,\n        \"tsv\": pd.read_table,\n        \"xls\": pd.read_excel,\n        \"xlsx\": pd.read_excel,\n    }\nelse:\n    enabled = False\n\nlogger = logging.getLogger(__name__)\n\n\nclass YandexDisk(BaseSQLQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def type(cls):\n        return \"yandex_disk\"\n\n    @classmethod\n    def name(cls):\n        return \"Yandex Disk\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\n                \"token\": {\"type\": \"string\", \"title\": \"OAuth Token\"},\n            },\n            \"secret\": [\"token\"],\n            \"required\": [\"token\"],\n        }\n\n    def __init__(self, configuration):\n        super(YandexDisk, self).__init__(configuration)\n        self.syntax = \"yaml\"\n        self.base_url = \"https://cloud-api.yandex.net/v1/disk\"\n        self.list_path = \"counters\"\n\n    def _get_tables(self, schema):\n        offset = 0\n        limit = 100\n\n        while True:\n            tmp_response = self._send_query(\n                \"resources/public\", media_type=\"spreadsheet,text\", limit=limit, offset=offset\n            )\n\n            tmp_items = tmp_response[\"items\"]\n\n            for file_info in tmp_items:\n                file_name = file_info[\"name\"]\n                file_path = file_info[\"path\"].replace(\"disk:\", \"\")\n\n                file_extension = file_name.split(\".\")[-1].lower()\n                if file_extension not in EXTENSIONS_READERS:\n                    continue\n\n                schema[file_name] = {\"name\": file_name, \"columns\": [file_path]}\n\n            if len(tmp_items) < limit:\n                break\n\n            offset += limit\n\n        return list(schema.values())\n\n    def test_connection(self):\n        self._send_query()\n\n    def _send_query(self, url_path=\"\", **kwargs):\n        token = kwargs.pop(\"oauth_token\", self.configuration[\"token\"])\n        r = requests.get(\n            f\"{self.base_url}/{url_path}\",\n            headers={\"Authorization\": f\"OAuth {token}\"},\n            params=kwargs,\n        )\n\n        response_data = r.json()\n\n        if not r.ok:\n            error_message = f\"Code: {r.status_code}, message: {r.text}\"\n            raise Exception(error_message)\n        return response_data\n\n    def run_query(self, query, user):\n        logger.debug(\"Yandex Disk is about to execute query: %s\", query)\n        data = None\n\n        if not query:\n            error = \"Query is empty\"\n            return data, error\n\n        try:\n            params = yaml.safe_load(query)\n        except (ValueError, AttributeError) as e:\n            logger.exception(e)\n            error = f\"YAML read error: {str(e)}\"\n            return data, error\n\n        if not isinstance(params, dict):\n            error = \"The query format must be JSON or YAML\"\n            return data, error\n\n        if \"path\" not in params:\n            error = \"The query must contain path\"\n            return data, error\n\n        file_extension = params[\"path\"].split(\".\")[-1].lower()\n\n        read_params = {}\n        is_multiple_sheets = False\n\n        if file_extension not in EXTENSIONS_READERS:\n            error = f\"Unsupported file extension: {file_extension}\"\n            return data, error\n        elif file_extension in (\"xls\", \"xlsx\"):\n            read_params[\"sheet_name\"] = params.get(\"sheet_name\", 0)\n            if read_params[\"sheet_name\"] is None:\n                is_multiple_sheets = True\n\n        file_url = self._send_query(\"resources/download\", path=params[\"path\"])[\"href\"]\n\n        try:\n            df = EXTENSIONS_READERS[file_extension](file_url, **read_params)\n        except Exception as e:\n            logger.exception(e)\n            error = f\"Read file error: {str(e)}\"\n            return data, error\n\n        if is_multiple_sheets:\n            new_df = []\n            for sheet_name, sheet_df in df.items():\n                sheet_df[\"sheet_name\"] = sheet_name\n                new_df.append(sheet_df)\n            new_df = pd.concat(new_df, ignore_index=True)\n            df = new_df.copy()\n\n        data = pandas_to_result(df)\n        error = None\n\n        return data, error\n\n\nregister(YandexDisk)\n"
  },
  {
    "path": "redash/query_runner/yandex_metrica.py",
    "content": "import logging\nfrom urllib.parse import parse_qs, urlparse\n\nimport backoff\nimport requests\nimport yaml\n\nfrom redash.query_runner import (\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_STRING,\n    BaseSQLQueryRunner,\n    register,\n)\n\nlogger = logging.getLogger(__name__)\n\nCOLUMN_TYPES = {\n    \"date\": (\n        \"firstVisitDate\",\n        \"firstVisitStartOfYear\",\n        \"firstVisitStartOfQuarter\",\n        \"firstVisitStartOfMonth\",\n        \"firstVisitStartOfWeek\",\n    ),\n    \"datetime\": (\n        \"firstVisitStartOfHour\",\n        \"firstVisitStartOfDekaminute\",\n        \"firstVisitStartOfMinute\",\n        \"firstVisitDateTime\",\n        \"firstVisitHour\",\n        \"firstVisitHourMinute\",\n    ),\n    \"int\": (\n        \"pageViewsInterval\",\n        \"pageViews\",\n        \"firstVisitYear\",\n        \"firstVisitMonth\",\n        \"firstVisitDayOfMonth\",\n        \"firstVisitDayOfWeek\",\n        \"firstVisitMinute\",\n        \"firstVisitDekaminute\",\n    ),\n}\n\nfor type_, elements in COLUMN_TYPES.items():\n    for el in elements:\n        if \"first\" in el:\n            el = el.replace(\"first\", \"last\")\n            COLUMN_TYPES[type_] += (el,)\n\n\ndef parse_ym_response(response):\n    columns = []\n    dimensions_len = len(response[\"query\"][\"dimensions\"])\n\n    for h in response[\"query\"][\"dimensions\"] + response[\"query\"][\"metrics\"]:\n        friendly_name = h.split(\":\")[-1]\n        if friendly_name in COLUMN_TYPES[\"date\"]:\n            data_type = TYPE_DATE\n        elif friendly_name in COLUMN_TYPES[\"datetime\"]:\n            data_type = TYPE_DATETIME\n        else:\n            data_type = TYPE_STRING\n        columns.append({\"name\": h, \"friendly_name\": friendly_name, \"type\": data_type})\n\n    rows = []\n    for num, row in enumerate(response[\"data\"]):\n        res = {}\n        for i, d in enumerate(row[\"dimensions\"]):\n            res[columns[i][\"name\"]] = d[\"name\"]\n        for i, d in enumerate(row[\"metrics\"]):\n            res[columns[dimensions_len + i][\"name\"]] = d\n            if num == 0 and isinstance(d, float):\n                columns[dimensions_len + i][\"type\"] = TYPE_FLOAT\n        rows.append(res)\n\n    return {\"columns\": columns, \"rows\": rows}\n\n\nclass QuotaException(Exception):\n    pass\n\n\nclass YandexMetrica(BaseSQLQueryRunner):\n    should_annotate_query = False\n\n    @classmethod\n    def type(cls):\n        # This is written with a \"k\" for backward-compatibility. See #2874.\n        return \"yandex_metrika\"\n\n    @classmethod\n    def name(cls):\n        return \"Yandex Metrica\"\n\n    @classmethod\n    def configuration_schema(cls):\n        return {\n            \"type\": \"object\",\n            \"properties\": {\"token\": {\"type\": \"string\", \"title\": \"OAuth Token\"}},\n            \"secret\": [\"token\"],\n            \"required\": [\"token\"],\n        }\n\n    def __init__(self, configuration):\n        super(YandexMetrica, self).__init__(configuration)\n        self.syntax = \"yaml\"\n        self.url = \"https://api-metrica.yandex.com\"\n        self.list_path = \"counters\"\n\n    def _get_tables(self, schema):\n        counters = self._send_query(f\"management/v1/{self.list_path}\")\n\n        for row in counters[self.list_path]:\n            owner = row.get(\"owner_login\")\n            counter = f\"{row.get('name', 'Unknown')} | {row.get('id', 'Unknown')}\"\n            if owner not in schema:\n                schema[owner] = {\"name\": owner, \"columns\": []}\n\n            schema[owner][\"columns\"].append(counter)\n\n        return list(schema.values())\n\n    def test_connection(self):\n        self._send_query(f\"management/v1/{self.list_path}\")\n\n    @backoff.on_exception(backoff.fibo, QuotaException, max_tries=10)\n    def _send_query(self, path=\"stat/v1/data\", **kwargs):\n        token = kwargs.pop(\"oauth_token\", self.configuration[\"token\"])\n        r = requests.get(\n            f\"{self.url}/{path}\",\n            headers={\"Authorization\": f\"OAuth {token}\"},\n            params=kwargs,\n        )\n\n        response_data = r.json()\n\n        if not r.ok:\n            error_message = f\"Code: {r.status_code}, message: {r.text}\"\n            if r.status_code == 429:\n                logger.warning(\"Warning: 429 status code on Yandex Metrica query\")\n                raise QuotaException(error_message)\n            raise Exception(error_message)\n        return response_data\n\n    def run_query(self, query, user):\n        logger.debug(\"Metrica is about to execute query: %s\", query)\n        data = None\n        query = query.strip()\n        if query == \"\":\n            error = \"Query is empty\"\n            return data, error\n        try:\n            params = yaml.safe_load(query)\n        except ValueError as e:\n            logging.exception(e)\n            error = str(e)\n            return data, error\n\n        if isinstance(params, dict):\n            if \"url\" in params:\n                params = parse_qs(urlparse(params[\"url\"]).query, keep_blank_values=True)\n        else:\n            error = \"The query format must be JSON or YAML\"\n            return data, error\n\n        try:\n            data = parse_ym_response(self._send_query(**params))\n            error = None\n        except Exception as e:\n            logging.exception(e)\n            error = str(e)\n        return data, error\n\n\nclass YandexAppMetrica(YandexMetrica):\n    @classmethod\n    def type(cls):\n        # This is written with a \"k\" for backward-compatibility. See #2874.\n        return \"yandex_appmetrika\"\n\n    @classmethod\n    def name(cls):\n        return \"Yandex AppMetrica\"\n\n    def __init__(self, configuration):\n        super(YandexAppMetrica, self).__init__(configuration)\n        self.url = \"https://api.appmetrica.yandex.com\"\n        self.list_path = \"applications\"\n\n\nregister(YandexMetrica)\nregister(YandexAppMetrica)\n"
  },
  {
    "path": "redash/security.py",
    "content": "import functools\n\nfrom flask import request, session\nfrom flask_login import current_user\nfrom flask_talisman import talisman\nfrom flask_wtf.csrf import CSRFProtect, generate_csrf\n\nfrom redash import settings\n\ntalisman = talisman.Talisman()\ncsrf = CSRFProtect()\n\n\ndef csp_allows_embeding(fn):\n    @functools.wraps(fn)\n    def decorated(*args, **kwargs):\n        return fn(*args, **kwargs)\n\n    embedable_csp = talisman.content_security_policy + \"frame-ancestors *;\"\n    return talisman(content_security_policy=embedable_csp, frame_options=None)(decorated)\n\n\ndef init_app(app):\n    csrf.init_app(app)\n    app.config[\"WTF_CSRF_CHECK_DEFAULT\"] = False\n    app.config[\"WTF_CSRF_SSL_STRICT\"] = False\n    app.config[\"WTF_CSRF_TIME_LIMIT\"] = settings.CSRF_TIME_LIMIT\n    app.config[\"SESSION_COOKIE_NAME\"] = settings.SESSION_COOKIE_NAME\n\n    @app.after_request\n    def inject_csrf_token(response):\n        response.set_cookie(\"csrf_token\", generate_csrf())\n        return response\n\n    if settings.ENFORCE_CSRF:\n\n        @app.before_request\n        def check_csrf():\n            # BEGIN workaround until https://github.com/lepture/flask-wtf/pull/419 is merged\n            if request.blueprint in csrf._exempt_blueprints:\n                return\n\n            view = app.view_functions.get(request.endpoint)\n            if view is not None and f\"{view.__module__}.{view.__name__}\" in csrf._exempt_views:\n                return\n            # END workaround\n\n            if not current_user.is_authenticated or \"user_id\" in session:\n                csrf.protect()\n\n    talisman.init_app(\n        app,\n        feature_policy=settings.FEATURE_POLICY,\n        force_https=settings.ENFORCE_HTTPS,\n        force_https_permanent=settings.ENFORCE_HTTPS_PERMANENT,\n        force_file_save=settings.ENFORCE_FILE_SAVE,\n        frame_options=settings.FRAME_OPTIONS,\n        frame_options_allow_from=settings.FRAME_OPTIONS_ALLOW_FROM,\n        strict_transport_security=settings.HSTS_ENABLED,\n        strict_transport_security_preload=settings.HSTS_PRELOAD,\n        strict_transport_security_max_age=settings.HSTS_MAX_AGE,\n        strict_transport_security_include_subdomains=settings.HSTS_INCLUDE_SUBDOMAINS,\n        content_security_policy=settings.CONTENT_SECURITY_POLICY,\n        content_security_policy_report_uri=settings.CONTENT_SECURITY_POLICY_REPORT_URI,\n        content_security_policy_report_only=settings.CONTENT_SECURITY_POLICY_REPORT_ONLY,\n        content_security_policy_nonce_in=settings.CONTENT_SECURITY_POLICY_NONCE_IN,\n        referrer_policy=settings.REFERRER_POLICY,\n        session_cookie_secure=settings.SESSION_COOKIE_SECURE,\n        session_cookie_http_only=settings.SESSION_COOKIE_HTTPONLY,\n    )\n"
  },
  {
    "path": "redash/serializers/__init__.py",
    "content": "\"\"\"\nThis will eventually replace all the `to_dict` methods of the different model\nclasses we have. This will ensure cleaner code and better\nseparation of concerns.\n\"\"\"\n\nfrom flask_login import current_user\nfrom funcy import project\nfrom rq.job import JobStatus\nfrom rq.timeouts import JobTimeoutException\n\nfrom redash import models\nfrom redash.models.parameterized_query import ParameterizedQuery\nfrom redash.permissions import has_access, view_only\nfrom redash.serializers.query_result import (\n    serialize_query_result,\n    serialize_query_result_to_dsv,\n    serialize_query_result_to_xlsx,\n)\n\n\ndef public_widget(widget):\n    res = {\n        \"id\": widget.id,\n        \"width\": widget.width,\n        \"options\": widget.options,\n        \"text\": widget.text,\n        \"updated_at\": widget.updated_at,\n        \"created_at\": widget.created_at,\n    }\n\n    v = widget.visualization\n    if v and v.id:\n        res[\"visualization\"] = {\n            \"type\": v.type,\n            \"name\": v.name,\n            \"description\": v.description,\n            \"options\": v.options,\n            \"updated_at\": v.updated_at,\n            \"created_at\": v.created_at,\n            \"query\": {\n                \"id\": v.query_rel.id,\n                \"name\": v.query_rel.name,\n                \"description\": v.query_rel.description,\n                \"options\": v.query_rel.options,\n            },\n        }\n\n    return res\n\n\ndef public_dashboard(dashboard):\n    dashboard_dict = project(\n        serialize_dashboard(dashboard, with_favorite_state=False),\n        (\"name\", \"layout\", \"dashboard_filters_enabled\", \"updated_at\", \"created_at\", \"options\"),\n    )\n\n    widget_list = (\n        models.Widget.query.filter(models.Widget.dashboard_id == dashboard.id)\n        .outerjoin(models.Visualization)\n        .outerjoin(models.Query)\n    )\n\n    dashboard_dict[\"widgets\"] = [public_widget(w) for w in widget_list]\n    return dashboard_dict\n\n\nclass Serializer:\n    pass\n\n\nclass QuerySerializer(Serializer):\n    def __init__(self, object_or_list, **kwargs):\n        self.object_or_list = object_or_list\n        self.options = kwargs\n\n    def serialize(self):\n        if isinstance(self.object_or_list, models.Query):\n            result = serialize_query(self.object_or_list, **self.options)\n            if self.options.get(\"with_favorite_state\", True) and not current_user.is_api_user():\n                result[\"is_favorite\"] = models.Favorite.is_favorite(current_user.id, self.object_or_list)\n        else:\n            result = [serialize_query(query, **self.options) for query in self.object_or_list]\n            if self.options.get(\"with_favorite_state\", True):\n                queries = list(self.object_or_list)\n                favorites = models.Favorite.query.filter(\n                    models.Favorite.object_id.in_([o.id for o in queries]),\n                    models.Favorite.object_type == \"Query\",\n                    models.Favorite.user_id == current_user.id,\n                )\n                favorites_dict = {fav.object_id: fav for fav in favorites}\n\n                for query in result:\n                    favorite = favorites_dict.get(query[\"id\"])\n                    query[\"is_favorite\"] = favorite is not None\n                    if favorite:\n                        query[\"starred_at\"] = favorite.created_at\n\n        return result\n\n\ndef serialize_query(\n    query,\n    with_stats=False,\n    with_visualizations=False,\n    with_user=True,\n    with_last_modified_by=True,\n):\n    d = {\n        \"id\": query.id,\n        \"latest_query_data_id\": query.latest_query_data_id,\n        \"name\": query.name,\n        \"description\": query.description,\n        \"query\": query.query_text,\n        \"query_hash\": query.query_hash,\n        \"schedule\": query.schedule,\n        \"api_key\": query.api_key,\n        \"is_archived\": query.is_archived,\n        \"is_draft\": query.is_draft,\n        \"updated_at\": query.updated_at,\n        \"created_at\": query.created_at,\n        \"data_source_id\": query.data_source_id,\n        \"options\": query.options,\n        \"version\": query.version,\n        \"tags\": query.tags or [],\n        \"is_safe\": query.parameterized.is_safe,\n    }\n\n    if with_user:\n        d[\"user\"] = query.user.to_dict()\n    else:\n        d[\"user_id\"] = query.user_id\n\n    if with_last_modified_by:\n        d[\"last_modified_by\"] = query.last_modified_by.to_dict() if query.last_modified_by is not None else None\n    else:\n        d[\"last_modified_by_id\"] = query.last_modified_by_id\n\n    if with_stats:\n        if query.latest_query_data is not None:\n            d[\"retrieved_at\"] = query.retrieved_at\n            d[\"runtime\"] = query.runtime\n        else:\n            d[\"retrieved_at\"] = None\n            d[\"runtime\"] = None\n\n    if with_visualizations:\n        d[\"visualizations\"] = [serialize_visualization(vis, with_query=False) for vis in query.visualizations]\n\n    return d\n\n\ndef serialize_visualization(object, with_query=True):\n    d = {\n        \"id\": object.id,\n        \"type\": object.type,\n        \"name\": object.name,\n        \"description\": object.description,\n        \"options\": object.options,\n        \"updated_at\": object.updated_at,\n        \"created_at\": object.created_at,\n    }\n\n    if with_query:\n        d[\"query\"] = serialize_query(object.query_rel)\n\n    return d\n\n\ndef serialize_widget(object):\n    d = {\n        \"id\": object.id,\n        \"width\": object.width,\n        \"options\": object.options,\n        \"dashboard_id\": object.dashboard_id,\n        \"text\": object.text,\n        \"updated_at\": object.updated_at,\n        \"created_at\": object.created_at,\n    }\n\n    if object.visualization and object.visualization.id:\n        d[\"visualization\"] = serialize_visualization(object.visualization)\n\n    return d\n\n\ndef serialize_alert(alert, full=True):\n    d = {\n        \"id\": alert.id,\n        \"name\": alert.name,\n        \"options\": alert.options,\n        \"state\": alert.state,\n        \"last_triggered_at\": alert.last_triggered_at,\n        \"updated_at\": alert.updated_at,\n        \"created_at\": alert.created_at,\n        \"rearm\": alert.rearm,\n    }\n\n    if full:\n        d[\"query\"] = serialize_query(alert.query_rel)\n        d[\"user\"] = alert.user.to_dict()\n    else:\n        d[\"query_id\"] = alert.query_id\n        d[\"user_id\"] = alert.user_id\n\n    return d\n\n\ndef serialize_dashboard(obj, with_widgets=False, user=None, with_favorite_state=True):\n    layout = obj.layout\n\n    widgets = []\n\n    if with_widgets:\n        for w in obj.widgets:\n            if w.visualization_id is None:\n                widgets.append(serialize_widget(w))\n            elif user and has_access(w.visualization.query_rel, user, view_only):\n                widgets.append(serialize_widget(w))\n            else:\n                widget = project(\n                    serialize_widget(w),\n                    (\n                        \"id\",\n                        \"width\",\n                        \"dashboard_id\",\n                        \"options\",\n                        \"created_at\",\n                        \"updated_at\",\n                    ),\n                )\n                widget[\"restricted\"] = True\n                widgets.append(widget)\n    else:\n        widgets = None\n\n    d = {\n        \"id\": obj.id,\n        \"slug\": obj.name_as_slug,\n        \"name\": obj.name,\n        \"user_id\": obj.user_id,\n        \"user\": {\n            \"id\": obj.user.id,\n            \"name\": obj.user.name,\n            \"email\": obj.user.email,\n            \"profile_image_url\": obj.user.profile_image_url,\n        },\n        \"layout\": layout,\n        \"dashboard_filters_enabled\": obj.dashboard_filters_enabled,\n        \"widgets\": widgets,\n        \"options\": obj.options,\n        \"is_archived\": obj.is_archived,\n        \"is_draft\": obj.is_draft,\n        \"tags\": obj.tags or [],\n        \"updated_at\": obj.updated_at,\n        \"created_at\": obj.created_at,\n        \"version\": obj.version,\n    }\n\n    return d\n\n\nclass DashboardSerializer(Serializer):\n    def __init__(self, object_or_list, **kwargs):\n        self.object_or_list = object_or_list\n        self.options = kwargs\n\n    def serialize(self):\n        if isinstance(self.object_or_list, models.Dashboard):\n            result = serialize_dashboard(self.object_or_list, **self.options)\n            if self.options.get(\"with_favorite_state\", True) and not current_user.is_api_user():\n                result[\"is_favorite\"] = models.Favorite.is_favorite(current_user.id, self.object_or_list)\n        else:\n            result = [serialize_dashboard(obj, **self.options) for obj in self.object_or_list]\n            if self.options.get(\"with_favorite_state\", True):\n                dashboards = list(self.object_or_list)\n                favorites = models.Favorite.query.filter(\n                    models.Favorite.object_id.in_([o.id for o in dashboards]),\n                    models.Favorite.object_type == \"Dashboard\",\n                    models.Favorite.user_id == current_user.id,\n                )\n                favorites_dict = {fav.object_id: fav for fav in favorites}\n\n                for query in result:\n                    favorite = favorites_dict.get(query[\"id\"])\n                    query[\"is_favorite\"] = favorite is not None\n                    if favorite:\n                        query[\"starred_at\"] = favorite.created_at\n\n        return result\n\n\ndef serialize_job(job):\n    # TODO: this is mapping to the old Job class statuses. Need to update the client side and remove this\n    STATUSES = {\n        JobStatus.QUEUED: 1,\n        JobStatus.STARTED: 2,\n        JobStatus.FINISHED: 3,\n        JobStatus.FAILED: 4,\n        JobStatus.CANCELED: 5,\n        JobStatus.DEFERRED: 6,\n        JobStatus.SCHEDULED: 7,\n    }\n\n    job_status = job.get_status()\n    if job.is_started:\n        updated_at = job.started_at or 0\n    else:\n        updated_at = 0\n\n    status = STATUSES[job_status]\n    result = query_result_id = None\n\n    if job.is_cancelled:\n        error = \"Query cancelled by user.\"\n        status = 4\n    elif isinstance(job.result, Exception):\n        error = str(job.result)\n        status = 4\n    elif isinstance(job.result, dict) and \"error\" in job.result:\n        error = job.result[\"error\"]\n        status = 4\n    else:\n        error = \"\"\n        result = query_result_id = job.result\n\n    return {\n        \"job\": {\n            \"id\": job.id,\n            \"updated_at\": updated_at,\n            \"status\": status,\n            \"error\": error,\n            \"result\": result,\n            \"query_result_id\": query_result_id,\n        }\n    }\n"
  },
  {
    "path": "redash/serializers/query_result.py",
    "content": "import csv\nimport io\n\nimport xlsxwriter\nfrom dateutil.parser import isoparse as parse_date\nfrom funcy import project, rpartial\n\nfrom redash.authentication.org_resolving import current_org\nfrom redash.query_runner import TYPE_BOOLEAN, TYPE_DATE, TYPE_DATETIME\n\n\ndef _convert_format(fmt):\n    return (\n        fmt.replace(\"DD\", \"%d\")\n        .replace(\"MM\", \"%m\")\n        .replace(\"YYYY\", \"%Y\")\n        .replace(\"YY\", \"%y\")\n        .replace(\"HH\", \"%H\")\n        .replace(\"mm\", \"%M\")\n        .replace(\"ss\", \"%S\")\n        .replace(\"SSS\", \"%f\")\n    )\n\n\ndef _convert_bool(value):\n    if value is True:\n        return \"true\"\n    elif value is False:\n        return \"false\"\n\n    return value\n\n\ndef _convert_datetime(value, fmt):\n    if not value:\n        return value\n\n    try:\n        parsed = parse_date(value)\n        ret = parsed.strftime(fmt)\n    except Exception:\n        return value\n\n    return ret\n\n\ndef _get_column_lists(columns):\n    date_format = _convert_format(current_org.get_setting(\"date_format\"))\n    datetime_format = _convert_format(\n        \"{} {}\".format(\n            current_org.get_setting(\"date_format\"),\n            current_org.get_setting(\"time_format\"),\n        )\n    )\n\n    special_types = {\n        TYPE_BOOLEAN: _convert_bool,\n        TYPE_DATE: rpartial(_convert_datetime, date_format),\n        TYPE_DATETIME: rpartial(_convert_datetime, datetime_format),\n    }\n\n    fieldnames = []\n    special_columns = dict()\n\n    for col in columns:\n        fieldnames.append(col[\"name\"])\n\n        for col_type in special_types.keys():\n            if col[\"type\"] == col_type:\n                special_columns[col[\"name\"]] = special_types[col_type]\n\n    return fieldnames, special_columns\n\n\ndef serialize_query_result(query_result, is_api_user):\n    if is_api_user:\n        publicly_needed_keys = [\"data\", \"retrieved_at\"]\n        return project(query_result.to_dict(), publicly_needed_keys)\n    else:\n        return query_result.to_dict()\n\n\ndef serialize_query_result_to_dsv(query_result, delimiter):\n    s = io.StringIO()\n\n    query_data = query_result.data\n\n    fieldnames, special_columns = _get_column_lists(query_data[\"columns\"] or [])\n\n    writer = csv.DictWriter(s, extrasaction=\"ignore\", fieldnames=fieldnames, delimiter=delimiter)\n    writer.writeheader()\n\n    for row in query_data[\"rows\"]:\n        for col_name, converter in special_columns.items():\n            if col_name in row:\n                row[col_name] = converter(row[col_name])\n\n        writer.writerow(row)\n\n    return s.getvalue()\n\n\ndef serialize_query_result_to_xlsx(query_result):\n    output = io.BytesIO()\n\n    query_data = query_result.data\n    book = xlsxwriter.Workbook(output, {\"constant_memory\": True})\n    sheet = book.add_worksheet(\"result\")\n\n    column_names = []\n    for c, col in enumerate(query_data[\"columns\"]):\n        sheet.write(0, c, col[\"name\"])\n        column_names.append(col[\"name\"])\n\n    for r, row in enumerate(query_data[\"rows\"]):\n        for c, name in enumerate(column_names):\n            v = row.get(name)\n            if isinstance(v, (dict, list)):\n                v = str(v)\n            sheet.write(r + 1, c, v)\n\n    book.close()\n\n    return output.getvalue()\n"
  },
  {
    "path": "redash/settings/__init__.py",
    "content": "import importlib\nimport os\nimport ssl\n\nfrom flask_talisman import talisman\nfrom funcy import distinct, remove\n\nfrom redash.settings.helpers import (\n    add_decode_responses_to_redis_url,\n    array_from_string,\n    cast_int_or_default,\n    fix_assets_path,\n    int_or_none,\n    parse_boolean,\n    set_from_string,\n)\nfrom redash.settings.organization import DATE_FORMAT, TIME_FORMAT  # noqa\n\n# _REDIS_URL is the unchanged REDIS_URL we get from env vars, to be used later with RQ\n_REDIS_URL = os.environ.get(\"REDASH_REDIS_URL\", os.environ.get(\"REDIS_URL\", \"redis://localhost:6379/0\"))\n# This is the one to use for Redash' own connection:\nREDIS_URL = add_decode_responses_to_redis_url(_REDIS_URL)\nPROXIES_COUNT = int(os.environ.get(\"REDASH_PROXIES_COUNT\", \"1\"))\n\nSTATSD_HOST = os.environ.get(\"REDASH_STATSD_HOST\", \"127.0.0.1\")\nSTATSD_PORT = int(os.environ.get(\"REDASH_STATSD_PORT\", \"8125\"))\nSTATSD_PREFIX = os.environ.get(\"REDASH_STATSD_PREFIX\", \"redash\")\nSTATSD_USE_TAGS = parse_boolean(os.environ.get(\"REDASH_STATSD_USE_TAGS\", \"false\"))\n\n# Connection settings for Redash's own database (where we store the queries, results, etc)\nSQLALCHEMY_DATABASE_URI = os.environ.get(\n    \"REDASH_DATABASE_URL\", os.environ.get(\"DATABASE_URL\", \"postgresql:///postgres\")\n)\nSQLALCHEMY_MAX_OVERFLOW = int_or_none(os.environ.get(\"SQLALCHEMY_MAX_OVERFLOW\"))\nSQLALCHEMY_POOL_SIZE = int_or_none(os.environ.get(\"SQLALCHEMY_POOL_SIZE\"))\nSQLALCHEMY_DISABLE_POOL = parse_boolean(os.environ.get(\"SQLALCHEMY_DISABLE_POOL\", \"false\"))\nSQLALCHEMY_ENABLE_POOL_PRE_PING = parse_boolean(os.environ.get(\"SQLALCHEMY_ENABLE_POOL_PRE_PING\", \"false\"))\nSQLALCHEMY_TRACK_MODIFICATIONS = False\nSQLALCHEMY_ECHO = False\n\nRQ_REDIS_URL = os.environ.get(\"RQ_REDIS_URL\", _REDIS_URL)\n\n# The following enables periodic job (every 5 minutes) of removing unused query results.\nQUERY_RESULTS_CLEANUP_ENABLED = parse_boolean(os.environ.get(\"REDASH_QUERY_RESULTS_CLEANUP_ENABLED\", \"true\"))\nQUERY_RESULTS_CLEANUP_COUNT = int(os.environ.get(\"REDASH_QUERY_RESULTS_CLEANUP_COUNT\", \"100\"))\nQUERY_RESULTS_CLEANUP_MAX_AGE = int(os.environ.get(\"REDASH_QUERY_RESULTS_CLEANUP_MAX_AGE\", \"7\"))\n\nQUERY_RESULTS_EXPIRED_TTL_ENABLED = parse_boolean(os.environ.get(\"REDASH_QUERY_RESULTS_EXPIRED_TTL_ENABLED\", \"false\"))\n# default set query results expired ttl 86400 seconds\nQUERY_RESULTS_EXPIRED_TTL = int(os.environ.get(\"REDASH_QUERY_RESULTS_EXPIRED_TTL\", \"86400\"))\n\nSCHEMAS_REFRESH_SCHEDULE = int(os.environ.get(\"REDASH_SCHEMAS_REFRESH_SCHEDULE\", 30))\nSCHEMAS_REFRESH_TIMEOUT = int(os.environ.get(\"REDASH_SCHEMAS_REFRESH_TIMEOUT\", 300))\n\nAUTH_TYPE = os.environ.get(\"REDASH_AUTH_TYPE\", \"api_key\")\nINVITATION_TOKEN_MAX_AGE = int(os.environ.get(\"REDASH_INVITATION_TOKEN_MAX_AGE\", 60 * 60 * 24 * 7))\n\n# The secret key to use in the Flask app for various cryptographic features\nSECRET_KEY = os.environ.get(\"REDASH_COOKIE_SECRET\")\n\nif SECRET_KEY is None:\n    raise Exception(\n        \"You must set the REDASH_COOKIE_SECRET environment variable. Visit http://redash.io/help/open-source/admin-guide/secrets for more information.\"\n    )\n\n# The secret key to use when encrypting data source options\nDATASOURCE_SECRET_KEY = os.environ.get(\"REDASH_SECRET_KEY\", SECRET_KEY)\n\n# Whether and how to redirect non-HTTP requests to HTTPS. Disabled by default.\nENFORCE_HTTPS = parse_boolean(os.environ.get(\"REDASH_ENFORCE_HTTPS\", \"false\"))\nENFORCE_HTTPS_PERMANENT = parse_boolean(os.environ.get(\"REDASH_ENFORCE_HTTPS_PERMANENT\", \"false\"))\n# Whether file downloads are enforced or not.\nENFORCE_FILE_SAVE = parse_boolean(os.environ.get(\"REDASH_ENFORCE_FILE_SAVE\", \"true\"))\n\n# Whether api calls using the json query runner will block private addresses\nENFORCE_PRIVATE_ADDRESS_BLOCK = parse_boolean(os.environ.get(\"REDASH_ENFORCE_PRIVATE_IP_BLOCK\", \"true\"))\n\n# Whether to use secure cookies by default.\nCOOKIES_SECURE = parse_boolean(os.environ.get(\"REDASH_COOKIES_SECURE\", str(ENFORCE_HTTPS)))\n# Whether the session cookie is set to secure.\nSESSION_COOKIE_SECURE = parse_boolean(os.environ.get(\"REDASH_SESSION_COOKIE_SECURE\") or str(COOKIES_SECURE))\n# Whether the session cookie is set HttpOnly.\nSESSION_COOKIE_HTTPONLY = parse_boolean(os.environ.get(\"REDASH_SESSION_COOKIE_HTTPONLY\", \"true\"))\nSESSION_EXPIRY_TIME = int(os.environ.get(\"REDASH_SESSION_EXPIRY_TIME\", 60 * 60 * 6))\nSESSION_COOKIE_NAME = os.environ.get(\"REDASH_SESSION_COOKIE_NAME\", \"session\")\n\n# Whether the session cookie is set to secure.\nREMEMBER_COOKIE_SECURE = parse_boolean(os.environ.get(\"REDASH_REMEMBER_COOKIE_SECURE\") or str(COOKIES_SECURE))\n# Whether the remember cookie is set HttpOnly.\nREMEMBER_COOKIE_HTTPONLY = parse_boolean(os.environ.get(\"REDASH_REMEMBER_COOKIE_HTTPONLY\", \"true\"))\n# The amount of time before the remember cookie expires.\nREMEMBER_COOKIE_DURATION = int(os.environ.get(\"REDASH_REMEMBER_COOKIE_DURATION\", 60 * 60 * 24 * 31))\n\n# Doesn't set X-Frame-Options by default since it's highly dependent\n# on the specific deployment.\n# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options\n# for more information.\nFRAME_OPTIONS = os.environ.get(\"REDASH_FRAME_OPTIONS\", \"deny\")\nFRAME_OPTIONS_ALLOW_FROM = os.environ.get(\"REDASH_FRAME_OPTIONS_ALLOW_FROM\", \"\")\n\n# Whether and how to send Strict-Transport-Security response headers.\n# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security\n# for more information.\nHSTS_ENABLED = parse_boolean(os.environ.get(\"REDASH_HSTS_ENABLED\") or str(ENFORCE_HTTPS))\nHSTS_PRELOAD = parse_boolean(os.environ.get(\"REDASH_HSTS_PRELOAD\", \"false\"))\nHSTS_MAX_AGE = int(os.environ.get(\"REDASH_HSTS_MAX_AGE\", talisman.ONE_YEAR_IN_SECS))\nHSTS_INCLUDE_SUBDOMAINS = parse_boolean(os.environ.get(\"REDASH_HSTS_INCLUDE_SUBDOMAINS\", \"false\"))\n\n# Whether and how to send Content-Security-Policy response headers.\n# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy\n# for more information.\n# Overriding this value via an environment variables requires setting it\n# as a string in the general CSP format of a semicolon separated list of\n# individual CSP directives, see https://github.com/GoogleCloudPlatform/flask-talisman#example-7\n# for more information. E.g.:\nCONTENT_SECURITY_POLICY = os.environ.get(\n    \"REDASH_CONTENT_SECURITY_POLICY\",\n    \"default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; font-src 'self' data:; img-src 'self' http: https: data: blob:; object-src 'none'; frame-ancestors 'none'; frame-src redash.io;\",\n)\nCONTENT_SECURITY_POLICY_REPORT_URI = os.environ.get(\"REDASH_CONTENT_SECURITY_POLICY_REPORT_URI\", \"\")\nCONTENT_SECURITY_POLICY_REPORT_ONLY = parse_boolean(\n    os.environ.get(\"REDASH_CONTENT_SECURITY_POLICY_REPORT_ONLY\", \"false\")\n)\nCONTENT_SECURITY_POLICY_NONCE_IN = array_from_string(os.environ.get(\"REDASH_CONTENT_SECURITY_POLICY_NONCE_IN\", \"\"))\n\n# Whether and how to send Referrer-Policy response headers. Defaults to\n# 'strict-origin-when-cross-origin'.\n# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy\n# for more information.\nREFERRER_POLICY = os.environ.get(\"REDASH_REFERRER_POLICY\", \"strict-origin-when-cross-origin\")\n# Whether and how to send Feature-Policy response headers. Defaults to\n# an empty value.\n# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy\n# for more information.\nFEATURE_POLICY = os.environ.get(\"REDASH_FEATURE_POLICY\", \"\")\n\nMULTI_ORG = parse_boolean(os.environ.get(\"REDASH_MULTI_ORG\", \"false\"))\n\n# If Redash is behind a proxy it might sometimes receive a X-Forwarded-Proto of HTTP\n# even if your actual Redash URL scheme is HTTPS. This will cause Flask to build\n# the OAuth redirect URL incorrectly thus failing auth. This is especially common if\n# you're behind a SSL/TCP configured AWS ELB or similar.\n# This setting will force the URL scheme.\nGOOGLE_OAUTH_SCHEME_OVERRIDE = os.environ.get(\"REDASH_GOOGLE_OAUTH_SCHEME_OVERRIDE\", \"\")\n\nGOOGLE_CLIENT_ID = os.environ.get(\"REDASH_GOOGLE_CLIENT_ID\", \"\")\nGOOGLE_CLIENT_SECRET = os.environ.get(\"REDASH_GOOGLE_CLIENT_SECRET\", \"\")\nGOOGLE_OAUTH_ENABLED = bool(GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET)\n\n# If Redash is behind a proxy it might sometimes receive a X-Forwarded-Proto of HTTP\n# even if your actual Redash URL scheme is HTTPS. This will cause Flask to build\n# the SAML redirect URL incorrect thus failing auth. This is especially common if\n# you're behind a SSL/TCP configured AWS ELB or similar.\n# This setting will force the URL scheme.\nSAML_SCHEME_OVERRIDE = os.environ.get(\"REDASH_SAML_SCHEME_OVERRIDE\", \"\")\n\nSAML_ENCRYPTION_PEM_PATH = os.environ.get(\"REDASH_SAML_ENCRYPTION_PEM_PATH\", \"\")\nSAML_ENCRYPTION_CERT_PATH = os.environ.get(\"REDASH_SAML_ENCRYPTION_CERT_PATH\", \"\")\nSAML_ENCRYPTION_ENABLED = SAML_ENCRYPTION_PEM_PATH != \"\" and SAML_ENCRYPTION_CERT_PATH != \"\"\n\n# Enables the use of an externally-provided and trusted remote user via an HTTP\n# header.  The \"user\" must be an email address.\n#\n# By default the trusted header is X-Forwarded-Remote-User.  You can change\n# this by setting REDASH_REMOTE_USER_HEADER.\n#\n# Enabling this authentication method is *potentially dangerous*, and it is\n# your responsibility to ensure that only a trusted frontend (usually on the\n# same server) can talk to the redash backend server, otherwise people will be\n# able to login as anyone they want by directly talking to the redash backend.\n# You must *also* ensure that any special header in the original request is\n# removed or always overwritten by your frontend, otherwise your frontend may\n# pass it through to the backend unchanged.\n#\n# Note that redash will only check the remote user once, upon the first need\n# for a login, and then set a cookie which keeps the user logged in.  Dropping\n# the remote user header after subsequent requests won't automatically log the\n# user out.  Doing so could be done with further work, but usually it's\n# unnecessary.\n#\n# If you also set the organization setting auth_password_login_enabled to false,\n# then your authentication will be seamless.  Otherwise a link will be presented\n# on the login page to trigger remote user auth.\nREMOTE_USER_LOGIN_ENABLED = parse_boolean(os.environ.get(\"REDASH_REMOTE_USER_LOGIN_ENABLED\", \"false\"))\nREMOTE_USER_HEADER = os.environ.get(\"REDASH_REMOTE_USER_HEADER\", \"X-Forwarded-Remote-User\")\n\n# If the organization setting auth_password_login_enabled is not false, then users will still be\n# able to login through Redash instead of the LDAP server\nLDAP_LOGIN_ENABLED = parse_boolean(os.environ.get(\"REDASH_LDAP_LOGIN_ENABLED\", \"false\"))\n# Bind LDAP using SSL. Default is False\nLDAP_SSL = parse_boolean(os.environ.get(\"REDASH_LDAP_USE_SSL\", \"false\"))\n# Choose authentication method(SIMPLE, ANONYMOUS or NTLM). Default is SIMPLE\nLDAP_AUTH_METHOD = os.environ.get(\"REDASH_LDAP_AUTH_METHOD\", \"SIMPLE\")\n# The LDAP directory address (ex. ldap://10.0.10.1:389)\nLDAP_HOST_URL = os.environ.get(\"REDASH_LDAP_URL\", None)\n# The DN & password used to connect to LDAP to determine the identity of the user being authenticated.\n# For AD this should be \"org\\\\user\".\nLDAP_BIND_DN = os.environ.get(\"REDASH_LDAP_BIND_DN\", None)\nLDAP_BIND_DN_PASSWORD = os.environ.get(\"REDASH_LDAP_BIND_DN_PASSWORD\", \"\")\n# AD/LDAP email and display name keys\nLDAP_DISPLAY_NAME_KEY = os.environ.get(\"REDASH_LDAP_DISPLAY_NAME_KEY\", \"displayName\")\nLDAP_EMAIL_KEY = os.environ.get(\"REDASH_LDAP_EMAIL_KEY\", \"mail\")\n# Prompt that should be shown above username/email field.\nLDAP_CUSTOM_USERNAME_PROMPT = os.environ.get(\"REDASH_LDAP_CUSTOM_USERNAME_PROMPT\", \"LDAP/AD/SSO username:\")\n# LDAP Search DN TEMPLATE (for AD this should be \"(sAMAccountName=%(username)s)\"\")\nLDAP_SEARCH_TEMPLATE = os.environ.get(\"REDASH_LDAP_SEARCH_TEMPLATE\", \"(cn=%(username)s)\")\n# The schema to bind to (ex. cn=users,dc=ORG,dc=local)\nLDAP_SEARCH_DN = os.environ.get(\"REDASH_LDAP_SEARCH_DN\", os.environ.get(\"REDASH_SEARCH_DN\"))\n\nSTATIC_ASSETS_PATH = fix_assets_path(os.environ.get(\"REDASH_STATIC_ASSETS_PATH\", \"../client/dist/\"))\nFLASK_TEMPLATE_PATH = fix_assets_path(os.environ.get(\"REDASH_FLASK_TEMPLATE_PATH\", STATIC_ASSETS_PATH))\n# Time limit (in seconds) for scheduled queries. Set this to -1 to execute without a time limit.\nSCHEDULED_QUERY_TIME_LIMIT = int(os.environ.get(\"REDASH_SCHEDULED_QUERY_TIME_LIMIT\", -1))\n\n# Time limit (in seconds) for adhoc queries. Set this to -1 to execute without a time limit.\nADHOC_QUERY_TIME_LIMIT = int(os.environ.get(\"REDASH_ADHOC_QUERY_TIME_LIMIT\", -1))\n\nJOB_EXPIRY_TIME = int(os.environ.get(\"REDASH_JOB_EXPIRY_TIME\", 3600 * 12))\nJOB_DEFAULT_FAILURE_TTL = int(os.environ.get(\"REDASH_JOB_DEFAULT_FAILURE_TTL\", 7 * 24 * 60 * 60))\n\nLOG_LEVEL = os.environ.get(\"REDASH_LOG_LEVEL\", \"INFO\")\nLOG_STDOUT = parse_boolean(os.environ.get(\"REDASH_LOG_STDOUT\", \"false\"))\nLOG_PREFIX = os.environ.get(\"REDASH_LOG_PREFIX\", \"\")\nLOG_FORMAT = os.environ.get(\n    \"REDASH_LOG_FORMAT\",\n    LOG_PREFIX + \"[%(asctime)s][PID:%(process)d][%(levelname)s][%(name)s] %(message)s\",\n)\nRQ_WORKER_JOB_LOG_FORMAT = os.environ.get(\n    \"REDASH_RQ_WORKER_JOB_LOG_FORMAT\",\n    (\n        LOG_PREFIX + \"[%(asctime)s][PID:%(process)d][%(levelname)s][%(name)s] \"\n        \"job.func_name=%(job_func_name)s \"\n        \"job.id=%(job_id)s %(message)s\"\n    ),\n)\n\n# Mail settings:\nMAIL_SERVER = os.environ.get(\"REDASH_MAIL_SERVER\", \"localhost\")\nMAIL_PORT = int(os.environ.get(\"REDASH_MAIL_PORT\", 25))\nMAIL_USE_TLS = parse_boolean(os.environ.get(\"REDASH_MAIL_USE_TLS\", \"false\"))\nMAIL_USE_SSL = parse_boolean(os.environ.get(\"REDASH_MAIL_USE_SSL\", \"false\"))\nMAIL_USERNAME = os.environ.get(\"REDASH_MAIL_USERNAME\", None)\nMAIL_PASSWORD = os.environ.get(\"REDASH_MAIL_PASSWORD\", None)\nMAIL_DEFAULT_SENDER = os.environ.get(\"REDASH_MAIL_DEFAULT_SENDER\", None)\nMAIL_MAX_EMAILS = os.environ.get(\"REDASH_MAIL_MAX_EMAILS\", None)\nMAIL_ASCII_ATTACHMENTS = parse_boolean(os.environ.get(\"REDASH_MAIL_ASCII_ATTACHMENTS\", \"false\"))\n\n\ndef email_server_is_configured():\n    return MAIL_DEFAULT_SENDER is not None\n\n\nHOST = os.environ.get(\"REDASH_HOST\", \"\")\n\nSEND_FAILURE_EMAIL_INTERVAL = int(os.environ.get(\"REDASH_SEND_FAILURE_EMAIL_INTERVAL\", 60))\nMAX_FAILURE_REPORTS_PER_QUERY = int(os.environ.get(\"REDASH_MAX_FAILURE_REPORTS_PER_QUERY\", 100))\n\nALERTS_DEFAULT_MAIL_SUBJECT_TEMPLATE = os.environ.get(\n    \"REDASH_ALERTS_DEFAULT_MAIL_SUBJECT_TEMPLATE\", \"Alert: {alert_name} changed status to {state}\"\n)\n\nREDASH_ALERTS_DEFAULT_MAIL_BODY_TEMPLATE_FILE = os.environ.get(\n    \"REDASH_ALERTS_DEFAULT_MAIL_BODY_TEMPLATE_FILE\", fix_assets_path(\"templates/emails/alert.html\")\n)\n\n# How many requests are allowed per IP to the login page before\n# being throttled?\n# See https://flask-limiter.readthedocs.io/en/stable/#rate-limit-string-notation\n\nRATELIMIT_ENABLED = parse_boolean(os.environ.get(\"REDASH_RATELIMIT_ENABLED\", \"true\"))\nTHROTTLE_LOGIN_PATTERN = os.environ.get(\"REDASH_THROTTLE_LOGIN_PATTERN\", \"50/hour\")\nLIMITER_STORAGE = os.environ.get(\"REDASH_LIMITER_STORAGE\", REDIS_URL)\nTHROTTLE_PASS_RESET_PATTERN = os.environ.get(\"REDASH_THROTTLE_PASS_RESET_PATTERN\", \"10/hour\")\n\n# CORS settings for the Query Result API (and possibly future external APIs).\n# In most cases all you need to do is set REDASH_CORS_ACCESS_CONTROL_ALLOW_ORIGIN\n# to the calling domain (or domains in a comma separated list).\nACCESS_CONTROL_ALLOW_ORIGIN = set_from_string(os.environ.get(\"REDASH_CORS_ACCESS_CONTROL_ALLOW_ORIGIN\", \"\"))\nACCESS_CONTROL_ALLOW_CREDENTIALS = parse_boolean(\n    os.environ.get(\"REDASH_CORS_ACCESS_CONTROL_ALLOW_CREDENTIALS\", \"false\")\n)\nACCESS_CONTROL_REQUEST_METHOD = os.environ.get(\"REDASH_CORS_ACCESS_CONTROL_REQUEST_METHOD\", \"GET, POST, PUT\")\nACCESS_CONTROL_ALLOW_HEADERS = os.environ.get(\"REDASH_CORS_ACCESS_CONTROL_ALLOW_HEADERS\", \"Content-Type\")\n\n# Query Runners\ndefault_query_runners = [\n    \"redash.query_runner.athena\",\n    \"redash.query_runner.big_query\",\n    \"redash.query_runner.google_spreadsheets\",\n    \"redash.query_runner.graphite\",\n    \"redash.query_runner.mongodb\",\n    \"redash.query_runner.couchbase\",\n    \"redash.query_runner.mysql\",\n    \"redash.query_runner.pg\",\n    \"redash.query_runner.url\",\n    \"redash.query_runner.influx_db\",\n    \"redash.query_runner.influx_db_v2\",\n    \"redash.query_runner.elasticsearch\",\n    \"redash.query_runner.elasticsearch2\",\n    \"redash.query_runner.amazon_elasticsearch\",\n    \"redash.query_runner.trino\",\n    \"redash.query_runner.presto\",\n    \"redash.query_runner.pinot\",\n    \"redash.query_runner.databricks\",\n    \"redash.query_runner.hive_ds\",\n    \"redash.query_runner.impala_ds\",\n    \"redash.query_runner.vertica\",\n    \"redash.query_runner.clickhouse\",\n    \"redash.query_runner.tinybird\",\n    \"redash.query_runner.yandex_metrica\",\n    \"redash.query_runner.yandex_disk\",\n    \"redash.query_runner.rockset\",\n    \"redash.query_runner.treasuredata\",\n    \"redash.query_runner.sqlite\",\n    \"redash.query_runner.mssql\",\n    \"redash.query_runner.mssql_odbc\",\n    \"redash.query_runner.memsql_ds\",\n    \"redash.query_runner.jql\",\n    \"redash.query_runner.google_analytics\",\n    \"redash.query_runner.axibase_tsd\",\n    \"redash.query_runner.salesforce\",\n    \"redash.query_runner.query_results\",\n    \"redash.query_runner.prometheus\",\n    \"redash.query_runner.db2\",\n    \"redash.query_runner.druid\",\n    \"redash.query_runner.kylin\",\n    \"redash.query_runner.drill\",\n    \"redash.query_runner.uptycs\",\n    \"redash.query_runner.snowflake\",\n    \"redash.query_runner.phoenix\",\n    \"redash.query_runner.json_ds\",\n    \"redash.query_runner.cass\",\n    \"redash.query_runner.dgraph\",\n    \"redash.query_runner.azure_kusto\",\n    \"redash.query_runner.exasol\",\n    \"redash.query_runner.cloudwatch\",\n    \"redash.query_runner.cloudwatch_insights\",\n    \"redash.query_runner.corporate_memory\",\n    \"redash.query_runner.sparql_endpoint\",\n    \"redash.query_runner.excel\",\n    \"redash.query_runner.csv\",\n    \"redash.query_runner.databend\",\n    \"redash.query_runner.nz\",\n    \"redash.query_runner.arango\",\n    \"redash.query_runner.google_analytics4\",\n    \"redash.query_runner.google_search_console\",\n    \"redash.query_runner.ignite\",\n    \"redash.query_runner.oracle\",\n    \"redash.query_runner.e6data\",\n    \"redash.query_runner.risingwave\",\n    \"redash.query_runner.duckdb\",\n]\n\nenabled_query_runners = array_from_string(\n    os.environ.get(\"REDASH_ENABLED_QUERY_RUNNERS\", \",\".join(default_query_runners))\n)\nadditional_query_runners = array_from_string(os.environ.get(\"REDASH_ADDITIONAL_QUERY_RUNNERS\", \"\"))\ndisabled_query_runners = array_from_string(os.environ.get(\"REDASH_DISABLED_QUERY_RUNNERS\", \"\"))\n\nQUERY_RUNNERS = remove(\n    set(disabled_query_runners),\n    distinct(enabled_query_runners + additional_query_runners),\n)\n\ndynamic_settings = importlib.import_module(\n    os.environ.get(\"REDASH_DYNAMIC_SETTINGS_MODULE\", \"redash.settings.dynamic_settings\")\n)\n\n# Destinations\ndefault_destinations = [\n    \"redash.destinations.email\",\n    \"redash.destinations.slack\",\n    \"redash.destinations.webhook\",\n    \"redash.destinations.discord\",\n    \"redash.destinations.mattermost\",\n    \"redash.destinations.chatwork\",\n    \"redash.destinations.pagerduty\",\n    \"redash.destinations.hangoutschat\",\n    \"redash.destinations.microsoft_teams_webhook\",\n    \"redash.destinations.asana\",\n    \"redash.destinations.webex\",\n    \"redash.destinations.datadog\",\n]\n\nenabled_destinations = array_from_string(os.environ.get(\"REDASH_ENABLED_DESTINATIONS\", \",\".join(default_destinations)))\nadditional_destinations = array_from_string(os.environ.get(\"REDASH_ADDITIONAL_DESTINATIONS\", \"\"))\n\nDESTINATIONS = distinct(enabled_destinations + additional_destinations)\n\nEVENT_REPORTING_WEBHOOKS = array_from_string(os.environ.get(\"REDASH_EVENT_REPORTING_WEBHOOKS\", \"\"))\n\n# Support for Sentry (https://getsentry.com/). Just set your Sentry DSN to enable it:\nSENTRY_DSN = os.environ.get(\"REDASH_SENTRY_DSN\", \"\")\nSENTRY_ENVIRONMENT = os.environ.get(\"REDASH_SENTRY_ENVIRONMENT\")\n\n# Client side toggles:\nALLOW_SCRIPTS_IN_USER_INPUT = parse_boolean(os.environ.get(\"REDASH_ALLOW_SCRIPTS_IN_USER_INPUT\", \"false\"))\nDASHBOARD_REFRESH_INTERVALS = list(\n    map(\n        int,\n        array_from_string(os.environ.get(\"REDASH_DASHBOARD_REFRESH_INTERVALS\", \"60,300,600,1800,3600,43200,86400\")),\n    )\n)\nQUERY_REFRESH_INTERVALS = list(\n    map(\n        int,\n        array_from_string(\n            os.environ.get(\n                \"REDASH_QUERY_REFRESH_INTERVALS\",\n                \"60, 300, 600, 900, 1800, 3600, 7200, 10800, 14400, 18000, 21600, 25200, 28800, 32400, 36000, 39600, 43200, 86400, 604800, 1209600, 2592000\",\n            )\n        ),\n    )\n)\nPAGE_SIZE = int(os.environ.get(\"REDASH_PAGE_SIZE\", 20))\nPAGE_SIZE_OPTIONS = list(\n    map(\n        int,\n        array_from_string(os.environ.get(\"REDASH_PAGE_SIZE_OPTIONS\", \"5,10,20,50,100\")),\n    )\n)\nTABLE_CELL_MAX_JSON_SIZE = int(os.environ.get(\"REDASH_TABLE_CELL_MAX_JSON_SIZE\", 50000))\n\n# Features:\nVERSION_CHECK = parse_boolean(os.environ.get(\"REDASH_VERSION_CHECK\", \"true\"))\nFEATURE_DISABLE_REFRESH_QUERIES = parse_boolean(os.environ.get(\"REDASH_FEATURE_DISABLE_REFRESH_QUERIES\", \"false\"))\nFEATURE_SHOW_QUERY_RESULTS_COUNT = parse_boolean(os.environ.get(\"REDASH_FEATURE_SHOW_QUERY_RESULTS_COUNT\", \"true\"))\nFEATURE_ALLOW_CUSTOM_JS_VISUALIZATIONS = parse_boolean(\n    os.environ.get(\"REDASH_FEATURE_ALLOW_CUSTOM_JS_VISUALIZATIONS\", \"true\")\n)\nFEATURE_AUTO_PUBLISH_NAMED_QUERIES = parse_boolean(os.environ.get(\"REDASH_FEATURE_AUTO_PUBLISH_NAMED_QUERIES\", \"true\"))\nFEATURE_EXTENDED_ALERT_OPTIONS = parse_boolean(os.environ.get(\"REDASH_FEATURE_EXTENDED_ALERT_OPTIONS\", \"false\"))\n\n# BigQuery\nBIGQUERY_HTTP_TIMEOUT = int(os.environ.get(\"REDASH_BIGQUERY_HTTP_TIMEOUT\", \"600\"))\n\n# Allow Parameters in Embeds\n# WARNING: Deprecated!\n# See https://discuss.redash.io/t/support-for-parameters-in-embedded-visualizations/3337 for more details.\nALLOW_PARAMETERS_IN_EMBEDS = parse_boolean(os.environ.get(\"REDASH_ALLOW_PARAMETERS_IN_EMBEDS\", \"false\"))\n\n# Enhance schema fetching\nSCHEMA_RUN_TABLE_SIZE_CALCULATIONS = parse_boolean(\n    os.environ.get(\"REDASH_SCHEMA_RUN_TABLE_SIZE_CALCULATIONS\", \"false\")\n)\n\n# kylin\nKYLIN_OFFSET = int(os.environ.get(\"REDASH_KYLIN_OFFSET\", 0))\nKYLIN_LIMIT = int(os.environ.get(\"REDASH_KYLIN_LIMIT\", 50000))\nKYLIN_ACCEPT_PARTIAL = parse_boolean(os.environ.get(\"REDASH_KYLIN_ACCEPT_PARTIAL\", \"false\"))\n\n# sqlparse\nSQLPARSE_FORMAT_OPTIONS = {\n    \"reindent\": parse_boolean(os.environ.get(\"SQLPARSE_FORMAT_REINDENT\", \"true\")),\n    \"keyword_case\": os.environ.get(\"SQLPARSE_FORMAT_KEYWORD_CASE\", \"upper\"),\n}\n\n# requests\nREQUESTS_ALLOW_REDIRECTS = parse_boolean(os.environ.get(\"REDASH_REQUESTS_ALLOW_REDIRECTS\", \"false\"))\n\n# Enforces CSRF token validation on API requests.\n# This is turned off by default to avoid breaking any existing deployments but it is highly recommended to turn this toggle on to prevent CSRF attacks.\nENFORCE_CSRF = parse_boolean(os.environ.get(\"REDASH_ENFORCE_CSRF\", \"false\"))\n\n# Databricks\n\nCSRF_TIME_LIMIT = int(os.environ.get(\"REDASH_CSRF_TIME_LIMIT\", 3600 * 6))\n\n# Email blocked domains, use delimiter comma to separated multiple domains\nBLOCKED_DOMAINS = set_from_string(os.environ.get(\"REDASH_BLOCKED_DOMAINS\", \"qq.com\"))\n"
  },
  {
    "path": "redash/settings/dynamic_settings.py",
    "content": "from collections import defaultdict\n\n\n# Replace this method with your own implementation in case you want to limit the time limit on certain queries or users.\ndef query_time_limit(is_scheduled, user_id, org_id):\n    from redash import settings\n\n    if is_scheduled:\n        return settings.SCHEDULED_QUERY_TIME_LIMIT\n    else:\n        return settings.ADHOC_QUERY_TIME_LIMIT\n\n\ndef periodic_jobs():\n    \"\"\"Schedule any custom periodic jobs here. For example:\n\n    from time import timedelta\n    from somewhere import some_job, some_other_job\n\n    return [\n        {\"func\": some_job, \"interval\": timedelta(hours=1)},\n        {\"func\": some_other_job, \"interval\": timedelta(days=1)}\n    ]\n    \"\"\"\n    pass\n\n\n# This provides the ability to override the way we store QueryResult's data column.\n# Reference implementation: redash.models.DBPersistence\nQueryResultPersistence = None\n\n\ndef ssh_tunnel_auth():\n    \"\"\"\n    To enable data source connections via SSH tunnels, provide your SSH authentication\n    pkey here. Return a string pointing at your **private** key's path (which will be used\n    to extract the public key), or a `paramiko.pkey.PKey` instance holding your **public** key.\n    \"\"\"\n    return {\n        # 'ssh_pkey': 'path_to_private_key', # or instance of `paramiko.pkey.PKey`\n        # 'ssh_private_key_password': 'optional_passphrase_of_private_key',\n    }\n\n\ndef database_key_definitions(default):\n    \"\"\"\n    All primary/foreign keys in Redash are of type `db.Integer` by default.\n    You may choose to use different column types for primary/foreign keys. To do so, add an entry below for each model you'd like to modify.\n    For each model, add a tuple with the database type as the first item, and a dict including any kwargs for the column definition as the second item.\n    \"\"\"\n    definitions = defaultdict(lambda: default)\n    definitions.update(\n        {\n            # \"DataSource\": (db.String(255), {\n            #    \"default\": generate_key\n            # })\n        }\n    )\n\n    return definitions\n\n\n# Since you can define custom primary key types using `database_key_definitions`, you may want to load certain extensions when creating the database.\n# To do so, simply add the name of the extension you'd like to load to this list.\ndatabase_extensions = []\n"
  },
  {
    "path": "redash/settings/helpers.py",
    "content": "import os\nfrom urllib.parse import urlparse, urlunparse\n\n\ndef fix_assets_path(path):\n    fullpath = os.path.join(os.path.dirname(__file__), \"../\", path)\n    return fullpath\n\n\ndef array_from_string(s):\n    array = s.split(\",\")\n    if \"\" in array:\n        array.remove(\"\")\n\n    return array\n\n\ndef set_from_string(s):\n    return set(array_from_string(s))\n\n\ndef parse_boolean(s):\n    \"\"\"Takes a string and returns the equivalent as a boolean value.\"\"\"\n    s = s.strip().lower()\n    if s in (\"yes\", \"true\", \"on\", \"1\"):\n        return True\n    elif s in (\"no\", \"false\", \"off\", \"0\", \"none\"):\n        return False\n    else:\n        raise ValueError(\"Invalid boolean value %r\" % s)\n\n\ndef cast_int_or_default(val, default=None):\n    try:\n        return int(val)\n    except (ValueError, TypeError):\n        return default\n\n\ndef int_or_none(value):\n    if value is None:\n        return value\n\n    return int(value)\n\n\ndef add_decode_responses_to_redis_url(url):\n    \"\"\"Make sure that the Redis URL includes the `decode_responses` option.\"\"\"\n    parsed = urlparse(url)\n\n    query = \"decode_responses=True\"\n    if parsed.query and \"decode_responses\" not in parsed.query:\n        query = \"{}&{}\".format(parsed.query, query)\n    elif \"decode_responses\" in parsed.query:\n        query = parsed.query\n\n    return urlunparse(\n        [\n            parsed.scheme,\n            parsed.netloc,\n            parsed.path,\n            parsed.params,\n            query,\n            parsed.fragment,\n        ]\n    )\n"
  },
  {
    "path": "redash/settings/organization.py",
    "content": "import os\n\nfrom .helpers import parse_boolean\n\nif os.environ.get(\"REDASH_SAML_LOCAL_METADATA_PATH\") is not None:\n    print(\"DEPRECATION NOTICE:\\n\")\n    print(\"SAML_LOCAL_METADATA_PATH is no longer supported. Only URL metadata is supported now, please update\")\n    print(\"your configuration and reload.\")\n    raise SystemExit(1)\n\n\nPASSWORD_LOGIN_ENABLED = parse_boolean(os.environ.get(\"REDASH_PASSWORD_LOGIN_ENABLED\", \"true\"))\n\nSAML_LOGIN_TYPE = os.environ.get(\"REDASH_SAML_AUTH_TYPE\", \"\")\nSAML_METADATA_URL = os.environ.get(\"REDASH_SAML_METADATA_URL\", \"\")\nSAML_ENTITY_ID = os.environ.get(\"REDASH_SAML_ENTITY_ID\", \"\")\nSAML_NAMEID_FORMAT = os.environ.get(\"REDASH_SAML_NAMEID_FORMAT\", \"\")\nSAML_SSO_URL = os.environ.get(\"REDASH_SAML_SSO_URL\", \"\")\nSAML_X509_CERT = os.environ.get(\"REDASH_SAML_X509_CERT\", \"\")\nSAML_SP_SETTINGS = os.environ.get(\"REDASH_SAML_SP_SETTINGS\", \"\")\nif SAML_LOGIN_TYPE == \"static\":\n    SAML_LOGIN_ENABLED = SAML_SSO_URL != \"\" and SAML_METADATA_URL != \"\"\nelse:\n    SAML_LOGIN_ENABLED = SAML_METADATA_URL != \"\"\n\nDATE_FORMAT = os.environ.get(\"REDASH_DATE_FORMAT\", \"DD/MM/YY\")\nTIME_FORMAT = os.environ.get(\"REDASH_TIME_FORMAT\", \"HH:mm\")\nINTEGER_FORMAT = os.environ.get(\"REDASH_INTEGER_FORMAT\", \"0,0\")\nFLOAT_FORMAT = os.environ.get(\"REDASH_FLOAT_FORMAT\", \"0,0.00\")\nNULL_VALUE = os.environ.get(\"REDASH_NULL_VALUE\", \"null\")\nMULTI_BYTE_SEARCH_ENABLED = parse_boolean(os.environ.get(\"MULTI_BYTE_SEARCH_ENABLED\", \"false\"))\n\nJWT_LOGIN_ENABLED = parse_boolean(os.environ.get(\"REDASH_JWT_LOGIN_ENABLED\", \"false\"))\nJWT_AUTH_ISSUER = os.environ.get(\"REDASH_JWT_AUTH_ISSUER\", \"\")\nJWT_AUTH_PUBLIC_CERTS_URL = os.environ.get(\"REDASH_JWT_AUTH_PUBLIC_CERTS_URL\", \"\")\nJWT_AUTH_AUDIENCE = os.environ.get(\"REDASH_JWT_AUTH_AUDIENCE\", \"\")\nJWT_AUTH_ALGORITHMS = os.environ.get(\"REDASH_JWT_AUTH_ALGORITHMS\", \"HS256,RS256,ES256\").split(\",\")\nJWT_AUTH_COOKIE_NAME = os.environ.get(\"REDASH_JWT_AUTH_COOKIE_NAME\", \"\")\nJWT_AUTH_HEADER_NAME = os.environ.get(\"REDASH_JWT_AUTH_HEADER_NAME\", \"\")\n\nFEATURE_SHOW_PERMISSIONS_CONTROL = parse_boolean(os.environ.get(\"REDASH_FEATURE_SHOW_PERMISSIONS_CONTROL\", \"false\"))\nSEND_EMAIL_ON_FAILED_SCHEDULED_QUERIES = parse_boolean(\n    os.environ.get(\"REDASH_SEND_EMAIL_ON_FAILED_SCHEDULED_QUERIES\", \"false\")\n)\nHIDE_PLOTLY_MODE_BAR = parse_boolean(os.environ.get(\"HIDE_PLOTLY_MODE_BAR\", \"false\"))\nDISABLE_PUBLIC_URLS = parse_boolean(os.environ.get(\"REDASH_DISABLE_PUBLIC_URLS\", \"false\"))\n\nsettings = {\n    \"beacon_consent\": None,\n    \"auth_password_login_enabled\": PASSWORD_LOGIN_ENABLED,\n    \"auth_saml_enabled\": SAML_LOGIN_ENABLED,\n    \"auth_saml_type\": SAML_LOGIN_TYPE,\n    \"auth_saml_entity_id\": SAML_ENTITY_ID,\n    \"auth_saml_metadata_url\": SAML_METADATA_URL,\n    \"auth_saml_nameid_format\": SAML_NAMEID_FORMAT,\n    \"auth_saml_sso_url\": SAML_SSO_URL,\n    \"auth_saml_x509_cert\": SAML_X509_CERT,\n    \"auth_saml_sp_settings\": SAML_SP_SETTINGS,\n    \"date_format\": DATE_FORMAT,\n    \"time_format\": TIME_FORMAT,\n    \"integer_format\": INTEGER_FORMAT,\n    \"float_format\": FLOAT_FORMAT,\n    \"null_value\": NULL_VALUE,\n    \"multi_byte_search_enabled\": MULTI_BYTE_SEARCH_ENABLED,\n    \"auth_jwt_login_enabled\": JWT_LOGIN_ENABLED,\n    \"auth_jwt_auth_issuer\": JWT_AUTH_ISSUER,\n    \"auth_jwt_auth_public_certs_url\": JWT_AUTH_PUBLIC_CERTS_URL,\n    \"auth_jwt_auth_audience\": JWT_AUTH_AUDIENCE,\n    \"auth_jwt_auth_algorithms\": JWT_AUTH_ALGORITHMS,\n    \"auth_jwt_auth_cookie_name\": JWT_AUTH_COOKIE_NAME,\n    \"auth_jwt_auth_header_name\": JWT_AUTH_HEADER_NAME,\n    \"feature_show_permissions_control\": FEATURE_SHOW_PERMISSIONS_CONTROL,\n    \"send_email_on_failed_scheduled_queries\": SEND_EMAIL_ON_FAILED_SCHEDULED_QUERIES,\n    \"hide_plotly_mode_bar\": HIDE_PLOTLY_MODE_BAR,\n    \"disable_public_urls\": DISABLE_PUBLIC_URLS,\n}\n"
  },
  {
    "path": "redash/tasks/__init__.py",
    "content": "from rq.connections import pop_connection, push_connection\n\nfrom redash import rq_redis_connection\nfrom redash.tasks.alerts import check_alerts_for_query\nfrom redash.tasks.failure_report import send_aggregated_errors\nfrom redash.tasks.general import (\n    record_event,\n    send_mail,\n    sync_user_details,\n    version_check,\n)\nfrom redash.tasks.queries import (\n    cleanup_query_results,\n    empty_schedules,\n    enqueue_query,\n    execute_query,\n    refresh_queries,\n    refresh_schemas,\n    remove_ghost_locks,\n)\nfrom redash.tasks.schedule import (\n    periodic_job_definitions,\n    rq_scheduler,\n    schedule_periodic_jobs,\n)\nfrom redash.tasks.worker import Job, Queue, Worker\n\n\ndef init_app(app):\n    app.before_request(lambda: push_connection(rq_redis_connection))\n    app.teardown_request(lambda _: pop_connection())\n"
  },
  {
    "path": "redash/tasks/alerts.py",
    "content": "import datetime\n\nfrom flask import current_app\n\nfrom redash import models, utils\nfrom redash.worker import get_job_logger, job\n\nlogger = get_job_logger(__name__)\n\n\ndef notify_subscriptions(alert, new_state, metadata):\n    host = utils.base_url(alert.query_rel.org)\n    for subscription in alert.subscriptions:\n        try:\n            subscription.notify(alert, alert.query_rel, subscription.user, new_state, current_app, host, metadata)\n        except Exception:\n            logger.exception(\"Error with processing destination\")\n\n\ndef should_notify(alert, new_state):\n    passed_rearm_threshold = False\n    if alert.rearm and alert.last_triggered_at:\n        passed_rearm_threshold = alert.last_triggered_at + datetime.timedelta(seconds=alert.rearm) < utils.utcnow()\n\n    return new_state != alert.state or (alert.state == models.Alert.TRIGGERED_STATE and passed_rearm_threshold)\n\n\n@job(\"default\", timeout=300)\ndef check_alerts_for_query(query_id, metadata):\n    logger.debug(\"Checking query %d for alerts\", query_id)\n\n    query = models.Query.query.get(query_id)\n\n    for alert in query.alerts:\n        logger.info(\"Checking alert (%d) of query %d.\", alert.id, query_id)\n        new_state = alert.evaluate()\n\n        if should_notify(alert, new_state):\n            logger.info(\"Alert %d new state: %s\", alert.id, new_state)\n            old_state = alert.state\n\n            alert.state = new_state\n            alert.last_triggered_at = utils.utcnow()\n            models.db.session.commit()\n\n            if old_state == models.Alert.UNKNOWN_STATE and new_state == models.Alert.OK_STATE:\n                logger.debug(\"Skipping notification (previous state was unknown and now it's ok).\")\n                continue\n\n            if alert.muted:\n                logger.debug(\"Skipping notification (alert muted).\")\n                continue\n\n            notify_subscriptions(alert, new_state, metadata)\n"
  },
  {
    "path": "redash/tasks/databricks.py",
    "content": "from redash import models, redis_connection\nfrom redash.tasks.worker import Queue\nfrom redash.utils import json_dumps\nfrom redash.worker import job\n\nDATABRICKS_REDIS_EXPIRATION_TIME = 3600\n\n\n@job(\"schemas\", queue_class=Queue, at_front=True, timeout=300, ttl=90)\ndef get_databricks_databases(data_source_id, redis_key):\n    try:\n        data_source = models.DataSource.get_by_id(data_source_id)\n        databases = data_source.query_runner.get_databases()\n        redis_connection.set(redis_key, json_dumps(databases))\n        redis_connection.expire(redis_key, DATABRICKS_REDIS_EXPIRATION_TIME)\n        return databases\n    except Exception:\n        return {\"error\": {\"code\": 2, \"message\": \"Error retrieving database list.\"}}\n\n\n@job(\"schemas\", queue_class=Queue, at_front=True, timeout=300, ttl=90)\ndef get_database_tables_with_columns(data_source_id, database_name, redis_key):\n    try:\n        data_source = models.DataSource.get_by_id(data_source_id)\n        tables = data_source.query_runner.get_database_tables_with_columns(database_name)\n        # check for tables since it doesn't return an error when the requested database doesn't exist\n        if tables or redis_connection.exists(redis_key):\n            redis_connection.set(redis_key, json_dumps(tables))\n            redis_connection.expire(\n                redis_key,\n                DATABRICKS_REDIS_EXPIRATION_TIME,\n            )\n        return {\"schema\": tables, \"has_columns\": True}\n    except Exception:\n        return {\"error\": {\"code\": 2, \"message\": \"Error retrieving schema.\"}}\n\n\n@job(\"schemas\", queue_class=Queue, at_front=True, timeout=300, ttl=90)\ndef get_databricks_tables(data_source_id, database_name):\n    try:\n        data_source = models.DataSource.get_by_id(data_source_id)\n        tables = data_source.query_runner.get_database_tables_with_columns(database_name)\n        return {\"schema\": tables, \"has_columns\": False}\n    except Exception:\n        return {\"error\": {\"code\": 2, \"message\": \"Error retrieving schema.\"}}\n\n\n@job(\"schemas\", queue_class=Queue, at_front=True, timeout=300, ttl=90)\ndef get_databricks_table_columns(data_source_id, database_name, table_name):\n    try:\n        data_source = models.DataSource.get_by_id(data_source_id)\n        return data_source.query_runner.get_table_columns(database_name, table_name)\n    except Exception:\n        return {\"error\": {\"code\": 2, \"message\": \"Error retrieving table columns.\"}}\n"
  },
  {
    "path": "redash/tasks/failure_report.py",
    "content": "import datetime\nimport re\nfrom collections import Counter\n\nfrom redash import models, redis_connection, settings\nfrom redash.tasks.general import send_mail\nfrom redash.utils import base_url, json_dumps, json_loads, render_template\nfrom redash.worker import get_job_logger\n\nlogger = get_job_logger(__name__)\n\n\ndef key(user_id):\n    return \"aggregated_failures:{}\".format(user_id)\n\n\ndef comment_for(failure):\n    schedule_failures = failure.get(\"schedule_failures\")\n    if schedule_failures > settings.MAX_FAILURE_REPORTS_PER_QUERY * 0.75:\n        return \"\"\"NOTICE: This query has failed a total of {schedule_failures} times.\n        Reporting may stop when the query exceeds {max_failure_reports} overall failures.\"\"\".format(\n            schedule_failures=schedule_failures,\n            max_failure_reports=settings.MAX_FAILURE_REPORTS_PER_QUERY,\n        )\n\n\ndef send_aggregated_errors():\n    for k in redis_connection.scan_iter(key(\"*\")):\n        user_id = re.search(r\"\\d+\", k).group()\n        send_failure_report(user_id)\n\n\ndef send_failure_report(user_id):\n    user = models.User.get_by_id(user_id)\n    errors = [json_loads(e) for e in redis_connection.lrange(key(user_id), 0, -1)]\n\n    if errors:\n        errors.reverse()\n        occurrences = Counter((e.get(\"id\"), e.get(\"message\")) for e in errors)\n        unique_errors = {(e.get(\"id\"), e.get(\"message\")): e for e in errors}\n\n        context = {\n            \"failures\": [\n                {\n                    \"id\": v.get(\"id\"),\n                    \"name\": v.get(\"name\"),\n                    \"failed_at\": v.get(\"failed_at\"),\n                    \"failure_reason\": v.get(\"message\"),\n                    \"failure_count\": occurrences[k],\n                    \"comment\": comment_for(v),\n                }\n                for k, v in unique_errors.items()\n            ],\n            \"base_url\": base_url(user.org),\n        }\n\n        subject = f\"Redash failed to execute {len(unique_errors.keys())} of your scheduled queries\"\n        html, text = [\n            render_template(\"emails/failures.{}\".format(f), context)\n            for f in [\"html\", \"txt\"]\n        ]  # fmt: skip\n\n        send_mail.delay([user.email], subject, html, text)\n\n    redis_connection.delete(key(user_id))\n\n\ndef notify_of_failure(message, query):\n    subscribed = query.org.get_setting(\"send_email_on_failed_scheduled_queries\")\n    exceeded_threshold = query.schedule_failures >= settings.MAX_FAILURE_REPORTS_PER_QUERY\n\n    if subscribed and not query.user.is_disabled and not exceeded_threshold:\n        redis_connection.lpush(\n            key(query.user.id),\n            json_dumps(\n                {\n                    \"id\": query.id,\n                    \"name\": query.name,\n                    \"message\": message,\n                    \"schedule_failures\": query.schedule_failures,\n                    \"failed_at\": datetime.datetime.utcnow().strftime(\"%B %d, %Y %I:%M%p UTC\"),\n                }\n            ),\n        )\n\n\ndef track_failure(query, error):\n    logger.debug(error)\n\n    query.schedule_failures += 1\n    query.skip_updated_at = True\n    models.db.session.add(query)\n    models.db.session.commit()\n\n    notify_of_failure(error, query)\n"
  },
  {
    "path": "redash/tasks/general.py",
    "content": "import requests\nfrom flask_mail import Message\n\nfrom redash import mail, models, settings\nfrom redash.models import users\nfrom redash.query_runner import NotSupported\nfrom redash.tasks.worker import Queue\nfrom redash.version_check import run_version_check\nfrom redash.worker import get_job_logger, job\n\nlogger = get_job_logger(__name__)\n\n\n@job(\"default\")\ndef record_event(raw_event):\n    event = models.Event.record(raw_event)\n    models.db.session.commit()\n\n    for hook in settings.EVENT_REPORTING_WEBHOOKS:\n        logger.debug(\"Forwarding event to: %s\", hook)\n        try:\n            data = {\n                \"schema\": \"iglu:io.redash.webhooks/event/jsonschema/1-0-0\",\n                \"data\": event.to_dict(),\n            }\n            response = requests.post(hook, json=data)\n            if response.status_code != 200:\n                logger.error(\"Failed posting to %s: %s\", hook, response.content)\n        except Exception:\n            logger.exception(\"Failed posting to %s\", hook)\n\n\ndef version_check():\n    run_version_check()\n\n\n@job(\"default\")\ndef subscribe(form):\n    logger.info(\n        \"Subscribing to: [security notifications=%s], [newsletter=%s]\",\n        form[\"security_notifications\"],\n        form[\"newsletter\"],\n    )\n    data = {\n        \"admin_name\": form[\"name\"],\n        \"admin_email\": form[\"email\"],\n        \"org_name\": form[\"org_name\"],\n        \"security_notifications\": form[\"security_notifications\"],\n        \"newsletter\": form[\"newsletter\"],\n    }\n    requests.post(\"https://version.redash.io/subscribe\", json=data)\n\n\n@job(\"emails\")\ndef send_mail(to, subject, html, text):\n    try:\n        message = Message(recipients=to, subject=subject, html=html, body=text)\n\n        mail.send(message)\n    except Exception:\n        logger.exception(\"Failed sending message: %s\", message.subject)\n\n\n@job(\"queries\", timeout=30, ttl=90)\ndef test_connection(data_source_id):\n    try:\n        data_source = models.DataSource.get_by_id(data_source_id)\n        data_source.query_runner.test_connection()\n    except Exception as e:\n        return e\n    else:\n        return True\n\n\n@job(\"schemas\", queue_class=Queue, at_front=True, timeout=settings.SCHEMAS_REFRESH_TIMEOUT, ttl=90)\ndef get_schema(data_source_id, refresh):\n    try:\n        data_source = models.DataSource.get_by_id(data_source_id)\n        return data_source.get_schema(refresh)\n    except NotSupported:\n        return {\n            \"error\": {\n                \"code\": 1,\n                \"message\": \"Data source type does not support retrieving schema\",\n            }\n        }\n    except Exception as e:\n        return {\"error\": {\"code\": 2, \"message\": \"Error retrieving schema\", \"details\": str(e)}}\n\n\ndef sync_user_details():\n    users.sync_last_active_at()\n"
  },
  {
    "path": "redash/tasks/queries/__init__.py",
    "content": "from .execution import enqueue_query, execute_query\nfrom .maintenance import (\n    cleanup_query_results,\n    empty_schedules,\n    refresh_queries,\n    refresh_schemas,\n    remove_ghost_locks,\n)\n"
  },
  {
    "path": "redash/tasks/queries/execution.py",
    "content": "import signal\nimport sys\nimport time\nfrom collections import deque\n\nimport redis\nfrom rq import get_current_job\nfrom rq.exceptions import NoSuchJobError\nfrom rq.job import JobStatus\nfrom rq.timeouts import JobTimeoutException\n\nfrom redash import models, redis_connection, settings\nfrom redash.query_runner import InterruptException\nfrom redash.tasks.alerts import check_alerts_for_query\nfrom redash.tasks.failure_report import track_failure\nfrom redash.tasks.worker import Job, Queue\nfrom redash.utils import gen_query_hash, utcnow\nfrom redash.worker import get_job_logger\n\nlogger = get_job_logger(__name__)\nTIMEOUT_MESSAGE = \"Query exceeded Redash query execution time limit.\"\n\n\ndef _job_lock_id(query_hash, data_source_id):\n    return \"query_hash_job:%s:%s\" % (data_source_id, query_hash)\n\n\ndef _unlock(query_hash, data_source_id):\n    redis_connection.delete(_job_lock_id(query_hash, data_source_id))\n\n\ndef enqueue_query(query, data_source, user_id, is_api_key=False, scheduled_query=None, metadata={}):\n    query_hash = gen_query_hash(query)\n    logger.info(\"Inserting job for %s with metadata=%s\", query_hash, metadata)\n    try_count = 0\n    job = None\n\n    while try_count < 5:\n        try_count += 1\n\n        pipe = redis_connection.pipeline()\n        try:\n            pipe.watch(_job_lock_id(query_hash, data_source.id))\n            job_id = pipe.get(_job_lock_id(query_hash, data_source.id))\n            if job_id:\n                logger.info(\"[%s] Found existing job: %s\", query_hash, job_id)\n                job_complete = None\n                job_cancelled = None\n\n                try:\n                    job = Job.fetch(job_id)\n                    job_exists = True\n                    status = job.get_status()\n                    job_complete = status in [JobStatus.FINISHED, JobStatus.FAILED]\n                    job_cancelled = job.is_cancelled\n\n                    if job_complete:\n                        message = \"job found is complete (%s)\" % status\n                    elif job_cancelled:\n                        message = \"job found has been cancelled\"\n                except NoSuchJobError:\n                    message = \"job found has expired\"\n                    job_exists = False\n\n                lock_is_irrelevant = job_complete or job_cancelled or not job_exists\n\n                if lock_is_irrelevant:\n                    logger.info(\"[%s] %s, removing lock\", query_hash, message)\n                    redis_connection.delete(_job_lock_id(query_hash, data_source.id))\n                    job = None\n\n            if not job:\n                pipe.multi()\n\n                if scheduled_query:\n                    queue_name = data_source.scheduled_queue_name\n                    scheduled_query_id = scheduled_query.id\n                else:\n                    queue_name = data_source.queue_name\n                    scheduled_query_id = None\n\n                time_limit = settings.dynamic_settings.query_time_limit(scheduled_query, user_id, data_source.org_id)\n                metadata[\"Queue\"] = queue_name\n\n                queue = Queue(queue_name)\n                enqueue_kwargs = {\n                    \"user_id\": user_id,\n                    \"scheduled_query_id\": scheduled_query_id,\n                    \"is_api_key\": is_api_key,\n                    \"job_timeout\": time_limit,\n                    \"failure_ttl\": settings.JOB_DEFAULT_FAILURE_TTL,\n                    \"meta\": {\n                        \"data_source_id\": data_source.id,\n                        \"org_id\": data_source.org_id,\n                        \"scheduled\": scheduled_query_id is not None,\n                        \"query_id\": metadata.get(\"query_id\"),\n                        \"user_id\": user_id,\n                    },\n                }\n\n                if not scheduled_query:\n                    enqueue_kwargs[\"result_ttl\"] = settings.JOB_EXPIRY_TIME\n\n                job = queue.enqueue(execute_query, query, data_source.id, metadata, **enqueue_kwargs)\n\n                logger.info(\"[%s] Created new job: %s\", query_hash, job.id)\n                pipe.set(\n                    _job_lock_id(query_hash, data_source.id),\n                    job.id,\n                    settings.JOB_EXPIRY_TIME,\n                )\n                pipe.execute()\n            break\n\n        except redis.WatchError:\n            continue\n        finally:\n            pipe.reset()\n\n    if not job:\n        logger.error(\"[Manager][%s] Failed adding job for query.\", query_hash)\n\n    return job\n\n\ndef signal_handler(*args):\n    raise InterruptException\n\n\nclass QueryExecutionError(Exception):\n    pass\n\n\ndef _resolve_user(user_id, is_api_key, query_id):\n    if user_id is not None:\n        if is_api_key:\n            api_key = user_id\n            if query_id is not None:\n                q = models.Query.get_by_id(query_id)\n            else:\n                q = models.Query.by_api_key(api_key)\n\n            return models.ApiUser(api_key, q.org, q.groups)\n        else:\n            return models.User.get_by_id(user_id)\n    else:\n        return None\n\n\ndef _get_size_iterative(dict_obj):\n    \"\"\"Iteratively finds size of objects in bytes\"\"\"\n    seen = set()\n    size = 0\n    objects = deque([dict_obj])\n\n    while objects:\n        current = objects.popleft()\n        if id(current) in seen:\n            continue\n        seen.add(id(current))\n        size += sys.getsizeof(current)\n\n        if isinstance(current, dict):\n            objects.extend(current.keys())\n            objects.extend(current.values())\n        elif hasattr(current, \"__dict__\"):\n            objects.append(current.__dict__)\n        elif hasattr(current, \"__iter__\") and not isinstance(current, (str, bytes, bytearray)):\n            objects.extend(current)\n\n    return size\n\n\nclass QueryExecutor:\n    def __init__(self, query, data_source_id, user_id, is_api_key, metadata, is_scheduled_query):\n        self.job = get_current_job()\n        self.query = query\n        self.data_source_id = data_source_id\n        self.metadata = metadata\n        self.data_source = self._load_data_source()\n        self.query_id = metadata.get(\"query_id\")\n        self.user = _resolve_user(user_id, is_api_key, metadata.get(\"query_id\"))\n        self.query_model = (\n            models.Query.query.get(self.query_id)\n            if self.query_id and self.query_id != \"adhoc\"\n            else None\n        )  # fmt: skip\n\n        # Close DB connection to prevent holding a connection for a long time while the query is executing.\n        models.db.session.close()\n        self.query_hash = gen_query_hash(self.query)\n        self.is_scheduled_query = is_scheduled_query\n        if self.is_scheduled_query:\n            # Load existing tracker or create a new one if the job was created before code update:\n            models.scheduled_queries_executions.update(self.query_model.id)\n\n    def run(self):\n        signal.signal(signal.SIGINT, signal_handler)\n        started_at = time.time()\n\n        logger.debug(\"Executing query:\\n%s\", self.query)\n        self._log_progress(\"executing_query\")\n\n        query_runner = self.data_source.query_runner\n        annotated_query = self._annotate_query(query_runner)\n\n        try:\n            data, error = query_runner.run_query(annotated_query, self.user)\n        except Exception as e:\n            if isinstance(e, JobTimeoutException):\n                error = TIMEOUT_MESSAGE\n            else:\n                error = str(e)\n\n            data = None\n            logger.warning(\"Unexpected error while running query:\", exc_info=1)\n\n        run_time = time.time() - started_at\n\n        logger.info(\n            \"job=execute_query query_hash=%s ds_id=%d data_length=%s error=[%s]\",\n            self.query_hash,\n            self.data_source_id,\n            data and _get_size_iterative(data),\n            error,\n        )\n\n        _unlock(self.query_hash, self.data_source.id)\n\n        if error is not None and data is None:\n            result = QueryExecutionError(error)\n            if self.is_scheduled_query:\n                self.query_model = models.db.session.merge(self.query_model, load=False)\n                track_failure(self.query_model, error)\n            raise result\n        else:\n            if self.query_model and self.query_model.schedule_failures > 0:\n                self.query_model = models.db.session.merge(self.query_model, load=False)\n                self.query_model.schedule_failures = 0\n                self.query_model.skip_updated_at = True\n                models.db.session.add(self.query_model)\n\n            query_result = models.QueryResult.store_result(\n                self.data_source.org_id,\n                self.data_source,\n                self.query_hash,\n                self.query,\n                data,\n                run_time,\n                utcnow(),\n            )\n\n            updated_query_ids = models.Query.update_latest_result(query_result)\n\n            models.db.session.commit()  # make sure that alert sees the latest query result\n            self._log_progress(\"checking_alerts\")\n            for query_id in updated_query_ids:\n                check_alerts_for_query.delay(query_id, self.metadata)\n            self._log_progress(\"finished\")\n\n            result = query_result.id\n            models.db.session.commit()\n            return result\n\n    def _annotate_query(self, query_runner):\n        self.metadata[\"Job ID\"] = self.job.id\n        self.metadata[\"Query Hash\"] = self.query_hash\n        self.metadata[\"Scheduled\"] = self.is_scheduled_query\n\n        return query_runner.annotate_query(self.query, self.metadata)\n\n    def _log_progress(self, state):\n        logger.info(\n            \"job=execute_query state=%s query_hash=%s type=%s ds_id=%d \"\n            \"job_id=%s queue=%s query_id=%s username=%s\",  # fmt: skip\n            state,\n            self.query_hash,\n            self.data_source.type,\n            self.data_source.id,\n            self.job.id,\n            self.metadata.get(\"Queue\", \"unknown\"),\n            self.metadata.get(\"query_id\", \"unknown\"),\n            self.metadata.get(\"Username\", \"unknown\"),\n        )\n\n    def _load_data_source(self):\n        logger.info(\"job=execute_query state=load_ds ds_id=%d\", self.data_source_id)\n        return models.DataSource.query.get(self.data_source_id)\n\n\n# user_id is added last as a keyword argument for backward compatability -- to support executing previously submitted\n# jobs before the upgrade to this version.\ndef execute_query(\n    query,\n    data_source_id,\n    metadata,\n    user_id=None,\n    scheduled_query_id=None,\n    is_api_key=False,\n):\n    try:\n        return QueryExecutor(\n            query,\n            data_source_id,\n            user_id,\n            is_api_key,\n            metadata,\n            scheduled_query_id is not None,\n        ).run()\n    except QueryExecutionError as e:\n        models.db.session.rollback()\n        return e\n"
  },
  {
    "path": "redash/tasks/queries/maintenance.py",
    "content": "import logging\nimport time\n\nfrom rq.timeouts import JobTimeoutException\n\nfrom redash import models, redis_connection, settings, statsd_client\nfrom redash.models.parameterized_query import (\n    InvalidParameterError,\n    QueryDetachedFromDataSourceError,\n)\nfrom redash.monitor import rq_job_ids\nfrom redash.query_runner import NotSupported\nfrom redash.tasks.failure_report import track_failure\nfrom redash.utils import json_dumps, sentry\nfrom redash.worker import get_job_logger, job\n\nfrom .execution import enqueue_query\n\nlogger = get_job_logger(__name__)\n\n\ndef empty_schedules():\n    logger.info(\"Deleting schedules of past scheduled queries...\")\n\n    queries = models.Query.past_scheduled_queries()\n    for query in queries:\n        query.schedule = None\n    models.db.session.commit()\n\n    logger.info(\"Deleted %d schedules.\", len(queries))\n\n\ndef _should_refresh_query(query):\n    if settings.FEATURE_DISABLE_REFRESH_QUERIES:\n        logger.info(\"Disabled refresh queries.\")\n        return False\n    elif query.org.is_disabled:\n        logger.debug(\"Skipping refresh of %s because org is disabled.\", query.id)\n        return False\n    elif query.data_source is None:\n        logger.debug(\"Skipping refresh of %s because the datasource is none.\", query.id)\n        return False\n    elif query.data_source.paused:\n        logger.debug(\n            \"Skipping refresh of %s because datasource - %s is paused (%s).\",\n            query.id,\n            query.data_source.name,\n            query.data_source.pause_reason,\n        )\n        return False\n    else:\n        return True\n\n\ndef _apply_default_parameters(query):\n    parameters = {p[\"name\"]: p.get(\"value\") for p in query.parameters}\n    if any(parameters):\n        try:\n            return query.parameterized.apply(parameters).query\n        except InvalidParameterError as e:\n            error = f\"Skipping refresh of {query.id} because of invalid parameters: {str(e)}\"\n            track_failure(query, error)\n            raise\n        except QueryDetachedFromDataSourceError as e:\n            error = (\n                f\"Skipping refresh of {query.id} because a related dropdown \"\n                f\"query ({e.query_id}) is unattached to any datasource.\"\n            )\n            track_failure(query, error)\n            raise\n    else:\n        return query.query_text\n\n\nclass RefreshQueriesError(Exception):\n    pass\n\n\ndef _apply_auto_limit(query_text, query):\n    should_apply_auto_limit = query.options.get(\"apply_auto_limit\", False)\n    return query.data_source.query_runner.apply_auto_limit(query_text, should_apply_auto_limit)\n\n\ndef refresh_queries():\n    started_at = time.time()\n    logger.info(\"Refreshing queries...\")\n    enqueued = []\n    for query in models.Query.outdated_queries():\n        if not _should_refresh_query(query):\n            continue\n\n        try:\n            query_text = _apply_default_parameters(query)\n            query_text = _apply_auto_limit(query_text, query)\n            enqueue_query(\n                query_text,\n                query.data_source,\n                query.user_id,\n                scheduled_query=query,\n                metadata={\"query_id\": query.id, \"Username\": query.user.get_actual_user()},\n            )\n            enqueued.append(query)\n        except Exception as e:\n            message = \"Could not enqueue query %d due to %s\" % (query.id, repr(e))\n            logging.info(message)\n            error = RefreshQueriesError(message).with_traceback(e.__traceback__)\n            sentry.capture_exception(error)\n\n    status = {\n        \"started_at\": started_at,\n        \"outdated_queries_count\": len(enqueued),\n        \"last_refresh_at\": time.time(),\n        \"query_ids\": json_dumps([q.id for q in enqueued]),\n    }\n\n    redis_connection.hset(\"redash:status\", mapping=status)\n    logger.info(\"Done refreshing queries: %s\" % status)\n\n\ndef cleanup_query_results():\n    \"\"\"\n    Job to cleanup unused query results -- such that no query links to them anymore, and older than\n    settings.QUERY_RESULTS_CLEANUP_MAX_AGE (a week by default, so it's less likely to be open in someone's browser and be used).\n\n    Each time the job deletes only settings.QUERY_RESULTS_CLEANUP_COUNT (100 by default) query results so it won't choke\n    the database in case of many such results.\n    \"\"\"\n\n    logger.info(\n        \"Running query results clean up (removing maximum of %d unused results, that are %d days old or more)\",\n        settings.QUERY_RESULTS_CLEANUP_COUNT,\n        settings.QUERY_RESULTS_CLEANUP_MAX_AGE,\n    )\n\n    unused_query_results = models.QueryResult.unused(settings.QUERY_RESULTS_CLEANUP_MAX_AGE)\n    deleted_count = models.QueryResult.query.filter(\n        models.QueryResult.id.in_(unused_query_results.limit(settings.QUERY_RESULTS_CLEANUP_COUNT).subquery())\n    ).delete(synchronize_session=False)\n    models.db.session.commit()\n    logger.info(\"Deleted %d unused query results.\", deleted_count)\n\n\ndef remove_ghost_locks():\n    \"\"\"\n    Removes query locks that reference a non existing RQ job.\n    \"\"\"\n    keys = redis_connection.keys(\"query_hash_job:*\")\n    locks = {k: redis_connection.get(k) for k in keys}\n    jobs = list(rq_job_ids())\n\n    count = 0\n\n    for lock, job_id in locks.items():\n        if job_id not in jobs:\n            redis_connection.delete(lock)\n            count += 1\n\n    logger.info(\"Locks found: {}, Locks removed: {}\".format(len(locks), count))\n\n\n@job(\"schemas\", timeout=settings.SCHEMAS_REFRESH_TIMEOUT)\ndef refresh_schema(data_source_id):\n    ds = models.DataSource.get_by_id(data_source_id)\n    logger.info(\"task=refresh_schema state=start ds_id=%s\", ds.id)\n    start_time = time.time()\n    try:\n        ds.get_schema(refresh=True)\n        logger.info(\n            \"task=refresh_schema state=finished ds_id=%s runtime=%.2f\",\n            ds.id,\n            time.time() - start_time,\n        )\n        statsd_client.incr(\"refresh_schema.success\")\n    except JobTimeoutException:\n        logger.info(\n            \"task=refresh_schema state=timeout ds_id=%s runtime=%.2f\",\n            ds.id,\n            time.time() - start_time,\n        )\n        statsd_client.incr(\"refresh_schema.timeout\")\n    except NotSupported:\n        logger.debug(\"Datasource %s does not support schema refresh\", ds.name)\n    except Exception:\n        logger.warning(\"Failed refreshing schema for the data source: %s\", ds.name, exc_info=1)\n        statsd_client.incr(\"refresh_schema.error\")\n        logger.info(\n            \"task=refresh_schema state=failed ds_id=%s runtime=%.2f\",\n            ds.id,\n            time.time() - start_time,\n        )\n\n\ndef refresh_schemas():\n    \"\"\"\n    Refreshes the data sources schemas.\n    \"\"\"\n    blacklist = [int(ds_id) for ds_id in redis_connection.smembers(\"data_sources:schema:blacklist\") if ds_id]\n    global_start_time = time.time()\n\n    logger.info(\"task=refresh_schemas state=start\")\n\n    for ds in models.DataSource.query:\n        if ds.paused:\n            logger.info(\n                \"task=refresh_schema state=skip ds_id=%s reason=paused(%s)\",\n                ds.id,\n                ds.pause_reason,\n            )\n        elif ds.id in blacklist:\n            logger.info(\"task=refresh_schema state=skip ds_id=%s reason=blacklist\", ds.id)\n        elif ds.org.is_disabled:\n            logger.info(\"task=refresh_schema state=skip ds_id=%s reason=org_disabled\", ds.id)\n        else:\n            refresh_schema.delay(ds.id)\n\n    logger.info(\n        \"task=refresh_schemas state=finish total_runtime=%.2f\",\n        time.time() - global_start_time,\n    )\n"
  },
  {
    "path": "redash/tasks/schedule.py",
    "content": "import hashlib\nimport json\nimport logging\nfrom datetime import datetime, timedelta\n\nfrom rq.job import Job\nfrom rq_scheduler import Scheduler\n\nfrom redash import rq_redis_connection, settings\nfrom redash.tasks.failure_report import send_aggregated_errors\nfrom redash.tasks.general import sync_user_details, version_check\nfrom redash.tasks.queries import (\n    cleanup_query_results,\n    empty_schedules,\n    refresh_queries,\n    refresh_schemas,\n    remove_ghost_locks,\n)\nfrom redash.tasks.worker import Queue\n\nlogger = logging.getLogger(__name__)\n\n\nclass StatsdRecordingScheduler(Scheduler):\n    \"\"\"\n    RQ Scheduler Mixin that uses Redash's custom RQ Queue class to increment/modify metrics via Statsd\n    \"\"\"\n\n    queue_class = Queue\n\n\nrq_scheduler = StatsdRecordingScheduler(connection=rq_redis_connection, queue_name=\"periodic\", interval=5)\n\n\ndef job_id(kwargs):\n    metadata = kwargs.copy()\n    metadata[\"func\"] = metadata[\"func\"].__name__\n\n    return hashlib.sha1(json.dumps(metadata, sort_keys=True).encode()).hexdigest()\n\n\ndef prep(kwargs):\n    interval = kwargs[\"interval\"]\n    if isinstance(interval, timedelta):\n        interval = int(interval.total_seconds())\n\n    kwargs[\"interval\"] = interval\n    kwargs[\"result_ttl\"] = kwargs.get(\"result_ttl\", interval * 2)\n\n    return kwargs\n\n\ndef schedule(kwargs):\n    rq_scheduler.schedule(scheduled_time=datetime.utcnow(), id=job_id(kwargs), **kwargs)\n\n\ndef periodic_job_definitions():\n    jobs = [\n        {\"func\": refresh_queries, \"timeout\": 600, \"interval\": 30, \"result_ttl\": 600},\n        {\n            \"func\": remove_ghost_locks,\n            \"interval\": timedelta(minutes=1),\n            \"result_ttl\": 600,\n        },\n        {\"func\": empty_schedules, \"interval\": timedelta(minutes=60)},\n        {\n            \"func\": refresh_schemas,\n            \"interval\": timedelta(minutes=settings.SCHEMAS_REFRESH_SCHEDULE),\n        },\n        {\n            \"func\": sync_user_details,\n            \"timeout\": 60,\n            \"interval\": timedelta(minutes=1),\n            \"result_ttl\": 600,\n        },\n        {\n            \"func\": send_aggregated_errors,\n            \"interval\": timedelta(minutes=settings.SEND_FAILURE_EMAIL_INTERVAL),\n        },\n    ]\n\n    if settings.VERSION_CHECK:\n        jobs.append({\"func\": version_check, \"interval\": timedelta(days=1)})\n\n    if settings.QUERY_RESULTS_CLEANUP_ENABLED:\n        jobs.append({\"func\": cleanup_query_results, \"interval\": timedelta(minutes=5)})\n\n    # Add your own custom periodic jobs in your dynamic_settings module.\n    jobs.extend(settings.dynamic_settings.periodic_jobs() or [])\n\n    return jobs\n\n\ndef schedule_periodic_jobs(jobs):\n    job_definitions = [prep(job) for job in jobs]\n\n    jobs_to_clean_up = Job.fetch_many(\n        set([job.id for job in rq_scheduler.get_jobs()]) - set([job_id(job) for job in job_definitions]),\n        rq_redis_connection,\n    )\n\n    jobs_to_schedule = [job for job in job_definitions if job_id(job) not in rq_scheduler]\n\n    for job in jobs_to_clean_up:\n        logger.info(\"Removing %s (%s) from schedule.\", job.id, job.func_name)\n        rq_scheduler.cancel(job)\n        job.delete()\n\n    for job in jobs_to_schedule:\n        logger.info(\n            \"Scheduling %s (%s) with interval %s.\",\n            job_id(job),\n            job[\"func\"].__name__,\n            job.get(\"interval\"),\n        )\n        schedule(job)\n"
  },
  {
    "path": "redash/tasks/worker.py",
    "content": "import errno\nimport os\nimport signal\nimport sys\n\nfrom rq import Queue as BaseQueue\nfrom rq.job import Job as BaseJob\nfrom rq.job import JobStatus\nfrom rq.timeouts import HorseMonitorTimeoutException\nfrom rq.utils import utcnow\nfrom rq.worker import (\n    HerokuWorker,  # HerokuWorker implements graceful shutdown on SIGTERM\n    Worker,\n)\n\nfrom redash import statsd_client\n\n# HerokuWorker does not work in OSX https://github.com/getredash/redash/issues/5413\nif sys.platform == \"darwin\":\n    BaseWorker = Worker\nelse:\n    BaseWorker = HerokuWorker\n\n\nclass CancellableJob(BaseJob):\n    def cancel(self, pipeline=None):\n        self.meta[\"cancelled\"] = True\n        self.save_meta()\n\n        super().cancel(pipeline=pipeline)\n\n    @property\n    def is_cancelled(self):\n        return self.meta.get(\"cancelled\", False)\n\n\nclass StatsdRecordingQueue(BaseQueue):\n    \"\"\"\n    RQ Queue Mixin that overrides `enqueue_call` to increment metrics via Statsd\n    \"\"\"\n\n    def enqueue_job(self, *args, **kwargs):\n        job = super().enqueue_job(*args, **kwargs)\n        statsd_client.incr(\"rq.jobs.created.{}\".format(self.name))\n        return job\n\n\nclass CancellableQueue(BaseQueue):\n    job_class = CancellableJob\n\n\nclass RedashQueue(StatsdRecordingQueue, CancellableQueue):\n    pass\n\n\nclass StatsdRecordingWorker(BaseWorker):\n    \"\"\"\n    RQ Worker Mixin that overrides `execute_job` to increment/modify metrics via Statsd\n    \"\"\"\n\n    def execute_job(self, job, queue):\n        statsd_client.incr(\"rq.jobs.running.{}\".format(queue.name))\n        statsd_client.incr(\"rq.jobs.started.{}\".format(queue.name))\n        try:\n            super().execute_job(job, queue)\n        finally:\n            statsd_client.decr(\"rq.jobs.running.{}\".format(queue.name))\n            if job.get_status() == JobStatus.FINISHED:\n                statsd_client.incr(\"rq.jobs.finished.{}\".format(queue.name))\n            else:\n                statsd_client.incr(\"rq.jobs.failed.{}\".format(queue.name))\n\n\nclass HardLimitingWorker(BaseWorker):\n    \"\"\"\n    RQ's work horses enforce time limits by setting a timed alarm and stopping jobs\n    when they reach their time limits. However, the work horse may be entirely blocked\n    and may not respond to the alarm interrupt. Since respecting timeouts is critical\n    in Redash (if we don't respect them, workers may be infinitely stuck and as a result,\n    service may be denied for other queries), we enforce two time limits:\n    1. A soft time limit, enforced by the work horse\n    2. A hard time limit, enforced by the parent worker\n\n    The HardLimitingWorker class changes the default monitoring behavior of the default\n    RQ Worker by checking if the work horse is still busy with the job, even after\n    it should have timed out (+ a grace period of 15s). If it does, it kills the work horse.\n    \"\"\"\n\n    grace_period = 15\n    queue_class = RedashQueue\n    job_class = CancellableJob\n\n    def stop_executing_job(self, job):\n        os.kill(self.horse_pid, signal.SIGINT)\n        self.log.warning(\"Job %s has been cancelled.\", job.id)\n\n    def soft_limit_exceeded(self, job):\n        job_has_time_limit = job.timeout != -1\n\n        if job_has_time_limit:\n            seconds_under_monitor = (utcnow() - self.monitor_started).seconds\n            return seconds_under_monitor > job.timeout + self.grace_period\n        else:\n            return False\n\n    def enforce_hard_limit(self, job):\n        self.log.warning(\n            \"Job %s exceeded timeout of %ds (+%ds grace period) but work horse did not terminate it. \"\n            \"Killing the work horse.\",\n            job.id,\n            job.timeout,\n            self.grace_period,\n        )\n        self.kill_horse()\n\n    def monitor_work_horse(self, job: \"Job\", queue: \"Queue\"):\n        \"\"\"The worker will monitor the work horse and make sure that it\n        either executes successfully or the status of the job is set to\n        failed\n\n        Args:\n            job (Job): _description_\n            queue (Queue): _description_\n        \"\"\"\n        self.monitor_started = utcnow()\n        retpid = ret_val = rusage = None\n        job.started_at = utcnow()\n        while True:\n            try:\n                with self.death_penalty_class(self.job_monitoring_interval, HorseMonitorTimeoutException):\n                    retpid, ret_val, rusage = self.wait_for_horse()\n                break\n            except HorseMonitorTimeoutException:\n                # Horse has not exited yet and is still running.\n                # Send a heartbeat to keep the worker alive.\n                self.set_current_job_working_time((utcnow() - job.started_at).total_seconds())\n\n                job.refresh()\n                # Kill the job from this side if something is really wrong (interpreter lock/etc).\n                if job.timeout != -1 and self.current_job_working_time > (job.timeout + 60):  # type: ignore\n                    self.heartbeat(self.job_monitoring_interval + 60)\n                    self.kill_horse()\n                    self.wait_for_horse()\n                    break\n\n                self.maintain_heartbeats(job)\n\n                if job.is_cancelled:\n                    self.stop_executing_job(job)\n\n                if self.soft_limit_exceeded(job):\n                    self.enforce_hard_limit(job)\n\n            except OSError as e:\n                # In case we encountered an OSError due to EINTR (which is\n                # caused by a SIGINT or SIGTERM signal during\n                # os.waitpid()), we simply ignore it and enter the next\n                # iteration of the loop, waiting for the child to end.  In\n                # any other case, this is some other unexpected OS error,\n                # which we don't want to catch, so we re-raise those ones.\n                if e.errno != errno.EINTR:\n                    raise\n                # Send a heartbeat to keep the worker alive.\n                self.heartbeat()\n\n        self.set_current_job_working_time(0)\n        self._horse_pid = 0  # Set horse PID to 0, horse has finished working\n        if ret_val == os.EX_OK:  # The process exited normally.\n            return\n\n        job_status = job.get_status()\n\n        if job_status is None:  # Job completed and its ttl has expired\n            return\n        elif self._stopped_job_id == job.id:\n            # Work-horse killed deliberately\n            self.log.warning(\"Job stopped by user, moving job to FailedJobRegistry\")\n            if job.stopped_callback:\n                job.execute_stopped_callback(self.death_penalty_class)\n            self.handle_job_failure(job, queue=queue, exc_string=\"Job stopped by user, work-horse terminated.\")\n        elif job_status not in [JobStatus.FINISHED, JobStatus.FAILED]:\n            if not job.ended_at:\n                job.ended_at = utcnow()\n\n            # Unhandled failure: move the job to the failed queue\n            signal_msg = f\" (signal {os.WTERMSIG(ret_val)})\" if ret_val and os.WIFSIGNALED(ret_val) else \"\"\n            exc_string = f\"Work-horse terminated unexpectedly; waitpid returned {ret_val}{signal_msg}; \"\n            self.log.warning(\"Moving job to FailedJobRegistry (%s)\", exc_string)\n\n            self.handle_work_horse_killed(job, retpid, ret_val, rusage)\n            self.handle_job_failure(job, queue=queue, exc_string=exc_string)\n\n\nclass RedashWorker(StatsdRecordingWorker, HardLimitingWorker):\n    queue_class = RedashQueue\n\n\nJob = CancellableJob\nQueue = RedashQueue\nWorker = RedashWorker\n"
  },
  {
    "path": "redash/templates/_includes/signed_out_tail.html",
    "content": ""
  },
  {
    "path": "redash/templates/_includes/tail.html",
    "content": ""
  },
  {
    "path": "redash/templates/emails/alert.html",
    "content": "<!DOCTYPE html>\n<head>\n<style>\nspan.status {\n  color: #fff;\n  border-radius: 2px;\n  padding: 3px 6px 4px;\n}\nspan.UNKNOWN {\n  background-color: #ff9800;\n}\nspan.TRIGGERED {\n  background-color: #f44336;\n}\nspan.OK {\n  background-color: #4caf50;\n}\n\nbody {\n  font-size: 13px;\n  font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;\n  color: #595959;\n}\n\na {\n  color: #2196f3;\n}\n\ntable {\n  width: 80%;\n}\n\nthead {\n  background: #fafafa;\n  white-space: nowrap;\n  text-align: left;\n}\n\nth, td {\n  padding: 7px 10px;\n  font-size: 13px;\n  border-bottom: 1px solid #f0f0f0;\n}\n\nfooter {\n  display: flex;\n  justify-content: center;\n  margin-top: 6em;\n}\n</style>\n</head>\n\n<body>\n<p>\n  <span class=\"status {{{ALERT_STATUS}}}\">STATUS: {{ALERT_STATUS}}</span>\n</p>\n\n<p>\n  CONDITION:\n  <code>\n  {{QUERY_RESULT_VALUE}} {{ALERT_CONDITION}} {{ALERT_THRESHOLD}}\n  </code>\n</p>\n\n<p>\n  QUERY:\n  <a href=\"{{QUERY_URL}}\">{{QUERY_NAME}}</a>\n</p>\n\n<table>\n  <thead>\n    <tr>\n    {{#QUERY_RESULT_COLS}}\n    <th>{{friendly_name}}</th>\n    {{/QUERY_RESULT_COLS}}\n  </tr>\n  </thead>\n\n{{#QUERY_RESULT_TABLE}}\n  <tr>\n    {{#.}}\n    <td>{{.}}</td>\n    {{/.}}\n  </tr>\n{{/QUERY_RESULT_TABLE}}\n</table>\n\n<footer>\n</footer>\n\n</body>\n</html>\n"
  },
  {
    "path": "redash/templates/emails/failures.html",
    "content": "<html>\n\n<head>\n    <style>\n        body {\n            margin: 0;\n        }\n    </style>\n</head>\n\n<body style=\"margin: 0;\">\n    <div\n        style=\"background: #f6f8f9; font-family: arial; font-size: 14px; padding: 20px; color: #333; line-height: 20px\">\n        <h1 style=\"font-size: 14px; font-weight: normal;\">Redash failed to run the following queries:</h1>\n\n        {% for failure in failures %}\n\t\t    <div style=\"background: white;padding: 5px 20px 20px 20px; border-radius: 3px; box-shadow: 0 4px 9px -3px rgba(102,136,153,.15); margin-top: 20px\">\n\t\t    \t<h2 style=\"font-size: 16px; position: relative; padding-right: 90px;\">{{failure.name}} <a href=\"{{base_url}}/queries/{{failure.id}}\" style=\"background-color: #ff7964;font-size: 13px;color:  white;text-decoration: none;padding: 0 5px;display: inline-block;border-radius: 3px;font-weight: normal;position: absolute;right: 0;top:-1px\">Go\n\t\t    \t\t\tto Query</a></h2>\n\t\t    \t<div>\n\t\t    \t\t<p>\n\t\t    \t\t\tLast failed: {{failure.failed_at}}\n                        {% if failure.failure_count > 1 %}\n                            <br />\n                            <small style=\"color: #8c8c8c;font-size: 13px;\">&nbsp; + {{failure.failure_count - 1}} more failures since last report</small>\n                        {% endif %}\n\t\t    \t\t</p>\n\t\t    \t\t<h3 style=\"font-size: 14px; font-weight:normal; margin-bottom: 6px\">Exception</h3>\n\t\t    \t\t<div style=\"background: #ececec;padding: 8px;border-radius: 3px;font-family: monospace; word-break: break-all;\">{{failure.failure_reason}}</div>\n                </div>\n\n                {% if failure.comment %}\n                    <div style=\"margin-top: 25px;font-size: 13px;border-top: 1px solid #ececec;padding-top: 19px;\">{{failure.comment}}</div>\n                {% endif %}\n            </div>\n        {% endfor %}\n\n    </div>\n</body>\n\n</html>\n"
  },
  {
    "path": "redash/templates/emails/failures.txt",
    "content": "Redash failed to run the following queries:\n\n{% for failure in failures %}\n\n{{failure.name}} ({{base_url}}/queries/{{failure.id}})\nLast failed: {{failure.failed_at}}{% if failure.failure_count > 1 %} + {{failure.failure_count - 1}} more failures since last report{% endif %}\nException:\n\n{{failure.failure_reason}}\n\n{% if failure.comment %}\n{{failure.comment}}\n{% endif %}\n\n{% endfor %}"
  },
  {
    "path": "redash/templates/emails/invite.html",
    "content": "{% extends \"emails/layout.html\" %}\n\n{% block content %}\n\n<p class=\"intercom-align-left\" style=\"line-height: 1.5; margin: 0 0 17px; text-align: left !important\" align=\"left\">Hi {{ invited.name }},</p>\n<h2 class=\"intercom-align-left\" style=\"color: #282F33; font-size: 18px; font-weight: bold; margin-bottom: 7px; margin-top: 30px; text-align: left !important\" align=\"left\">\n    {{ inviter.name }} (<a href=\"mailto:{{ inviter.email }}\" target=\"_blank\" style=\"border: none; color: #1251BA; outline: none !important\">{{ inviter.email }}</a>)\n    invited you to join the Redash account of {{ org.name }}.\n</h2>\n<table class=\"intercom-container intercom-align-center\" align=\"center\" style=\"border-collapse: collapse; border-spacing: 0; margin: 17px auto; table-layout: fixed; text-align: center !important\"><tr><td style=\"background: #0071b2; border: 1px none #dadada; border-radius: 3px; font-family: Helvetica, Arial, sans-serif; font-size: 16px; margin: 0; padding: 12px 35px; text-align: left; vertical-align: top\" align=\"left\" bgcolor=\"#0071b2\" valign=\"top\"><a class=\"intercom-h2b-button\" target=\"_blank\" href=\"{{ invite_url }}\" style=\"background: #0071b2; border: none; border-radius: 3px; color: white; display: inline-block; font-size: 14px; font-weight: bold; outline: none !important; text-decoration: none\">Setup Account</a></td></tr></table>\n<p class=\"intercom-align-left\" style=\"line-height: 1.5; margin: 0 0 17px; text-align: left !important\" align=\"left\">\n    <small>You may copy/paste this link into your browser: {{ invite_url }}</small>\n</p>\n<p class=\"intercom-align-left\" style=\"line-height: 1.5; margin: 0 0 17px; text-align: left !important\" align=\"left\">\n    Your sign-in email is: {{ invited.email }}<br>\n    Your Redash account is: <a href=\"{{ url_for('redash.index', org_slug=org.slug, _external=True) }}\" target=\"_blank\" style=\"border: none; color: #1251BA; outline: none !important\">{{ url_for('redash.index', org_slug=org.slug, _external=True) }}</a>\n</p>\n\n{% endblock %}\n"
  },
  {
    "path": "redash/templates/emails/invite.txt",
    "content": "Hi {{ invited.name }},\n\n{{ inviter.name }} ({{inviter.email}}) invited you to join the Redash account of {{org.name}}.\n\nUse the following link to setup your account:\n\n{{ invite_url }}\n\nYour sign-in email: {{invited.email}}\nYour Redash account: {{ url_for('redash.index', org_slug=org.slug, _external=True) }}"
  },
  {
    "path": "redash/templates/emails/layout.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html>\n<head>\n<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n<meta name=\"viewport\" content=\"width=device-width\">\n<meta name=\"format-detection\" content=\"telephone=no\">\n<title>{{ subject }}</title>\n\n<style type=\"text/css\" data-premailer=\"ignore\">\n  #outlook a{\n    padding: 0;\n  }\n  body{\n    -webkit-text-size-adjust: none;\n    -ms-text-size-adjust: none;\n    font-weight: 400;\n    margin: 0;\n    padding: 0;\n  }\n  .ReadMsgBody{\n    width: 100%;\n  }\n  .ExternalClass{\n    width: 100%;\n  }\n  img {\n    border: 0;\n    max-width: 100%;\n    height: auto;\n    outline: none;\n    display: inline-block;\n    margin: 0;\n    padding: 0;\n    text-decoration: none;\n    line-height: 100%;\n  }\n  #backgroundTable{\n    height: 100% !important;\n    margin: 0;\n    padding: 0;\n    width: 100% !important;\n  }\n</style>\n\n\n\n\n<style type=\"text/css\">\n  /**\n   * Generic\n   */\n\n  .main {\n    font-family: Helvetica, Arial, sans-serif;\n    letter-spacing: 0;\n  }\n\n  .main .main-td {\n    padding: 40px 60px;\n    border: 1px solid #dddddd;\n    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05);\n    border-radius: 2px;\n  }\n\n  table {\n    border-spacing: 0;\n    border-collapse: separate;\n    table-layout: fixed;\n  }\n\n  td {\n    font-size: 16px;\n    padding: 0;\n    font-family: Helvetica, Arial, sans-serif;\n  }\n\n  a {\n    border: none;\n    outline: none !important;\n  }\n\n\n  /**\n   * Header\n   */\n\n  .header td img {\n    padding: 15px 0 30px;\n    text-align: left;\n  }\n\n  .header .logo {\n    text-align: left;\n  }\n\n  /**\n   * Content\n   */\n\n  .content-td {\n    font-size: 15px;\n    line-height: 22px;\n    padding: 0;\n    color: #525252;\n  }\n\n  .content-td > :first-child {\n    margin-top: 0;\n  }\n\n  .content-td h1 {\n    font-size: 26px;\n    line-height: 33px;\n    color: #282F33;\n    margin-bottom: 7px;\n    margin-top: 30px;\n    font-weight: normal;\n  }\n\n  .content-td h2 {\n    font-size: 18px;\n    font-weight: bold;\n    color: #282F33;\n    margin-top: 30px;\n    margin-bottom: 7px;\n  }\n\n  .content-td h1 + h2 {\n    margin-top: 0px !important;\n  }\n\n  .content-td h2 + h1 {\n    margin-top: 0px !important;\n  }\n\n  .content-td h3 ,\n  .content-td h4 ,\n  .content-td h5 {\n    font-size: 16px;\n    font-weight: bold;\n    margin-bottom: 5px;\n  }\n\n  .content-td p {\n    margin: 0 0 17px 0;\n    line-height: 1.5;\n  }\n\n  .content-td p img,\n  .content-td h1 img,\n  .content-td h2 img,\n  .content-td li img,\n  .content-td .intercom-h2b-button img {\n    margin: 0;\n    padding: 0;\n  }\n\n\n\n  .content-td p.intro {\n    font-size: 20px;\n    line-height: 30px;\n  }\n\n  .content-td blockquote {\n    margin: 40px 0;\n    font-style: italic;\n    color: #8C8C8C;\n    font-size: 18px;\n    text-align: center;\n    padding: 0 30px;\n    font-family: Georgia, sans-serif;\n    quotes: none;\n  }\n\n  .content-td blockquote a {\n    color: #8C8C8C;\n  }\n\n  .content-td ul {\n    list-style: disc;\n    margin: 0 0 20px 40px;\n    padding: 0;\n  }\n\n  .content-td ol {\n    list-style: decimal;\n    margin: 0 0 20px 40px;\n    padding: 0;\n  }\n\n  .content-td img {\n    max-width: 100%;\n    margin: 17px 0;\n  }\n\n  .content-td .intercom-container {\n    margin-bottom: 16px;\n  }\n\n  .content-td hr {\n    border: none;\n    border-top: 1px solid #DDD;\n    margin: 50px 30% 50px 30%;\n  }\n\n  .content-td table {\n    border-collapse: collapse;\n    border-spacing: 0;\n    margin-bottom: 20px;\n  }\n\n  .content-td td,\n  .content-td th {\n    padding: 5px 7px;\n    border: 1px solid #DADADA;\n    text-align: left;\n    vertical-align: top;\n  }\n\n  .content-td th {\n    font-weight: bold;\n    background: #F6F6F6;\n  }\n\n  .content-td a {\n    color: #1251BA;\n  }\n\n  .content td.content-td table.intercom-container {\n    margin: 17px 0;\n  }\n  .content td.content-td table.intercom-container.intercom-align-center {\n    margin-left: auto;\n    margin-right: auto;\n  }\n\n  .content td.content-td table.intercom-container td {\n    background-color: #0071b2;\n    padding: 12px 35px;\n    border-radius: 3px;\n    font-family: Helvetica, Arial, sans-serif;\n    margin: 0;\n    border: none;\n  }\n\n  .content td.content-td table.intercom-container .intercom-h2b-button {\n    display: inline-block;\n    color: white;\n    font-weight: bold;\n    font-size: 14px;\n    text-decoration: none;\n    background-color: #0071b2;\n    border: none !important;\n    border-radius: 3px;\n  }\n\n  .footer-td {\n    text-align: center;\n    padding: 21px 30px 15px;\n  }\n\n  .footer-td p ,\n  .footer-td a {\n    font-size: 12px;\n    color: #b7b7b7;\n    text-decoration: none;\n    font-weight: 300;\n  }\n\n  .footer-td p {\n    margin: 0 0 6px 0;\n  }\n\n  .footer-td p.address {\n    margin-top: 9px;\n    line-height: 1.5em;\n  }\n\n  .footer-td p.powered-by {\n    margin-top: 18px;\n  }\n\n  .footer-td p.unsub {\n    margin: 0;\n  }\n\n  .footer .unsub a {\n    text-decoration: underline;\n    display: block;\n    margin: 12px 0 0;\n  }\n\n  p.unsub a {\n    text-decoration: underline;\n  }\n\n  .footer-td p.powered-by a {\n    font-weight: bold;\n  }\n\n  .footer-td textarea {\n    text-decoration: none;\n    font-size: 12px;\n    color: #b7b7b7;\n    font-family: Helvetica, Arial, sans-serif;\n    padding: 9px 0;\n    font-weight: 300;\n    line-height: normal;\n\n  }\n\n  a.intercom-h2b-button {\n    background-color: #0071b2;\n    font-size: 14px;\n    color: #FFF;\n    font-weight: bold;\n    border-radius: 3px;\n    display: inline-block;\n    text-decoration: none;\n\n  }\n</style>\n\n\n<style type=\"text/css\" data-premailer=\"ignore\">\n  @media screen and (max-width: 595px) {\n    body {\n      padding: 10px !important;\n    }\n    .main {\n      width: 100% !important;\n    }\n    .main .main-td {\n      padding: 20px !important;\n    }\n    .header td {\n      text-align: center;\n    }\n  }\n\n  .content-td blockquote + * {\n    margin-top: 20px !important;\n  }\n\n  .ExternalClass .content-td h1 {\n    padding: 20px 0 !important;\n  }\n\n  .ExternalClass .content-td h2 {\n    padding: 0 0 5px !important;\n  }\n\n  .ExternalClass .content-td p {\n    padding: 10px 0 !important;\n  }\n\n  .ExternalClass .content-td .intercom-container {\n    padding: 5px 0 !important;\n  }\n\n  .ExternalClass .content-td hr + * {\n    padding-top: 30px !important;\n  }\n\n  .ExternalClass .content-td ol ,\n  .ExternalClass .content-td ul {\n    padding: 0 0 20px 40px !important;\n    margin: 0 !important;\n  }\n\n  .ExternalClass .content-td ol li,\n  .ExternalClass .content-td ul li {\n    padding: 3px 0 !important;\n    margin: 0 !important;\n  }\n\n  .ExternalClass table .footer-td p {\n    padding: 0 0 6px 0 !important;\n    margin: 0 !important;\n  }\n\n  .ExternalClass table .footer-td p.powered-by ,\n  .ExternalClass table .footer-td p.unsub {\n    padding-top: 15px;\n  }\n\n</style>\n\n\n\n\n\n\n\n<style type=\"text/css\">\n  .intercom-align-right {\n  text-align: right !important;\n}\n.intercom-align-center {\n  text-align: center !important;\n}\n.intercom-align-left {\n  text-align: left !important;\n}\n/* Over-ride for RTL */\n.right-to-left .intercom-align-right{\n  text-align: left !important;\n}\n.right-to-left .intercom-align-left{\n  text-align: right !important;\n}\n.right-to-left .intercom-align-left {\n  text-align:right !important;\n}\n.right-to-left li {\n  text-align:right !important;\n  direction: rtl;\n}\n.right-to-left .intercom-align-left img,\n.right-to-left .intercom-align-left .intercom-h2b-button {\n  margin-left: 0 !important;\n}\n.intercom-attachment,\n.intercom-attachments,\n.intercom-attachments td,\n.intercom-attachments th,\n.intercom-attachments tr,\n.intercom-attachments tbody,\n.intercom-attachments .icon,\n.intercom-attachments .icon img {\n  border: none !important;\n  box-shadow: none !important;\n  padding: 0 !important;\n  margin: 0 !important;\n}\n.intercom-attachments {\n  margin: 10px 0 !important;\n}\n.intercom-attachments .icon,\n.intercom-attachments .icon img {\n  width: 16px !important;\n  height: 16px !important;\n}\n.intercom-attachments .icon {\n  padding-right: 5px !important;\n}\n.intercom-attachment {\n  display: inline-block !important;\n  margin-bottom: 5px !important;\n}\n\n.intercom-interblocks-content-card {\n  width: 334px;\n  max-height: 136px;\n  max-width: 100%;\n  overflow: hidden;\n  border-radius: 20px;\n  font-size: 16px;\n  border: 1px solid #e0e0e0;\n}\n\n.intercom-interblocks-article-icon {\n  width: 22.5%;\n  height: 136px;\n  float: left;\n  background-color: #fafafa;\n  background-image: url('https://static.intercomassets.com/assets/article_book-1a595be287f73c0d02f548f513bfc831.png');\n  background-repeat: no-repeat;\n  background-size: 32px;\n  background-position: center;\n}\n\n.intercom-interblocks-article-text {\n  width: 77.5%;\n  float: right;\n  background-color: #fff;\n}\n\n.intercom-interblocks-article-title {\n  color: #455a64;\n  height: 40px;\n  margin: 10px 15px;\n  line-height: 1.3;\n  font-weight: bold;\n  overflow: hidden;\n}\n\n.intercom-interblocks-article-body {\n  color: #74848b;\n  height: 30px;\n  margin: 10px 15px;\n  font-size: 12px;\n  font-weight: 500;\n  line-height: 1.3;\n  overflow: hidden;\n}\n\n.intercom-interblocks-article-author {\n  margin: 10px 15px;\n  height: 24px;\n  line-height: normal;\n}\n\n.intercom-interblocks-article-author-avatar {\n  width: 16px;\n  height: 16px;\n  display: inline-block;\n  vertical-align: middle;\n}\n\nimg.intercom-interblocks-article-author-avatar-image {\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  margin: 0;\n  vertical-align: top;\n}\n\n.intercom-interblocks-article-author-name {\n  color: #74848b;\n  line-height: 1.2;\n  margin: 0 0 0 5px;\n  display: inline-block;\n  font-size: 12px;\n  font-weight: 500;\n  overflow: hidden;\n  vertical-align: middle;\n}\n\n</style>\n\n\n\n</head>\n\n<body style=\"background: #f9f9f9; margin: 0px; padding: 20px\" bgcolor=\"f9f9f9\">\n\n  <table cellspacing=\"0\" border=\"0\" cellpadding=\"0\" align=\"center\" width=\"595\" bgcolor=\"white\" class=\"main\" style=\"border-collapse: separate; border-spacing: 0; font-family: Helvetica, Arial, sans-serif; letter-spacing: 0; table-layout: fixed\">\n    <tr>\n      <td class=\"main-td\" style=\"border: 1px solid #dddddd; border-radius: 2px; box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05); font-family: Helvetica, Arial, sans-serif; font-size: 16px; padding: 40px 60px\">\n\n\n\n  <table width=\"100%\" class=\"header\" style=\"border-collapse: separate; border-spacing: 0; table-layout: fixed\">\n    <tr>\n      <td class=\"logo\" style=\"font-family: Helvetica, Arial, sans-serif; font-size: 16px; padding: 0; text-align: left\" align=\"left\">\n\n          <img src=\"https://redash.io/assets/images/logo.png\" width=\"165\" height=\"86\" class=\"featured\" style=\"padding: 15px 0 30px; text-align: left\">\n\n      </td>\n    </tr>\n  </table>\n\n        <table width=\"100%\" class=\"content\" style=\"border-collapse: separate; border-spacing: 0; table-layout: fixed\">\n          <tr>\n            <td class=\"content-td\" style=\"color: #525252; font-family: Helvetica, Arial, sans-serif; font-size: 15px; line-height: 22px; padding: 0\">\n                {% block content %}\n                {% endblock %}\n            </td>\n          </tr>\n        </table>\n\n      </td>\n    </tr>\n  </table>\n\n</html>\n"
  },
  {
    "path": "redash/templates/emails/reset.html",
    "content": "{% extends \"emails/layout.html\" %}\n\n{% block content %}\n\n<p class=\"intercom-align-left\" style=\"line-height: 1.5; margin: 0 0 17px; text-align: left !important\" align=\"left\">Hi {{ user.name }},</p>\n<h2 class=\"intercom-align-left\" style=\"color: #282F33; font-size: 18px; font-weight: bold; margin-bottom: 7px; margin-top: 30px; text-align: left !important\" align=\"left\">\n    You told us you forgot your password. If you really did, click here to choose a new one:\n</h2>\n<table class=\"intercom-container intercom-align-center\" align=\"center\" style=\"border-collapse: collapse; border-spacing: 0; margin: 17px auto; table-layout: fixed; text-align: center !important\"><tr><td style=\"background: #0071b2; border: 1px none #dadada; border-radius: 3px; font-family: Helvetica, Arial, sans-serif; font-size: 16px; margin: 0; padding: 12px 35px; text-align: left; vertical-align: top\" align=\"left\" bgcolor=\"#0071b2\" valign=\"top\"><a class=\"intercom-h2b-button\" target=\"_blank\" href=\"{{ reset_link }}\" style=\"background: #0071b2; border: none; border-radius: 3px; color: white; display: inline-block; font-size: 14px; font-weight: bold; outline: none !important; text-decoration: none\">Choose a new password</a></td></tr></table>\n<p class=\"intercom-align-left\" style=\"line-height: 1.5; margin: 0 0 17px; text-align: left !important\" align=\"left\">\n    <small>Need the raw link? {{ reset_link }}</small>\n</p>\n<p class=\"intercom-align-left\" style=\"line-height: 1.5; margin: 0 0 17px; text-align: left !important\" align=\"left\">\n    If you didn't mean to reset your password, then you can just ignore this email; your password will not change.\n</p>\n\n{% endblock %}\n"
  },
  {
    "path": "redash/templates/emails/reset.txt",
    "content": "Hi {{ user.name }},\n\nYou told us you forgot your password. If you really did, click here to choose a new one:\n\n{{ reset_link }}\n\nIf you didn't mean to reset your password, then you can just ignore this email; your password will not change.\n\n"
  },
  {
    "path": "redash/templates/emails/reset_disabled.html",
    "content": "{% extends \"emails/layout.html\" %}\n\n{% block content %}\n\n<p class=\"intercom-align-left\" style=\"line-height: 1.5; margin: 0 0 17px; text-align: left !important\" align=\"left\">Hi {{ user.name }},</p>\n<h2 class=\"intercom-align-left\" style=\"color: #282F33; font-size: 18px; font-weight: bold; margin-bottom: 7px; margin-top: 30px; text-align: left !important\" align=\"left\">\n        You asked for a password reset email, but your Redash account is disabled and therefore can't reset the password. Please contact your Redash admin for enabling again your account.\n</h2>\n\n{% endblock %}\n"
  },
  {
    "path": "redash/templates/emails/reset_disabled.txt",
    "content": "Hi {{ user.name }},\n\nYou asked for a password reset email, but your Redash account is disabled and therefore can't reset the password. Please contact your Redash admin for enabling again your account.\n"
  },
  {
    "path": "redash/templates/emails/verify.html",
    "content": "{% extends \"emails/layout.html\" %}\n\n{% block content %}\n\n<p class=\"intercom-align-left\" style=\"line-height: 1.5; margin: 0 0 17px; text-align: left !important\" align=\"left\">Hi {{ user.name }},</p>\n<h2 class=\"intercom-align-left\" style=\"color: #282F33; font-size: 18px; font-weight: bold; margin-bottom: 7px; margin-top: 30px; text-align: left !important\" align=\"left\">\n    Please verify that {{ user.email }} is your correct email address by visiting the following link:\n</h2>\n<table class=\"intercom-container intercom-align-center\" align=\"center\" style=\"border-collapse: collapse; border-spacing: 0; margin: 17px auto; table-layout: fixed; text-align: center !important\"><tr><td style=\"background: #0071b2; border: 1px none #dadada; border-radius: 3px; font-family: Helvetica, Arial, sans-serif; font-size: 16px; margin: 0; padding: 12px 35px; text-align: left; vertical-align: top\" align=\"left\" bgcolor=\"#0071b2\" valign=\"top\"><a class=\"intercom-h2b-button\" target=\"_blank\"\n                href=\"{{ verify_url }}\" style=\"background: #0071b2; border: none; border-radius: 3px; color: white; display: inline-block; font-size: 14px; font-weight: bold; outline: none !important; text-decoration: none\">Verify Address</a></td></tr></table>\n<p class=\"intercom-align-left\" style=\"line-height: 1.5; margin: 0 0 17px; text-align: left !important\" align=\"left\">\n    <small>You may copy/paste this link into your browser: {{ verify_url }}</small>\n</p>\n\n{% endblock %}\n"
  },
  {
    "path": "redash/templates/emails/verify.txt",
    "content": "Hi {{ user.name }},\n\nPlease verify that {{ user.email }} is your correct email address by visiting the following link:\n\n{{ verify_url }}\n\nThank you.\n"
  },
  {
    "path": "redash/templates/error.html",
    "content": "{% set hide_page_header = true %}\n\n{% extends \"layouts/signed_out.html\" %}\n\n{% block title %}Error :-({% endblock %}\n\n{% block content %}\n<div class=\"fixed-width-page\">\n  <div class=\"bg-white tiled error-state m-t-0\">\n    <div class=\"error-state__icon\">\n      <i class=\"zmdi zmdi-alert-circle-o\"></i>\n    </div>\n    <div class=\"error-state__details\">\n      <h4>{{ error_message }}</h4>\n    </div>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "redash/templates/forgot.html",
    "content": "{% extends \"layouts/signed_out.html\" %}\n{% block title %}Password Reset{% endblock %}\n{% block content %}\n<div class=\"fixed-width-page\">\n  <div class=\"bg-white tiled\">\n    {% if submitted %}\n      <div>\n        If we found an account associated with that email address, you will find an email from us in your inbox shortly.\n      </div>\n    {% else %}\n      <form role=\"form\" method=\"post\">\n        <input type=\"hidden\" name=\"csrf_token\" value=\"{{ csrf_token() }}\"/>\n        <div class=\"form-group\">\n          <label for=\"email\">Enter the email address you used for this account:</label>\n          <input type=\"email\" class=\"form-control\" name=\"email\" value=\"{{email}}\">\n        </div>\n        <button type=\"submit\" class=\"btn btn-primary btn-block m-t-25\">Reset my password</button>\n      </form>\n    {% endif %}\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "redash/templates/invite.html",
    "content": "{% extends \"layouts/signed_out.html\" %}\n\n{% block title %}Welcome to Redash!{% endblock %}\n\n{% block content %}\n<div class=\"fixed-width-page\">\n  <div class=\"bg-white tiled\">\n    <div class=\"m-b-25\">\n      {% if show_google_openid or show_saml_login or show_remote_user_login or show_ldap_login %}\n        To create your account, please choose a password or login with your SSO provider.\n      {% else %}\n        To create your account, please choose a password.\n      {% endif %}\n    </div>\n\n    {% with messages = get_flashed_messages() %}\n      {% if messages %}\n        {% for message in messages %}\n          <div class=\"alert alert-danger\" role=\"alert\">{{ message }}</div>\n        {% endfor %}\n      {% endif %}\n    {% endwith %}\n\n    {% if show_google_openid %}\n      <a href=\"{{ google_auth_url }}\" class=\"login-button btn btn-default btn-block\">\n        <img src=\"/static/images/google_logo.svg\">\n        Login with Google\n      </a>\n    {% endif %}\n\n    {% if show_saml_login %}\n      <a href=\"{{ url_for('saml_auth.sp_initiated', org_slug=org_slug) }}\" class=\"login-button btn btn-default btn-block\">SAML Login</a>\n    {% endif %}\n\n    {% if show_remote_user_login %}\n      <a href=\"{{ url_for('remote_user_auth.login') }}\" class=\"login-button btn btn-default btn-block\">Remote User Login</a>\n    {% endif %}\n\n    {% if show_ldap_login %}\n      <a href=\"{{ url_for('ldap_auth.login') }}\" class=\"login-button btn btn-default btn-block\">LDAP/SSO Login</a>\n    {% endif %}\n\n    {% if show_google_openid or show_saml_login or show_remote_user_login or show_ldap_login %}\n      <hr>\n    {% endif %}\n\n    <form role=\"form\" method=\"post\" name=\"invite\">\n      <input type=\"hidden\" name=\"csrf_token\" value=\"{{ csrf_token() }}\"/>\n      <div class=\"form-group\">\n        <label for=\"password\">Password</label>\n        <input type=\"password\" class=\"form-control\" id=\"password\" name=\"password\">\n      </div>\n      <button type=\"submit\" class=\"btn btn-primary btn-block m-t-25\">Set Password</button>\n    </form>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "redash/templates/layouts/signed_out.html",
    "content": "<!DOCTYPE html>\n<html class=\"no-js\">\n<head>\n  <title>{% block title %}{% endblock %}</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n\n  <link rel=\"stylesheet\" href=\"{{ asset_url('server.css') }}\">\n\n  <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/static/images/favicon-32x32.png\">\n  <link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"/static/images/favicon-96x96.png\">\n  <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/static/images/favicon-16x16.png\">\n  {% block head %}{% endblock %}\n</head>\n<body class=\"d-flex flex-column justify-content-center align-items-center signed-out\">\n\n  <div>\n    <div class=\"header\">\n      <div class=\"text-center m-b-15\">\n        <a href=\"/\"><img src=\"/static/images/redash_icon_small.png\"></a>\n      </div>\n      {% if not hide_page_header %}\n        <h3 class=\"text-center m-t-0 m-b-25\">{{ self.title() }}</h3>\n      {% endif %}\n    </div>\n    {% block content %}{% endblock %}\n  </div>\n\n{% block scripts %}{% endblock %}\n\n{% include '_includes/signed_out_tail.html' %}\n\n</body>\n</html>\n"
  },
  {
    "path": "redash/templates/login.html",
    "content": "{% extends \"layouts/signed_out.html\" %}\n\n{% block title %}Login to Redash{% endblock %}\n\n{% block content %}\n<div class=\"fixed-width-page\">\n  <div class=\"bg-white tiled\">\n    {% with messages = get_flashed_messages() %}\n      {% if messages %}\n        {% for message in messages %}\n          <div class=\"alert alert-danger\" role=\"alert\" data-test=\"ErrorMessage\">{{ message }}</div>\n        {% endfor %}\n      {% endif %}\n    {% endwith %}\n\n    {% if show_google_openid %}\n      <a href=\"{{ google_auth_url }}\" class=\"login-button btn btn-default btn-block\">\n        <img src=\"/static/images/google_logo.svg\">\n        Login with Google\n      </a>\n    {% endif %}\n\n    {% if show_saml_login %}\n      <a href=\"{{ url_for('saml_auth.sp_initiated', org_slug=org_slug, next=next) }}\" class=\"login-button btn btn-default btn-block\">SAML Login</a>\n    {% endif %}\n\n    {% if show_remote_user_login %}\n      <a href=\"{{ url_for('remote_user_auth.login', org_slug=org_slug, next=next) }}\" class=\"login-button btn btn-default btn-block\">Remote User Login</a>\n    {% endif %}\n\n    {% if show_ldap_login %}\n      <a href=\"{{ url_for('ldap_auth.login', org_slug=org_slug, next=next) }}\" class=\"login-button btn btn-default btn-block\">LDAP/SSO Login</a>\n    {% endif %}\n\n    {% if show_password_login %}\n    {% if show_google_openid or show_saml_login or show_remote_user_login or show_ldap_login %}\n      <hr>\n    {% endif %}\n\n    <form role=\"form\" method=\"post\" name=\"login\">\n      <input type=\"hidden\" name=\"csrf_token\" value=\"{{ csrf_token() }}\"/>\n      <div class=\"form-group\">\n        <label for=\"inputEmail\">{{ username_prompt or 'Email' }}</label>\n        <input type=\"text\" class=\"form-control\" id=\"inputEmail\" name=\"email\" value=\"{{email}}\" data-test=\"Email\">\n      </div>\n      <div class=\"form-group\">\n        <label for=\"inputPassword\">Password</label>\n        <input type=\"password\" class=\"form-control\" id=\"inputPassword\" name=\"password\" data-test=\"Password\">\n      </div>\n      <div class=\"form-group\">\n        <input type=\"checkbox\" id=\"inputRemember\" name=\"remember\" checked>\n        <label for=\"inputRemember\">Remember me</label>\n      </div>\n\n      <button type=\"submit\" class=\"btn btn-primary btn-block m-t-25\">Log In</button>\n    </form>\n    {% if not hide_forgot_password %}\n      <div class=\"m-t-25\">\n        <a href=\"{{ url_for(\"redash.forgot_password\", org_slug=org_slug) }}\">I forgot my password</a>\n      </div>\n    {% endif %}\n    {% endif %}\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "redash/templates/reset.html",
    "content": "{% extends \"layouts/signed_out.html\" %}\n\n{% block title %}Password Reset{% endblock %}\n\n{% block content %}\n<div class=\"fixed-width-page\">\n  <div class=\"bg-white tiled\">\n    {% with messages = get_flashed_messages() %}\n      {% if messages %}\n        {% for message in messages %}\n          <div class=\"alert alert-danger\" role=\"alert\">{{ message }}</div>\n        {% endfor %}\n      {% endif %}\n    {% endwith %}\n    <form role=\"form\" method=\"post\" name=\"invite\">\n      <input type=\"hidden\" name=\"csrf_token\" value=\"{{ csrf_token() }}\"/>\n      <div class=\"form-group\">\n        <label for=\"password\">Enter your new password:</label>\n        <input type=\"password\" class=\"form-control\" id=\"password\" name=\"password\" placeholder=\"Password\">\n      </div>\n      <button type=\"submit\" class=\"btn btn-primary btn-block m-t-25\">Change my password</button>\n    </form>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "redash/templates/setup.html",
    "content": "{% extends \"layouts/signed_out.html\" %}\n\n{% block title %}Redash Initial Setup{% endblock %}\n\n{% macro render_field_errors(field) -%}\n  {% if field.errors %}\n    {% for e in field.errors %}\n      <p class=\"help-block\">{{ e }}</p>\n    {% endfor %}\n  {% endif %}\n{%- endmacro %}\n\n{% macro render_field(field, help_block=None) -%}\n  <div class=\"form-group {% if field.errors %}has-error{% endif %}\">\n    {{ field.label() }}\n    {{ field(class='form-control') }}\n    {% if help_block %}\n        <p class=\"help-block\">{{ help_block }}</p>\n    {% endif %}\n    {{ render_field_errors(field) }}\n  </div>\n{%- endmacro %}\n\n{% block content %}\n<div class=\"fixed-width-page\">\n  <div class=\"bg-white tiled\">\n    <h4 class=\"m-t-0\">Welcome to Redash!</h4>\n    <div>Before you can use your instance, you need to do a quick setup.</div>\n\n    {% with messages = get_flashed_messages() %}\n      {% if messages %}\n        {% for message in messages %}\n          <div class=\"alert alert-warning\" role=\"alert\">{{ message }}</div>\n        {% endfor %}\n      {% endif %}\n    {% endwith %}\n\n    <form role=\"form\" method=\"post\" name=\"create_account\">\n      <input type=\"hidden\" name=\"csrf_token\" value=\"{{ csrf_token() }}\"/>\n      <h4 class=\"m-t-25\">Admin User</h4>\n      {{ render_field(form.name) }}\n      {{ render_field(form.email) }}\n      {{ render_field(form.password) }}\n\n      <div class=\"checkbox\">\n        <label>\n          {{ form.security_notifications() }}\n          Subscribe to Security Notifications\n        </label>\n      </div>\n\n      <div class=\"checkbox\">\n        <label>\n          {{ form.newsletter() }}\n          Subscribe to newsletter (version updates, no more than once a month)\n        </label>\n      </div>\n\n      <h4 class=\"m-t-25\">General</h4>\n\n      {{ render_field(form.org_name, help_block=\"Used in email notifications and the UI.\") }}\n\n      <button type=\"submit\" class=\"btn btn-primary btn-block m-t-25\">Setup</button>\n    </form>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "redash/templates/verify.html",
    "content": "{% extends \"layouts/signed_out.html\" %}\n{% block title %}E-mail Verified{% endblock %}\n{% block head %}\n<meta http-equiv=\"refresh\" content=\"5;URL='{{ next_url }}'\" />\n{% endblock %}\n{% block content %}\n<div class=\"fixed-width-page\">\n  <div class=\"bg-white tiled\">\n      <div>\n          Thanks for verifying your email address. You will be redirected back to the app within a few seconds. <a href=\"{{ next_url }}\">Click here</a> if you are not redirected.\n      </div>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "redash/utils/__init__.py",
    "content": "import binascii\nimport codecs\nimport csv\nimport datetime\nimport decimal\nimport hashlib\nimport io\nimport json\nimport math\nimport os\nimport random\nimport re\nimport sys\nimport uuid\n\nimport pystache\nimport pytz\nimport sqlparse\nfrom flask import current_app\nfrom funcy import select_values\nfrom sqlalchemy.orm.query import Query\n\nfrom redash import settings\n\nfrom .human_time import parse_human_time\n\nCOMMENTS_REGEX = re.compile(r\"/\\*.*?\\*/\")\nWRITER_ENCODING = os.environ.get(\"REDASH_CSV_WRITER_ENCODING\", \"utf-8\")\nWRITER_ERRORS = os.environ.get(\"REDASH_CSV_WRITER_ERRORS\", \"strict\")\n\n\ndef utcnow():\n    \"\"\"Return datetime.now value with timezone specified.\n\n    Without the timezone data, when the timestamp stored to the database it gets the current timezone of the server,\n    which leads to errors in calculations.\n    \"\"\"\n    return datetime.datetime.now(pytz.utc)\n\n\ndef dt_from_timestamp(timestamp, tz_aware=True):\n    timestamp = datetime.datetime.utcfromtimestamp(float(timestamp))\n\n    if tz_aware:\n        timestamp = timestamp.replace(tzinfo=pytz.utc)\n\n    return timestamp\n\n\ndef slugify(s):\n    return re.sub(r\"[^a-z0-9_\\-]+\", \"-\", s.lower())\n\n\ndef gen_query_hash(sql):\n    \"\"\"Return hash of the given query after stripping all comments, line breaks\n    and multiple spaces.\n\n    The following queries will get different ids:\n        1. SELECT 1 FROM table WHERE column='Value';\n        2. SELECT 1 FROM table where column='value';\n    \"\"\"\n    sql = COMMENTS_REGEX.sub(\"\", sql)\n    sql = \"\".join(sql.split())\n    return hashlib.md5(sql.encode(\"utf-8\"), usedforsecurity=False).hexdigest()\n\n\ndef generate_token(length):\n    chars = \"abcdefghijklmnopqrstuvwxyz\" \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" \"0123456789\"\n\n    rand = random.SystemRandom()\n    return \"\".join(rand.choice(chars) for x in range(length))\n\n\nclass JSONEncoder(json.JSONEncoder):\n    \"\"\"Adapter for `json.dumps`.\"\"\"\n\n    def __init__(self, **kwargs):\n        from redash.query_runner import query_runners\n\n        self.encoders = [r.custom_json_encoder for r in query_runners.values() if hasattr(r, \"custom_json_encoder\")]\n        super().__init__(**kwargs)\n\n    def default(self, o):\n        for encoder in self.encoders:\n            result = encoder(self, o)\n            if result:\n                return result\n        if isinstance(o, Query):\n            result = list(o)\n        elif isinstance(o, decimal.Decimal):\n            result = float(o)\n        elif isinstance(o, (datetime.timedelta, uuid.UUID)):\n            result = str(o)\n        # See \"Date Time String Format\" in the ECMA-262 specification.\n        elif isinstance(o, datetime.datetime):\n            result = o.isoformat()\n            if o.microsecond:\n                result = result[:23] + result[26:]\n            if result.endswith(\"+00:00\"):\n                result = result[:-6] + \"Z\"\n        elif isinstance(o, datetime.date):\n            result = o.isoformat()\n        elif isinstance(o, datetime.time):\n            if o.utcoffset() is not None:\n                raise ValueError(\"JSON can't represent timezone-aware times.\")\n            result = o.isoformat()\n            if o.microsecond:\n                result = result[:12]\n        elif isinstance(o, memoryview):\n            result = binascii.hexlify(o).decode()\n        elif isinstance(o, bytes):\n            result = binascii.hexlify(o).decode()\n        else:\n            result = super().default(o)\n        return result\n\n\ndef json_loads(data, *args, **kwargs):\n    \"\"\"A custom JSON loading function which passes all parameters to the\n    json.loads function.\"\"\"\n    return json.loads(data, *args, **kwargs)\n\n\n# Convert NaN, Inf, and -Inf to None, as they are not valid JSON values.\ndef _sanitize_data(data):\n    if isinstance(data, dict):\n        return {k: _sanitize_data(v) for k, v in data.items()}\n    if isinstance(data, list):\n        return [_sanitize_data(v) for v in data]\n    if isinstance(data, float) and (math.isnan(data) or math.isinf(data)):\n        return None\n    return data\n\n\ndef json_dumps(data, *args, **kwargs):\n    \"\"\"A custom JSON dumping function which passes all parameters to the\n    json.dumps function.\"\"\"\n    kwargs.setdefault(\"cls\", JSONEncoder)\n    kwargs.setdefault(\"ensure_ascii\", False)\n    # Float value nan or inf in Python should be render to None or null in json.\n    # Using allow_nan = True will make Python render nan as NaN, leading to parse error in front-end\n    kwargs.setdefault(\"allow_nan\", False)\n    return json.dumps(_sanitize_data(data), *args, **kwargs)\n\n\ndef mustache_render(template, context=None, **kwargs):\n    renderer = pystache.Renderer(escape=lambda u: u)\n    return renderer.render(template, context, **kwargs)\n\n\ndef mustache_render_escape(template, context=None, **kwargs):\n    renderer = pystache.Renderer()\n    return renderer.render(template, context, **kwargs)\n\n\ndef build_url(request, host, path):\n    parts = request.host.split(\":\")\n    if len(parts) > 1:\n        port = parts[1]\n        if (port, request.scheme) not in ((\"80\", \"http\"), (\"443\", \"https\")):\n            host = \"{}:{}\".format(host, port)\n\n    return \"{}://{}{}\".format(request.scheme, host, path)\n\n\nclass UnicodeWriter:\n    \"\"\"\n    A CSV writer which will write rows to CSV file \"f\",\n    which is encoded in the given encoding.\n    \"\"\"\n\n    def __init__(self, f, dialect=csv.excel, encoding=WRITER_ENCODING, **kwds):\n        # Redirect output to a queue\n        self.queue = io.StringIO()\n        self.writer = csv.writer(self.queue, dialect=dialect, **kwds)\n        self.stream = f\n        self.encoder = codecs.getincrementalencoder(encoding)()\n\n    def _encode_utf8(self, val):\n        if isinstance(val, str):\n            return val.encode(WRITER_ENCODING, WRITER_ERRORS)\n\n        return val\n\n    def writerow(self, row):\n        self.writer.writerow([self._encode_utf8(s) for s in row])\n        # Fetch UTF-8 output from the queue ...\n        data = self.queue.getvalue()\n        data = data.decode(WRITER_ENCODING)\n        # ... and reencode it into the target encoding\n        data = self.encoder.encode(data)\n        # write to the target stream\n        self.stream.write(data)\n        # empty queue\n        self.queue.truncate(0)\n\n    def writerows(self, rows):\n        for row in rows:\n            self.writerow(row)\n\n\ndef collect_parameters_from_request(args):\n    parameters = {}\n\n    for k, v in args.items():\n        if k.startswith(\"p_\"):\n            parameters[k[2:]] = v\n\n    return parameters\n\n\ndef base_url(org):\n    if settings.MULTI_ORG:\n        return \"{}/{}\".format(settings.HOST, org.slug)\n\n    return settings.HOST\n\n\ndef filter_none(d):\n    return select_values(lambda v: v is not None, d)\n\n\ndef to_filename(s):\n    s = re.sub(r'[<>:\"\\\\\\/|?*]+', \" \", s, flags=re.UNICODE)\n    s = re.sub(r\"\\s+\", \"_\", s, flags=re.UNICODE)\n    return s.strip(\"_\")\n\n\ndef deprecated():\n    def wrapper(K):\n        setattr(K, \"deprecated\", True)\n        return K\n\n    return wrapper\n\n\ndef render_template(path, context):\n    \"\"\"Render a template with context, without loading the entire app context.\n    Using Flask's `render_template` function requires the entire app context to load, which in turn triggers any\n    function decorated with the `context_processor` decorator, which is not explicitly required for rendering purposes.\n    \"\"\"\n    return current_app.jinja_env.get_template(path).render(**context)\n"
  },
  {
    "path": "redash/utils/configuration.py",
    "content": "import copy\n\nimport jsonschema\nfrom jsonschema import ValidationError\nfrom sqlalchemy.ext.mutable import Mutable\n\nfrom redash.utils import json_dumps, json_loads\n\nSECRET_PLACEHOLDER = \"--------\"\n\n\nclass ConfigurationContainer(Mutable):\n    @classmethod\n    def coerce(cls, key, value):\n        if not isinstance(value, ConfigurationContainer):\n            if isinstance(value, dict):\n                return ConfigurationContainer(value)\n\n            # this call will raise ValueError\n            return Mutable.coerce(key, value)\n        else:\n            return value\n\n    def __init__(self, config, schema=None):\n        self._config = config\n        self.set_schema(schema)\n\n    def set_schema(self, schema):\n        configuration_schema = copy.deepcopy(schema)\n        if isinstance(configuration_schema, dict):\n            for prop in configuration_schema.get(\"properties\", {}).values():\n                if \"extendedEnum\" in prop:\n                    prop[\"enum\"] = [option[\"value\"] for option in prop[\"extendedEnum\"]]\n                    del prop[\"extendedEnum\"]\n        self._schema = configuration_schema\n\n    @property\n    def schema(self):\n        if self._schema is None:\n            raise RuntimeError(\"Schema missing.\")\n\n        return self._schema\n\n    def is_valid(self):\n        try:\n            self.validate()\n        except (ValidationError, ValueError):\n            return False\n\n        return True\n\n    def validate(self):\n        jsonschema.validate(self._config, self._schema)\n\n    def to_json(self):\n        return json_dumps(self._config, sort_keys=True)\n\n    def iteritems(self):\n        return self._config.items()\n\n    def to_dict(self, mask_secrets=False):\n        if mask_secrets is False or \"secret\" not in self.schema:\n            return self._config\n\n        config = self._config.copy()\n        for key in config:\n            if key in self.schema[\"secret\"]:\n                config[key] = SECRET_PLACEHOLDER\n\n        return config\n\n    def update(self, new_config):\n        jsonschema.validate(new_config, self.schema)\n\n        config = {}\n        for k, v in new_config.items():\n            if k in self.schema.get(\"secret\", []) and v == SECRET_PLACEHOLDER:\n                config[k] = self[k]\n            else:\n                config[k] = v\n\n        self._config = config\n        self.changed()\n\n    def get(self, *args, **kwargs):\n        return self._config.get(*args, **kwargs)\n\n    def __setitem__(self, key, value):\n        self._config[key] = value\n        self.changed()\n\n    def __getitem__(self, item):\n        if item in self._config:\n            return self._config[item]\n\n        raise KeyError(item)\n\n    def __contains__(self, item):\n        return item in self._config\n\n    @classmethod\n    def from_json(cls, config_in_json):\n        if config_in_json is None:\n            return cls({})\n        return cls(json_loads(config_in_json))\n"
  },
  {
    "path": "redash/utils/human_time.py",
    "content": "from datetime import datetime\nfrom time import mktime\n\nimport parsedatetime\n\ncal = parsedatetime.Calendar()\n\n\ndef parse_human_time(s):\n    time_struct, _ = cal.parse(s)\n    return datetime.fromtimestamp(mktime(time_struct))\n"
  },
  {
    "path": "redash/utils/pandas.py",
    "content": "import logging\nfrom importlib.util import find_spec\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n)\n\nlogger = logging.getLogger(__name__)\n\npandas_installed = find_spec(\"pandas\") and find_spec(\"numpy\")\n\nif pandas_installed:\n    import numpy as np\n    import pandas as pd\n\n    def get_column_types_from_dataframe(df: pd.DataFrame) -> list:\n        columns = []\n        for column_name, column_type in df.dtypes.items():\n            if column_type in (np.bool_,):\n                redash_type = TYPE_BOOLEAN\n            elif column_type in (np.int64, np.int32):\n                redash_type = TYPE_INTEGER\n            elif column_type in (np.float64,):\n                redash_type = TYPE_FLOAT\n            elif column_type in (np.datetime64, np.dtype(\"<M8[ns]\")):\n                if df.empty:\n                    redash_type = TYPE_DATETIME\n                elif len(df[column_name].head(1).astype(str).loc[0]) > 10:\n                    redash_type = TYPE_DATETIME\n                else:\n                    redash_type = TYPE_DATE\n            else:\n                redash_type = TYPE_STRING\n\n            columns.append({\"name\": column_name, \"friendly_name\": column_name, \"type\": redash_type})\n\n        return columns\n\n    def pandas_to_result(df: pd.DataFrame) -> dict:\n        columns = get_column_types_from_dataframe(df)\n        rows = df.to_dict(\"records\")\n        return {\"columns\": columns, \"rows\": rows}\n"
  },
  {
    "path": "redash/utils/query_order.py",
    "content": "# Copyright (c) 2012, Konsta Vesterinen\n#\n# All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions are met:\n#\n# * Redistributions of source code must retain the above copyright notice, this\n#   list of conditions and the following disclaimer.\n#\n# * Redistributions in binary form must reproduce the above copyright notice,\n#   this list of conditions and the following disclaimer in the documentation\n#   and/or other materials provided with the distribution.\n#\n# * The names of the contributors may not be used to endorse or promote products\n#   derived from this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT,\n# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nfrom inspect import isclass\n\nimport sqlalchemy as sa\nfrom sqlalchemy.orm import mapperlib\nfrom sqlalchemy.orm.properties import ColumnProperty\nfrom sqlalchemy.orm.query import _ColumnEntity\nfrom sqlalchemy.orm.util import AliasedInsp\nfrom sqlalchemy.sql.expression import asc, desc, nullslast\n\n\ndef get_query_descriptor(query, entity, attr):\n    if attr in query_labels(query):\n        return attr\n    else:\n        entity = get_query_entity_by_alias(query, entity)\n        if entity:\n            descriptor = get_descriptor(entity, attr)\n            if hasattr(descriptor, \"property\") and isinstance(descriptor.property, sa.orm.RelationshipProperty):\n                return\n            return descriptor\n\n\ndef query_labels(query):\n    \"\"\"\n    Return all labels for given SQLAlchemy query object.\n    Example::\n        query = session.query(\n            Category,\n            db.func.count(Article.id).label('articles')\n        )\n        query_labels(query)  # ['articles']\n    :param query: SQLAlchemy Query object\n    \"\"\"\n    return [\n        entity._label_name for entity in query._entities if isinstance(entity, _ColumnEntity) and entity._label_name\n    ]\n\n\ndef get_query_entity_by_alias(query, alias):\n    entities = get_query_entities(query)\n    if not alias:\n        return entities[0]\n    for entity in entities:\n        if isinstance(entity, sa.orm.util.AliasedClass):\n            name = sa.inspect(entity).name\n        else:\n            name = get_mapper(entity).tables[0].name\n        if name == alias:\n            return entity\n\n\ndef get_query_entities(query):\n    \"\"\"\n    Return a list of all entities present in given SQLAlchemy query object.\n    Examples::\n        from sqlalchemy_utils import get_query_entities\n        query = session.query(Category)\n        get_query_entities(query)  # [<Category>]\n        query = session.query(Category.id)\n        get_query_entities(query)  # [<Category>]\n    This function also supports queries with joins.\n    ::\n        query = session.query(Category).join(Article)\n        get_query_entities(query)  # [<Category>, <Article>]\n    .. versionchanged: 0.26.7\n        This function now returns a list instead of generator\n    :param query: SQLAlchemy Query object\n    \"\"\"\n    exprs = [\n        d[\"expr\"] if is_labeled_query(d[\"expr\"]) or isinstance(d[\"expr\"], sa.Column) else d[\"entity\"]\n        for d in query.column_descriptions\n    ]\n    return [get_query_entity(expr) for expr in exprs] + [get_query_entity(entity) for entity in query._join_entities]\n\n\ndef is_labeled_query(expr):\n    return isinstance(expr, sa.sql.elements.Label) and isinstance(\n        list(expr.base_columns)[0], (sa.sql.selectable.Select, sa.sql.selectable.ScalarSelect)\n    )\n\n\ndef get_query_entity(expr):\n    if isinstance(expr, sa.orm.attributes.InstrumentedAttribute):\n        return expr.parent.class_\n    elif isinstance(expr, sa.Column):\n        return expr.table\n    elif isinstance(expr, AliasedInsp):\n        return expr.entity\n    return expr\n\n\ndef get_mapper(mixed):\n    \"\"\"\n    Return related SQLAlchemy Mapper for given SQLAlchemy object.\n    :param mixed: SQLAlchemy Table / Alias / Mapper / declarative model object\n    ::\n        from sqlalchemy_utils import get_mapper\n        get_mapper(User)\n        get_mapper(User())\n        get_mapper(User.__table__)\n        get_mapper(User.__mapper__)\n        get_mapper(sa.orm.aliased(User))\n        get_mapper(sa.orm.aliased(User.__table__))\n    Raises:\n        ValueError: if multiple mappers were found for given argument\n    .. versionadded: 0.26.1\n    \"\"\"\n    if isinstance(mixed, sa.orm.query._MapperEntity):\n        mixed = mixed.expr\n    elif isinstance(mixed, sa.Column):\n        mixed = mixed.table\n    elif isinstance(mixed, sa.orm.query._ColumnEntity):\n        mixed = mixed.expr\n    if isinstance(mixed, sa.orm.Mapper):\n        return mixed\n    if isinstance(mixed, sa.orm.util.AliasedClass):\n        return sa.inspect(mixed).mapper\n    if isinstance(mixed, sa.sql.selectable.Alias):\n        mixed = mixed.element\n    if isinstance(mixed, AliasedInsp):\n        return mixed.mapper\n    if isinstance(mixed, sa.orm.attributes.InstrumentedAttribute):\n        mixed = mixed.class_\n    if isinstance(mixed, sa.Table):\n        mappers = [mapper for mapper in mapperlib._mapper_registry if mixed in mapper.tables]\n        if len(mappers) > 1:\n            raise ValueError(\"Multiple mappers found for table '%s'.\" % mixed.name)\n        elif not mappers:\n            raise ValueError(\"Could not get mapper for table '%s'.\" % mixed.name)\n        else:\n            return mappers[0]\n    if not isclass(mixed):\n        mixed = type(mixed)\n    return sa.inspect(mixed)\n\n\ndef get_polymorphic_mappers(mixed):\n    if isinstance(mixed, AliasedInsp):\n        return mixed.with_polymorphic_mappers\n    else:\n        return mixed.polymorphic_map.values()\n\n\ndef get_descriptor(entity, attr):\n    mapper = sa.inspect(entity)\n    for key, descriptor in get_all_descriptors(mapper).items():\n        if attr == key:\n            prop = descriptor.property if hasattr(descriptor, \"property\") else None\n            if isinstance(prop, ColumnProperty):\n                if isinstance(entity, sa.orm.util.AliasedClass):\n                    for c in mapper.selectable.c:\n                        if c.key == attr:\n                            return c\n                else:\n                    # If the property belongs to a class that uses\n                    # polymorphic inheritance we have to take into account\n                    # situations where the attribute exists in child class\n                    # but not in parent class.\n                    return getattr(prop.parent.class_, attr)\n            else:\n                # Handle synonyms, relationship properties and hybrid\n                # properties\n                if isinstance(entity, sa.orm.util.AliasedClass):\n                    return getattr(entity, attr)\n                try:\n                    return getattr(mapper.class_, attr)\n                except AttributeError:\n                    pass\n\n\ndef get_all_descriptors(expr):\n    if isinstance(expr, sa.sql.selectable.Selectable):\n        return expr.c\n    insp = sa.inspect(expr)\n    try:\n        polymorphic_mappers = get_polymorphic_mappers(insp)\n    except sa.exc.NoInspectionAvailable:\n        return get_mapper(expr).all_orm_descriptors\n    else:\n        attrs = dict(get_mapper(expr).all_orm_descriptors)\n        for submapper in polymorphic_mappers:\n            for key, descriptor in submapper.all_orm_descriptors.items():\n                if key not in attrs:\n                    attrs[key] = descriptor\n        return attrs\n\n\nclass QuerySorterException(Exception):\n    pass\n\n\nclass QuerySorter:\n    def __init__(self, silent=True, separator=\"-\"):\n        self.separator = separator\n        self.silent = silent\n\n    def assign_order_by(self, entity, attr, func):\n        expr = get_query_descriptor(self.query, entity, attr)\n        if expr is not None:\n            return self.query.order_by(nullslast(func(expr)))\n        if not self.silent:\n            raise QuerySorterException(\"Could not sort query with expression '%s'\" % attr)\n        return self.query\n\n    def parse_sort_arg(self, arg):\n        if arg[0] == self.separator:\n            func = desc\n            arg = arg[1:]\n        else:\n            func = asc\n        parts = arg.split(self.separator)\n        return {\n            \"entity\": parts[0] if len(parts) > 1 else None,\n            \"attr\": parts[1] if len(parts) > 1 else arg,\n            \"func\": func,\n        }\n\n    def __call__(self, query, *args):\n        self.query = query\n        for sort in args:\n            if not sort:\n                continue\n            self.query = self.assign_order_by(**self.parse_sort_arg(sort))\n        return self.query\n\n\ndef sort_query(query, *args, **kwargs):\n    \"\"\"\n    Applies an sql ORDER BY for given query. This function can be easily used\n    with user-defined sorting.\n    The examples use the following model definition:\n    ::\n        import sqlalchemy as sa\n        from sqlalchemy import create_engine\n        from sqlalchemy.orm import sessionmaker\n        from sqlalchemy.ext.declarative import declarative_base\n        from sqlalchemy_utils import sort_query\n        engine = create_engine(\n            'sqlite:///'\n        )\n        Base = declarative_base()\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        class Category(Base):\n            __tablename__ = 'category'\n            id = sa.Column(sa.Integer, primary_key=True)\n            name = sa.Column(sa.Unicode(255))\n        class Article(Base):\n            __tablename__ = 'article'\n            id = sa.Column(sa.Integer, primary_key=True)\n            name = sa.Column(sa.Unicode(255))\n            category_id = sa.Column(sa.Integer, sa.ForeignKey(Category.id))\n            category = sa.orm.relationship(\n                Category, primaryjoin=category_id == Category.id\n            )\n    1. Applying simple ascending sort\n    ::\n        query = session.query(Article)\n        query = sort_query(query, 'name')\n    2. Applying descending sort\n    ::\n        query = sort_query(query, '-name')\n    3. Applying sort to custom calculated label\n    ::\n        query = session.query(\n            Category, sa.func.count(Article.id).label('articles')\n        )\n        query = sort_query(query, 'articles')\n    4. Applying sort to joined table column\n    ::\n        query = session.query(Article).join(Article.category)\n        query = sort_query(query, 'category-name')\n    :param query:\n        query to be modified\n    :param sort:\n        string that defines the label or column to sort the query by\n    :param silent:\n        Whether or not to raise exceptions if unknown sort column\n        is passed. By default this is `True` indicating that no errors should\n        be raised for unknown columns.\n    \"\"\"\n    return QuerySorter(**kwargs)(query, *args)\n"
  },
  {
    "path": "redash/utils/requests_session.py",
    "content": "import warnings\n\nfrom redash import settings\n\nwith warnings.catch_warnings():\n    # Supress advocate warning below\n    #   /usr/local/lib/python3.13/site-packages/advocate/api.py:102: SyntaxWarning: invalid escape sequence '\\*'\n    #   server-1     |   :param \\*\\*kwargs: Optional arguments that ``request`` takes.\n    warnings.filterwarnings(\"ignore\", category=SyntaxWarning, module=r\".*advocate.*\")\n\n    from advocate.exceptions import UnacceptableAddressException  # noqa: F401, E402\n\n    if settings.ENFORCE_PRIVATE_ADDRESS_BLOCK:\n        import advocate as requests_or_advocate\n    else:\n        import requests as requests_or_advocate\n\n\nclass ConfiguredSession(requests_or_advocate.Session):\n    def request(self, *args, **kwargs):\n        if not settings.REQUESTS_ALLOW_REDIRECTS:\n            kwargs.update({\"allow_redirects\": False})\n        return super().request(*args, **kwargs)\n\n\nrequests_session = ConfiguredSession()\n"
  },
  {
    "path": "redash/utils/sentry.py",
    "content": "import os\n\nimport sentry_sdk\nfrom funcy import iffy\nfrom sentry_sdk.integrations.flask import FlaskIntegration\nfrom sentry_sdk.integrations.redis import RedisIntegration\nfrom sentry_sdk.integrations.rq import RqIntegration\nfrom sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration\n\nfrom redash import __version__, settings\n\nTRACES_SAMPLE_RATE = float(os.environ.get(\"SENTRY_TRACES_SAMPLE_RATE\", \"0.0\"))\n\nNON_REPORTED_EXCEPTIONS = [\"QueryExecutionError\"]\n\n\ndef before_send(event, hint):\n    if \"exc_info\" in hint:\n        exc_type, exc_value, tb = hint[\"exc_info\"]\n        if any([(e in str(type(exc_value))) for e in NON_REPORTED_EXCEPTIONS]):\n            return None\n\n    return event\n\n\ndef init():\n    if settings.SENTRY_DSN:\n        sentry_sdk.init(\n            dsn=settings.SENTRY_DSN,\n            environment=settings.SENTRY_ENVIRONMENT,\n            release=__version__,\n            before_send=before_send,\n            send_default_pii=True,\n            integrations=[\n                FlaskIntegration(),\n                SqlalchemyIntegration(),\n                RedisIntegration(),\n                RqIntegration(),\n            ],\n            traces_sample_rate=TRACES_SAMPLE_RATE,\n        )\n\n\ncapture_exception = iffy(lambda _: settings.SENTRY_DSN, sentry_sdk.capture_exception)\n"
  },
  {
    "path": "redash/version_check.py",
    "content": "import logging\n\nimport requests\nimport semver\n\nfrom redash import __version__ as current_version\nfrom redash import redis_connection\nfrom redash.models import Organization, db\n\nREDIS_KEY = \"new_version_available\"\n\n\ndef usage_data():\n    counts_query = \"\"\"\n    SELECT 'users_count' as name, count(0) as value\n    FROM users\n    WHERE disabled_at is null\n\n    UNION ALL\n\n    SELECT 'queries_count' as name, count(0) as value\n    FROM queries\n    WHERE is_archived is false\n\n    UNION ALL\n\n    SELECT 'alerts_count' as name, count(0) as value\n    FROM alerts\n\n    UNION ALL\n\n    SELECT 'dashboards_count' as name, count(0) as value\n    FROM dashboards\n    WHERE is_archived is false\n\n    UNION ALL\n\n    SELECT 'widgets_count' as name, count(0) as value\n    FROM widgets\n    WHERE visualization_id is not null\n\n    UNION ALL\n\n    SELECT 'textbox_count' as name, count(0) as value\n    FROM widgets\n    WHERE visualization_id is null\n    \"\"\"\n\n    data_sources_query = \"SELECT type, count(0) FROM data_sources GROUP by 1\"\n    visualizations_query = \"SELECT type, count(0) FROM visualizations GROUP by 1\"\n    destinations_query = \"SELECT type, count(0) FROM notification_destinations GROUP by 1\"\n\n    data = {name: value for (name, value) in db.session.execute(counts_query)}\n    data[\"data_sources\"] = {name: value for (name, value) in db.session.execute(data_sources_query)}\n    data[\"visualization_types\"] = {name: value for (name, value) in db.session.execute(visualizations_query)}\n    data[\"destination_types\"] = {name: value for (name, value) in db.session.execute(destinations_query)}\n\n    return data\n\n\ndef run_version_check():\n    logging.info(\"Performing version check.\")\n    logging.info(\"Current version: %s\", current_version)\n\n    data = {\"current_version\": current_version}\n\n    if Organization.query.first().get_setting(\"beacon_consent\"):\n        data[\"usage\"] = usage_data()\n\n    try:\n        response = requests.post(\n            \"https://version.redash.io/api/report?channel=stable\",\n            json=data,\n            timeout=3.0,\n        )\n        latest_version = response.json()[\"release\"][\"version\"]\n\n        _compare_and_update(latest_version)\n    except requests.RequestException:\n        logging.exception(\"Failed checking for new version.\")\n    except (ValueError, KeyError):\n        logging.exception(\"Failed checking for new version (probably bad/non-JSON response).\")\n\n\ndef reset_new_version_status():\n    latest_version = get_latest_version()\n    if latest_version:\n        _compare_and_update(latest_version)\n\n\ndef get_latest_version():\n    return redis_connection.get(REDIS_KEY)\n\n\ndef _compare_and_update(latest_version):\n    # TODO: support alpha channel (allow setting which channel to check & parse build number)\n    is_newer = semver.compare(current_version, latest_version) == -1\n    logging.info(\"Latest version: %s (newer: %s)\", latest_version, is_newer)\n\n    if is_newer:\n        redis_connection.set(REDIS_KEY, latest_version)\n    else:\n        redis_connection.delete(REDIS_KEY)\n"
  },
  {
    "path": "redash/worker.py",
    "content": "import logging\nfrom functools import partial\n\nfrom rq import get_current_job\nfrom rq.decorators import job as rq_job\n\nfrom redash import rq_redis_connection, settings\nfrom redash.tasks.worker import Queue as RedashQueue\n\ndefault_operational_queues = [\"periodic\", \"emails\", \"default\"]\ndefault_query_queues = [\"scheduled_queries\", \"queries\", \"schemas\"]\ndefault_queues = default_operational_queues + default_query_queues\n\n\nclass StatsdRecordingJobDecorator(rq_job):  # noqa\n    \"\"\"\n    RQ Job Decorator mixin that uses our Queue class to ensure metrics are accurately incremented in Statsd\n    \"\"\"\n\n    queue_class = RedashQueue\n\n\njob = partial(\n    StatsdRecordingJobDecorator, connection=rq_redis_connection, failure_ttl=settings.JOB_DEFAULT_FAILURE_TTL\n)\n\n\nclass CurrentJobFilter(logging.Filter):\n    def filter(self, record):\n        current_job = get_current_job()\n\n        record.job_id = current_job.id if current_job else \"\"\n        record.job_func_name = current_job.func_name if current_job else \"\"\n\n        return True\n\n\ndef get_job_logger(name):\n    logger = logging.getLogger(\"rq.job.\" + name)\n\n    handler = logging.StreamHandler()\n    handler.formatter = logging.Formatter(settings.RQ_WORKER_JOB_LOG_FORMAT)\n    handler.addFilter(CurrentJobFilter())\n\n    logger.addHandler(handler)\n    logger.propagate = False\n\n    return logger\n"
  },
  {
    "path": "redash/wsgi.py",
    "content": "from redash import create_app\n\napp = create_app()\n"
  },
  {
    "path": "scripts/README.md",
    "content": "You can use this folder to add scripts and configurations to customize Redash build and development loop.\n\n## How to customize Webpack\n\n### Configurable parameters\n\nYou can override the values of configurable parameters by exporting a `CONFIG` object from the module located at `scripts/config`.\n\nCurrently the following parameters are supported:\n\n- **staticPath**: Override the location of Redash static files (default = `/static/`).\n\n#### Example Configuration (`scripts/config.js`):\n\n```javascript\nmodule.exports = {\n  staticPath: \"my/redash/static/path\"\n};\n```\n\n### Programmatically\n\nFor advanced customization, you can provide a script to apply any kind of overrides to the default config as provided by `webpack.config.js`.\n\nThe override module must be located under `scripts/webpack/overrides`. It should export a `function` that receives the Webpack configuration object and returns the overridden version.\n\n#### Example Override Script (`scripts/webpack/overrides.js`):\n\nThis is an example of an override that enables Webpack stats.\n\n```javascript\nfunction applyOverrides(webpackConfig) {\n  return {\n    ...webpackConfig,\n    stats: {\n      children: true,\n      modules: true,\n      chunkModules: true\n    }\n  };\n}\n\nmodule.exports = applyOverrides;\n```\n"
  },
  {
    "path": "setup/README.md",
    "content": "# Setup script for Redash with Docker on Ubuntu 18.04.\n\nThe setup script moved to its own repository:\n\n[https://github.com/getredash/setup](https://github.com/getredash/setup)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "import datetime\nimport logging\nimport os\nfrom contextlib import contextmanager\nfrom unittest import TestCase\n\nos.environ[\"REDASH_REDIS_URL\"] = os.environ.get(\"REDASH_REDIS_URL\", \"redis://localhost:6379/0\").replace(\"/0\", \"/5\")\n# Use different url for RQ to avoid DB being cleaned up:\nos.environ[\"RQ_REDIS_URL\"] = os.environ.get(\"REDASH_REDIS_URL\", \"redis://localhost:6379/0\").replace(\"/5\", \"/6\")\n\n# Dummy values for oauth login\nos.environ[\"REDASH_GOOGLE_CLIENT_ID\"] = \"dummy\"\nos.environ[\"REDASH_GOOGLE_CLIENT_SECRET\"] = \"dummy\"\nos.environ[\"REDASH_MULTI_ORG\"] = \"true\"\n\n# Make sure rate limit is enabled\nos.environ[\"REDASH_RATELIMIT_ENABLED\"] = \"true\"\n\nos.environ[\"REDASH_ENFORCE_CSRF\"] = \"false\"\n\nfrom redash import limiter, redis_connection  # noqa: E402\nfrom redash.app import create_app  # noqa: E402\nfrom redash.models import db  # noqa: E402\nfrom redash.utils import json_dumps  # noqa: E402\nfrom tests.factories import Factory, user_factory  # noqa: E402\n\nlogging.disable(logging.INFO)\nlogging.getLogger(\"metrics\").setLevel(logging.ERROR)\n\n\ndef authenticate_request(c, user):\n    with c.session_transaction() as sess:\n        sess[\"_user_id\"] = user.get_id()\n\n\n@contextmanager\ndef authenticated_user(c, user=None):\n    if not user:\n        user = user_factory.create()\n        db.session.commit()\n    authenticate_request(c, user)\n\n    yield user\n\n\nclass BaseTestCase(TestCase):\n    def setUp(self):\n        self.app = create_app()\n        self.db = db\n        self.app.config[\"TESTING\"] = True\n        limiter.enabled = False\n        self.app_ctx = self.app.app_context()\n        self.app_ctx.push()\n        db.session.close()\n        db.drop_all()\n        db.create_all()\n        self.factory = Factory()\n        self.client = self.app.test_client()\n\n    def tearDown(self):\n        db.session.remove()\n        db.get_engine(self.app).dispose()\n        self.app_ctx.pop()\n        redis_connection.flushdb()\n\n    def make_request(\n        self,\n        method,\n        path,\n        org=None,\n        user=None,\n        data=None,\n        is_json=True,\n        follow_redirects=False,\n    ):\n        if user is None:\n            user = self.factory.user\n\n        if org is None:\n            org = self.factory.org\n\n        if org is not False:\n            path = \"/{}{}\".format(org.slug, path)\n\n        if user:\n            authenticate_request(self.client, user)\n\n        method_fn = getattr(self.client, method.lower())\n        headers = {}\n\n        if data and is_json:\n            data = json_dumps(data)\n\n        if is_json:\n            content_type = \"application/json\"\n        else:\n            content_type = None\n\n        response = method_fn(\n            path,\n            data=data,\n            headers=headers,\n            content_type=content_type,\n            follow_redirects=follow_redirects,\n        )\n        return response\n\n    def get_request(self, path, org=None, headers=None, client=None):\n        if org:\n            path = \"/{}{}\".format(org.slug, path)\n\n        if client is None:\n            client = self.client\n        return client.get(path, headers=headers)\n\n    def post_request(self, path, data=None, org=None, headers=None):\n        if org:\n            path = \"/{}{}\".format(org.slug, path)\n\n        return self.client.post(path, data=data, headers=headers)\n\n    def assertResponseEqual(self, expected, actual):\n        for k, v in expected.items():\n            if isinstance(v, datetime.datetime) or isinstance(actual[k], datetime.datetime):\n                continue\n\n            if isinstance(v, list):\n                continue\n\n            if isinstance(v, dict):\n                self.assertResponseEqual(v, actual[k])\n                continue\n\n            self.assertEqual(\n                v,\n                actual[k],\n                \"{} not equal (expected: {}, actual: {}).\".format(k, v, actual[k]),\n            )\n"
  },
  {
    "path": "tests/destinations/test_webhook.py",
    "content": "import json\nfrom unittest import mock\n\nfrom redash.destinations.webhook import Webhook\nfrom redash.models import Alert\n\n\ndef test_webhook_notify_handles_unicode():\n    # Create a mock alert with all the properties needed by serialize_alert\n    alert = mock.Mock()\n    alert.id = 1\n    alert.name = \"Test Alert\"\n    alert.custom_subject = \"Test Subject With Unicode: 晨\"\n    alert.custom_body = \"Test Body\"\n    alert.options = {}\n    alert.state = \"ok\"\n    alert.last_triggered_at = None\n    alert.updated_at = \"2025-12-02T08:00:00Z\"\n    alert.created_at = \"2025-12-02T08:00:00Z\"\n    alert.rearm = None\n    alert.query_id = 10\n    alert.user_id = 20\n\n    query = mock.Mock()\n    user = mock.Mock()\n    app = mock.Mock()\n    host = \"http://redash.local\"\n    options = {\"url\": \"https://example.com/webhook\", \"username\": \"user\", \"password\": \"password\"}\n    metadata = {}\n    new_state = Alert.TRIGGERED_STATE\n    destination = Webhook(options)\n\n    with mock.patch(\"redash.destinations.webhook.requests.post\") as mock_post:\n        mock_response = mock.Mock()\n        mock_response.status_code = 200\n        mock_post.return_value = mock_response\n\n        destination.notify(alert, query, user, new_state, app, host, metadata, options)\n\n        # Get the data passed to the mock\n        call_args, call_kwargs = mock_post.call_args\n        sent_data = call_kwargs[\"data\"]\n\n        # 1. Make sure we send bytes\n        assert isinstance(sent_data, bytes)\n\n        # 2. Make sure the bytes are the correct UTF-8 encoded JSON\n        decoded_data = json.loads(sent_data.decode(\"utf-8\"))\n        assert decoded_data[\"alert\"][\"title\"] == alert.custom_subject\n        assert \"Test Subject With Unicode: 晨\" in sent_data.decode(\"utf-8\")\n"
  },
  {
    "path": "tests/factories.py",
    "content": "from passlib.apps import custom_app_context as pwd_context\n\nimport redash.models\nfrom redash.models import db\nfrom redash.permissions import ACCESS_TYPE_MODIFY\nfrom redash.utils import gen_query_hash, utcnow\nfrom redash.utils.configuration import ConfigurationContainer\n\n\nclass ModelFactory:\n    def __init__(self, model, **kwargs):\n        self.model = model\n        self.kwargs = kwargs\n\n    def _get_kwargs(self, override_kwargs):\n        kwargs = self.kwargs.copy()\n        kwargs.update(override_kwargs)\n\n        for key, arg in kwargs.items():\n            if callable(arg):\n                kwargs[key] = arg()\n\n        return kwargs\n\n    def create(self, **override_kwargs):\n        kwargs = self._get_kwargs(override_kwargs)\n        obj = self.model(**kwargs)\n        db.session.add(obj)\n        db.session.commit()\n        return obj\n\n\nclass Sequence:\n    def __init__(self, string):\n        self.sequence = 0\n        self.string = string\n\n    def __call__(self):\n        self.sequence += 1\n\n        return self.string.format(self.sequence)\n\n\nuser_factory = ModelFactory(\n    redash.models.User,\n    name=\"John Doe\",\n    email=Sequence(\"test{}@example.com\"),\n    password_hash=pwd_context.hash(\"test1234\"),\n    group_ids=[2],\n    org_id=1,\n)\n\norg_factory = ModelFactory(\n    redash.models.Organization,\n    name=Sequence(\"Org {}\"),\n    slug=Sequence(\"org{}.example.com\"),\n    settings={},\n)\n\ndata_source_factory = ModelFactory(\n    redash.models.DataSource,\n    name=Sequence(\"Test {}\"),\n    type=\"pg\",\n    # If we don't use lambda here it will reuse the same options between tests:\n    options=lambda: ConfigurationContainer.from_json('{\"dbname\": \"test\"}'),\n    org_id=1,\n)\n\ndashboard_factory = ModelFactory(\n    redash.models.Dashboard,\n    name=\"test\",\n    user=user_factory.create,\n    layout=[],\n    is_draft=False,\n    org=1,\n)\n\napi_key_factory = ModelFactory(redash.models.ApiKey, object=dashboard_factory.create)\n\nquery_factory = ModelFactory(\n    redash.models.Query,\n    name=\"Query\",\n    description=\"\",\n    query_text=\"SELECT 1\",\n    user=user_factory.create,\n    is_archived=False,\n    is_draft=False,\n    schedule=None,\n    data_source=data_source_factory.create,\n    org_id=1,\n)\n\nquery_with_params_factory = ModelFactory(\n    redash.models.Query,\n    name=\"New Query with Params\",\n    description=\"\",\n    query_text=\"SELECT {{param1}}\",\n    user=user_factory.create,\n    is_archived=False,\n    is_draft=False,\n    schedule={},\n    data_source=data_source_factory.create,\n    org_id=1,\n)\n\naccess_permission_factory = ModelFactory(\n    redash.models.AccessPermission,\n    object_id=query_factory.create,\n    object_type=redash.models.Query.__name__,\n    access_type=ACCESS_TYPE_MODIFY,\n    grantor=user_factory.create,\n    grantee=user_factory.create,\n)\n\nalert_factory = ModelFactory(\n    redash.models.Alert,\n    name=Sequence(\"Alert {}\"),\n    query_rel=query_factory.create,\n    user=user_factory.create,\n    options={},\n)\n\nquery_result_factory = ModelFactory(\n    redash.models.QueryResult,\n    data={\"columns\": {}, \"rows\": []},\n    runtime=1,\n    retrieved_at=utcnow,\n    query_text=\"SELECT 1\",\n    query_hash=gen_query_hash(\"SELECT 1\"),\n    data_source=data_source_factory.create,\n    org_id=1,\n)\n\nvisualization_factory = ModelFactory(\n    redash.models.Visualization,\n    type=\"CHART\",\n    query_rel=query_factory.create,\n    name=\"Chart\",\n    description=\"\",\n    options={},\n)\n\nwidget_factory = ModelFactory(\n    redash.models.Widget,\n    width=1,\n    options={},\n    dashboard=dashboard_factory.create,\n    visualization=visualization_factory.create,\n)\n\ndestination_factory = ModelFactory(\n    redash.models.NotificationDestination,\n    org_id=1,\n    user=user_factory.create,\n    name=Sequence(\"Destination {}\"),\n    type=\"slack\",\n    options=lambda: ConfigurationContainer.from_json('{\"url\": \"https://www.slack.com\"}'),\n)\n\nalert_subscription_factory = ModelFactory(\n    redash.models.AlertSubscription,\n    user=user_factory.create,\n    destination=destination_factory.create,\n    alert=alert_factory.create,\n)\n\nquery_snippet_factory = ModelFactory(\n    redash.models.QuerySnippet,\n    trigger=Sequence(\"trigger {}\"),\n    description=\"description\",\n    snippet=\"snippet\",\n)\n\n\nclass Factory:\n    def __init__(self):\n        self.org, self.admin_group, self.default_group = redash.models.init_db()\n        self._data_source = None\n        self._user = None\n\n    @property\n    def user(self):\n        if self._user is None:\n            self._user = self.create_user()\n            # Test setup creates users, they need to be in the db by the time\n            # the handler's db transaction starts.\n            db.session.commit()\n        return self._user\n\n    @property\n    def data_source(self):\n        if self._data_source is None:\n            self._data_source = data_source_factory.create(org=self.org)\n            db.session.add(redash.models.DataSourceGroup(group=self.default_group, data_source=self._data_source))\n\n        return self._data_source\n\n    def create_org(self, **kwargs):\n        org = org_factory.create(**kwargs)\n        self.create_group(org=org, type=redash.models.Group.BUILTIN_GROUP, name=\"default\")\n        self.create_group(\n            org=org,\n            type=redash.models.Group.BUILTIN_GROUP,\n            name=\"admin\",\n            permissions=[\"admin\"],\n        )\n\n        return org\n\n    def create_user(self, **kwargs):\n        args = {\"org\": self.org, \"group_ids\": [self.default_group.id]}\n\n        if \"org\" in kwargs:\n            args[\"group_ids\"] = [kwargs[\"org\"].default_group.id]\n\n        args.update(kwargs)\n        return user_factory.create(**args)\n\n    def create_admin(self, **kwargs):\n        args = {\n            \"org\": self.org,\n            \"group_ids\": [self.admin_group.id, self.default_group.id],\n        }\n\n        if \"org\" in kwargs:\n            args[\"group_ids\"] = [\n                kwargs[\"org\"].default_group.id,\n                kwargs[\"org\"].admin_group.id,\n            ]\n\n        args.update(kwargs)\n        return user_factory.create(**args)\n\n    def create_group(self, **kwargs):\n        args = {\"name\": \"Group\", \"org\": self.org}\n\n        args.update(kwargs)\n\n        g = redash.models.Group(**args)\n        return g\n\n    def create_alert(self, **kwargs):\n        args = {\"user\": self.user, \"query_rel\": self.create_query()}\n\n        args.update(**kwargs)\n        return alert_factory.create(**args)\n\n    def create_alert_subscription(self, **kwargs):\n        args = {\"user\": self.user, \"alert\": self.create_alert()}\n\n        args.update(**kwargs)\n        return alert_subscription_factory.create(**args)\n\n    def create_data_source(self, **kwargs):\n        group = None\n        if \"group\" in kwargs:\n            group = kwargs.pop(\"group\")\n        args = {\"org\": self.org}\n        args.update(kwargs)\n\n        if group and \"org\" not in kwargs:\n            args[\"org\"] = group.org\n\n        view_only = args.pop(\"view_only\", False)\n        data_source = data_source_factory.create(**args)\n\n        if group:\n            db.session.add(redash.models.DataSourceGroup(group=group, data_source=data_source, view_only=view_only))\n\n        return data_source\n\n    def create_dashboard(self, **kwargs):\n        args = {\"user\": self.user, \"org\": self.org}\n        args.update(kwargs)\n        return dashboard_factory.create(**args)\n\n    def create_query(self, **kwargs):\n        args = {\"user\": self.user, \"data_source\": self.data_source, \"org\": self.org}\n        args.update(kwargs)\n        return query_factory.create(**args)\n\n    def create_query_with_params(self, **kwargs):\n        args = {\"user\": self.user, \"data_source\": self.data_source, \"org\": self.org}\n        args.update(kwargs)\n        return query_with_params_factory.create(**args)\n\n    def create_access_permission(self, **kwargs):\n        args = {\"grantor\": self.user}\n        args.update(kwargs)\n        return access_permission_factory.create(**args)\n\n    def create_query_result(self, **kwargs):\n        args = {\"data_source\": self.data_source}\n\n        args.update(kwargs)\n\n        if \"data_source\" in args and \"org\" not in args:\n            args[\"org\"] = args[\"data_source\"].org\n\n        return query_result_factory.create(**args)\n\n    def create_visualization(self, **kwargs):\n        args = {\"query_rel\": self.create_query()}\n        args.update(kwargs)\n        return visualization_factory.create(**args)\n\n    def create_visualization_with_params(self, **kwargs):\n        args = {\"query_rel\": self.create_query_with_params()}\n        args.update(kwargs)\n        return visualization_factory.create(**args)\n\n    def create_widget(self, **kwargs):\n        args = {\n            \"dashboard\": self.create_dashboard(),\n            \"visualization\": self.create_visualization(),\n        }\n        args.update(kwargs)\n        return widget_factory.create(**args)\n\n    def create_api_key(self, **kwargs):\n        args = {\"org\": self.org}\n        args.update(kwargs)\n        return api_key_factory.create(**args)\n\n    def create_destination(self, **kwargs):\n        args = {\"org\": self.org}\n        args.update(kwargs)\n        return destination_factory.create(**args)\n\n    def create_query_snippet(self, **kwargs):\n        args = {\"user\": self.user, \"org\": self.org}\n        args.update(kwargs)\n        return query_snippet_factory.create(**args)\n"
  },
  {
    "path": "tests/handlers/__init__.py",
    "content": ""
  },
  {
    "path": "tests/handlers/test_alerts.py",
    "content": "import datetime\n\nfrom mock import patch\n\nfrom redash.models import Alert, AlertSubscription, db\nfrom redash.utils import utcnow\nfrom tests import BaseTestCase\n\n\nclass TestAlertResourceGet(BaseTestCase):\n    def test_returns_200_if_allowed(self):\n        alert = self.factory.create_alert()\n\n        rv = self.make_request(\"get\", \"/api/alerts/{}\".format(alert.id))\n        self.assertEqual(rv.status_code, 200)\n\n    def test_returns_403_if_not_allowed(self):\n        data_source = self.factory.create_data_source(group=self.factory.create_group())\n        query = self.factory.create_query(data_source=data_source)\n        alert = self.factory.create_alert(query_rel=query)\n        db.session.commit()\n        rv = self.make_request(\"get\", \"/api/alerts/{}\".format(alert.id))\n        self.assertEqual(rv.status_code, 403)\n\n    def test_returns_404_if_admin_from_another_org(self):\n        second_org = self.factory.create_org()\n        second_org_admin = self.factory.create_admin(org=second_org)\n\n        alert = self.factory.create_alert()\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/alerts/{}\".format(alert.id),\n            org=second_org,\n            user=second_org_admin,\n        )\n        self.assertEqual(rv.status_code, 404)\n\n\nclass TestAlertResourcePost(BaseTestCase):\n    def test_updates_alert(self):\n        alert = self.factory.create_alert()\n        rv = self.make_request(\"post\", \"/api/alerts/{}\".format(alert.id), data={\"name\": \"Testing\"})\n        self.assertEqual(rv.status_code, 200)\n\n\nclass TestAlertEvaluateResource(BaseTestCase):\n    @patch(\"redash.handlers.alerts.notify_subscriptions\")\n    def test_evaluates_alert_and_notifies(self, mock_notify_subscriptions):\n        query = self.factory.create_query(\n            data_source=self.factory.create_data_source(group=self.factory.create_group())\n        )\n        retrieved_at = utcnow() - datetime.timedelta(days=1)\n        query_result = self.factory.create_query_result(\n            retrieved_at=retrieved_at,\n            query_text=query.query_text,\n            query_hash=query.query_hash,\n        )\n        query.latest_query_data = query_result\n        alert = self.factory.create_alert(query_rel=query)\n        rv = self.make_request(\"post\", \"/api/alerts/{}/eval\".format(alert.id))\n\n        self.assertEqual(rv.status_code, 200)\n        mock_notify_subscriptions.assert_called()\n\n\nclass TestAlertResourceDelete(BaseTestCase):\n    def test_removes_alert_and_subscriptions(self):\n        subscription = self.factory.create_alert_subscription()\n        alert = subscription.alert\n        db.session.commit()\n        rv = self.make_request(\"delete\", \"/api/alerts/{}\".format(alert.id))\n        self.assertEqual(rv.status_code, 200)\n\n        self.assertEqual(Alert.query.get(subscription.alert.id), None)\n        self.assertEqual(AlertSubscription.query.get(subscription.id), None)\n\n    def test_returns_403_if_not_allowed(self):\n        alert = self.factory.create_alert()\n\n        user = self.factory.create_user()\n        rv = self.make_request(\"delete\", \"/api/alerts/{}\".format(alert.id), user=user)\n        self.assertEqual(rv.status_code, 403)\n\n        rv = self.make_request(\n            \"delete\",\n            \"/api/alerts/{}\".format(alert.id),\n            user=self.factory.create_admin(),\n        )\n        self.assertEqual(rv.status_code, 200)\n\n    def test_returns_404_for_unauthorized_users(self):\n        alert = self.factory.create_alert()\n\n        second_org = self.factory.create_org()\n        second_org_admin = self.factory.create_admin(org=second_org)\n        rv = self.make_request(\"delete\", \"/api/alerts/{}\".format(alert.id), user=second_org_admin)\n        self.assertEqual(rv.status_code, 404)\n\n\nclass TestAlertListGet(BaseTestCase):\n    def test_returns_all_alerts(self):\n        alert = self.factory.create_alert()\n        rv = self.make_request(\"get\", \"/api/alerts\")\n\n        self.assertEqual(rv.status_code, 200)\n\n        alert_ids = [a[\"id\"] for a in rv.json]\n        self.assertIn(alert.id, alert_ids)\n\n    def test_returns_alerts_only_from_users_groups(self):\n        alert = self.factory.create_alert()\n        query = self.factory.create_query(\n            data_source=self.factory.create_data_source(group=self.factory.create_group())\n        )\n        alert2 = self.factory.create_alert(query_rel=query)\n        rv = self.make_request(\"get\", \"/api/alerts\")\n\n        self.assertEqual(rv.status_code, 200)\n\n        alert_ids = [a[\"id\"] for a in rv.json]\n        self.assertIn(alert.id, alert_ids)\n        self.assertNotIn(alert2.id, alert_ids)\n\n\nclass TestAlertListPost(BaseTestCase):\n    def test_returns_200_if_has_access_to_query(self):\n        query = self.factory.create_query()\n        destination = self.factory.create_destination()\n        db.session.commit()\n        rv = self.make_request(\n            \"post\",\n            \"/api/alerts\",\n            data=dict(\n                name=\"Alert\",\n                query_id=query.id,\n                destination_id=destination.id,\n                options={},\n                rearm=100,\n            ),\n        )\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"rearm\"], 100)\n\n    def test_fails_if_doesnt_have_access_to_query(self):\n        data_source = self.factory.create_data_source(group=self.factory.create_group())\n        query = self.factory.create_query(data_source=data_source)\n        destination = self.factory.create_destination()\n        db.session.commit()\n        rv = self.make_request(\n            \"post\",\n            \"/api/alerts\",\n            data=dict(\n                name=\"Alert\",\n                query_id=query.id,\n                destination_id=destination.id,\n                options={},\n            ),\n        )\n        self.assertEqual(rv.status_code, 403)\n\n\nclass TestAlertSubscriptionListResourcePost(BaseTestCase):\n    def test_subscribers_user_to_alert(self):\n        alert = self.factory.create_alert()\n        destination = self.factory.create_destination()\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/alerts/{}/subscriptions\".format(alert.id),\n            data=dict(destination_id=destination.id),\n        )\n        self.assertEqual(rv.status_code, 200)\n        self.assertIn(self.factory.user, alert.subscribers())\n\n    def test_doesnt_subscribers_user_to_alert_without_access(self):\n        data_source = self.factory.create_data_source(group=self.factory.create_group())\n        query = self.factory.create_query(data_source=data_source)\n        alert = self.factory.create_alert(query_rel=query)\n        destination = self.factory.create_destination()\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/alerts/{}/subscriptions\".format(alert.id),\n            data=dict(destination_id=destination.id),\n        )\n        self.assertEqual(rv.status_code, 403)\n        self.assertNotIn(self.factory.user, alert.subscribers())\n\n\nclass TestAlertSubscriptionListResourceGet(BaseTestCase):\n    def test_returns_subscribers(self):\n        alert = self.factory.create_alert()\n\n        rv = self.make_request(\"get\", \"/api/alerts/{}/subscriptions\".format(alert.id))\n        self.assertEqual(rv.status_code, 200)\n\n    def test_doesnt_return_subscribers_when_not_allowed(self):\n        data_source = self.factory.create_data_source(group=self.factory.create_group())\n        query = self.factory.create_query(data_source=data_source)\n        alert = self.factory.create_alert(query_rel=query)\n\n        rv = self.make_request(\"get\", \"/api/alerts/{}/subscriptions\".format(alert.id))\n        self.assertEqual(rv.status_code, 403)\n\n\nclass TestAlertSubscriptionresourceDelete(BaseTestCase):\n    def test_only_subscriber_or_admin_can_unsubscribe(self):\n        subscription = self.factory.create_alert_subscription()\n        alert = subscription.alert\n        user = subscription.user\n        path = \"/api/alerts/{}/subscriptions/{}\".format(alert.id, subscription.id)\n\n        other_user = self.factory.create_user()\n\n        response = self.make_request(\"delete\", path, user=other_user)\n        self.assertEqual(response.status_code, 403)\n\n        response = self.make_request(\"delete\", path, user=user)\n        self.assertEqual(response.status_code, 200)\n\n        subscription_two = AlertSubscription(alert=alert, user=other_user)\n        admin_user = self.factory.create_admin()\n        db.session.add_all([subscription_two, admin_user])\n        db.session.commit()\n        path = \"/api/alerts/{}/subscriptions/{}\".format(alert.id, subscription_two.id)\n        response = self.make_request(\"delete\", path, user=admin_user)\n        self.assertEqual(response.status_code, 200)\n"
  },
  {
    "path": "tests/handlers/test_authentication.py",
    "content": "import time\n\nimport mock\n\nfrom redash import limiter, settings\nfrom redash.authentication.account import invite_token\nfrom redash.models import User\nfrom tests import BaseTestCase\n\n\nclass TestResetPassword(BaseTestCase):\n    def test_shows_reset_password_form(self):\n        user = self.factory.create_user(is_invitation_pending=False)\n        token = invite_token(user)\n        response = self.get_request(\"/reset/{}\".format(token), org=self.factory.org)\n        self.assertEqual(response.status_code, 200)\n\n\nclass TestInvite(BaseTestCase):\n    def test_expired_invite_token(self):\n        with mock.patch(\"time.time\") as patched_time:\n            patched_time.return_value = time.time() - (7 * 24 * 3600) - 10\n            token = invite_token(self.factory.user)\n\n        response = self.get_request(\"/invite/{}\".format(token), org=self.factory.org)\n        self.assertEqual(response.status_code, 400)\n\n    def test_invalid_invite_token(self):\n        response = self.get_request(\"/invite/badtoken\", org=self.factory.org)\n        self.assertEqual(response.status_code, 400)\n\n    def test_valid_token(self):\n        user = self.factory.create_user(is_invitation_pending=True)\n        token = invite_token(user)\n        response = self.get_request(\"/invite/{}\".format(token), org=self.factory.org)\n        self.assertEqual(response.status_code, 200)\n\n    def test_already_active_user(self):\n        token = invite_token(self.factory.user)\n        self.post_request(\n            \"/invite/{}\".format(token),\n            data={\"password\": \"test1234\"},\n            org=self.factory.org,\n        )\n        response = self.get_request(\"/invite/{}\".format(token), org=self.factory.org)\n        self.assertEqual(response.status_code, 400)\n\n\nclass TestInvitePost(BaseTestCase):\n    def test_empty_password(self):\n        token = invite_token(self.factory.user)\n        response = self.post_request(\"/invite/{}\".format(token), data={\"password\": \"\"}, org=self.factory.org)\n        self.assertEqual(response.status_code, 400)\n\n    def test_invalid_password(self):\n        token = invite_token(self.factory.user)\n        response = self.post_request(\"/invite/{}\".format(token), data={\"password\": \"1234\"}, org=self.factory.org)\n        self.assertEqual(response.status_code, 400)\n\n    def test_bad_token(self):\n        response = self.post_request(\n            \"/invite/{}\".format(\"jdsnfkjdsnfkj\"),\n            data={\"password\": \"1234\"},\n            org=self.factory.org,\n        )\n        self.assertEqual(response.status_code, 400)\n\n    def test_user_invited_before_invitation_pending_check(self):\n        user = self.factory.create_user(details={})\n        token = invite_token(user)\n        response = self.post_request(\n            \"/invite/{}\".format(token),\n            data={\"password\": \"test1234\"},\n            org=self.factory.org,\n        )\n        self.assertEqual(response.status_code, 302)\n\n    def test_already_active_user(self):\n        token = invite_token(self.factory.user)\n        self.post_request(\n            \"/invite/{}\".format(token),\n            data={\"password\": \"test1234\"},\n            org=self.factory.org,\n        )\n        response = self.post_request(\n            \"/invite/{}\".format(token),\n            data={\"password\": \"test1234\"},\n            org=self.factory.org,\n        )\n        self.assertEqual(response.status_code, 400)\n\n    def test_valid_password(self):\n        user = self.factory.create_user(is_invitation_pending=True)\n        token = invite_token(user)\n        password = \"test1234\"\n        response = self.post_request(\n            \"/invite/{}\".format(token),\n            data={\"password\": password},\n            org=self.factory.org,\n        )\n        self.assertEqual(response.status_code, 302)\n        user = User.query.get(user.id)\n        self.assertTrue(user.verify_password(password))\n        self.assertFalse(user.is_invitation_pending)\n\n\nclass TestLogin(BaseTestCase):\n    def test_throttle_login(self):\n        limiter.enabled = True\n        # Extract the limit from settings (ex: '50/day')\n        limit = settings.THROTTLE_LOGIN_PATTERN.split(\"/\")[0]\n        for _ in range(0, int(limit)):\n            self.get_request(\"/login\", org=self.factory.org)\n\n        response = self.get_request(\"/login\", org=self.factory.org)\n        self.assertEqual(response.status_code, 429)\n\n    def test_throttle_password_reset(self):\n        limiter.enabled = True\n        # Extract the limit from settings (ex: '10/hour')\n        limit = settings.THROTTLE_PASS_RESET_PATTERN.split(\"/\")[0]\n        for _ in range(0, int(limit)):\n            self.get_request(\"/forgot\", org=self.factory.org)\n\n        response = self.get_request(\"/forgot\", org=self.factory.org)\n        self.assertEqual(response.status_code, 429)\n\n\nclass TestSession(BaseTestCase):\n    # really simple test just to trigger this route\n    def test_get(self):\n        self.make_request(\"get\", \"/default/api/session\", user=self.factory.user, org=False)\n"
  },
  {
    "path": "tests/handlers/test_dashboards.py",
    "content": "from redash.models import AccessPermission, ApiKey, Dashboard, db\nfrom redash.permissions import ACCESS_TYPE_MODIFY\nfrom redash.serializers import serialize_dashboard\nfrom redash.utils import json_loads\nfrom tests import BaseTestCase\n\n\nclass TestDashboardListResource(BaseTestCase):\n    def test_create_new_dashboard(self):\n        dashboard_name = \"Test Dashboard\"\n        rv = self.make_request(\"post\", \"/api/dashboards\", data={\"name\": dashboard_name})\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"name\"], \"Test Dashboard\")\n        self.assertEqual(rv.json[\"user_id\"], self.factory.user.id)\n        self.assertEqual(rv.json[\"layout\"], [])\n\n\nclass TestDashboardListGetResource(BaseTestCase):\n    def test_returns_dashboards(self):\n        d1 = self.factory.create_dashboard()\n        d2 = self.factory.create_dashboard()\n        d3 = self.factory.create_dashboard()\n\n        rv = self.make_request(\"get\", \"/api/dashboards\")\n\n        assert len(rv.json[\"results\"]) == 3\n        assert set([result[\"id\"] for result in rv.json[\"results\"]]) == set([d1.id, d2.id, d3.id])\n\n    def test_filters_with_tags(self):\n        d1 = self.factory.create_dashboard(tags=[\"test\"])\n        self.factory.create_dashboard()\n        self.factory.create_dashboard()\n\n        rv = self.make_request(\"get\", \"/api/dashboards?tags=test\")\n        assert len(rv.json[\"results\"]) == 1\n        assert set([result[\"id\"] for result in rv.json[\"results\"]]) == set([d1.id])\n\n    def test_search_term(self):\n        d1 = self.factory.create_dashboard(name=\"Sales\")\n        d2 = self.factory.create_dashboard(name=\"Q1 sales\")\n        self.factory.create_dashboard(name=\"Ops\")\n\n        rv = self.make_request(\"get\", \"/api/dashboards?q=sales\")\n        assert len(rv.json[\"results\"]) == 2\n        assert set([result[\"id\"] for result in rv.json[\"results\"]]) == set([d1.id, d2.id])\n\n\nclass TestDashboardResourceGet(BaseTestCase):\n    def test_get_dashboard(self):\n        d1 = self.factory.create_dashboard()\n        rv = self.make_request(\"get\", \"/api/dashboards/{0}\".format(d1.id))\n        self.assertEqual(rv.status_code, 200)\n\n        expected = serialize_dashboard(d1, with_widgets=True, with_favorite_state=False)\n        actual = json_loads(rv.data)\n\n        self.assertResponseEqual(expected, actual)\n\n    def test_get_dashboard_with_slug(self):\n        d1 = self.factory.create_dashboard()\n        rv = self.make_request(\"get\", \"/api/dashboards/{0}?legacy\".format(d1.slug))\n        self.assertEqual(rv.status_code, 200)\n\n        expected = serialize_dashboard(d1, with_widgets=True, with_favorite_state=False)\n        actual = json_loads(rv.data)\n\n        self.assertResponseEqual(expected, actual)\n\n    def test_get_dashboard_filters_unauthorized_widgets(self):\n        dashboard = self.factory.create_dashboard()\n\n        restricted_ds = self.factory.create_data_source(group=self.factory.create_group())\n        query = self.factory.create_query(data_source=restricted_ds)\n        vis = self.factory.create_visualization(query_rel=query)\n        restricted_widget = self.factory.create_widget(visualization=vis, dashboard=dashboard)\n        widget = self.factory.create_widget(dashboard=dashboard)\n        dashboard.layout = [[widget.id, restricted_widget.id]]\n        db.session.commit()\n\n        rv = self.make_request(\"get\", \"/api/dashboards/{0}\".format(dashboard.id))\n        self.assertEqual(rv.status_code, 200)\n        self.assertTrue(rv.json[\"widgets\"][0][\"restricted\"])\n        self.assertNotIn(\"restricted\", rv.json[\"widgets\"][1])\n\n    def test_get_non_existing_dashboard(self):\n        rv = self.make_request(\"get\", \"/api/dashboards/-1\")\n        self.assertEqual(rv.status_code, 404)\n\n\nclass TestDashboardResourcePost(BaseTestCase):\n    def test_update_dashboard(self):\n        d = self.factory.create_dashboard()\n        new_name = \"New Name\"\n        rv = self.make_request(\n            \"post\",\n            \"/api/dashboards/{0}\".format(d.id),\n            data={\"name\": new_name, \"layout\": []},\n        )\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"name\"], new_name)\n\n    def test_raises_error_in_case_of_conflict(self):\n        d = self.factory.create_dashboard()\n        d.name = \"Updated\"\n        db.session.commit()\n        new_name = \"New Name\"\n        rv = self.make_request(\n            \"post\",\n            \"/api/dashboards/{0}\".format(d.id),\n            data={\"name\": new_name, \"layout\": [], \"version\": d.version - 1},\n        )\n\n        self.assertEqual(rv.status_code, 409)\n\n    def test_overrides_existing_if_no_version_specified(self):\n        d = self.factory.create_dashboard()\n        d.name = \"Updated\"\n\n        new_name = \"New Name\"\n        rv = self.make_request(\n            \"post\",\n            \"/api/dashboards/{0}\".format(d.id),\n            data={\"name\": new_name, \"layout\": []},\n        )\n\n        self.assertEqual(rv.status_code, 200)\n\n    def test_works_for_non_owner_with_permission(self):\n        d = self.factory.create_dashboard()\n        user = self.factory.create_user()\n\n        new_name = \"New Name\"\n        rv = self.make_request(\n            \"post\",\n            \"/api/dashboards/{0}\".format(d.id),\n            data={\"name\": new_name, \"layout\": [], \"version\": d.version},\n            user=user,\n        )\n        self.assertEqual(rv.status_code, 403)\n\n        AccessPermission.grant(obj=d, access_type=ACCESS_TYPE_MODIFY, grantee=user, grantor=d.user)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/dashboards/{0}\".format(d.id),\n            data={\"name\": new_name, \"layout\": [], \"version\": d.version},\n            user=user,\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"name\"], new_name)\n\n\nclass TestDashboardForkResourcePost(BaseTestCase):\n    def test_forks_a_dashboard(self):\n        dashboard = self.factory.create_dashboard()\n\n        rv = self.make_request(\"post\", \"/api/dashboards/{}/fork\".format(dashboard.id))\n\n        self.assertEqual(rv.status_code, 200)\n\n\nclass TestDashboardResourceDelete(BaseTestCase):\n    def test_delete_dashboard(self):\n        d = self.factory.create_dashboard()\n\n        rv = self.make_request(\"delete\", \"/api/dashboards/{0}\".format(d.id))\n        self.assertEqual(rv.status_code, 200)\n\n        d = Dashboard.get_by_id_and_org(d.id, d.org)\n        self.assertTrue(d.is_archived)\n\n\nclass TestDashboardShareResourcePost(BaseTestCase):\n    def test_creates_api_key(self):\n        dashboard = self.factory.create_dashboard()\n\n        res = self.make_request(\"post\", \"/api/dashboards/{}/share\".format(dashboard.id))\n        self.assertEqual(res.status_code, 200)\n        self.assertEqual(res.json[\"api_key\"], ApiKey.get_by_object(dashboard).api_key)\n\n    def test_requires_admin_or_owner(self):\n        dashboard = self.factory.create_dashboard()\n        user = self.factory.create_user()\n\n        res = self.make_request(\"post\", \"/api/dashboards/{}/share\".format(dashboard.id), user=user)\n        self.assertEqual(res.status_code, 403)\n\n        user.group_ids.append(self.factory.org.admin_group.id)\n\n        res = self.make_request(\"post\", \"/api/dashboards/{}/share\".format(dashboard.id), user=user)\n        self.assertEqual(res.status_code, 200)\n\n\nclass TestDashboardShareResourceDelete(BaseTestCase):\n    def test_disables_api_key(self):\n        dashboard = self.factory.create_dashboard()\n        ApiKey.create_for_object(dashboard, self.factory.user)\n\n        res = self.make_request(\"delete\", \"/api/dashboards/{}/share\".format(dashboard.id))\n        self.assertEqual(res.status_code, 200)\n        self.assertIsNone(ApiKey.get_by_object(dashboard))\n\n    def test_ignores_when_no_api_key_exists(self):\n        dashboard = self.factory.create_dashboard()\n\n        res = self.make_request(\"delete\", \"/api/dashboards/{}/share\".format(dashboard.id))\n        self.assertEqual(res.status_code, 200)\n\n    def test_requires_admin_or_owner(self):\n        dashboard = self.factory.create_dashboard()\n        user = self.factory.create_user()\n\n        res = self.make_request(\"delete\", \"/api/dashboards/{}/share\".format(dashboard.id), user=user)\n        self.assertEqual(res.status_code, 403)\n\n        user.group_ids.append(self.factory.org.admin_group.id)\n\n        res = self.make_request(\"delete\", \"/api/dashboards/{}/share\".format(dashboard.id), user=user)\n        self.assertEqual(res.status_code, 200)\n"
  },
  {
    "path": "tests/handlers/test_data_sources.py",
    "content": "from funcy import pairwise\n\nfrom redash.models import DataSource\nfrom tests import BaseTestCase\n\n\nclass TestDataSourceGetSchema(BaseTestCase):\n    def test_fails_if_user_doesnt_belong_to_org(self):\n        other_user = self.factory.create_user(org=self.factory.create_org())\n        response = self.make_request(\n            \"get\",\n            \"/api/data_sources/{}/schema\".format(self.factory.data_source.id),\n            user=other_user,\n        )\n        self.assertEqual(response.status_code, 404)\n\n        other_admin = self.factory.create_admin(org=self.factory.create_org())\n        response = self.make_request(\n            \"get\",\n            \"/api/data_sources/{}/schema\".format(self.factory.data_source.id),\n            user=other_admin,\n        )\n        self.assertEqual(response.status_code, 404)\n\n\nclass TestDataSourceListGet(BaseTestCase):\n    def test_returns_each_data_source_once(self):\n        group = self.factory.create_group()\n        self.factory.user.group_ids.append(group.id)\n        self.factory.data_source.add_group(group)\n        self.factory.data_source.add_group(self.factory.org.default_group)\n        response = self.make_request(\"get\", \"/api/data_sources\", user=self.factory.user)\n\n        self.assertEqual(len(response.json), 1)\n\n    def test_returns_data_sources_ordered_by_id(self):\n        self.factory.create_data_source(group=self.factory.org.default_group)\n        self.factory.create_data_source(group=self.factory.org.default_group)\n        response = self.make_request(\"get\", \"/api/data_sources\", user=self.factory.user)\n        ids = [datasource[\"id\"] for datasource in response.json]\n        self.assertTrue(all(left <= right for left, right in pairwise(ids)))\n\n\nclass DataSourceTypesTest(BaseTestCase):\n    def test_returns_data_for_admin(self):\n        admin = self.factory.create_admin()\n        rv = self.make_request(\"get\", \"/api/data_sources/types\", user=admin)\n        self.assertEqual(rv.status_code, 200)\n\n    def test_returns_403_for_non_admin(self):\n        rv = self.make_request(\"get\", \"/api/data_sources/types\")\n        self.assertEqual(rv.status_code, 403)\n\n\nclass TestDataSourceResourceGet(BaseTestCase):\n    def setUp(self):\n        super(TestDataSourceResourceGet, self).setUp()\n        self.path = \"/api/data_sources/{}\".format(self.factory.data_source.id)\n\n    def test_returns_all_data_for_admins(self):\n        admin = self.factory.create_admin()\n        rv = self.make_request(\"get\", self.path, user=admin)\n        self.assertEqual(rv.status_code, 200)\n        self.assertIn(\"view_only\", rv.json)\n        self.assertIn(\"options\", rv.json)\n\n    def test_returns_only_view_only_for_users_without_list_permissions(self):\n        group = self.factory.create_group(permissions=[])\n        data_source = self.factory.create_data_source(group=group, view_only=True)\n        user = self.factory.create_user(group_ids=[group.id])\n\n        rv = self.make_request(\"get\", \"/api/data_sources/{}\".format(data_source.id), user=user)\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json, {\"view_only\": True})\n\n    def test_returns_limited_data_for_non_admin_in_the_default_group(self):\n        user = self.factory.create_user()\n        self.assertTrue(user.has_permission(\"list_data_sources\"))\n\n        rv = self.make_request(\"get\", self.path, user=user)\n        self.assertEqual(rv.status_code, 200)\n        self.assertNotIn(\"options\", rv.json)\n        self.assertIn(\"view_only\", rv.json)\n\n    def test_returns_403_for_non_admin_in_group_without_permission(self):\n        group = self.factory.create_group()\n        user = self.factory.create_user(group_ids=[group.id])\n        rv = self.make_request(\"get\", self.path, user=user)\n        self.assertEqual(rv.status_code, 403)\n\n\nclass TestDataSourceResourcePost(BaseTestCase):\n    def setUp(self):\n        super(TestDataSourceResourcePost, self).setUp()\n        self.path = \"/api/data_sources/{}\".format(self.factory.data_source.id)\n\n    def test_returns_400_when_configuration_invalid(self):\n        admin = self.factory.create_admin()\n        rv = self.make_request(\n            \"post\",\n            self.path,\n            data={\"name\": \"DS 1\", \"type\": \"pg\", \"options\": {}},\n            user=admin,\n        )\n\n        self.assertEqual(rv.status_code, 400)\n\n    def test_updates_data_source(self):\n        admin = self.factory.create_admin()\n        new_name = \"New Name\"\n        new_options = {\"dbname\": \"newdb\"}\n        rv = self.make_request(\n            \"post\",\n            self.path,\n            data={\"name\": new_name, \"type\": \"pg\", \"options\": new_options},\n            user=admin,\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        data_source = DataSource.query.get(self.factory.data_source.id)\n\n        self.assertEqual(data_source.name, new_name)\n        self.assertEqual(data_source.options.to_dict(), new_options)\n\n\nclass TestDataSourceResourceDelete(BaseTestCase):\n    def test_deletes_the_data_source(self):\n        data_source = self.factory.create_data_source()\n        admin = self.factory.create_admin()\n\n        rv = self.make_request(\"delete\", \"/api/data_sources/{}\".format(data_source.id), user=admin)\n\n        self.assertEqual(204, rv.status_code)\n        self.assertIsNone(DataSource.query.get(data_source.id))\n\n\nclass TestDataSourceListResourcePost(BaseTestCase):\n    def test_returns_400_when_missing_fields(self):\n        admin = self.factory.create_admin()\n        rv = self.make_request(\"post\", \"/api/data_sources\", user=admin)\n        self.assertEqual(rv.status_code, 400)\n\n        rv = self.make_request(\"post\", \"/api/data_sources\", data={\"name\": \"DS 1\"}, user=admin)\n\n        self.assertEqual(rv.status_code, 400)\n\n    def test_returns_400_when_configuration_invalid(self):\n        admin = self.factory.create_admin()\n        rv = self.make_request(\n            \"post\",\n            \"/api/data_sources\",\n            data={\"name\": \"DS 1\", \"type\": \"pg\", \"options\": {}},\n            user=admin,\n        )\n\n        self.assertEqual(rv.status_code, 400)\n\n    def test_creates_data_source(self):\n        admin = self.factory.create_admin()\n        rv = self.make_request(\n            \"post\",\n            \"/api/data_sources\",\n            data={\"name\": \"DS 1\", \"type\": \"pg\", \"options\": {\"dbname\": \"redash\"}},\n            user=admin,\n        )\n\n        self.assertEqual(rv.status_code, 200)\n\n        self.assertIsNotNone(DataSource.query.get(rv.json[\"id\"]))\n\n\nclass TestDataSourcePausePost(BaseTestCase):\n    def test_pauses_data_source(self):\n        admin = self.factory.create_admin()\n        rv = self.make_request(\n            \"post\",\n            \"/api/data_sources/{}/pause\".format(self.factory.data_source.id),\n            user=admin,\n        )\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(DataSource.query.get(self.factory.data_source.id).paused, True)\n\n    def test_pause_sets_reason(self):\n        admin = self.factory.create_admin()\n        rv = self.make_request(\n            \"post\",\n            \"/api/data_sources/{}/pause\".format(self.factory.data_source.id),\n            user=admin,\n            data={\"reason\": \"testing\"},\n        )\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(DataSource.query.get(self.factory.data_source.id).paused, True)\n        self.assertEqual(DataSource.query.get(self.factory.data_source.id).pause_reason, \"testing\")\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/data_sources/{}/pause?reason=test\".format(self.factory.data_source.id),\n            user=admin,\n        )\n        self.assertEqual(DataSource.query.get(self.factory.data_source.id).pause_reason, \"test\")\n\n    def test_requires_admin(self):\n        rv = self.make_request(\"post\", \"/api/data_sources/{}/pause\".format(self.factory.data_source.id))\n        self.assertEqual(rv.status_code, 403)\n\n\nclass TestDataSourcePauseDelete(BaseTestCase):\n    def test_resumes_data_source(self):\n        admin = self.factory.create_admin()\n        self.factory.data_source.pause()\n        rv = self.make_request(\n            \"delete\",\n            \"/api/data_sources/{}/pause\".format(self.factory.data_source.id),\n            user=admin,\n        )\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(DataSource.query.get(self.factory.data_source.id).paused, False)\n\n    def test_requires_admin(self):\n        rv = self.make_request(\"delete\", \"/api/data_sources/{}/pause\".format(self.factory.data_source.id))\n        self.assertEqual(rv.status_code, 403)\n"
  },
  {
    "path": "tests/handlers/test_destinations.py",
    "content": "import json\nimport textwrap\nfrom unittest import mock\n\nfrom redash.destinations.asana import Asana\nfrom redash.destinations.datadog import Datadog\nfrom redash.destinations.discord import Discord\nfrom redash.destinations.slack import Slack\nfrom redash.destinations.webex import Webex\nfrom redash.models import Alert, NotificationDestination\nfrom tests import BaseTestCase\n\n\nclass TestDestinationListResource(BaseTestCase):\n    def test_get_returns_all_destinations(self):\n        self.factory.create_destination()\n        self.factory.create_destination()\n\n        rv = self.make_request(\"get\", \"/api/destinations\", user=self.factory.user)\n        self.assertEqual(len(rv.json), 2)\n\n    def test_get_returns_only_destinations_of_current_org(self):\n        self.factory.create_destination()\n        self.factory.create_destination()\n        self.factory.create_destination(org=self.factory.create_org())\n\n        rv = self.make_request(\"get\", \"/api/destinations\", user=self.factory.user)\n        self.assertEqual(len(rv.json), 2)\n\n    def test_post_creates_new_destination(self):\n        data = {\n            \"options\": {\"addresses\": \"test@example.com\"},\n            \"name\": \"Test\",\n            \"type\": \"email\",\n        }\n        rv = self.make_request(\"post\", \"/api/destinations\", user=self.factory.create_admin(), data=data)\n        self.assertEqual(rv.status_code, 200)\n        pass\n\n    def test_post_requires_admin(self):\n        data = {\n            \"options\": {\"addresses\": \"test@example.com\"},\n            \"name\": \"Test\",\n            \"type\": \"email\",\n        }\n        rv = self.make_request(\"post\", \"/api/destinations\", user=self.factory.user, data=data)\n        self.assertEqual(rv.status_code, 403)\n\n    def test_returns_400_when_name_already_exists(self):\n        d1 = self.factory.create_destination()\n        data = {\n            \"options\": {\"addresses\": \"test@example.com\"},\n            \"name\": d1.name,\n            \"type\": \"email\",\n        }\n\n        rv = self.make_request(\"post\", \"/api/destinations\", user=self.factory.create_admin(), data=data)\n        self.assertEqual(rv.status_code, 400)\n\n\nclass TestDestinationResource(BaseTestCase):\n    def test_get(self):\n        d = self.factory.create_destination()\n        rv = self.make_request(\"get\", \"/api/destinations/{}\".format(d.id), user=self.factory.create_admin())\n        self.assertEqual(rv.status_code, 200)\n\n    def test_delete(self):\n        d = self.factory.create_destination()\n        rv = self.make_request(\n            \"delete\",\n            \"/api/destinations/{}\".format(d.id),\n            user=self.factory.create_admin(),\n        )\n        self.assertEqual(rv.status_code, 204)\n        self.assertIsNone(NotificationDestination.query.get(d.id))\n\n    def test_post(self):\n        d = self.factory.create_destination()\n        data = {\n            \"name\": \"updated\",\n            \"type\": d.type,\n            \"options\": {\"url\": \"https://www.slack.com/updated\"},\n        }\n\n        with self.app.app_context():\n            rv = self.make_request(\n                \"post\",\n                \"/api/destinations/{}\".format(d.id),\n                user=self.factory.create_admin(),\n                data=data,\n            )\n\n        self.assertEqual(rv.status_code, 200)\n\n        d = NotificationDestination.query.get(d.id)\n        self.assertEqual(d.name, data[\"name\"])\n        self.assertEqual(d.options[\"url\"], data[\"options\"][\"url\"])\n\n\ndef test_discord_notify_calls_requests_post():\n    alert = mock.Mock(spec_set=[\"id\", \"name\", \"options\", \"custom_body\", \"render_template\"])\n    alert.id = 1\n    alert.name = \"Test Alert\"\n    alert.options = {\n        \"custom_subject\": \"Test custom subject\",\n        \"custom_body\": \"Test custom body\",\n    }\n    alert.custom_body = alert.options[\"custom_body\"]\n    alert.render_template = mock.Mock(return_value={\"Rendered\": \"template\"})\n    query = mock.Mock()\n    query.id = 1\n\n    user = mock.Mock()\n    app = mock.Mock()\n    host = \"https://localhost:5000\"\n    options = {\"url\": \"https://discordapp.com/api/webhooks/test\"}\n    metadata = {\"Scheduled\": False}\n    new_state = Alert.TRIGGERED_STATE\n    destination = Discord(options)\n\n    with mock.patch(\"redash.destinations.discord.requests.post\") as mock_post:\n        mock_response = mock.Mock()\n        mock_response.status_code = 204\n        mock_post.return_value = mock_response\n\n        destination.notify(alert, query, user, new_state, app, host, metadata, options)\n\n        expected_payload = {\n            \"content\": \"Test custom subject\",\n            \"embeds\": [\n                {\n                    \"color\": \"12597547\",\n                    \"fields\": [\n                        {\"name\": \"Query\", \"value\": f\"{host}/queries/{query.id}\", \"inline\": True},\n                        {\"name\": \"Alert\", \"value\": f\"{host}/alerts/{alert.id}\", \"inline\": True},\n                        {\"name\": \"Description\", \"value\": \"Test custom body\"},\n                    ],\n                }\n            ],\n        }\n\n        mock_post.assert_called_once_with(\n            \"https://discordapp.com/api/webhooks/test\",\n            data=json.dumps(expected_payload),\n            headers={\"Content-Type\": \"application/json\"},\n            timeout=5.0,\n        )\n\n        assert mock_response.status_code == 204\n\n\ndef test_asana_notify_calls_requests_post():\n    alert = mock.Mock(spec_set=[\"id\", \"name\", \"options\", \"render_template\"])\n    alert.id = 1\n    alert.name = \"Test Alert\"\n    alert.options = {\n        \"custom_subject\": \"Test custom subject\",\n        \"custom_body\": \"Test custom body\",\n    }\n    alert.render_template = mock.Mock(return_value={\"Rendered\": \"template\"})\n    query = mock.Mock()\n    query.id = 1\n\n    user = mock.Mock()\n    app = mock.Mock()\n    host = \"https://localhost:5000\"\n    options = {\"pat\": \"abcd\", \"project_id\": \"1234\"}\n    metadata = {\"Scheduled\": False}\n\n    new_state = Alert.TRIGGERED_STATE\n    destination = Asana(options)\n\n    with mock.patch(\"redash.destinations.asana.requests.post\") as mock_post:\n        mock_response = mock.Mock()\n        mock_response.status_code = 204\n        mock_post.return_value = mock_response\n\n        destination.notify(alert, query, user, new_state, app, host, metadata, options)\n\n        notes = textwrap.dedent(\n            f\"\"\"\n        {alert.name} has TRIGGERED.\n\n        Query: {host}/queries/{query.id}\n        Alert: {host}/alerts/{alert.id}\n        \"\"\"\n        ).strip()\n\n        expected_payload = {\n            \"name\": f\"[Redash Alert] TRIGGERED: {alert.name}\",\n            \"notes\": notes,\n            \"projects\": [\"1234\"],\n        }\n\n        mock_post.assert_called_once_with(\n            destination.api_base_url,\n            data=expected_payload,\n            timeout=5.0,\n            headers={\"Authorization\": \"Bearer abcd\"},\n        )\n\n        assert mock_response.status_code == 204\n\n\ndef test_slack_notify_calls_requests_post():\n    alert = mock.Mock(spec_set=[\"id\", \"name\", \"custom_subject\", \"custom_body\", \"render_template\"])\n    alert.id = 1\n    alert.name = \"Test Alert\"\n    alert.custom_subject = \"Test custom subject\"\n    alert.custom_body = \"Test custom body\"\n\n    alert.render_template = mock.Mock(return_value={\"Rendered\": \"template\"})\n    query = mock.Mock()\n    query.id = 1\n\n    user = mock.Mock()\n    app = mock.Mock()\n    host = \"https://localhost:5000\"\n    options = {\"url\": \"https://slack.com/api/api.test\"}\n    metadata = {\"Scheduled\": False}\n\n    new_state = Alert.TRIGGERED_STATE\n    destination = Slack(options)\n\n    with mock.patch(\"redash.destinations.slack.requests.post\") as mock_post:\n        mock_response = mock.Mock()\n        mock_response.status_code = 204\n        mock_post.return_value = mock_response\n\n        destination.notify(alert, query, user, new_state, app, host, metadata, options)\n\n        query_link = f\"{host}/queries/{query.id}\"\n        alert_link = f\"{host}/alerts/{alert.id}\"\n\n        expected_payload = {\n            \"attachments\": [\n                {\n                    \"text\": \"Test custom subject\",\n                    \"color\": \"#c0392b\",\n                    \"fields\": [\n                        {\"title\": \"Query\", \"type\": \"mrkdwn\", \"value\": query_link},\n                        {\"title\": \"Alert\", \"type\": \"mrkdwn\", \"value\": alert_link},\n                        {\"title\": \"Description\", \"value\": \"Test custom body\"},\n                    ],\n                }\n            ]\n        }\n\n        mock_post.assert_called_once_with(\n            \"https://slack.com/api/api.test\",\n            data=json.dumps(expected_payload).encode(),\n            timeout=5.0,\n        )\n\n        assert mock_response.status_code == 204\n\n\ndef test_webex_notify_calls_requests_post():\n    alert = mock.Mock(spec_set=[\"id\", \"name\", \"custom_subject\", \"custom_body\", \"render_template\"])\n    alert.id = 1\n    alert.name = \"Test Alert\"\n    alert.custom_subject = \"Test custom subject\"\n    alert.custom_body = \"Test custom body\"\n    alert.render_template = mock.Mock(return_value={\"Rendered\": \"template\"})\n\n    query = mock.Mock()\n    query.id = 1\n\n    user = mock.Mock()\n    app = mock.Mock()\n    host = \"https://localhost:5000\"\n    options = {\n        \"webex_bot_token\": \"abcd\",\n        \"to_room_ids\": \"1234,5678\",\n        \"to_person_emails\": \"example1@test.com,example2@test.com\",\n    }\n    metadata = {\"Scheduled\": False}\n\n    new_state = Alert.TRIGGERED_STATE\n    destination = Webex(options)\n\n    with mock.patch(\"redash.destinations.webex.requests.post\") as mock_post:\n        mock_response = mock.Mock()\n        mock_response.status_code = 200\n        mock_post.return_value = mock_response\n\n        destination.notify(alert, query, user, new_state, app, host, metadata, options)\n\n        query_link = f\"{host}/queries/{query.id}\"\n        alert_link = f\"{host}/alerts/{alert.id}\"\n\n        expected_attachments = Webex.formatted_attachments_template(\n            alert.custom_subject, alert.custom_body, query_link, alert_link\n        )\n\n        expected_payload_room = {\n            \"markdown\": alert.custom_subject + \"\\n\" + alert.custom_body,\n            \"attachments\": expected_attachments,\n            \"roomId\": \"1234\",\n        }\n\n        expected_payload_email = {\n            \"markdown\": alert.custom_subject + \"\\n\" + alert.custom_body,\n            \"attachments\": expected_attachments,\n            \"toPersonEmail\": \"example1@test.com\",\n        }\n\n        # Check that requests.post was called for both roomId and toPersonEmail destinations\n        mock_post.assert_any_call(\n            destination.api_base_url,\n            json=expected_payload_room,\n            headers={\"Authorization\": \"Bearer abcd\"},\n            timeout=5.0,\n        )\n\n        mock_post.assert_any_call(\n            destination.api_base_url,\n            json=expected_payload_email,\n            headers={\"Authorization\": \"Bearer abcd\"},\n            timeout=5.0,\n        )\n\n        assert mock_response.status_code == 200\n\n\ndef test_webex_notify_handles_blank_entries():\n    alert = mock.Mock(spec_set=[\"id\", \"name\", \"custom_subject\", \"custom_body\", \"render_template\"])\n    alert.id = 1\n    alert.name = \"Test Alert\"\n    alert.custom_subject = \"Test custom subject\"\n    alert.custom_body = \"Test custom body\"\n    alert.render_template = mock.Mock(return_value={\"Rendered\": \"template\"})\n\n    query = mock.Mock()\n    query.id = 1\n\n    user = mock.Mock()\n    app = mock.Mock()\n    host = \"https://localhost:5000\"\n    options = {\n        \"webex_bot_token\": \"abcd\",\n        \"to_room_ids\": \"\",\n        \"to_person_emails\": \"\",\n    }\n    metadata = {\"Scheduled\": False}\n\n    new_state = Alert.TRIGGERED_STATE\n    destination = Webex(options)\n\n    with mock.patch(\"redash.destinations.webex.requests.post\") as mock_post:\n        destination.notify(alert, query, user, new_state, app, host, metadata, options)\n\n        # Ensure no API calls are made when destinations are blank\n        mock_post.assert_not_called()\n\n\ndef test_webex_notify_handles_2d_array():\n    alert = mock.Mock(spec_set=[\"id\", \"name\", \"custom_subject\", \"custom_body\", \"render_template\"])\n    alert.id = 1\n    alert.name = \"Test Alert\"\n    alert.custom_subject = \"Test custom subject\"\n    alert.custom_body = \"Test custom body with table [['Col1', 'Col2'], ['Val1', 'Val2']]\"\n    alert.render_template = mock.Mock(return_value={\"Rendered\": \"template\"})\n\n    query = mock.Mock()\n    query.id = 1\n\n    user = mock.Mock()\n    app = mock.Mock()\n    host = \"https://localhost:5000\"\n    options = {\n        \"webex_bot_token\": \"abcd\",\n        \"to_room_ids\": \"1234\",\n    }\n    metadata = {\"Scheduled\": False}\n\n    new_state = Alert.TRIGGERED_STATE\n    destination = Webex(options)\n\n    with mock.patch(\"redash.destinations.webex.requests.post\") as mock_post:\n        mock_response = mock.Mock()\n        mock_response.status_code = 200\n        mock_post.return_value = mock_response\n\n        destination.notify(alert, query, user, new_state, app, host, metadata, options)\n\n        query_link = f\"{host}/queries/{query.id}\"\n        alert_link = f\"{host}/alerts/{alert.id}\"\n\n        expected_attachments = Webex.formatted_attachments_template(\n            alert.custom_subject, alert.custom_body, query_link, alert_link\n        )\n\n        expected_payload = {\n            \"markdown\": alert.custom_subject + \"\\n\" + alert.custom_body,\n            \"attachments\": expected_attachments,\n            \"roomId\": \"1234\",\n        }\n\n        mock_post.assert_called_once_with(\n            destination.api_base_url,\n            json=expected_payload,\n            headers={\"Authorization\": \"Bearer abcd\"},\n            timeout=5.0,\n        )\n\n        assert mock_response.status_code == 200\n\n\ndef test_webex_notify_handles_1d_array():\n    alert = mock.Mock(spec_set=[\"id\", \"name\", \"custom_subject\", \"custom_body\", \"render_template\"])\n    alert.id = 1\n    alert.name = \"Test Alert\"\n    alert.custom_subject = \"Test custom subject\"\n    alert.custom_body = \"Test custom body with 1D array, however unlikely ['Col1', 'Col2']\"\n    alert.render_template = mock.Mock(return_value={\"Rendered\": \"template\"})\n\n    query = mock.Mock()\n    query.id = 1\n\n    user = mock.Mock()\n    app = mock.Mock()\n    host = \"https://localhost:5000\"\n    options = {\n        \"webex_bot_token\": \"abcd\",\n        \"to_room_ids\": \"1234\",\n    }\n    metadata = {\"Scheduled\": False}\n\n    new_state = Alert.TRIGGERED_STATE\n    destination = Webex(options)\n\n    with mock.patch(\"redash.destinations.webex.requests.post\") as mock_post:\n        mock_response = mock.Mock()\n        mock_response.status_code = 200\n        mock_post.return_value = mock_response\n\n        destination.notify(alert, query, user, new_state, app, host, metadata, options)\n\n        query_link = f\"{host}/queries/{query.id}\"\n        alert_link = f\"{host}/alerts/{alert.id}\"\n\n        expected_attachments = Webex.formatted_attachments_template(\n            alert.custom_subject, alert.custom_body, query_link, alert_link\n        )\n\n        expected_payload = {\n            \"markdown\": alert.custom_subject + \"\\n\" + alert.custom_body,\n            \"attachments\": expected_attachments,\n            \"roomId\": \"1234\",\n        }\n\n        mock_post.assert_called_once_with(\n            destination.api_base_url,\n            json=expected_payload,\n            headers={\"Authorization\": \"Bearer abcd\"},\n            timeout=5.0,\n        )\n\n        assert mock_response.status_code == 200\n\n\ndef test_datadog_notify_calls_requests_post():\n    alert = mock.Mock(spec_set=[\"id\", \"name\", \"custom_subject\", \"custom_body\", \"render_template\"])\n    alert.id = 1\n    alert.name = \"Test Alert\"\n    alert.custom_subject = \"Test custom subject\"\n    alert.custom_body = \"Test custom body\"\n    alert.render_template = mock.Mock(return_value={\"Rendered\": \"template\"})\n    query = mock.Mock()\n    query.id = 1\n\n    user = mock.Mock()\n    app = mock.Mock()\n    host = \"https://localhost:5000\"\n    options = {\n        \"api_key\": \"my-api-key\",\n        \"tags\": \"foo:bar,zoo:baz\",\n        \"priority\": \"normal\",\n        \"source_type_name\": \"postgres\",\n    }\n    metadata = {\"Scheduled\": False}\n    new_state = Alert.TRIGGERED_STATE\n    destination = Datadog(options)\n\n    with mock.patch(\"redash.destinations.datadog.requests.post\") as mock_post:\n        mock_response = mock.Mock()\n        mock_response.status_code = 202\n        mock_post.return_value = mock_response\n\n        destination.notify(alert, query, user, new_state, app, host, metadata, options)\n\n        expected_payload = {\n            \"title\": \"Test custom subject\",\n            \"text\": \"Test custom body\\nQuery: https://localhost:5000/queries/1\\nAlert: https://localhost:5000/alerts/1\",\n            \"alert_type\": \"error\",\n            \"priority\": \"normal\",\n            \"source_type_name\": \"postgres\",\n            \"aggregation_key\": \"redash:https://localhost:5000/alerts/1\",\n            \"tags\": [\n                \"foo:bar\",\n                \"zoo:baz\",\n                \"redash\",\n                \"query_id:1\",\n                \"alert_id:1\",\n            ],\n        }\n\n        mock_post.assert_called_once_with(\n            \"https://api.datadoghq.com/api/v1/events\",\n            data=json.dumps(expected_payload),\n            headers={\n                \"Accept\": \"application/json\",\n                \"Content-Type\": \"application/json\",\n                \"DD-API-KEY\": \"my-api-key\",\n            },\n            timeout=5.0,\n        )\n\n        assert mock_response.status_code == 202\n"
  },
  {
    "path": "tests/handlers/test_embed.py",
    "content": "from redash.models import db\nfrom tests import BaseTestCase\n\n\nclass TestUnembedables(BaseTestCase):\n    def test_not_embedable(self):\n        query = self.factory.create_query()\n        res = self.make_request(\"get\", \"/api/queries/{0}\".format(query.id))\n        self.assertEqual(res.status_code, 200)\n        self.assertIn(\"frame-ancestors 'none'\", res.headers[\"Content-Security-Policy\"])\n        self.assertEqual(res.headers[\"X-Frame-Options\"], \"deny\")\n\n\nclass TestEmbedVisualization(BaseTestCase):\n    def test_sucesss(self):\n        vis = self.factory.create_visualization()\n        vis.query_rel.latest_query_data = self.factory.create_query_result()\n        db.session.add(vis.query_rel)\n\n        res = self.make_request(\n            \"get\",\n            \"/embed/query/{}/visualization/{}\".format(vis.query_rel.id, vis.id),\n            is_json=False,\n        )\n        self.assertEqual(res.status_code, 200)\n        self.assertIn(\"frame-ancestors *\", res.headers[\"Content-Security-Policy\"])\n        self.assertNotIn(\"X-Frame-Options\", res.headers)\n\n\n# TODO: this should be applied to the new API endpoint\nclass TestPublicDashboard(BaseTestCase):\n    def test_success(self):\n        dashboard = self.factory.create_dashboard()\n        api_key = self.factory.create_api_key(object=dashboard)\n\n        res = self.make_request(\n            \"get\",\n            \"/public/dashboards/{}\".format(api_key.api_key),\n            user=False,\n            is_json=False,\n        )\n        self.assertEqual(res.status_code, 200)\n        self.assertIn(\"frame-ancestors *\", res.headers[\"Content-Security-Policy\"])\n        self.assertNotIn(\"X-Frame-Options\", res.headers)\n\n    def test_works_for_logged_in_user(self):\n        dashboard = self.factory.create_dashboard()\n        api_key = self.factory.create_api_key(object=dashboard)\n\n        res = self.make_request(\"get\", \"/public/dashboards/{}\".format(api_key.api_key), is_json=False)\n        self.assertEqual(res.status_code, 200)\n\n    def test_bad_token(self):\n        res = self.make_request(\"get\", \"/public/dashboards/bad-token\", user=False, is_json=False)\n        self.assertEqual(res.status_code, 302)\n\n    def test_inactive_token(self):\n        dashboard = self.factory.create_dashboard()\n        api_key = self.factory.create_api_key(object=dashboard, active=False)\n        res = self.make_request(\n            \"get\",\n            \"/public/dashboards/{}\".format(api_key.api_key),\n            user=False,\n            is_json=False,\n        )\n        self.assertEqual(res.status_code, 302)\n\n    # Not relevant for now, as tokens in api_keys table are only created for dashboards. Once this changes, we should\n    # add this test.\n    # def test_token_doesnt_belong_to_dashboard(self):\n    #     pass\n\n\nclass TestAPIPublicDashboard(BaseTestCase):\n    def test_success(self):\n        dashboard = self.factory.create_dashboard()\n        api_key = self.factory.create_api_key(object=dashboard)\n\n        res = self.make_request(\n            \"get\",\n            \"/api/dashboards/public/{}\".format(api_key.api_key),\n            user=False,\n            is_json=False,\n        )\n        self.assertEqual(res.status_code, 200)\n        self.assertIn(\"frame-ancestors *\", res.headers[\"Content-Security-Policy\"])\n        self.assertNotIn(\"X-Frame-Options\", res.headers)\n\n    def test_works_for_logged_in_user(self):\n        dashboard = self.factory.create_dashboard()\n        api_key = self.factory.create_api_key(object=dashboard)\n\n        res = self.make_request(\"get\", \"/api/dashboards/public/{}\".format(api_key.api_key), is_json=False)\n        self.assertEqual(res.status_code, 200)\n\n    def test_bad_token(self):\n        res = self.make_request(\"get\", \"/api/dashboards/public/bad-token\", user=False, is_json=False)\n        self.assertEqual(res.status_code, 404)\n\n    def test_inactive_token(self):\n        dashboard = self.factory.create_dashboard()\n        api_key = self.factory.create_api_key(object=dashboard, active=False)\n        res = self.make_request(\n            \"get\",\n            \"/api/dashboards/public/{}\".format(api_key.api_key),\n            user=False,\n            is_json=False,\n        )\n        self.assertEqual(res.status_code, 404)\n\n    # Not relevant for now, as tokens in api_keys table are only created for dashboards. Once this changes, we should\n    # add this test.\n    # def test_token_doesnt_belong_to_dashboard(self):\n    #     pass\n"
  },
  {
    "path": "tests/handlers/test_favorites.py",
    "content": "from tests import BaseTestCase\n\n\nclass TestQueryFavoriteResource(BaseTestCase):\n    def test_favorite(self):\n        query = self.factory.create_query()\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/favorite\".format(query.id))\n        self.assertEqual(rv.status_code, 200)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}\".format(query.id))\n        self.assertEqual(rv.json[\"is_favorite\"], True)\n\n    def test_duplicate_favorite(self):\n        query = self.factory.create_query()\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/favorite\".format(query.id))\n        self.assertEqual(rv.status_code, 200)\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/favorite\".format(query.id))\n        self.assertEqual(rv.status_code, 200)\n\n    def test_unfavorite(self):\n        query = self.factory.create_query()\n        rv = self.make_request(\"post\", \"/api/queries/{}/favorite\".format(query.id))\n        rv = self.make_request(\"delete\", \"/api/queries/{}/favorite\".format(query.id))\n        self.assertEqual(rv.status_code, 200)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}\".format(query.id))\n        self.assertEqual(rv.json[\"is_favorite\"], False)\n\n\nclass TestQueryFavoriteListResource(BaseTestCase):\n    def test_get_favorites(self):\n        rv = self.make_request(\"get\", \"/api/queries/favorites\")\n        self.assertEqual(rv.status_code, 200)\n"
  },
  {
    "path": "tests/handlers/test_groups.py",
    "content": "from funcy import project\n\nfrom redash.models import DataSource, Group, db\nfrom tests import BaseTestCase\n\n\nclass TestGroupDataSourceListResource(BaseTestCase):\n    def test_returns_only_groups_for_current_org(self):\n        group = self.factory.create_group(org=self.factory.create_org())\n        self.factory.create_data_source(group=group)\n        db.session.flush()\n        response = self.make_request(\n            \"get\",\n            \"/api/groups/{}/data_sources\".format(group.id),\n            user=self.factory.create_admin(),\n        )\n        self.assertEqual(response.status_code, 404)\n\n    def test_list(self):\n        group = self.factory.create_group()\n        ds = self.factory.create_data_source(group=group)\n        db.session.flush()\n        response = self.make_request(\n            \"get\",\n            \"/api/groups/{}/data_sources\".format(group.id),\n            user=self.factory.create_admin(),\n        )\n        self.assertEqual(response.status_code, 200)\n        self.assertEqual(len(response.json), 1)\n        self.assertEqual(response.json[0][\"id\"], ds.id)\n\n\nclass TestGroupResourceList(BaseTestCase):\n    def test_list_admin(self):\n        self.factory.create_group(org=self.factory.create_org())\n        response = self.make_request(\"get\", \"/api/groups\", user=self.factory.create_admin())\n        g_keys = [\"type\", \"id\", \"name\", \"permissions\"]\n\n        def filtergroups(gs):\n            return [project(g, g_keys) for g in gs]\n\n        self.assertEqual(\n            filtergroups(response.json),\n            filtergroups(g.to_dict() for g in [self.factory.admin_group, self.factory.default_group]),\n        )\n\n    def test_list(self):\n        group1 = self.factory.create_group(org=self.factory.create_org(), permissions=[\"view_dashboard\"])\n        db.session.flush()\n        u = self.factory.create_user(group_ids=[self.factory.default_group.id, group1.id])\n        db.session.flush()\n        response = self.make_request(\"get\", \"/api/groups\", user=u)\n        g_keys = [\"type\", \"id\", \"name\", \"permissions\"]\n\n        def filtergroups(gs):\n            return [project(g, g_keys) for g in gs]\n\n        self.assertEqual(\n            filtergroups(response.json),\n            filtergroups(g.to_dict() for g in [self.factory.default_group, group1]),\n        )\n\n\nclass TestGroupResourcePost(BaseTestCase):\n    def test_doesnt_change_builtin_groups(self):\n        current_name = self.factory.default_group.name\n\n        response = self.make_request(\n            \"post\",\n            \"/api/groups/{}\".format(self.factory.default_group.id),\n            user=self.factory.create_admin(),\n            data={\"name\": \"Another Name\"},\n        )\n\n        self.assertEqual(response.status_code, 400)\n        self.assertEqual(current_name, Group.query.get(self.factory.default_group.id).name)\n\n\nclass TestGroupResourceDelete(BaseTestCase):\n    def test_allowed_only_to_admin(self):\n        group = self.factory.create_group()\n\n        response = self.make_request(\"delete\", \"/api/groups/{}\".format(group.id))\n        self.assertEqual(response.status_code, 403)\n\n        response = self.make_request(\n            \"delete\",\n            \"/api/groups/{}\".format(group.id),\n            user=self.factory.create_admin(),\n        )\n        self.assertEqual(response.status_code, 200)\n        self.assertIsNone(Group.query.get(group.id))\n\n    def test_cant_delete_builtin_group(self):\n        for group in [self.factory.default_group, self.factory.admin_group]:\n            response = self.make_request(\n                \"delete\",\n                \"/api/groups/{}\".format(group.id),\n                user=self.factory.create_admin(),\n            )\n            self.assertEqual(response.status_code, 400)\n\n    def test_can_delete_group_with_data_sources(self):\n        group = self.factory.create_group()\n        data_source = self.factory.create_data_source(group=group)\n\n        response = self.make_request(\n            \"delete\",\n            \"/api/groups/{}\".format(group.id),\n            user=self.factory.create_admin(),\n        )\n\n        self.assertEqual(response.status_code, 200)\n\n        self.assertEqual(data_source, DataSource.query.get(data_source.id))\n\n\nclass TestGroupResourceGet(BaseTestCase):\n    def test_returns_group(self):\n        rv = self.make_request(\"get\", \"/api/groups/{}\".format(self.factory.default_group.id))\n        self.assertEqual(rv.status_code, 200)\n\n    def test_doesnt_return_if_user_not_member_or_admin(self):\n        rv = self.make_request(\"get\", \"/api/groups/{}\".format(self.factory.admin_group.id))\n        self.assertEqual(rv.status_code, 403)\n"
  },
  {
    "path": "tests/handlers/test_order_results.py",
    "content": "from redash import models\nfrom redash.handlers.base import order_results\nfrom redash.models import db\nfrom tests import BaseTestCase\n\n\nclass TestOrderResults(BaseTestCase):\n    def setUp(self):\n        super().setUp()\n\n        user1 = self.factory.create_user(name=\"Charlie\")\n        user2 = self.factory.create_user(name=\"Bravo\")\n        user3 = self.factory.create_user(name=\"Alpha\")\n\n        q1 = self.factory.create_query(name=\"a\", user=user1)\n        q2 = self.factory.create_query(name=\"b\", user=user2)\n        q3 = self.factory.create_query(name=\"c\", user=user3)\n\n        db.session.add(user1)\n        db.session.add(user2)\n        db.session.add(user3)\n\n        db.session.add(q1)\n        db.session.add(q2)\n        db.session.add(q3)\n        db.session.commit()\n\n        self.results = db.session.query(models.Query)\n        self.results = self.results.join(models.User, models.Query.user_id == models.User.id)\n\n        self.allowed_orders = {\n            \"name\": \"name\",\n            \"-name\": \"-name\",\n            \"users-name\": \"users-name\",\n            \"-users-name\": \"-users-name\",\n        }\n        self.default_order = \"-name\"\n\n    def test_no_order_no_fallback(self):\n        with self.app.test_request_context(\"/items?order=\"):\n            ordered_results = order_results(self.results, self.default_order, self.allowed_orders, fallback=False)\n            self.assertEqual(self.results, ordered_results)\n\n    def test_no_order_yes_fallback(self):\n        with self.app.test_request_context(\"/items?order=\"):\n            ordered_results = order_results(self.results, self.default_order, self.allowed_orders, fallback=True)\n            ordered_results = [entry.name for entry in ordered_results]\n            self.assertEqual(ordered_results, [\"c\", \"b\", \"a\"])\n\n    def test_invalid_order_no_fallback(self):\n        with self.app.test_request_context(\"/items?order=some_invalid_order\"):\n            ordered_results = order_results(self.results, self.default_order, self.allowed_orders, fallback=False)\n            ordered_results = [entry.name for entry in ordered_results]\n            self.assertEqual(ordered_results, [entry.name for entry in self.results])\n\n    def test_invalid_order_yes_fallback(self):\n        with self.app.test_request_context(\"/items?order=some_invalid_order\"):\n            ordered_results = order_results(self.results, self.default_order, self.allowed_orders, fallback=True)\n            ordered_results = [entry.name for entry in ordered_results]\n            self.assertEqual(ordered_results, [\"c\", \"b\", \"a\"])\n\n    def test_valid_requested_order_no_fallback(self):\n        with self.app.test_request_context(\"/items?order=name\"):\n            ordered_results = order_results(self.results, self.default_order, self.allowed_orders, fallback=False)\n            ordered_results = [entry.name for entry in ordered_results]\n            self.assertEqual(ordered_results, [\"a\", \"b\", \"c\"])\n\n    def test_valid_requested_order_yes_fallback(self):\n        with self.app.test_request_context(\"/items?order=name\"):\n            ordered_results = order_results(self.results, self.default_order, self.allowed_orders, fallback=True)\n            ordered_results = [entry.name for entry in ordered_results]\n            self.assertEqual(ordered_results, [\"a\", \"b\", \"c\"])\n\n    def test_requested_entity_no_fallback(self):\n        with self.app.test_request_context(\"/items?order=users-name\"):\n            ordered_results = order_results(self.results, self.default_order, self.allowed_orders, fallback=False)\n            ordered_results = [entry.name for entry in ordered_results]\n            self.assertEqual(ordered_results, [\"c\", \"b\", \"a\"])\n\n    def test_requested_entity_yes_fallback(self):\n        with self.app.test_request_context(\"/items?order=-users-name\"):\n            ordered_results = order_results(self.results, self.default_order, self.allowed_orders, fallback=True)\n            ordered_results = [entry.name for entry in ordered_results]\n            self.assertEqual(ordered_results, [\"a\", \"b\", \"c\"])\n\n    def test_order_by_attached(self):\n        self.results = self.results.order_by(models.Query.name)\n        with self.app.test_request_context(\"/items?order=-name\"):\n            ordered_results = order_results(self.results, self.default_order, self.allowed_orders, fallback=False)\n            ordered_results = [entry.name for entry in ordered_results]\n            self.assertEqual(ordered_results, [\"c\", \"b\", \"a\"])\n"
  },
  {
    "path": "tests/handlers/test_paginate.py",
    "content": "from unittest import TestCase\n\nfrom mock import MagicMock\nfrom werkzeug.exceptions import BadRequest\n\nfrom redash.handlers.base import paginate\n\n\nclass DummyResults:\n    items = [i for i in range(25)]\n\n\ndummy_results = DummyResults()\n\n\nclass TestPaginate(TestCase):\n    def setUp(self):\n        self.query_set = MagicMock()\n        self.query_set.count = MagicMock(return_value=102)\n        self.query_set.paginate = MagicMock(return_value=dummy_results)\n\n    def test_returns_paginated_results(self):\n        page = paginate(self.query_set, 1, 25, lambda x: x)\n        self.assertEqual(page[\"page\"], 1)\n        self.assertEqual(page[\"page_size\"], 25)\n        self.assertEqual(page[\"count\"], 102)\n        self.assertEqual(page[\"results\"], dummy_results.items)\n\n    def test_raises_error_for_bad_page(self):\n        self.assertRaises(BadRequest, lambda: paginate(self.query_set, -1, 25, lambda x: x))\n        self.assertRaises(BadRequest, lambda: paginate(self.query_set, 6, 25, lambda x: x))\n\n    def test_raises_error_for_bad_page_size(self):\n        self.assertRaises(BadRequest, lambda: paginate(self.query_set, 1, 251, lambda x: x))\n        self.assertRaises(BadRequest, lambda: paginate(self.query_set, 1, -1, lambda x: x))\n"
  },
  {
    "path": "tests/handlers/test_permissions.py",
    "content": "from redash.models import AccessPermission\nfrom redash.permissions import ACCESS_TYPE_MODIFY\nfrom tests import BaseTestCase\n\n\nclass TestObjectPermissionsListGet(BaseTestCase):\n    def test_returns_empty_list_when_no_permissions(self):\n        query = self.factory.create_query()\n        user = self.factory.user\n        rv = self.make_request(\"get\", \"/api/queries/{}/acl\".format(query.id), user=user)\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual({}, rv.json)\n\n    def test_returns_permissions(self):\n        query = self.factory.create_query()\n        user = self.factory.user\n\n        AccessPermission.grant(\n            obj=query,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=self.factory.user,\n        )\n\n        rv = self.make_request(\"get\", \"/api/queries/{}/acl\".format(query.id), user=user)\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertIn(\"modify\", rv.json)\n        self.assertEqual(user.id, rv.json[\"modify\"][0][\"id\"])\n\n    def test_returns_404_for_outside_of_organization_users(self):\n        query = self.factory.create_query()\n        user = self.factory.create_user(org=self.factory.create_org())\n        rv = self.make_request(\"get\", \"/api/queries/{}/acl\".format(query.id), user=user)\n\n        self.assertEqual(rv.status_code, 404)\n\n\nclass TestObjectPermissionsListPost(BaseTestCase):\n    def test_creates_permission_if_the_user_is_an_owner(self):\n        query = self.factory.create_query()\n        other_user = self.factory.create_user()\n\n        data = {\"access_type\": ACCESS_TYPE_MODIFY, \"user_id\": other_user.id}\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/acl\".format(query.id), user=query.user, data=data)\n\n        self.assertEqual(200, rv.status_code)\n        self.assertTrue(AccessPermission.exists(query, ACCESS_TYPE_MODIFY, other_user))\n\n    def test_returns_403_if_the_user_isnt_owner(self):\n        query = self.factory.create_query()\n        other_user = self.factory.create_user()\n\n        data = {\"access_type\": ACCESS_TYPE_MODIFY, \"user_id\": other_user.id}\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/acl\".format(query.id), user=other_user, data=data)\n        self.assertEqual(403, rv.status_code)\n\n    def test_returns_400_if_the_grantee_isnt_from_organization(self):\n        query = self.factory.create_query()\n        other_user = self.factory.create_user(org=self.factory.create_org())\n\n        data = {\"access_type\": ACCESS_TYPE_MODIFY, \"user_id\": other_user.id}\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/acl\".format(query.id), user=query.user, data=data)\n        self.assertEqual(400, rv.status_code)\n\n    def test_returns_404_if_the_user_from_different_org(self):\n        query = self.factory.create_query()\n        other_user = self.factory.create_user(org=self.factory.create_org())\n\n        data = {\"access_type\": ACCESS_TYPE_MODIFY, \"user_id\": other_user.id}\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/acl\".format(query.id), user=other_user, data=data)\n        self.assertEqual(404, rv.status_code)\n\n    def test_accepts_only_correct_access_types(self):\n        query = self.factory.create_query()\n        other_user = self.factory.create_user()\n\n        data = {\"access_type\": \"random string\", \"user_id\": other_user.id}\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/acl\".format(query.id), user=query.user, data=data)\n\n        self.assertEqual(400, rv.status_code)\n\n\nclass TestObjectPermissionsListDelete(BaseTestCase):\n    def test_removes_permission(self):\n        query = self.factory.create_query()\n        user = self.factory.user\n        other_user = self.factory.create_user()\n\n        data = {\"access_type\": ACCESS_TYPE_MODIFY, \"user_id\": other_user.id}\n\n        AccessPermission.grant(\n            obj=query,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=other_user,\n        )\n\n        rv = self.make_request(\"delete\", \"/api/queries/{}/acl\".format(query.id), user=user, data=data)\n\n        self.assertEqual(rv.status_code, 200)\n\n        self.assertFalse(AccessPermission.exists(query, ACCESS_TYPE_MODIFY, other_user))\n\n    def test_removes_permission_created_by_another_user(self):\n        query = self.factory.create_query()\n        other_user = self.factory.create_user()\n\n        data = {\"access_type\": ACCESS_TYPE_MODIFY, \"user_id\": other_user.id}\n\n        AccessPermission.grant(\n            obj=query,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=other_user,\n        )\n\n        rv = self.make_request(\n            \"delete\",\n            \"/api/queries/{}/acl\".format(query.id),\n            user=self.factory.create_admin(),\n            data=data,\n        )\n\n        self.assertEqual(rv.status_code, 200)\n\n        self.assertFalse(AccessPermission.exists(query, ACCESS_TYPE_MODIFY, other_user))\n\n    def test_returns_404_for_outside_of_organization_users(self):\n        query = self.factory.create_query()\n        user = self.factory.create_user(org=self.factory.create_org())\n        data = {\"access_type\": ACCESS_TYPE_MODIFY, \"user_id\": user.id}\n        rv = self.make_request(\"delete\", \"/api/queries/{}/acl\".format(query.id), user=user, data=data)\n\n        self.assertEqual(rv.status_code, 404)\n\n    def test_returns_403_for_non_owner(self):\n        query = self.factory.create_query()\n        user = self.factory.create_user()\n\n        data = {\"access_type\": ACCESS_TYPE_MODIFY, \"user_id\": user.id}\n        rv = self.make_request(\"delete\", \"/api/queries/{}/acl\".format(query.id), user=user, data=data)\n\n        self.assertEqual(rv.status_code, 403)\n\n    def test_returns_200_even_if_there_is_no_permission(self):\n        query = self.factory.create_query()\n        user = self.factory.create_user()\n\n        data = {\"access_type\": ACCESS_TYPE_MODIFY, \"user_id\": user.id}\n\n        rv = self.make_request(\"delete\", \"/api/queries/{}/acl\".format(query.id), user=query.user, data=data)\n\n        self.assertEqual(rv.status_code, 200)\n\n\nclass TestCheckPermissionsGet(BaseTestCase):\n    def test_returns_true_for_existing_permission(self):\n        query = self.factory.create_query()\n        other_user = self.factory.create_user()\n\n        AccessPermission.grant(\n            obj=query,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=other_user,\n        )\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}/acl/{}\".format(query.id, ACCESS_TYPE_MODIFY),\n            user=other_user,\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(True, rv.json[\"response\"])\n\n    def test_returns_false_for_existing_permission(self):\n        query = self.factory.create_query()\n        other_user = self.factory.create_user()\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}/acl/{}\".format(query.id, ACCESS_TYPE_MODIFY),\n            user=other_user,\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(False, rv.json[\"response\"])\n\n    def test_returns_404_for_outside_of_org_users(self):\n        query = self.factory.create_query()\n        other_user = self.factory.create_user(org=self.factory.create_org())\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}/acl/{}\".format(query.id, ACCESS_TYPE_MODIFY),\n            user=other_user,\n        )\n\n        self.assertEqual(rv.status_code, 404)\n"
  },
  {
    "path": "tests/handlers/test_queries.py",
    "content": "from redash import models\nfrom redash.models import db\nfrom redash.permissions import ACCESS_TYPE_MODIFY\nfrom redash.serializers import serialize_query\nfrom tests import BaseTestCase\n\n\nclass TestQueryResourceGet(BaseTestCase):\n    def test_get_query(self):\n        query = self.factory.create_query()\n\n        rv = self.make_request(\"get\", \"/api/queries/{0}\".format(query.id))\n\n        self.assertEqual(rv.status_code, 200)\n        expected = serialize_query(query, with_visualizations=True)\n        expected[\"can_edit\"] = True\n        expected[\"is_favorite\"] = False\n        self.assertResponseEqual(expected, rv.json)\n\n    def test_get_all_queries(self):\n        [self.factory.create_query() for _ in range(10)]\n        rv = self.make_request(\"get\", \"/api/queries\")\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(len(rv.json[\"results\"]), 10)\n\n    def test_query_without_data_source_should_be_available_only_by_admin(self):\n        query = self.factory.create_query()\n        query.data_source = None\n        db.session.add(query)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}\".format(query.id))\n        self.assertEqual(rv.status_code, 403)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}\".format(query.id), user=self.factory.create_admin())\n        self.assertEqual(rv.status_code, 200)\n\n    def test_query_only_accessible_to_users_from_its_organization(self):\n        second_org = self.factory.create_org()\n        second_org_admin = self.factory.create_admin(org=second_org)\n\n        query = self.factory.create_query()\n        query.data_source = None\n        db.session.add(query)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}\".format(query.id), user=second_org_admin)\n        self.assertEqual(rv.status_code, 404)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}\".format(query.id), user=self.factory.create_admin())\n        self.assertEqual(rv.status_code, 200)\n\n    def test_query_search(self):\n        names = [\"Harder\", \"Better\", \"Faster\", \"Stronger\"]\n        for name in names:\n            self.factory.create_query(name=name)\n\n        rv = self.make_request(\"get\", \"/api/queries?q=better\")\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(len(rv.json[\"results\"]), 1)\n\n        rv = self.make_request(\"get\", \"/api/queries?q=better or faster\")\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(len(rv.json[\"results\"]), 2)\n\n        # test the old search API and that it redirects to the new one\n        rv = self.make_request(\"get\", \"/api/queries/search?q=stronger\")\n        self.assertEqual(rv.status_code, 301)\n        self.assertIn(\"/api/queries?q=stronger\", rv.headers[\"Location\"])\n\n        rv = self.make_request(\"get\", \"/api/queries/search?q=stronger\", follow_redirects=True)\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(len(rv.json[\"results\"]), 1)\n\n\nclass TestQueryResourcePost(BaseTestCase):\n    def test_update_query(self):\n        admin = self.factory.create_admin()\n        query = self.factory.create_query()\n\n        new_ds = self.factory.create_data_source()\n        new_qr = self.factory.create_query_result()\n\n        data = {\n            \"name\": \"Testing\",\n            \"query\": \"select 2\",\n            \"latest_query_data_id\": new_qr.id,\n            \"data_source_id\": new_ds.id,\n        }\n\n        rv = self.make_request(\"post\", \"/api/queries/{0}\".format(query.id), data=data, user=admin)\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"name\"], data[\"name\"])\n        self.assertEqual(rv.json[\"last_modified_by\"][\"id\"], admin.id)\n        self.assertEqual(rv.json[\"query\"], data[\"query\"])\n        self.assertEqual(rv.json[\"data_source_id\"], data[\"data_source_id\"])\n        self.assertEqual(rv.json[\"latest_query_data_id\"], data[\"latest_query_data_id\"])\n\n    def test_raises_error_in_case_of_conflict(self):\n        q = self.factory.create_query()\n        q.name = \"Another Name\"\n        db.session.add(q)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{0}\".format(q.id),\n            data={\"name\": \"Testing\", \"version\": q.version - 1},\n            user=self.factory.user,\n        )\n        self.assertEqual(rv.status_code, 409)\n\n    def test_prevents_association_with_view_only_data_sources(self):\n        view_only_data_source = self.factory.create_data_source(view_only=True)\n        my_data_source = self.factory.create_data_source()\n\n        my_query = self.factory.create_query(data_source=my_data_source)\n        db.session.add(my_query)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{0}\".format(my_query.id),\n            data={\"data_source_id\": view_only_data_source.id},\n            user=self.factory.user,\n        )\n\n        self.assertEqual(rv.status_code, 403)\n\n    def test_allows_association_with_authorized_dropdown_queries(self):\n        data_source = self.factory.create_data_source(group=self.factory.default_group)\n\n        other_query = self.factory.create_query(data_source=data_source)\n        db.session.add(other_query)\n\n        my_query = self.factory.create_query(data_source=data_source)\n        db.session.add(my_query)\n\n        options = {\n            \"parameters\": [\n                {\"name\": \"foo\", \"type\": \"query\", \"queryId\": other_query.id},\n                {\"name\": \"bar\", \"type\": \"query\", \"queryId\": other_query.id},\n            ]\n        }\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{0}\".format(my_query.id),\n            data={\"options\": options},\n            user=self.factory.user,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n    def test_prevents_association_with_unauthorized_dropdown_queries(self):\n        other_data_source = self.factory.create_data_source(group=self.factory.create_group())\n        other_query = self.factory.create_query(data_source=other_data_source)\n        db.session.add(other_query)\n\n        my_data_source = self.factory.create_data_source(group=self.factory.create_group())\n        my_query = self.factory.create_query(data_source=my_data_source)\n        db.session.add(my_query)\n\n        options = {\"parameters\": [{\"type\": \"query\", \"queryId\": other_query.id}]}\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{0}\".format(my_query.id),\n            data={\"options\": options},\n            user=self.factory.user,\n        )\n        self.assertEqual(rv.status_code, 403)\n\n    def test_prevents_association_with_non_existing_dropdown_queries(self):\n        my_data_source = self.factory.create_data_source(group=self.factory.create_group())\n        my_query = self.factory.create_query(data_source=my_data_source)\n        db.session.add(my_query)\n\n        options = {\"parameters\": [{\"type\": \"query\", \"queryId\": 100000}]}\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{0}\".format(my_query.id),\n            data={\"options\": options},\n            user=self.factory.user,\n        )\n        self.assertEqual(rv.status_code, 400)\n\n    def test_overrides_existing_if_no_version_specified(self):\n        q = self.factory.create_query()\n        q.name = \"Another Name\"\n        db.session.add(q)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{0}\".format(q.id),\n            data={\"name\": \"Testing\"},\n            user=self.factory.user,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n    def test_works_for_non_owner_with_permission(self):\n        query = self.factory.create_query()\n        user = self.factory.create_user()\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{0}\".format(query.id),\n            data={\"name\": \"Testing\"},\n            user=user,\n        )\n        self.assertEqual(rv.status_code, 403)\n\n        models.AccessPermission.grant(obj=query, access_type=ACCESS_TYPE_MODIFY, grantee=user, grantor=query.user)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{0}\".format(query.id),\n            data={\"name\": \"Testing\"},\n            user=user,\n        )\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"name\"], \"Testing\")\n        self.assertEqual(rv.json[\"last_modified_by\"][\"id\"], user.id)\n\n\nclass TestQueryListResourceGet(BaseTestCase):\n    def test_returns_queries(self):\n        q1 = self.factory.create_query()\n        q2 = self.factory.create_query()\n        q3 = self.factory.create_query()\n\n        rv = self.make_request(\"get\", \"/api/queries\")\n\n        assert len(rv.json[\"results\"]) == 3\n        assert set([result[\"id\"] for result in rv.json[\"results\"]]) == {q1.id, q2.id, q3.id}\n\n    def test_filters_with_tags(self):\n        q1 = self.factory.create_query(tags=[\"test\"])\n        self.factory.create_query()\n        self.factory.create_query()\n\n        rv = self.make_request(\"get\", \"/api/queries?tags=test\")\n        assert len(rv.json[\"results\"]) == 1\n        assert set([result[\"id\"] for result in rv.json[\"results\"]]) == {q1.id}\n\n    def test_search_term(self):\n        q1 = self.factory.create_query(name=\"Sales\")\n        q2 = self.factory.create_query(name=\"Q1 sales\")\n        self.factory.create_query(name=\"Ops\")\n\n        rv = self.make_request(\"get\", \"/api/queries?q=sales\")\n        assert len(rv.json[\"results\"]) == 2\n        assert set([result[\"id\"] for result in rv.json[\"results\"]]) == {q1.id, q2.id}\n\n\nclass TestQueryListResourcePost(BaseTestCase):\n    def test_create_query(self):\n        query_data = {\n            \"name\": \"Testing\",\n            \"query\": \"SELECT 1\",\n            \"schedule\": {\"interval\": \"3600\"},\n            \"data_source_id\": self.factory.data_source.id,\n        }\n\n        rv = self.make_request(\"post\", \"/api/queries\", data=query_data)\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertLessEqual(query_data.items(), rv.json.items())\n        self.assertEqual(rv.json[\"user\"][\"id\"], self.factory.user.id)\n        self.assertIsNotNone(rv.json[\"api_key\"])\n        self.assertIsNotNone(rv.json[\"query_hash\"])\n\n        query = models.Query.query.get(rv.json[\"id\"])\n        self.assertEqual(len(list(query.visualizations)), 1)\n        self.assertTrue(query.is_draft)\n\n    def test_allows_association_with_authorized_dropdown_queries(self):\n        data_source = self.factory.create_data_source(group=self.factory.default_group)\n\n        other_query = self.factory.create_query(data_source=data_source)\n        db.session.add(other_query)\n\n        query_data = {\n            \"name\": \"Testing\",\n            \"query\": \"SELECT 1\",\n            \"schedule\": {\"interval\": \"3600\"},\n            \"data_source_id\": self.factory.data_source.id,\n            \"options\": {\n                \"parameters\": [\n                    {\"name\": \"foo\", \"type\": \"query\", \"queryId\": other_query.id},\n                    {\"name\": \"bar\", \"type\": \"query\", \"queryId\": other_query.id},\n                ]\n            },\n        }\n\n        rv = self.make_request(\"post\", \"/api/queries\", data=query_data)\n        self.assertEqual(rv.status_code, 200)\n\n    def test_prevents_association_with_unauthorized_dropdown_queries(self):\n        other_data_source = self.factory.create_data_source(group=self.factory.create_group())\n        other_query = self.factory.create_query(data_source=other_data_source)\n        db.session.add(other_query)\n\n        my_data_source = self.factory.create_data_source(group=self.factory.create_group())\n\n        query_data = {\n            \"name\": \"Testing\",\n            \"query\": \"SELECT 1\",\n            \"schedule\": {\"interval\": \"3600\"},\n            \"data_source_id\": my_data_source.id,\n            \"options\": {\"parameters\": [{\"type\": \"query\", \"queryId\": other_query.id}]},\n        }\n\n        rv = self.make_request(\"post\", \"/api/queries\", data=query_data)\n        self.assertEqual(rv.status_code, 403)\n\n    def test_prevents_association_with_non_existing_dropdown_queries(self):\n        query_data = {\n            \"name\": \"Testing\",\n            \"query\": \"SELECT 1\",\n            \"schedule\": {\"interval\": \"3600\"},\n            \"data_source_id\": self.factory.data_source.id,\n            \"options\": {\"parameters\": [{\"type\": \"query\", \"queryId\": 100000}]},\n        }\n\n        rv = self.make_request(\"post\", \"/api/queries\", data=query_data)\n        self.assertEqual(rv.status_code, 400)\n\n\nclass TestQueryArchiveResourceGet(BaseTestCase):\n    def test_returns_queries(self):\n        q1 = self.factory.create_query(is_archived=True)\n        q2 = self.factory.create_query(is_archived=True)\n        self.factory.create_query()\n\n        rv = self.make_request(\"get\", \"/api/queries/archive\")\n\n        assert len(rv.json[\"results\"]) == 2\n        assert set([result[\"id\"] for result in rv.json[\"results\"]]) == {q1.id, q2.id}\n\n    def test_search_term(self):\n        q1 = self.factory.create_query(name=\"Sales\", is_archived=True)\n        q2 = self.factory.create_query(name=\"Q1 sales\", is_archived=True)\n        self.factory.create_query(name=\"Q2 sales\")\n\n        rv = self.make_request(\"get\", \"/api/queries/archive?q=sales\")\n        assert len(rv.json[\"results\"]) == 2\n        assert set([result[\"id\"] for result in rv.json[\"results\"]]) == {q1.id, q2.id}\n\n\nclass QueryRefreshTest(BaseTestCase):\n    def setUp(self):\n        super(QueryRefreshTest, self).setUp()\n\n        self.query = self.factory.create_query()\n        self.path = \"/api/queries/{}/refresh\".format(self.query.id)\n\n    def test_refresh_regular_query(self):\n        response = self.make_request(\"post\", self.path)\n        self.assertEqual(200, response.status_code)\n\n    def test_refresh_of_query_with_parameters(self):\n        self.query.query_text = \"SELECT {{param}}\"\n        db.session.add(self.query)\n\n        response = self.make_request(\"post\", \"{}?p_param=1\".format(self.path))\n        self.assertEqual(200, response.status_code)\n\n    def test_refresh_of_query_with_parameters_without_parameters(self):\n        self.query.query_text = \"SELECT {{param}}\"\n        db.session.add(self.query)\n\n        response = self.make_request(\"post\", \"{}\".format(self.path))\n        self.assertEqual(400, response.status_code)\n\n    def test_refresh_query_you_dont_have_access_to(self):\n        group = self.factory.create_group()\n        db.session.add(group)\n        db.session.commit()\n        user = self.factory.create_user(group_ids=[group.id])\n        response = self.make_request(\"post\", self.path, user=user)\n        self.assertEqual(403, response.status_code)\n\n    def test_refresh_forbiden_with_query_api_key(self):\n        response = self.make_request(\"post\", \"{}?api_key={}\".format(self.path, self.query.api_key), user=False)\n        self.assertEqual(403, response.status_code)\n\n        response = self.make_request(\n            \"post\",\n            \"{}?api_key={}\".format(self.path, self.factory.user.api_key),\n            user=False,\n        )\n        self.assertEqual(200, response.status_code)\n\n\nclass TestQueryRegenerateApiKey(BaseTestCase):\n    def test_non_admin_cannot_regenerate_api_key_of_other_user(self):\n        query_creator = self.factory.create_user()\n        query = self.factory.create_query(user=query_creator)\n        other_user = self.factory.create_user()\n        orig_api_key = query.api_key\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{}/regenerate_api_key\".format(query.id),\n            user=other_user,\n        )\n        self.assertEqual(rv.status_code, 403)\n\n        reloaded_query = models.Query.query.get(query.id)\n        self.assertEqual(orig_api_key, reloaded_query.api_key)\n\n    def test_admin_can_regenerate_api_key_of_other_user(self):\n        query_creator = self.factory.create_user()\n        query = self.factory.create_query(user=query_creator)\n        admin_user = self.factory.create_admin()\n        orig_api_key = query.api_key\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{}/regenerate_api_key\".format(query.id),\n            user=admin_user,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n        reloaded_query = models.Query.query.get(query.id)\n        self.assertNotEqual(orig_api_key, reloaded_query.api_key)\n\n    def test_admin_can_regenerate_api_key_of_myself(self):\n        query_creator = self.factory.create_user()\n        admin_user = self.factory.create_admin()\n        query = self.factory.create_query(user=query_creator)\n        orig_api_key = query.api_key\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{}/regenerate_api_key\".format(query.id),\n            user=admin_user,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n        updated_query = models.Query.query.get(query.id)\n        self.assertNotEqual(orig_api_key, updated_query.api_key)\n\n    def test_user_can_regenerate_api_key_of_myself(self):\n        user = self.factory.create_user()\n        query = self.factory.create_query(user=user)\n        orig_api_key = query.api_key\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/regenerate_api_key\".format(query.id), user=user)\n        self.assertEqual(rv.status_code, 200)\n\n        updated_query = models.Query.query.get(query.id)\n        self.assertNotEqual(orig_api_key, updated_query.api_key)\n\n\nclass TestQueryForkResourcePost(BaseTestCase):\n    def test_forks_a_query(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)\n        query = self.factory.create_query(data_source=ds)\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/fork\".format(query.id))\n\n        self.assertEqual(rv.status_code, 200)\n\n    def test_must_have_full_access_to_data_source(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)\n        query = self.factory.create_query(data_source=ds)\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/fork\".format(query.id))\n\n        self.assertEqual(rv.status_code, 403)\n\n\nclass TestFormatSQLQueryAPI(BaseTestCase):\n    def test_format_sql_query(self):\n        admin = self.factory.create_admin()\n        query = \"select a,b,c FROM foobar Where x=1 and y=2;\"\n        expected = \"\"\"SELECT a,\n       b,\n       c\nFROM foobar\nWHERE x=1\n  AND y=2;\"\"\"\n\n        rv = self.make_request(\"post\", \"/api/queries/format\", user=admin, data={\"query\": query})\n\n        self.assertEqual(rv.json[\"query\"], expected)\n"
  },
  {
    "path": "tests/handlers/test_query_results.py",
    "content": "from redash.handlers.query_results import error_messages, run_query\nfrom redash.models import db\nfrom tests import BaseTestCase\n\n\nclass TestRunQuery(BaseTestCase):\n    def test_run_query_with_no_data_source(self):\n        response, status = run_query(None, None, None, None, None)\n        self.assertDictEqual(response, error_messages[\"no_data_source\"][0])\n        self.assertEqual(status, error_messages[\"no_data_source\"][1])\n\n\nclass TestQueryResultsCacheHeaders(BaseTestCase):\n    def test_uses_cache_headers_for_specific_result(self):\n        query_result = self.factory.create_query_result()\n        query = self.factory.create_query(latest_query_data=query_result)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}/results/{}.json\".format(query.id, query_result.id))\n        self.assertIn(\"Cache-Control\", rv.headers)\n\n    def test_doesnt_use_cache_headers_for_non_specific_result(self):\n        query_result = self.factory.create_query_result()\n        query = self.factory.create_query(latest_query_data=query_result)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}/results.json\".format(query.id))\n        self.assertNotIn(\"Cache-Control\", rv.headers)\n\n    def test_returns_404_if_no_cached_result_found(self):\n        query = self.factory.create_query(latest_query_data=None)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}/results.json\".format(query.id))\n        self.assertEqual(404, rv.status_code)\n\n\nclass TestQueryResultsContentDispositionHeaders(BaseTestCase):\n    def test_supports_unicode(self):\n        query_result = self.factory.create_query_result()\n        query = self.factory.create_query(name=\"עברית\", latest_query_data=query_result)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}/results.json\".format(query.id))\n        # This is what gunicorn will do with it\n        try:\n            rv.headers[\"Content-Disposition\"].encode(\"ascii\")\n        except Exception as e:\n            self.fail(repr(e))\n\n\nclass TestQueryResultListAPI(BaseTestCase):\n    def test_get_existing_result(self):\n        query_result = self.factory.create_query_result()\n        query = self.factory.create_query()\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/query_results\",\n            data={\n                \"data_source_id\": self.factory.data_source.id,\n                \"query\": query.query_text,\n            },\n        )\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(query_result.id, rv.json[\"query_result\"][\"id\"])\n\n    def test_execute_new_query(self):\n        self.factory.create_query_result()\n        query = self.factory.create_query()\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/query_results\",\n            data={\n                \"data_source_id\": self.factory.data_source.id,\n                \"query\": query.query_text,\n                \"max_age\": 0,\n            },\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertNotIn(\"query_result\", rv.json)\n        self.assertIn(\"job\", rv.json)\n\n    def test_add_limit_change_query_sql(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, type=\"pg\")\n        query = self.factory.create_query(query_text=\"SELECT 2\", data_source=ds)\n        self.factory.create_query_result(data_source=ds, query_hash=query.query_hash)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/query_results\",\n            data={\"data_source_id\": ds.id, \"query\": query.query_text, \"apply_auto_limit\": True},\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertNotIn(\"query_result\", rv.json)\n        self.assertIn(\"job\", rv.json)\n\n    def test_add_limit_no_change_for_nonsql(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, type=\"prometheus\")\n        query = self.factory.create_query(query_text=\"SELECT 5\", data_source=ds)\n        query_result = self.factory.create_query_result(data_source=ds, query_hash=query.query_hash)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/query_results\",\n            data={\"data_source_id\": ds.id, \"query\": query.query_text, \"apply_auto_limit\": True},\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(query_result.id, rv.json[\"query_result\"][\"id\"])\n\n    def test_execute_query_without_access(self):\n        group = self.factory.create_group()\n        db.session.commit()\n        user = self.factory.create_user(group_ids=[group.id])\n        query = self.factory.create_query()\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/query_results\",\n            data={\n                \"data_source_id\": self.factory.data_source.id,\n                \"query\": query.query_text,\n                \"max_age\": 0,\n            },\n            user=user,\n        )\n\n        self.assertEqual(rv.status_code, 403)\n        self.assertIn(\"job\", rv.json)\n\n    def test_execute_query_with_params(self):\n        query = \"SELECT {{param}}\"\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/query_results\",\n            data={\n                \"data_source_id\": self.factory.data_source.id,\n                \"query\": query,\n                \"max_age\": 0,\n            },\n        )\n\n        self.assertEqual(rv.status_code, 400)\n        self.assertIn(\"job\", rv.json)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/query_results\",\n            data={\n                \"data_source_id\": self.factory.data_source.id,\n                \"query\": query,\n                \"parameters\": {\"param\": 1},\n                \"max_age\": 0,\n            },\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertIn(\"job\", rv.json)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/query_results?p_param=1\",\n            data={\n                \"data_source_id\": self.factory.data_source.id,\n                \"query\": query,\n                \"max_age\": 0,\n            },\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertIn(\"job\", rv.json)\n\n    def test_execute_on_paused_data_source(self):\n        self.factory.data_source.pause()\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/query_results\",\n            data={\n                \"data_source_id\": self.factory.data_source.id,\n                \"query\": \"SELECT 1\",\n                \"max_age\": 0,\n            },\n        )\n\n        self.assertEqual(rv.status_code, 400)\n        self.assertNotIn(\"query_result\", rv.json)\n        self.assertIn(\"job\", rv.json)\n\n    def test_execute_without_data_source(self):\n        rv = self.make_request(\"post\", \"/api/query_results\", data={\"query\": \"SELECT 1\", \"max_age\": 0})\n\n        self.assertEqual(rv.status_code, 401)\n        self.assertDictEqual(rv.json, error_messages[\"select_data_source\"][0])\n\n\nclass TestQueryResultAPI(BaseTestCase):\n    def test_has_no_access_to_data_source(self):\n        ds = self.factory.create_data_source(group=self.factory.create_group())\n        query_result = self.factory.create_query_result(data_source=ds)\n\n        rv = self.make_request(\"get\", \"/api/query_results/{}\".format(query_result.id))\n        self.assertEqual(rv.status_code, 403)\n\n    def test_has_view_only_access_to_data_source(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)\n        query_result = self.factory.create_query_result(data_source=ds)\n\n        rv = self.make_request(\"get\", \"/api/query_results/{}\".format(query_result.id))\n        self.assertEqual(rv.status_code, 200)\n\n    def test_has_full_access_to_data_source(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)\n        query_result = self.factory.create_query_result(data_source=ds)\n\n        rv = self.make_request(\"get\", \"/api/query_results/{}\".format(query_result.id))\n        self.assertEqual(rv.status_code, 200)\n\n    def test_execute_new_query(self):\n        query = self.factory.create_query()\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/results\".format(query.id), data={\"parameters\": {}})\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertIn(\"job\", rv.json)\n\n    def test_execute_but_has_no_access_to_data_source(self):\n        ds = self.factory.create_data_source(group=self.factory.create_group())\n        query = self.factory.create_query(data_source=ds)\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/results\".format(query.id))\n        self.assertEqual(rv.status_code, 403)\n        self.assertDictEqual(rv.json, error_messages[\"no_permission\"][0])\n\n    def test_execute_with_no_parameter_values(self):\n        query = self.factory.create_query()\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/results\".format(query.id))\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertIn(\"job\", rv.json)\n\n    def test_prevents_execution_of_unsafe_queries_on_view_only_data_sources(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)\n        query = self.factory.create_query(data_source=ds, options={\"parameters\": [{\"name\": \"foo\", \"type\": \"text\"}]})\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/results\".format(query.id), data={\"parameters\": {}})\n        self.assertEqual(rv.status_code, 403)\n        self.assertDictEqual(rv.json, error_messages[\"unsafe_on_view_only\"][0])\n\n    def test_allows_execution_of_safe_queries_on_view_only_data_sources(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)\n        query = self.factory.create_query(data_source=ds, options={\"parameters\": [{\"name\": \"foo\", \"type\": \"number\"}]})\n\n        rv = self.make_request(\"post\", \"/api/queries/{}/results\".format(query.id), data={\"parameters\": {}})\n        self.assertEqual(rv.status_code, 200)\n\n    def test_get_latest_query_result_with_apply_auto_limit(self):\n        query = self.factory.create_query(\n            options={\"parameters\": [{\"name\": \"foo\", \"type\": \"number\"}], \"apply_auto_limit\": True}\n        )\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{}/results\".format(query.id),\n            data={\"parameters\": {}, \"apply_auto_limit\": True},\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertIn(\"job\", rv.json)\n\n    def test_prevents_execution_of_unsafe_queries_using_api_key(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)\n        query = self.factory.create_query(data_source=ds, options={\"parameters\": [{\"name\": \"foo\", \"type\": \"text\"}]})\n\n        data = {\"parameters\": {\"foo\": \"bar\"}}\n        rv = self.make_request(\n            \"post\",\n            \"/api/queries/{}/results?api_key={}\".format(query.id, query.api_key),\n            data=data,\n        )\n        self.assertEqual(rv.status_code, 403)\n        self.assertDictEqual(rv.json, error_messages[\"unsafe_when_shared\"][0])\n\n    def test_access_with_query_api_key(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)\n        query = self.factory.create_query()\n        query_result = self.factory.create_query_result(data_source=ds, query_text=query.query_text)\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}/results/{}.json?api_key={}\".format(query.id, query_result.id, query.api_key),\n            user=False,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n    def test_access_with_query_api_key_without_query_result_id(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)\n        query = self.factory.create_query()\n        query_result = self.factory.create_query_result(\n            data_source=ds, query_text=query.query_text, query_hash=query.query_hash\n        )\n        query.latest_query_data = query_result\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}/results.json?api_key={}\".format(query.id, query.api_key),\n            user=False,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n    def test_query_api_key_and_different_query_result(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)\n        query = self.factory.create_query(query_text=\"SELECT 8\")\n        query_result2 = self.factory.create_query_result(data_source=ds, query_hash=\"something-different\")\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}/results/{}.json?api_key={}\".format(query.id, query_result2.id, query.api_key),\n            user=False,\n        )\n        self.assertEqual(rv.status_code, 404)\n\n    def test_signed_in_user_and_different_query_result(self):\n        ds2 = self.factory.create_data_source(group=self.factory.org.admin_group, view_only=False)\n        query = self.factory.create_query(query_text=\"SELECT 8\")\n        query_result2 = self.factory.create_query_result(data_source=ds2, query_hash=\"something-different\")\n\n        rv = self.make_request(\"get\", \"/api/queries/{}/results/{}.json\".format(query.id, query_result2.id))\n        self.assertEqual(rv.status_code, 403)\n\n\nclass TestQueryResultDropdownResource(BaseTestCase):\n    def test_checks_for_access_to_the_query(self):\n        ds2 = self.factory.create_data_source(group=self.factory.org.admin_group, view_only=False)\n        query = self.factory.create_query(data_source=ds2)\n\n        rv = self.make_request(\"get\", \"/api/queries/{}/dropdown\".format(query.id))\n\n        self.assertEqual(rv.status_code, 403)\n\n\nclass TestQueryDropdownsResource(BaseTestCase):\n    def test_prevents_access_if_unassociated_and_doesnt_have_access(self):\n        query = self.factory.create_query()\n        ds2 = self.factory.create_data_source(group=self.factory.org.admin_group, view_only=False)\n        unrelated_dropdown_query = self.factory.create_query(data_source=ds2)\n\n        # unrelated_dropdown_query has not been associated with query\n        # user does not have direct access to unrelated_dropdown_query\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}/dropdowns/{}\".format(query.id, unrelated_dropdown_query.id),\n        )\n\n        self.assertEqual(rv.status_code, 403)\n\n    def test_allows_access_if_unassociated_but_user_has_access(self):\n        query = self.factory.create_query()\n\n        query_result = self.factory.create_query_result()\n        data = {\"rows\": [], \"columns\": [{\"name\": \"whatever\"}]}\n        query_result = self.factory.create_query_result(data=data)\n        unrelated_dropdown_query = self.factory.create_query(latest_query_data=query_result)\n\n        # unrelated_dropdown_query has not been associated with query\n        # user has direct access to unrelated_dropdown_query\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}/dropdowns/{}\".format(query.id, unrelated_dropdown_query.id),\n        )\n\n        self.assertEqual(rv.status_code, 200)\n\n    def test_allows_access_if_associated_and_has_access_to_parent(self):\n        query_result = self.factory.create_query_result()\n        data = {\"rows\": [], \"columns\": [{\"name\": \"whatever\"}]}\n        query_result = self.factory.create_query_result(data=data)\n        dropdown_query = self.factory.create_query(latest_query_data=query_result)\n\n        options = {\"parameters\": [{\"name\": \"param\", \"type\": \"query\", \"queryId\": dropdown_query.id}]}\n        query = self.factory.create_query(options=options)\n\n        # dropdown_query has been associated with query\n        # user has access to query\n\n        rv = self.make_request(\"get\", \"/api/queries/{}/dropdowns/{}\".format(query.id, dropdown_query.id))\n\n        self.assertEqual(rv.status_code, 200)\n\n    def test_prevents_access_if_associated_and_doesnt_have_access_to_parent(self):\n        ds2 = self.factory.create_data_source(group=self.factory.org.admin_group, view_only=False)\n        dropdown_query = self.factory.create_query(data_source=ds2)\n        options = {\"parameters\": [{\"name\": \"param\", \"type\": \"query\", \"queryId\": dropdown_query.id}]}\n        query = self.factory.create_query(data_source=ds2, options=options)\n\n        # dropdown_query has been associated with query\n        # user doesnt have access to either query\n\n        rv = self.make_request(\"get\", \"/api/queries/{}/dropdowns/{}\".format(query.id, dropdown_query.id))\n\n        self.assertEqual(rv.status_code, 403)\n\n\nclass TestQueryResultExcelResponse(BaseTestCase):\n    def test_renders_excel_file(self):\n        query = self.factory.create_query()\n        query_result = self.factory.create_query_result()\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}/results/{}.xlsx\".format(query.id, query_result.id),\n            is_json=False,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n    def test_renders_excel_file_when_rows_have_missing_columns(self):\n        query = self.factory.create_query()\n        data = {\n            \"rows\": [{\"test\": 1}, {\"test\": 2, \"test2\": 3}],\n            \"columns\": [{\"name\": \"test\"}, {\"name\": \"test2\"}],\n        }\n        query_result = self.factory.create_query_result(data=data)\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}/results/{}.xlsx\".format(query.id, query_result.id),\n            is_json=False,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n\nclass TestJobResource(BaseTestCase):\n    def test_cancels_queued_queries(self):\n        QUEUED = 1\n        FAILED = 4\n\n        query = self.factory.create_query()\n        job_id = self.make_request(\n            \"post\",\n            f\"/api/queries/{query.id}/results\",\n            data={\"parameters\": {}},\n        ).json[\n            \"job\"\n        ][\"id\"]\n\n        status = self.make_request(\"get\", f\"/api/jobs/{job_id}\").json[\"job\"][\"status\"]\n        self.assertEqual(status, QUEUED)\n\n        self.make_request(\"delete\", f\"/api/jobs/{job_id}\")\n\n        job = self.make_request(\"get\", f\"/api/jobs/{job_id}\").json[\"job\"]\n        self.assertEqual(job[\"status\"], FAILED)\n        self.assertTrue(\"cancelled\" in job[\"error\"])\n"
  },
  {
    "path": "tests/handlers/test_query_snippets.py",
    "content": "from redash.models import QuerySnippet\nfrom tests import BaseTestCase\n\n\nclass TestQuerySnippetResource(BaseTestCase):\n    def test_get_snippet(self):\n        snippet = self.factory.create_query_snippet()\n\n        rv = self.make_request(\"get\", \"/api/query_snippets/{}\".format(snippet.id))\n\n        for field in (\"snippet\", \"description\", \"trigger\"):\n            self.assertEqual(rv.json[field], getattr(snippet, field))\n\n    def test_update_snippet(self):\n        snippet = self.factory.create_query_snippet()\n\n        data = {\n            \"snippet\": \"updated\",\n            \"trigger\": \"updated trigger\",\n            \"description\": \"updated description\",\n        }\n\n        rv = self.make_request(\"post\", \"/api/query_snippets/{}\".format(snippet.id), data=data)\n\n        for field in (\"snippet\", \"description\", \"trigger\"):\n            self.assertEqual(rv.json[field], data[field])\n\n    def test_delete_snippet(self):\n        snippet = self.factory.create_query_snippet()\n        self.make_request(\"delete\", \"/api/query_snippets/{}\".format(snippet.id))\n\n        self.assertIsNone(QuerySnippet.query.get(snippet.id))\n\n\nclass TestQuerySnippetListResource(BaseTestCase):\n    def test_create_snippet(self):\n        data = {\n            \"snippet\": \"updated\",\n            \"trigger\": \"updated trigger\",\n            \"description\": \"updated description\",\n        }\n\n        rv = self.make_request(\"post\", \"/api/query_snippets\", data=data)\n        self.assertEqual(rv.status_code, 200)\n\n    def test_list_all_snippets(self):\n        snippet1 = self.factory.create_query_snippet()\n        snippet2 = self.factory.create_query_snippet()\n        snippet_diff_org = self.factory.create_query_snippet(org=self.factory.create_org())\n\n        rv = self.make_request(\"get\", \"/api/query_snippets\")\n        ids = [s[\"id\"] for s in rv.json]\n\n        self.assertIn(snippet1.id, ids)\n        self.assertIn(snippet2.id, ids)\n        self.assertNotIn(snippet_diff_org.id, ids)\n"
  },
  {
    "path": "tests/handlers/test_settings.py",
    "content": "from redash.models import Organization\nfrom tests import BaseTestCase\n\n\nclass TestOrganizationSettings(BaseTestCase):\n    def test_post(self):\n        admin = self.factory.create_admin()\n        rv = self.make_request(\n            \"post\",\n            \"/api/settings/organization\",\n            data={\"auth_password_login_enabled\": False},\n            user=admin,\n        )\n        self.assertEqual(rv.json[\"settings\"][\"auth_password_login_enabled\"], False)\n        self.assertEqual(self.factory.org.settings[\"settings\"][\"auth_password_login_enabled\"], False)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/settings/organization\",\n            data={\"auth_password_login_enabled\": True},\n            user=admin,\n        )\n        updated_org = Organization.get_by_slug(self.factory.org.slug)\n        self.assertEqual(rv.json[\"settings\"][\"auth_password_login_enabled\"], True)\n        self.assertEqual(updated_org.settings[\"settings\"][\"auth_password_login_enabled\"], True)\n\n    def test_updates_google_apps_domains(self):\n        admin = self.factory.create_admin()\n        domains = [\"example.com\"]\n        self.make_request(\n            \"post\",\n            \"/api/settings/organization\",\n            data={\"auth_google_apps_domains\": domains},\n            user=admin,\n        )\n        updated_org = Organization.get_by_slug(self.factory.org.slug)\n        self.assertEqual(updated_org.google_apps_domains, domains)\n\n    def test_get_returns_google_appas_domains(self):\n        admin = self.factory.create_admin()\n        domains = [\"example.com\"]\n        admin.org.settings[Organization.SETTING_GOOGLE_APPS_DOMAINS] = domains\n\n        rv = self.make_request(\"get\", \"/api/settings/organization\", user=admin)\n        self.assertEqual(rv.json[\"settings\"][\"auth_google_apps_domains\"], domains)\n"
  },
  {
    "path": "tests/handlers/test_users.py",
    "content": "from mock import patch\n\nfrom redash import models\nfrom tests import BaseTestCase\n\n\nclass TestUserListResourcePost(BaseTestCase):\n    def test_returns_403_for_non_admin(self):\n        rv = self.make_request(\"post\", \"/api/users\")\n        self.assertEqual(rv.status_code, 403)\n\n    def test_returns_400_when_missing_fields(self):\n        admin = self.factory.create_admin()\n\n        rv = self.make_request(\"post\", \"/api/users\", user=admin)\n        self.assertEqual(rv.status_code, 400)\n\n        rv = self.make_request(\"post\", \"/api/users\", data={\"name\": \"User\"}, user=admin)\n        self.assertEqual(rv.status_code, 400)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/users\",\n            data={\"name\": \"User\", \"email\": \"bademailaddress\"},\n            user=admin,\n        )\n        self.assertEqual(rv.status_code, 400)\n\n    def test_returns_400_when_using_temporary_email(self):\n        admin = self.factory.create_admin()\n\n        test_user = {\"name\": \"User\", \"email\": \"user@mailinator.com\", \"password\": \"test\"}\n        rv = self.make_request(\"post\", \"/api/users\", data=test_user, user=admin)\n        self.assertEqual(rv.status_code, 400)\n\n        test_user[\"email\"] = \"arik@qq.com\"\n        rv = self.make_request(\"post\", \"/api/users\", data=test_user, user=admin)\n        self.assertEqual(rv.status_code, 400)\n\n    def test_creates_user(self):\n        admin = self.factory.create_admin()\n\n        test_user = {\"name\": \"User\", \"email\": \"user@example.com\", \"password\": \"test\"}\n        rv = self.make_request(\"post\", \"/api/users\", data=test_user, user=admin)\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"name\"], test_user[\"name\"])\n        self.assertEqual(rv.json[\"email\"], test_user[\"email\"])\n\n    @patch(\"redash.settings.email_server_is_configured\", return_value=False)\n    def test_shows_invite_link_when_email_is_not_configured(self, _):\n        admin = self.factory.create_admin()\n\n        test_user = {\"name\": \"User\", \"email\": \"user@example.com\"}\n        rv = self.make_request(\"post\", \"/api/users\", data=test_user, user=admin)\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertTrue(\"invite_link\" in rv.json)\n\n    @patch(\"redash.settings.email_server_is_configured\", return_value=True)\n    def test_does_not_show_invite_link_when_email_is_configured(self, _):\n        admin = self.factory.create_admin()\n\n        test_user = {\"name\": \"User\", \"email\": \"user@example.com\"}\n        rv = self.make_request(\"post\", \"/api/users\", data=test_user, user=admin)\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertFalse(\"invite_link\" in rv.json)\n\n    def test_creates_user_case_insensitive_email(self):\n        admin = self.factory.create_admin()\n\n        test_user = {\"name\": \"User\", \"email\": \"User@Example.com\", \"password\": \"test\"}\n        rv = self.make_request(\"post\", \"/api/users\", data=test_user, user=admin)\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"name\"], test_user[\"name\"])\n        self.assertEqual(rv.json[\"email\"], \"user@example.com\")\n\n    def test_returns_400_when_email_taken(self):\n        admin = self.factory.create_admin()\n\n        test_user = {\"name\": \"User\", \"email\": admin.email, \"password\": \"test\"}\n        rv = self.make_request(\"post\", \"/api/users\", data=test_user, user=admin)\n\n        self.assertEqual(rv.status_code, 400)\n\n    def test_returns_400_when_email_taken_case_insensitive(self):\n        admin = self.factory.create_admin()\n\n        test_user1 = {\"name\": \"User\", \"email\": \"user@example.com\", \"password\": \"test\"}\n        rv = self.make_request(\"post\", \"/api/users\", data=test_user1, user=admin)\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"email\"], \"user@example.com\")\n\n        test_user2 = {\"name\": \"User\", \"email\": \"user@Example.com\", \"password\": \"test\"}\n        rv = self.make_request(\"post\", \"/api/users\", data=test_user2, user=admin)\n\n        self.assertEqual(rv.status_code, 400)\n\n\nclass TestUserListGet(BaseTestCase):\n    def create_filters_fixtures(self):\n        class PlainObject:\n            pass\n\n        result = PlainObject()\n        now = models.db.func.now()\n\n        result.enabled_active1 = self.factory.create_user(disabled_at=None, is_invitation_pending=None).id\n        result.enabled_active2 = self.factory.create_user(disabled_at=None, is_invitation_pending=False).id\n        result.enabled_pending = self.factory.create_user(disabled_at=None, is_invitation_pending=True).id\n        result.disabled_active1 = self.factory.create_user(disabled_at=now, is_invitation_pending=None).id\n        result.disabled_active2 = self.factory.create_user(disabled_at=now, is_invitation_pending=False).id\n        result.disabled_pending = self.factory.create_user(disabled_at=now, is_invitation_pending=True).id\n\n        return result\n\n    def make_request_and_return_ids(self, *args, **kwargs):\n        rv = self.make_request(*args, **kwargs)\n        return [user[\"id\"] for user in rv.json[\"results\"]]\n\n    def assertUsersListMatches(self, actual_ids, expected_ids, unexpected_ids):\n        actual_ids = set(actual_ids)\n        expected_ids = set(expected_ids)\n        unexpected_ids = set(unexpected_ids)\n        self.assertSetEqual(actual_ids.intersection(expected_ids), expected_ids)\n        self.assertSetEqual(actual_ids.intersection(unexpected_ids), set())\n\n    def test_returns_users_for_given_org_only(self):\n        user1 = self.factory.user\n        user2 = self.factory.create_user()\n        org = self.factory.create_org()\n        user3 = self.factory.create_user(org=org)\n\n        user_ids = self.make_request_and_return_ids(\"get\", \"/api/users\")\n        self.assertUsersListMatches(user_ids, [user1.id, user2.id], [user3.id])\n\n    def test_gets_all_enabled(self):\n        users = self.create_filters_fixtures()\n        user_ids = self.make_request_and_return_ids(\"get\", \"/api/users\")\n        self.assertUsersListMatches(\n            user_ids,\n            [users.enabled_active1, users.enabled_active2, users.enabled_pending],\n            [users.disabled_active1, users.disabled_active2, users.disabled_pending],\n        )\n\n    def test_gets_all_disabled(self):\n        users = self.create_filters_fixtures()\n        user_ids = self.make_request_and_return_ids(\"get\", \"/api/users?disabled=true\")\n        self.assertUsersListMatches(\n            user_ids,\n            [users.disabled_active1, users.disabled_active2, users.disabled_pending],\n            [users.enabled_active1, users.enabled_active2, users.enabled_pending],\n        )\n\n    def test_gets_all_enabled_and_active(self):\n        users = self.create_filters_fixtures()\n        user_ids = self.make_request_and_return_ids(\"get\", \"/api/users?pending=false\")\n        self.assertUsersListMatches(\n            user_ids,\n            [users.enabled_active1, users.enabled_active2],\n            [\n                users.enabled_pending,\n                users.disabled_active1,\n                users.disabled_active2,\n                users.disabled_pending,\n            ],\n        )\n\n    def test_gets_all_enabled_and_pending(self):\n        users = self.create_filters_fixtures()\n        user_ids = self.make_request_and_return_ids(\"get\", \"/api/users?pending=true\")\n        self.assertUsersListMatches(\n            user_ids,\n            [users.enabled_pending],\n            [\n                users.enabled_active1,\n                users.enabled_active2,\n                users.disabled_active1,\n                users.disabled_active2,\n                users.disabled_pending,\n            ],\n        )\n\n    def test_gets_all_disabled_and_active(self):\n        users = self.create_filters_fixtures()\n        user_ids = self.make_request_and_return_ids(\"get\", \"/api/users?disabled=true&pending=false\")\n        self.assertUsersListMatches(\n            user_ids,\n            [users.disabled_active1, users.disabled_active2],\n            [\n                users.disabled_pending,\n                users.enabled_active1,\n                users.enabled_active2,\n                users.enabled_pending,\n            ],\n        )\n\n    def test_gets_all_disabled_and_pending(self):\n        users = self.create_filters_fixtures()\n        user_ids = self.make_request_and_return_ids(\"get\", \"/api/users?disabled=true&pending=true\")\n        self.assertUsersListMatches(\n            user_ids,\n            [users.disabled_pending],\n            [\n                users.disabled_active1,\n                users.disabled_active2,\n                users.enabled_active1,\n                users.enabled_active2,\n                users.enabled_pending,\n            ],\n        )\n\n\nclass TestUserResourceGet(BaseTestCase):\n    def test_returns_api_key_for_your_own_user(self):\n        rv = self.make_request(\"get\", \"/api/users/{}\".format(self.factory.user.id))\n        self.assertIn(\"api_key\", rv.json)\n\n    def test_returns_api_key_for_other_user_when_admin(self):\n        other_user = self.factory.user\n        admin = self.factory.create_admin()\n\n        rv = self.make_request(\"get\", \"/api/users/{}\".format(other_user.id), user=admin)\n        self.assertIn(\"api_key\", rv.json)\n\n    def test_doesnt_return_api_key_for_other_user(self):\n        other_user = self.factory.create_user()\n\n        rv = self.make_request(\"get\", \"/api/users/{}\".format(other_user.id))\n        self.assertNotIn(\"api_key\", rv.json)\n\n    def test_doesnt_return_user_from_different_org(self):\n        org = self.factory.create_org()\n        other_user = self.factory.create_user(org=org)\n\n        rv = self.make_request(\"get\", \"/api/users/{}\".format(other_user.id))\n        self.assertEqual(rv.status_code, 404)\n\n\nclass TestUserResourcePost(BaseTestCase):\n    def test_returns_403_for_non_admin_changing_not_his_own(self):\n        other_user = self.factory.create_user()\n\n        rv = self.make_request(\"post\", \"/api/users/{}\".format(other_user.id), data={\"name\": \"New Name\"})\n        self.assertEqual(rv.status_code, 403)\n\n    def test_returns_200_for_non_admin_changing_his_own(self):\n        rv = self.make_request(\n            \"post\",\n            \"/api/users/{}\".format(self.factory.user.id),\n            data={\"name\": \"New Name\"},\n        )\n        self.assertEqual(rv.status_code, 200)\n\n    @patch(\"redash.settings.email_server_is_configured\", return_value=True)\n    def test_marks_email_as_not_verified_when_changed(self, _):\n        user = self.factory.user\n        user.is_email_verified = True\n        self.make_request(\"post\", \"/api/users/{}\".format(user.id), data={\"email\": \"donald@trump.biz\"})\n        self.assertFalse(user.is_email_verified)\n\n    @patch(\"redash.settings.email_server_is_configured\", return_value=False)\n    def test_doesnt_mark_email_as_not_verified_when_changed_and_email_server_is_not_configured(self, _):\n        user = self.factory.user\n        user.is_email_verified = True\n        self.make_request(\"post\", \"/api/users/{}\".format(user.id), data={\"email\": \"donald@trump.biz\"})\n        self.assertTrue(user.is_email_verified)\n\n    def test_returns_200_for_admin_changing_other_user(self):\n        admin = self.factory.create_admin()\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/users/{}\".format(self.factory.user.id),\n            data={\"name\": \"New Name\"},\n            user=admin,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n    def test_fails_password_change_without_old_password(self):\n        rv = self.make_request(\n            \"post\",\n            \"/api/users/{}\".format(self.factory.user.id),\n            data={\"password\": \"new password\"},\n        )\n        self.assertEqual(rv.status_code, 403)\n\n    def test_fails_password_change_with_incorrect_old_password(self):\n        rv = self.make_request(\n            \"post\",\n            \"/api/users/{}\".format(self.factory.user.id),\n            data={\"password\": \"new password\", \"old_password\": \"wrong\"},\n        )\n        self.assertEqual(rv.status_code, 403)\n\n    def test_changes_password(self):\n        new_password = \"new password\"\n        old_password = \"old password\"\n\n        self.factory.user.hash_password(old_password)\n        models.db.session.add(self.factory.user)\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/users/{}\".format(self.factory.user.id),\n            data={\"password\": new_password, \"old_password\": old_password},\n        )\n        self.assertEqual(rv.status_code, 200)\n\n        user = models.User.query.get(self.factory.user.id)\n        self.assertTrue(user.verify_password(new_password))\n\n    def test_returns_400_when_using_temporary_email(self):\n        admin = self.factory.create_admin()\n\n        test_user = {\"email\": \"user@mailinator.com\"}\n        rv = self.make_request(\n            \"post\",\n            \"/api/users/{}\".format(self.factory.user.id),\n            data=test_user,\n            user=admin,\n        )\n        self.assertEqual(rv.status_code, 400)\n\n        test_user[\"email\"] = \"arik@qq.com\"\n        rv = self.make_request(\"post\", \"/api/users\", data=test_user, user=admin)\n        self.assertEqual(rv.status_code, 400)\n\n    def test_changing_email_ends_any_other_sessions_of_current_user(self):\n        with self.client as c:\n            # visit profile page\n            self.make_request(\"get\", \"/api/users/{}\".format(self.factory.user.id))\n            with c.session_transaction() as sess:\n                previous = sess[\"_user_id\"]\n\n            # change e-mail address - this will result in a new `user_id` value inside the session\n            self.make_request(\n                \"post\",\n                \"/api/users/{}\".format(self.factory.user.id),\n                data={\"email\": \"john@doe.com\"},\n            )\n\n        with self.app.test_client() as c:\n            # force the old `user_id`, simulating that the user is logged in from another browser\n            with c.session_transaction() as sess:\n                sess[\"_user_id\"] = previous\n            rv = self.get_request(\"/api/users/{}\".format(self.factory.user.id), client=c)\n\n            self.assertEqual(rv.status_code, 404)\n\n    def test_changing_email_does_not_end_current_session(self):\n        self.make_request(\"get\", \"/api/users/{}\".format(self.factory.user.id))\n\n        with self.client as c:\n            with c.session_transaction() as sess:\n                previous = sess[\"_user_id\"]\n\n        self.make_request(\n            \"post\",\n            \"/api/users/{}\".format(self.factory.user.id),\n            data={\"email\": \"john@doe.com\"},\n        )\n\n        with self.client as c:\n            with c.session_transaction() as sess:\n                current = sess[\"_user_id\"]\n\n        # make sure the session's `user_id` has changed to reflect the new identity, thus not logging the user out\n        self.assertNotEqual(previous, current)\n\n    def test_admin_can_change_user_groups(self):\n        admin_user = self.factory.create_admin()\n        other_user = self.factory.create_user(group_ids=[1])\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/users/{}\".format(other_user.id),\n            data={\"group_ids\": [1, 2]},\n            user=admin_user,\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(models.User.query.get(other_user.id).group_ids, [1, 2])\n\n    def test_admin_can_delete_user(self):\n        admin_user = self.factory.create_admin()\n        other_user = self.factory.create_user(is_invitation_pending=True)\n\n        rv = self.make_request(\"delete\", \"/api/users/{}\".format(other_user.id), user=admin_user)\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(models.User.query.get(other_user.id), None)\n\n\nclass TestUserDisable(BaseTestCase):\n    def test_non_admin_cannot_disable_user(self):\n        other_user = self.factory.create_user()\n        self.assertFalse(other_user.is_disabled)\n\n        rv = self.make_request(\"post\", \"/api/users/{}/disable\".format(other_user.id), user=other_user)\n        self.assertEqual(rv.status_code, 403)\n\n        # user should stay enabled\n        other_user = models.User.query.get(other_user.id)\n        self.assertFalse(other_user.is_disabled)\n\n    def test_admin_can_disable_user(self):\n        admin_user = self.factory.create_admin()\n        other_user = self.factory.create_user()\n        self.assertFalse(other_user.is_disabled)\n\n        rv = self.make_request(\"post\", \"/api/users/{}/disable\".format(other_user.id), user=admin_user)\n        self.assertEqual(rv.status_code, 200)\n\n        # user should become disabled\n        other_user = models.User.query.get(other_user.id)\n        self.assertTrue(other_user.is_disabled)\n\n    def test_admin_can_disable_another_admin(self):\n        admin_user1 = self.factory.create_admin()\n        admin_user2 = self.factory.create_admin()\n        self.assertFalse(admin_user2.is_disabled)\n\n        rv = self.make_request(\"post\", \"/api/users/{}/disable\".format(admin_user2.id), user=admin_user1)\n        self.assertEqual(rv.status_code, 200)\n\n        # user should become disabled\n        admin_user2 = models.User.query.get(admin_user2.id)\n        self.assertTrue(admin_user2.is_disabled)\n\n    def test_admin_cannot_disable_self(self):\n        admin_user = self.factory.create_admin()\n        self.assertFalse(admin_user.is_disabled)\n\n        rv = self.make_request(\"post\", \"/api/users/{}/disable\".format(admin_user.id), user=admin_user)\n        self.assertEqual(rv.status_code, 403)\n\n        # user should stay enabled\n        admin_user = models.User.query.get(admin_user.id)\n        self.assertFalse(admin_user.is_disabled)\n\n    def test_admin_can_enable_user(self):\n        admin_user = self.factory.create_admin()\n        other_user = self.factory.create_user(disabled_at=\"2018-03-08 00:00\")\n        self.assertTrue(other_user.is_disabled)\n\n        rv = self.make_request(\"delete\", \"/api/users/{}/disable\".format(other_user.id), user=admin_user)\n        self.assertEqual(rv.status_code, 200)\n\n        # user should become enabled\n        other_user = models.User.query.get(other_user.id)\n        self.assertFalse(other_user.is_disabled)\n\n    def test_admin_can_enable_another_admin(self):\n        admin_user1 = self.factory.create_admin()\n        admin_user2 = self.factory.create_admin(disabled_at=\"2018-03-08 00:00\")\n        self.assertTrue(admin_user2.is_disabled)\n\n        rv = self.make_request(\"delete\", \"/api/users/{}/disable\".format(admin_user2.id), user=admin_user1)\n        self.assertEqual(rv.status_code, 200)\n\n        # user should become enabled\n        admin_user2 = models.User.query.get(admin_user2.id)\n        self.assertFalse(admin_user2.is_disabled)\n\n    def test_disabled_user_cannot_login(self):\n        user = self.factory.create_user(disabled_at=\"2018-03-08 00:00\")\n        user.hash_password(\"password\")\n\n        self.db.session.add(user)\n        self.db.session.commit()\n\n        with patch(\"redash.handlers.authentication.login_user\") as login_user_mock:\n            rv = self.post_request(\n                \"/login\",\n                data={\"email\": user.email, \"password\": \"password\"},\n                org=self.factory.org,\n            )\n            # login handler should not be called\n            login_user_mock.assert_not_called()\n            # check if error is raised\n            self.assertEqual(rv.status_code, 200)\n            self.assertIn(\"Wrong email or password\", rv.data.decode())\n\n    def test_disabled_user_should_not_access_api(self):\n        # Note: some API does not require user, so check the one which requires\n\n        # 1. create user; the user should have access to API\n        user = self.factory.create_user()\n        rv = self.make_request(\"get\", \"/api/dashboards\", user=user)\n        self.assertEqual(rv.status_code, 200)\n\n        # 2. disable user; now API access should be forbidden\n        user.disable()\n        self.db.session.add(user)\n        self.db.session.commit()\n\n        rv = self.make_request(\"get\", \"/api/dashboards\", user=user)\n        self.assertNotEqual(rv.status_code, 200)\n\n    def test_disabled_user_should_not_receive_restore_password_email(self):\n        admin_user = self.factory.create_admin()\n\n        # user should receive email\n        user = self.factory.create_user()\n        with patch(\"redash.handlers.users.send_password_reset_email\") as send_password_reset_email_mock:\n            send_password_reset_email_mock.return_value = \"reset_token\"\n            rv = self.make_request(\"post\", \"/api/users/{}/reset_password\".format(user.id), user=admin_user)\n            self.assertEqual(rv.status_code, 200)\n            send_password_reset_email_mock.assert_called_with(user)\n\n        # disable user; now should not receive email\n        user.disable()\n        self.db.session.add(user)\n        self.db.session.commit()\n\n        with patch(\"redash.handlers.users.send_password_reset_email\") as send_password_reset_email_mock:\n            send_password_reset_email_mock.return_value = \"reset_token\"\n            rv = self.make_request(\"post\", \"/api/users/{}/reset_password\".format(user.id), user=admin_user)\n            self.assertEqual(rv.status_code, 404)\n            send_password_reset_email_mock.assert_not_called()\n\n\nclass TestUserRegenerateApiKey(BaseTestCase):\n    def test_non_admin_cannot_regenerate_other_user_api_key(self):\n        admin_user = self.factory.create_admin()\n        other_user = self.factory.create_user()\n        orig_api_key = other_user.api_key\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/users/{}/regenerate_api_key\".format(other_user.id),\n            user=admin_user,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n        other_user = models.User.query.get(other_user.id)\n        self.assertNotEqual(orig_api_key, other_user.api_key)\n\n    def test_admin_can_regenerate_other_user_api_key(self):\n        user1 = self.factory.create_user()\n        user2 = self.factory.create_user()\n        orig_user2_api_key = user2.api_key\n\n        rv = self.make_request(\"post\", \"/api/users/{}/regenerate_api_key\".format(user2.id), user=user1)\n        self.assertEqual(rv.status_code, 403)\n\n        user = models.User.query.get(user2.id)\n        self.assertEqual(orig_user2_api_key, user.api_key)\n\n    def test_admin_can_regenerate_api_key_myself(self):\n        admin_user = self.factory.create_admin()\n        orig_api_key = admin_user.api_key\n\n        rv = self.make_request(\n            \"post\",\n            \"/api/users/{}/regenerate_api_key\".format(admin_user.id),\n            user=admin_user,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n        user = models.User.query.get(admin_user.id)\n        self.assertNotEqual(orig_api_key, user.api_key)\n\n    def test_user_can_regenerate_api_key_myself(self):\n        user = self.factory.create_user()\n        orig_api_key = user.api_key\n\n        rv = self.make_request(\"post\", \"/api/users/{}/regenerate_api_key\".format(user.id), user=user)\n        self.assertEqual(rv.status_code, 200)\n\n        user = models.User.query.get(user.id)\n        self.assertNotEqual(orig_api_key, user.api_key)\n"
  },
  {
    "path": "tests/handlers/test_visualizations.py",
    "content": "from redash import models\nfrom tests import BaseTestCase\n\n\nclass VisualizationResourceTest(BaseTestCase):\n    def test_create_visualization(self):\n        query = self.factory.create_query()\n        models.db.session.commit()\n        data = {\n            \"query_id\": query.id,\n            \"name\": \"Chart\",\n            \"description\": \"\",\n            \"options\": {},\n            \"type\": \"CHART\",\n        }\n\n        rv = self.make_request(\"post\", \"/api/visualizations\", data=data)\n\n        self.assertEqual(rv.status_code, 200)\n        data.pop(\"query_id\")\n        self.assertEqual(rv.json, {**rv.json, **data})\n\n    def test_delete_visualization(self):\n        visualization = self.factory.create_visualization()\n        models.db.session.commit()\n        rv = self.make_request(\"delete\", \"/api/visualizations/{}\".format(visualization.id))\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(models.Visualization.query.count(), 0)\n\n    def test_update_visualization(self):\n        visualization = self.factory.create_visualization()\n        models.db.session.commit()\n        rv = self.make_request(\n            \"post\",\n            \"/api/visualizations/{0}\".format(visualization.id),\n            data={\"name\": \"After Update\"},\n        )\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"name\"], \"After Update\")\n\n    def test_only_owner_collaborator_or_admin_can_create_visualization(self):\n        query = self.factory.create_query()\n        other_user = self.factory.create_user()\n        admin = self.factory.create_admin()\n        admin_from_diff_org = self.factory.create_admin(org=self.factory.create_org())\n        models.db.session.commit()\n        models.db.session.refresh(admin)\n        models.db.session.refresh(other_user)\n        models.db.session.refresh(admin_from_diff_org)\n        data = {\n            \"query_id\": query.id,\n            \"name\": \"Chart\",\n            \"description\": \"\",\n            \"options\": {},\n            \"type\": \"CHART\",\n        }\n\n        rv = self.make_request(\"post\", \"/api/visualizations\", data=data, user=admin)\n        self.assertEqual(rv.status_code, 200)\n\n        rv = self.make_request(\"post\", \"/api/visualizations\", data=data, user=other_user)\n        self.assertEqual(rv.status_code, 403)\n\n        self.make_request(\n            \"post\",\n            \"/api/queries/{}/acl\".format(query.id),\n            data={\"access_type\": \"modify\", \"user_id\": other_user.id},\n        )\n        rv = self.make_request(\"post\", \"/api/visualizations\", data=data, user=other_user)\n        self.assertEqual(rv.status_code, 200)\n\n        rv = self.make_request(\"post\", \"/api/visualizations\", data=data, user=admin_from_diff_org)\n        self.assertEqual(rv.status_code, 404)\n\n    def test_only_owner_collaborator_or_admin_can_edit_visualization(self):\n        vis = self.factory.create_visualization()\n        models.db.session.flush()\n        path = \"/api/visualizations/{}\".format(vis.id)\n        data = {\"name\": \"After Update\"}\n\n        other_user = self.factory.create_user()\n        admin = self.factory.create_admin()\n        admin_from_diff_org = self.factory.create_admin(org=self.factory.create_org())\n        models.db.session.commit()\n        models.db.session.refresh(admin)\n        models.db.session.refresh(other_user)\n        models.db.session.refresh(admin_from_diff_org)\n\n        rv = self.make_request(\"post\", path, user=admin, data=data)\n        self.assertEqual(rv.status_code, 200)\n\n        rv = self.make_request(\"post\", path, user=other_user, data=data)\n        self.assertEqual(rv.status_code, 403)\n\n        self.make_request(\n            \"post\",\n            \"/api/queries/{}/acl\".format(vis.query_id),\n            data={\"access_type\": \"modify\", \"user_id\": other_user.id},\n        )\n        rv = self.make_request(\"post\", path, user=other_user, data=data)\n        self.assertEqual(rv.status_code, 200)\n\n        rv = self.make_request(\"post\", path, user=admin_from_diff_org, data=data)\n        self.assertEqual(rv.status_code, 404)\n\n    def test_only_owner_collaborator_or_admin_can_delete_visualization(self):\n        vis = self.factory.create_visualization()\n        models.db.session.flush()\n        path = \"/api/visualizations/{}\".format(vis.id)\n\n        other_user = self.factory.create_user()\n        admin = self.factory.create_admin()\n        admin_from_diff_org = self.factory.create_admin(org=self.factory.create_org())\n\n        models.db.session.commit()\n        models.db.session.refresh(admin)\n        models.db.session.refresh(other_user)\n        models.db.session.refresh(admin_from_diff_org)\n        rv = self.make_request(\"delete\", path, user=admin)\n        self.assertEqual(rv.status_code, 200)\n\n        vis = self.factory.create_visualization()\n        models.db.session.commit()\n        path = \"/api/visualizations/{}\".format(vis.id)\n\n        rv = self.make_request(\"delete\", path, user=other_user)\n        self.assertEqual(rv.status_code, 403)\n\n        self.make_request(\n            \"post\",\n            \"/api/queries/{}/acl\".format(vis.query_id),\n            data={\"access_type\": \"modify\", \"user_id\": other_user.id},\n        )\n\n        rv = self.make_request(\"delete\", path, user=other_user)\n        self.assertEqual(rv.status_code, 200)\n\n        vis = self.factory.create_visualization()\n        models.db.session.commit()\n        path = \"/api/visualizations/{}\".format(vis.id)\n\n        rv = self.make_request(\"delete\", path, user=admin_from_diff_org)\n        self.assertEqual(rv.status_code, 404)\n\n    def test_deleting_a_visualization_deletes_dashboard_widgets(self):\n        vis = self.factory.create_visualization()\n        widget = self.factory.create_widget(visualization=vis)\n\n        self.make_request(\"delete\", \"/api/visualizations/{}\".format(vis.id))\n\n        self.assertIsNone(models.Widget.query.filter(models.Widget.id == widget.id).first())\n"
  },
  {
    "path": "tests/handlers/test_widgets.py",
    "content": "from redash import models\nfrom tests import BaseTestCase\n\n\nclass WidgetAPITest(BaseTestCase):\n    def create_widget(self, dashboard, visualization, width=1):\n        data = {\n            \"visualization_id\": visualization.id,\n            \"dashboard_id\": dashboard.id,\n            \"options\": {},\n            \"width\": width,\n        }\n\n        rv = self.make_request(\"post\", \"/api/widgets\", data=data)\n\n        return rv\n\n    def test_create_widget(self):\n        dashboard = self.factory.create_dashboard()\n        vis = self.factory.create_visualization()\n\n        rv = self.create_widget(dashboard, vis)\n        self.assertEqual(rv.status_code, 200)\n\n    def test_wont_create_widget_for_visualization_you_dont_have_access_to(self):\n        dashboard = self.factory.create_dashboard()\n        vis = self.factory.create_visualization()\n        ds = self.factory.create_data_source(group=self.factory.create_group())\n        vis.query_rel.data_source = ds\n\n        models.db.session.add(vis.query_rel)\n\n        data = {\n            \"visualization_id\": vis.id,\n            \"dashboard_id\": dashboard.id,\n            \"options\": {},\n            \"width\": 1,\n        }\n\n        rv = self.make_request(\"post\", \"/api/widgets\", data=data)\n        self.assertEqual(rv.status_code, 403)\n\n    def test_create_text_widget(self):\n        dashboard = self.factory.create_dashboard()\n\n        data = {\n            \"visualization_id\": None,\n            \"text\": \"Sample text.\",\n            \"dashboard_id\": dashboard.id,\n            \"options\": {},\n            \"width\": 2,\n        }\n\n        rv = self.make_request(\"post\", \"/api/widgets\", data=data)\n\n        self.assertEqual(rv.status_code, 200)\n        self.assertEqual(rv.json[\"text\"], \"Sample text.\")\n\n    def test_delete_widget(self):\n        widget = self.factory.create_widget()\n\n        rv = self.make_request(\"delete\", \"/api/widgets/{0}\".format(widget.id))\n\n        self.assertEqual(rv.status_code, 200)\n        dashboard = models.Dashboard.get_by_slug_and_org(widget.dashboard.slug, widget.dashboard.org)\n        self.assertEqual(dashboard.widgets.count(), 0)\n"
  },
  {
    "path": "tests/metrics/__init__.py",
    "content": ""
  },
  {
    "path": "tests/metrics/test_database.py",
    "content": "from mock import ANY, patch\n\nfrom tests import BaseTestCase\n\n\n@patch(\"statsd.StatsClient.timing\")\nclass TestDatabaseMetrics(BaseTestCase):\n    def test_db_request_records_statsd_metrics(self, timing):\n        self.factory.create_query()\n        timing.assert_called_with(\"db.changes.insert\", ANY)\n"
  },
  {
    "path": "tests/metrics/test_request.py",
    "content": "from mock import ANY, patch\n\nfrom tests import BaseTestCase\n\n\n@patch(\"statsd.StatsClient.timing\")\nclass TestRequestMetrics(BaseTestCase):\n    def test_flask_request_records_statsd_metrics(self, timing):\n        self.client.get(\"/ping\")\n        timing.assert_called_once_with(\"requests.redash_ping.get\", ANY)\n"
  },
  {
    "path": "tests/models/__init__.py",
    "content": ""
  },
  {
    "path": "tests/models/test_alerts.py",
    "content": "import textwrap\nfrom unittest import TestCase\n\nfrom redash import settings\nfrom redash.models import OPERATORS, Alert, db, next_state\nfrom tests import BaseTestCase\n\n\nclass TestAlertAll(BaseTestCase):\n    def test_returns_all_alerts_for_given_groups(self):\n        ds1 = self.factory.data_source\n        group = self.factory.create_group()\n        ds2 = self.factory.create_data_source(group=group)\n\n        query1 = self.factory.create_query(data_source=ds1)\n        query2 = self.factory.create_query(data_source=ds2)\n\n        alert1 = self.factory.create_alert(query_rel=query1)\n        alert2 = self.factory.create_alert(query_rel=query2)\n        db.session.flush()\n\n        alerts = Alert.all(group_ids=[group.id, self.factory.default_group.id])\n        self.assertIn(alert1, alerts)\n        self.assertIn(alert2, alerts)\n\n        alerts = Alert.all(group_ids=[self.factory.default_group.id])\n        self.assertIn(alert1, alerts)\n        self.assertNotIn(alert2, alerts)\n\n        alerts = Alert.all(group_ids=[group.id])\n        self.assertNotIn(alert1, alerts)\n        self.assertIn(alert2, alerts)\n\n    def test_return_each_alert_only_once(self):\n        group = self.factory.create_group()\n        self.factory.data_source.add_group(group)\n\n        alert = self.factory.create_alert()\n\n        alerts = Alert.all(group_ids=[self.factory.default_group.id, group.id])\n        self.assertEqual(1, len(list(alerts)))\n        self.assertIn(alert, alerts)\n\n\ndef get_results(value):\n    return {\"rows\": [{\"foo\": value}], \"columns\": [{\"name\": \"foo\", \"type\": \"STRING\"}]}\n\n\nclass TestAlertEvaluate(BaseTestCase):\n    def create_alert(self, results, column=\"foo\", value=\"1\"):\n        result = self.factory.create_query_result(data=results)\n        query = self.factory.create_query(latest_query_data_id=result.id)\n        alert = self.factory.create_alert(\n            query_rel=query, options={\"selector\": \"first\", \"op\": \"equals\", \"column\": column, \"value\": value}\n        )\n        return alert\n\n    def test_evaluate_triggers_alert_when_equal(self):\n        alert = self.create_alert(get_results(1))\n        self.assertEqual(alert.evaluate(), Alert.TRIGGERED_STATE)\n\n    def test_evaluate_number_value_and_string_threshold(self):\n        alert = self.create_alert(get_results(1), value=\"string\")\n        self.assertEqual(alert.evaluate(), Alert.UNKNOWN_STATE)\n\n    def test_evaluate_return_unknown_when_missing_column(self):\n        alert = self.create_alert(get_results(1), column=\"bar\")\n        self.assertEqual(alert.evaluate(), Alert.UNKNOWN_STATE)\n\n    def test_evaluate_return_unknown_when_empty_results(self):\n        results = {\"rows\": [], \"columns\": [{\"name\": \"foo\", \"type\": \"STRING\"}]}\n        alert = self.create_alert(results)\n        self.assertEqual(alert.evaluate(), Alert.UNKNOWN_STATE)\n\n    def test_evaluates_correctly_with_first_selector(self):\n        results = {\"rows\": [{\"foo\": 1}, {\"foo\": 2}], \"columns\": [{\"name\": \"foo\", \"type\": \"INTEGER\"}]}\n        alert = self.create_alert(results)\n        alert.options[\"selector\"] = \"first\"\n        self.assertEqual(alert.evaluate(), Alert.TRIGGERED_STATE)\n        results = {\n            \"rows\": [{\"foo\": \"test\"}, {\"foo\": \"test\"}, {\"foo\": \"test\"}],\n            \"columns\": [{\"name\": \"foo\", \"type\": \"STRING\"}],\n        }\n        alert = self.create_alert(results)\n        alert.options[\"selector\"] = \"first\"\n        alert.options[\"op\"] = \"<\"\n        self.assertEqual(alert.evaluate(), Alert.UNKNOWN_STATE)\n\n    def test_evaluates_correctly_with_min_selector(self):\n        results = {\"rows\": [{\"foo\": 2}, {\"foo\": 1}], \"columns\": [{\"name\": \"foo\", \"type\": \"INTEGER\"}]}\n        alert = self.create_alert(results)\n        alert.options[\"selector\"] = \"min\"\n        self.assertEqual(alert.evaluate(), Alert.TRIGGERED_STATE)\n        results = {\n            \"rows\": [{\"foo\": \"test\"}, {\"foo\": \"test\"}, {\"foo\": \"test\"}],\n            \"columns\": [{\"name\": \"foo\", \"type\": \"STRING\"}],\n        }\n        alert = self.create_alert(results)\n        alert.options[\"selector\"] = \"min\"\n        self.assertEqual(alert.evaluate(), Alert.UNKNOWN_STATE)\n\n    def test_evaluates_correctly_with_max_selector(self):\n        results = {\"rows\": [{\"foo\": 1}, {\"foo\": 2}], \"columns\": [{\"name\": \"foo\", \"type\": \"INTEGER\"}]}\n        alert = self.create_alert(results)\n        alert.options[\"selector\"] = \"max\"\n        self.assertEqual(alert.evaluate(), Alert.OK_STATE)\n        results = {\n            \"rows\": [{\"foo\": \"test\"}, {\"foo\": \"test\"}, {\"foo\": \"test\"}],\n            \"columns\": [{\"name\": \"foo\", \"type\": \"STRING\"}],\n        }\n        alert = self.create_alert(results)\n        alert.options[\"selector\"] = \"max\"\n        self.assertEqual(alert.evaluate(), Alert.UNKNOWN_STATE)\n\n    def test_evaluate_alerts_without_query_rel(self):\n        query = self.factory.create_query(latest_query_data_id=None)\n        alert = self.factory.create_alert(\n            query_rel=query, options={\"selector\": \"first\", \"op\": \"equals\", \"column\": \"foo\", \"value\": \"1\"}\n        )\n        self.assertEqual(alert.evaluate(), Alert.UNKNOWN_STATE)\n\n    def test_evaluate_return_unknown_when_value_is_none(self):\n        alert = self.create_alert(get_results(None))\n        self.assertEqual(alert.evaluate(), Alert.UNKNOWN_STATE)\n\n\nclass TestNextState(TestCase):\n    def test_numeric_value(self):\n        self.assertEqual(Alert.TRIGGERED_STATE, next_state(OPERATORS.get(\"==\"), 1, \"1\"))\n        self.assertEqual(Alert.TRIGGERED_STATE, next_state(OPERATORS.get(\"==\"), 1, \"1.0\"))\n        self.assertEqual(Alert.TRIGGERED_STATE, next_state(OPERATORS.get(\">\"), \"5\", 1))\n\n    def test_numeric_value_and_plain_string(self):\n        self.assertEqual(Alert.UNKNOWN_STATE, next_state(OPERATORS.get(\"==\"), 1, \"string\"))\n\n    def test_non_numeric_value(self):\n        self.assertEqual(Alert.OK_STATE, next_state(OPERATORS.get(\"==\"), \"string\", \"1.0\"))\n\n    def test_string_value(self):\n        self.assertEqual(Alert.TRIGGERED_STATE, next_state(OPERATORS.get(\"==\"), \"string\", \"string\"))\n\n    def test_boolean_value(self):\n        self.assertEqual(Alert.TRIGGERED_STATE, next_state(OPERATORS.get(\"==\"), False, \"false\"))\n        self.assertEqual(Alert.TRIGGERED_STATE, next_state(OPERATORS.get(\"!=\"), False, \"true\"))\n\n\nclass TestAlertRenderTemplate(BaseTestCase):\n    def create_alert(self, results, column=\"foo\", value=\"5\"):\n        result = self.factory.create_query_result(data=results)\n        query = self.factory.create_query(latest_query_data_id=result.id)\n        alert = self.factory.create_alert(\n            query_rel=query, options={\"selector\": \"first\", \"op\": \"equals\", \"column\": column, \"value\": value}\n        )\n        return alert\n\n    def test_render_custom_alert_template(self):\n        alert = self.create_alert(get_results(1))\n        custom_alert = \"\"\"\n        <pre>\n        ALERT_STATUS        {{ALERT_STATUS}}\n        ALERT_SELECTOR      {{ALERT_SELECTOR}}\n        ALERT_CONDITION     {{ALERT_CONDITION}}\n        ALERT_THRESHOLD     {{ALERT_THRESHOLD}}\n        ALERT_NAME          {{ALERT_NAME}}\n        ALERT_URL           {{{ALERT_URL}}}\n        QUERY_NAME          {{QUERY_NAME}}\n        QUERY_URL           {{{QUERY_URL}}}\n        QUERY_RESULT_VALUE  {{QUERY_RESULT_VALUE}}\n        QUERY_RESULT_ROWS   {{{QUERY_RESULT_ROWS}}}\n        QUERY_RESULT_COLS   {{{QUERY_RESULT_COLS}}}\n        </pre>\n        \"\"\"\n        expected = \"\"\"\n        <pre>\n        ALERT_STATUS        UNKNOWN\n        ALERT_SELECTOR      first\n        ALERT_CONDITION     equals\n        ALERT_THRESHOLD     5\n        ALERT_NAME          %s\n        ALERT_URL           %s/default/alerts/%d\n        QUERY_NAME          Query\n        QUERY_URL           %s/default/queries/%d\n        QUERY_RESULT_VALUE  1\n        QUERY_RESULT_ROWS   [{'foo': 1}]\n        QUERY_RESULT_COLS   [{'name': 'foo', 'type': 'STRING'}]\n        </pre>\n        \"\"\" % (\n            alert.name,\n            settings.HOST,\n            alert.id,\n            settings.HOST,\n            alert.query_id,\n        )\n        result = alert.render_template(textwrap.dedent(custom_alert))\n        self.assertMultiLineEqual(result, textwrap.dedent(expected))\n\n    def test_render_custom_alert_template_query_table(self):\n        alert = self.create_alert(get_results(1))\n        custom_alert = \"\"\"\n        <table>\n        {{#QUERY_RESULT_TABLE}}\n          <tr>\n            {{#.}}\n            <td>{{.}}</td>\n            {{/.}}\n          </tr>\n        {{/QUERY_RESULT_TABLE}}\n        </table>\n        \"\"\"\n        expected = \"\"\"\n        <table>\n          <tr>\n            <td>1</td>\n          </tr>\n        </table>\n        \"\"\"\n        result = alert.render_template(textwrap.dedent(custom_alert))\n        self.assertMultiLineEqual(result, textwrap.dedent(expected))\n"
  },
  {
    "path": "tests/models/test_api_keys.py",
    "content": "from redash.models import ApiKey\nfrom tests import BaseTestCase\n\n\nclass TestApiKeyGetByObject(BaseTestCase):\n    def test_returns_none_if_not_exists(self):\n        dashboard = self.factory.create_dashboard()\n        self.assertIsNone(ApiKey.get_by_object(dashboard))\n\n    def test_returns_only_active_key(self):\n        dashboard = self.factory.create_dashboard()\n        api_key = self.factory.create_api_key(object=dashboard, active=False)\n        self.assertIsNone(ApiKey.get_by_object(dashboard))\n\n        api_key = self.factory.create_api_key(object=dashboard)\n        self.assertEqual(api_key, ApiKey.get_by_object(dashboard))\n"
  },
  {
    "path": "tests/models/test_changes.py",
    "content": "from redash.models import Change, ChangeTrackingMixin, Query, db\nfrom tests import BaseTestCase\n\n\ndef create_object(factory):\n    obj = Query(\n        name=\"Query\",\n        description=\"\",\n        query_text=\"SELECT 1\",\n        user=factory.user,\n        data_source=factory.data_source,\n        org=factory.org,\n    )\n\n    return obj\n\n\nclass TestChangesProperty(BaseTestCase):\n    def test_returns_initial_state(self):\n        obj = create_object(self.factory)\n\n        for change in Change.query.filter(Change.object == obj):\n            self.assertIsNone(change.change[\"previous\"])\n\n\nclass TestLogChange(BaseTestCase):\n    def obj(self):\n        obj = Query(\n            name=\"Query\",\n            description=\"\",\n            query_text=\"SELECT 1\",\n            user=self.factory.user,\n            data_source=self.factory.data_source,\n            org=self.factory.org,\n        )\n\n        return obj\n\n    def test_properly_logs_first_creation(self):\n        obj = create_object(self.factory)\n        obj.record_changes(changed_by=self.factory.user)\n        change = Change.last_change(obj)\n\n        self.assertIsNotNone(change)\n        self.assertEqual(change.object_version, 1)\n\n    def test_skips_unnecessary_fields(self):\n        obj = create_object(self.factory)\n        obj.record_changes(changed_by=self.factory.user)\n        change = Change.last_change(obj)\n\n        self.assertIsNotNone(change)\n        self.assertEqual(change.object_version, 1)\n        for field in ChangeTrackingMixin.skipped_fields:\n            self.assertNotIn(field, change.change)\n\n    def test_properly_log_modification(self):\n        obj = create_object(self.factory)\n        obj.record_changes(changed_by=self.factory.user)\n        obj.name = \"Query 2\"\n        obj.description = \"description\"\n        db.session.flush()\n        obj.record_changes(changed_by=self.factory.user)\n\n        change = Change.last_change(obj)\n\n        self.assertIsNotNone(change)\n        # TODO: https://github.com/getredash/redash/issues/1550\n        # self.assertEqual(change.object_version, 2)\n        self.assertEqual(change.object_version, obj.version)\n        self.assertIn(\"name\", change.change)\n        self.assertIn(\"description\", change.change)\n\n    def test_logs_create_method(self):\n        q = Query(\n            name=\"Query\",\n            description=\"\",\n            query_text=\"\",\n            user=self.factory.user,\n            data_source=self.factory.data_source,\n            org=self.factory.org,\n        )\n        change = Change.last_change(q)\n\n        self.assertIsNotNone(change)\n        self.assertEqual(q.user, change.user)\n"
  },
  {
    "path": "tests/models/test_dashboards.py",
    "content": "from redash.models import Dashboard, db\nfrom tests import BaseTestCase\n\n\nclass DashboardTest(BaseTestCase):\n    def create_tagged_dashboard(self, tags):\n        dashboard = self.factory.create_dashboard(tags=tags)\n        ds = self.factory.create_data_source(group=self.factory.default_group)\n        query = self.factory.create_query(data_source=ds)\n        # We need a bunch of visualizations and widgets configured\n        # to trigger wrong counts via the left outer joins.\n        vis1 = self.factory.create_visualization(query_rel=query)\n        vis2 = self.factory.create_visualization(query_rel=query)\n        vis3 = self.factory.create_visualization(query_rel=query)\n        widget1 = self.factory.create_widget(visualization=vis1, dashboard=dashboard)\n        widget2 = self.factory.create_widget(visualization=vis2, dashboard=dashboard)\n        widget3 = self.factory.create_widget(visualization=vis3, dashboard=dashboard)\n        dashboard.layout = [[widget1.id, widget2.id, widget3.id]]\n        db.session.commit()\n        return dashboard\n\n    def test_all_tags(self):\n        self.create_tagged_dashboard(tags=[\"tag1\"])\n        self.create_tagged_dashboard(tags=[\"tag1\", \"tag2\"])\n        self.create_tagged_dashboard(tags=[\"tag1\", \"tag2\", \"tag3\"])\n\n        self.assertEqual(\n            list(Dashboard.all_tags(self.factory.org, self.factory.user)),\n            [(\"tag1\", 3), (\"tag2\", 2), (\"tag3\", 1)],\n        )\n\n\nclass TestDashboardsByUser(BaseTestCase):\n    def test_returns_only_users_dashboards(self):\n        d = self.factory.create_dashboard(user=self.factory.user)\n        d2 = self.factory.create_dashboard(user=self.factory.create_user())\n\n        dashboards = Dashboard.by_user(self.factory.user)\n\n        # not using self.assertIn/NotIn because otherwise this fails :O\n        self.assertTrue(d in list(dashboards))\n        self.assertFalse(d2 in list(dashboards))\n\n    def test_returns_drafts_by_the_user(self):\n        d = self.factory.create_dashboard(is_draft=True)\n        d2 = self.factory.create_dashboard(is_draft=True, user=self.factory.create_user())\n\n        dashboards = Dashboard.by_user(self.factory.user)\n\n        # not using self.assertIn/NotIn because otherwise this fails :O\n        self.assertTrue(d in dashboards)\n        self.assertFalse(d2 in dashboards)\n\n    def test_returns_correct_number_of_dashboards(self):\n        # Solving https://github.com/getredash/redash/issues/5466\n\n        usr = self.factory.create_user()\n\n        ds1 = self.factory.create_data_source()\n        ds2 = self.factory.create_data_source()\n\n        qry1 = self.factory.create_query(data_source=ds1, user=usr)\n        qry2 = self.factory.create_query(data_source=ds2, user=usr)\n\n        viz1 = self.factory.create_visualization(\n            query_rel=qry1,\n        )\n        viz2 = self.factory.create_visualization(\n            query_rel=qry2,\n        )\n\n        def create_dashboard():\n            dash = self.factory.create_dashboard(name=\"boy howdy\", user=usr)\n            self.factory.create_widget(dashboard=dash, visualization=viz1)\n            self.factory.create_widget(dashboard=dash, visualization=viz2)\n\n            return dash\n\n        create_dashboard()\n        create_dashboard()\n\n        results = Dashboard.all(self.factory.org, usr.group_ids, usr.id)\n\n        self.assertEqual(2, results.count(), \"The incorrect number of dashboards were returned\")\n"
  },
  {
    "path": "tests/models/test_data_sources.py",
    "content": "import mock\nfrom mock import patch\n\nfrom redash.models import DataSource, Query, QueryResult\nfrom redash.utils.configuration import ConfigurationContainer\nfrom tests import BaseTestCase\n\n\nclass DataSourceTest(BaseTestCase):\n    def test_get_schema(self):\n        return_value = [{\"name\": \"table\", \"columns\": []}]\n\n        with mock.patch(\"redash.query_runner.pg.PostgreSQL.get_schema\") as patched_get_schema:\n            patched_get_schema.return_value = return_value\n\n            schema = self.factory.data_source.get_schema()\n\n            self.assertEqual(return_value, schema)\n\n    def test_get_schema_uses_cache(self):\n        return_value = [{\"name\": \"table\", \"columns\": []}]\n        with mock.patch(\"redash.query_runner.pg.PostgreSQL.get_schema\") as patched_get_schema:\n            patched_get_schema.return_value = return_value\n\n            self.factory.data_source.get_schema()\n            schema = self.factory.data_source.get_schema()\n\n            self.assertEqual(return_value, schema)\n            self.assertEqual(patched_get_schema.call_count, 1)\n\n    def test_get_schema_skips_cache_with_refresh_true(self):\n        return_value = [{\"name\": \"table\", \"columns\": []}]\n        with mock.patch(\"redash.query_runner.pg.PostgreSQL.get_schema\") as patched_get_schema:\n            patched_get_schema.return_value = return_value\n\n            self.factory.data_source.get_schema()\n            new_return_value = [{\"name\": \"new_table\", \"columns\": []}]\n            patched_get_schema.return_value = new_return_value\n            schema = self.factory.data_source.get_schema(refresh=True)\n\n            self.assertEqual(new_return_value, schema)\n            self.assertEqual(patched_get_schema.call_count, 2)\n\n    def test_schema_sorter(self):\n        input_data = [\n            {\"name\": \"zoo\", \"columns\": [\"is_zebra\", \"is_snake\", \"is_cow\"]},\n            {\n                \"name\": \"all_terain_vehicle\",\n                \"columns\": [\"has_wheels\", \"has_engine\", \"has_all_wheel_drive\"],\n            },\n        ]\n\n        expected_output = [\n            {\n                \"name\": \"all_terain_vehicle\",\n                \"columns\": [\"has_all_wheel_drive\", \"has_engine\", \"has_wheels\"],\n            },\n            {\"name\": \"zoo\", \"columns\": [\"is_cow\", \"is_snake\", \"is_zebra\"]},\n        ]\n\n        real_output = self.factory.data_source._sort_schema(input_data)\n\n        self.assertEqual(real_output, expected_output)\n\n    def test_model_uses_schema_sorter(self):\n        orig_schema = [\n            {\"name\": \"zoo\", \"columns\": [\"is_zebra\", \"is_snake\", \"is_cow\"]},\n            {\n                \"name\": \"all_terain_vehicle\",\n                \"columns\": [\"has_wheels\", \"has_engine\", \"has_all_wheel_drive\"],\n            },\n        ]\n\n        sorted_schema = [\n            {\n                \"name\": \"all_terain_vehicle\",\n                \"columns\": [\"has_all_wheel_drive\", \"has_engine\", \"has_wheels\"],\n            },\n            {\"name\": \"zoo\", \"columns\": [\"is_cow\", \"is_snake\", \"is_zebra\"]},\n        ]\n\n        with mock.patch(\"redash.query_runner.pg.PostgreSQL.get_schema\") as patched_get_schema:\n            patched_get_schema.return_value = orig_schema\n\n            out_schema = self.factory.data_source.get_schema()\n\n            self.assertEqual(out_schema, sorted_schema)\n\n    @patch(\"redash.redis_connection.set\")\n    def test_expires_schema(self, mock_redis):\n        # default of 30min + 7 days\n        expected_ttl = 606600\n\n        with mock.patch(\"redash.query_runner.pg.PostgreSQL.get_schema\") as patched_get_schema:\n            patched_get_schema.return_value = None\n            self.factory.data_source.get_schema(refresh=True)\n\n        mock_redis.assert_called_with(\"data_source:schema:1\", \"null\", ex=expected_ttl)\n\n\nclass TestDataSourceCreate(BaseTestCase):\n    def test_adds_data_source_to_default_group(self):\n        data_source = DataSource.create_with_group(\n            org=self.factory.org,\n            name=\"test\",\n            options=ConfigurationContainer.from_json('{\"dbname\": \"test\"}'),\n            type=\"pg\",\n        )\n        self.assertIn(self.factory.org.default_group.id, data_source.groups)\n\n\nclass TestDataSourceIsPaused(BaseTestCase):\n    def test_returns_false_by_default(self):\n        self.assertFalse(self.factory.data_source.paused)\n\n    def test_persists_selection(self):\n        self.factory.data_source.pause()\n        self.assertTrue(self.factory.data_source.paused)\n\n        self.factory.data_source.resume()\n        self.assertFalse(self.factory.data_source.paused)\n\n    def test_allows_setting_reason(self):\n        reason = \"Some good reason.\"\n        self.factory.data_source.pause(reason)\n        self.assertTrue(self.factory.data_source.paused)\n        self.assertEqual(self.factory.data_source.pause_reason, reason)\n\n    def test_resume_clears_reason(self):\n        self.factory.data_source.pause(\"Reason\")\n        self.factory.data_source.resume()\n        self.assertEqual(self.factory.data_source.pause_reason, None)\n\n    def test_reason_is_none_by_default(self):\n        self.assertEqual(self.factory.data_source.pause_reason, None)\n\n\nclass TestDataSourceDelete(BaseTestCase):\n    def test_deletes_the_data_source(self):\n        data_source = self.factory.create_data_source()\n        data_source.delete()\n\n        self.assertIsNone(DataSource.query.get(data_source.id))\n\n    def test_sets_queries_data_source_to_null(self):\n        data_source = self.factory.create_data_source()\n        query = self.factory.create_query(data_source=data_source)\n\n        data_source.delete()\n        self.assertIsNone(DataSource.query.get(data_source.id))\n        self.assertIsNone(Query.query.get(query.id).data_source_id)\n\n    def test_deletes_child_models(self):\n        data_source = self.factory.create_data_source()\n        self.factory.create_query_result(data_source=data_source)\n        self.factory.create_query(\n            data_source=data_source,\n            latest_query_data=self.factory.create_query_result(data_source=data_source),\n        )\n\n        data_source.delete()\n        self.assertIsNone(DataSource.query.get(data_source.id))\n        self.assertEqual(0, QueryResult.query.filter(QueryResult.data_source == data_source).count())\n\n    @patch(\"redash.redis_connection.delete\")\n    def test_deletes_schema(self, mock_redis):\n        data_source = self.factory.create_data_source()\n        data_source.delete()\n\n        mock_redis.assert_called_with(data_source._schema_key)\n"
  },
  {
    "path": "tests/models/test_parameterized_query.py",
    "content": "from collections import namedtuple\nfrom unittest import TestCase\n\nimport pytest\nfrom mock import patch\n\nfrom redash.models.parameterized_query import (\n    InvalidParameterError,\n    ParameterizedQuery,\n    QueryDetachedFromDataSourceError,\n    dropdown_values,\n)\n\n\nclass TestParameterizedQuery(TestCase):\n    def test_returns_empty_list_for_regular_query(self):\n        query = ParameterizedQuery(\"SELECT 1\")\n        self.assertEqual(set([]), query.missing_params)\n\n    def test_finds_all_params_when_missing(self):\n        query = ParameterizedQuery(\"SELECT {{param}} FROM {{table}}\")\n        self.assertEqual(set([\"param\", \"table\"]), query.missing_params)\n\n    def test_finds_all_params(self):\n        query = ParameterizedQuery(\"SELECT {{param}} FROM {{table}}\").apply({\"param\": \"value\", \"table\": \"value\"})\n        self.assertEqual(set([]), query.missing_params)\n\n    def test_deduplicates_params(self):\n        query = ParameterizedQuery(\"SELECT {{param}}, {{param}} FROM {{table}}\").apply(\n            {\"param\": \"value\", \"table\": \"value\"}\n        )\n        self.assertEqual(set([]), query.missing_params)\n\n    def test_handles_nested_params(self):\n        query = ParameterizedQuery(\n            \"SELECT {{param}}, {{param}} FROM {{table}} -- {{#test}} {{nested_param}} {{/test}}\"\n        ).apply({\"param\": \"value\", \"table\": \"value\"})\n        self.assertEqual(set([\"test\", \"nested_param\"]), query.missing_params)\n\n    def test_handles_objects(self):\n        query = ParameterizedQuery(\n            \"SELECT * FROM USERS WHERE created_at between '{{ created_at.start }}' and '{{ created_at.end }}'\"\n        ).apply({\"created_at\": {\"start\": 1, \"end\": 2}})\n        self.assertEqual(set([]), query.missing_params)\n\n    def test_raises_on_parameters_not_in_schema(self):\n        schema = [{\"name\": \"bar\", \"type\": \"text\"}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"qux\": 7})\n\n    def test_raises_on_invalid_text_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"text\"}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": 7})\n\n    @patch(\"redash.models.parameterized_query._is_number\", side_effect=ArithmeticError)\n    def test_raises_on_unexpected_validation_error(self, _):\n        schema = [{\"name\": \"bar\", \"type\": \"number\"}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": 5})\n\n    def test_validates_text_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"text\"}]\n        query = ParameterizedQuery(\"foo {{bar}}\", schema)\n\n        query.apply({\"bar\": \"baz\"})\n\n        self.assertEqual(\"foo baz\", query.text)\n\n    def test_validates_text_pattern_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"text-pattern\", \"regex\": \"a+\"}]\n        query = ParameterizedQuery(\"foo {{bar}}\", schema)\n\n        query.apply({\"bar\": \"a\"})\n\n        self.assertEqual(\"foo a\", query.text)\n\n    def test_raises_on_invalid_text_pattern_parameters(self):\n        schema = schema = [{\"name\": \"bar\", \"type\": \"text-pattern\", \"regex\": \"a+\"}]\n        query = ParameterizedQuery(\"foo {{bar}}\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": \"b\"})\n\n    def test_raises_on_invalid_number_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"number\"}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": \"baz\"})\n\n    def test_validates_number_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"number\"}]\n        query = ParameterizedQuery(\"foo {{bar}}\", schema)\n\n        query.apply({\"bar\": 7})\n\n        self.assertEqual(\"foo 7\", query.text)\n\n    def test_coerces_number_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"number\"}]\n        query = ParameterizedQuery(\"foo {{bar}}\", schema)\n\n        query.apply({\"bar\": \"3.14\"})\n\n        self.assertEqual(\"foo 3.14\", query.text)\n\n    def test_raises_on_invalid_date_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"date\"}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": \"baz\"})\n\n    def test_raises_on_none_for_date_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"date\"}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": None})\n\n    def test_validates_date_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"date\"}]\n        query = ParameterizedQuery(\"foo {{bar}}\", schema)\n\n        query.apply({\"bar\": \"2000-01-01 12:00:00\"})\n\n        self.assertEqual(\"foo 2000-01-01 12:00:00\", query.text)\n\n    def test_raises_on_invalid_enum_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"enum\", \"enumOptions\": [\"baz\", \"qux\"]}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": 7})\n\n    def test_raises_on_unlisted_enum_value_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"enum\", \"enumOptions\": [\"baz\", \"qux\"]}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": \"shlomo\"})\n\n    def test_raises_on_unlisted_enum_list_value_parameters(self):\n        schema = [\n            {\n                \"name\": \"bar\",\n                \"type\": \"enum\",\n                \"enumOptions\": [\"baz\", \"qux\"],\n                \"multiValuesOptions\": {\"separator\": \",\", \"prefix\": \"\", \"suffix\": \"\"},\n            }\n        ]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": [\"shlomo\", \"baz\"]})\n\n    def test_validates_enum_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"enum\", \"enumOptions\": [\"baz\", \"qux\"]}]\n        query = ParameterizedQuery(\"foo {{bar}}\", schema)\n\n        query.apply({\"bar\": \"baz\"})\n\n        self.assertEqual(\"foo baz\", query.text)\n\n    def test_validates_enum_list_value_parameters(self):\n        schema = [\n            {\n                \"name\": \"bar\",\n                \"type\": \"enum\",\n                \"enumOptions\": [\"baz\", \"qux\"],\n                \"multiValuesOptions\": {\"separator\": \",\", \"prefix\": \"'\", \"suffix\": \"'\"},\n            }\n        ]\n        query = ParameterizedQuery(\"foo {{bar}}\", schema)\n\n        query.apply({\"bar\": [\"qux\", \"baz\"]})\n\n        self.assertEqual(\"foo 'qux','baz'\", query.text)\n\n    @patch(\n        \"redash.models.parameterized_query.dropdown_values\",\n        return_value=[{\"value\": \"1\"}],\n    )\n    def test_validation_accepts_integer_values_for_dropdowns(self, _):\n        schema = [{\"name\": \"bar\", \"type\": \"query\", \"queryId\": 1}]\n        query = ParameterizedQuery(\"foo {{bar}}\", schema)\n\n        query.apply({\"bar\": 1})\n\n        self.assertEqual(\"foo 1\", query.text)\n\n    @patch(\"redash.models.parameterized_query.dropdown_values\")\n    def test_raises_on_invalid_query_parameters(self, _):\n        schema = [{\"name\": \"bar\", \"type\": \"query\", \"queryId\": 1}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": 7})\n\n    @patch(\n        \"redash.models.parameterized_query.dropdown_values\",\n        return_value=[{\"value\": \"baz\"}],\n    )\n    def test_raises_on_unlisted_query_value_parameters(self, _):\n        schema = [{\"name\": \"bar\", \"type\": \"query\", \"queryId\": 1}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": \"shlomo\"})\n\n    @patch(\n        \"redash.models.parameterized_query.dropdown_values\",\n        return_value=[{\"value\": \"baz\"}],\n    )\n    def test_validates_query_parameters(self, _):\n        schema = [{\"name\": \"bar\", \"type\": \"query\", \"queryId\": 1}]\n        query = ParameterizedQuery(\"foo {{bar}}\", schema)\n\n        query.apply({\"bar\": \"baz\"})\n\n        self.assertEqual(\"foo baz\", query.text)\n\n    def test_raises_on_invalid_date_range_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"date-range\"}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": \"baz\"})\n\n    def test_validates_date_range_parameters(self):\n        schema = [{\"name\": \"bar\", \"type\": \"date-range\"}]\n        query = ParameterizedQuery(\"foo {{bar.start}} {{bar.end}}\", schema)\n\n        query.apply({\"bar\": {\"start\": \"2000-01-01 12:00:00\", \"end\": \"2000-12-31 12:00:00\"}})\n\n        self.assertEqual(\"foo 2000-01-01 12:00:00 2000-12-31 12:00:00\", query.text)\n\n    def test_raises_on_unexpected_param_types(self):\n        schema = [{\"name\": \"bar\", \"type\": \"burrito\"}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        with pytest.raises(InvalidParameterError):\n            query.apply({\"bar\": \"baz\"})\n\n    def test_is_not_safe_if_expecting_text_parameter(self):\n        schema = [{\"name\": \"bar\", \"type\": \"text\"}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        self.assertFalse(query.is_safe)\n\n    def test_is_safe_if_not_expecting_text_parameter(self):\n        schema = [{\"name\": \"bar\", \"type\": \"number\"}]\n        query = ParameterizedQuery(\"foo\", schema)\n\n        self.assertTrue(query.is_safe)\n\n    def test_is_safe_if_not_expecting_any_parameters(self):\n        schema = []\n        query = ParameterizedQuery(\"foo\", schema)\n\n        self.assertTrue(query.is_safe)\n\n    @patch(\n        \"redash.models.parameterized_query._load_result\",\n        return_value={\n            \"columns\": [{\"name\": \"id\"}, {\"name\": \"Name\"}, {\"name\": \"Value\"}],\n            \"rows\": [{\"id\": 5, \"Name\": \"John\", \"Value\": \"John Doe\"}],\n        },\n    )\n    def test_dropdown_values_prefers_name_and_value_columns(self, _):\n        values = dropdown_values(1, None)\n        self.assertEqual(values, [{\"name\": \"John\", \"value\": \"John Doe\"}])\n\n    @patch(\n        \"redash.models.parameterized_query._load_result\",\n        return_value={\n            \"columns\": [{\"name\": \"id\"}, {\"name\": \"fish\"}, {\"name\": \"poultry\"}],\n            \"rows\": [{\"fish\": \"Clown\", \"id\": 5, \"poultry\": \"Hen\"}],\n        },\n    )\n    def test_dropdown_values_compromises_for_first_column(self, _):\n        values = dropdown_values(1, None)\n        self.assertEqual(values, [{\"name\": 5, \"value\": \"5\"}])\n\n    @patch(\n        \"redash.models.parameterized_query._load_result\",\n        return_value={\n            \"columns\": [{\"name\": \"ID\"}, {\"name\": \"fish\"}, {\"name\": \"poultry\"}],\n            \"rows\": [{\"fish\": \"Clown\", \"ID\": 5, \"poultry\": \"Hen\"}],\n        },\n    )\n    def test_dropdown_supports_upper_cased_columns(self, _):\n        values = dropdown_values(1, None)\n        self.assertEqual(values, [{\"name\": 5, \"value\": \"5\"}])\n\n    @patch(\n        \"redash.models.Query.get_by_id_and_org\",\n        return_value=namedtuple(\"Query\", \"data_source\")(None),\n    )\n    def test_dropdown_values_raises_when_query_is_detached_from_data_source(self, _):\n        with pytest.raises(QueryDetachedFromDataSourceError):\n            dropdown_values(1, None)\n"
  },
  {
    "path": "tests/models/test_permissions.py",
    "content": "from redash.models import AccessPermission\nfrom redash.permissions import ACCESS_TYPE_MODIFY, ACCESS_TYPE_VIEW\nfrom tests import BaseTestCase\n\n\nclass TestAccessPermissionGrant(BaseTestCase):\n    def test_creates_correct_object(self):\n        q = self.factory.create_query()\n        permission = AccessPermission.grant(\n            obj=q,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=self.factory.user,\n        )\n\n        self.assertEqual(permission.object, q)\n        self.assertEqual(permission.grantor, self.factory.user)\n        self.assertEqual(permission.grantee, self.factory.user)\n        self.assertEqual(permission.access_type, ACCESS_TYPE_MODIFY)\n\n    def test_returns_existing_object_if_exists(self):\n        q = self.factory.create_query()\n        permission1 = AccessPermission.grant(\n            obj=q,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=self.factory.user,\n        )\n\n        permission2 = AccessPermission.grant(\n            obj=q,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=self.factory.user,\n        )\n\n        self.assertEqual(permission1.id, permission2.id)\n\n\nclass TestAccessPermissionRevoke(BaseTestCase):\n    def test_deletes_nothing_when_no_permission_exists(self):\n        q = self.factory.create_query()\n        self.assertEqual(0, AccessPermission.revoke(q, self.factory.user, ACCESS_TYPE_MODIFY))\n\n    def test_deletes_permission(self):\n        q = self.factory.create_query()\n        AccessPermission.grant(\n            obj=q,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=self.factory.user,\n        )\n        self.assertEqual(1, AccessPermission.revoke(q, self.factory.user, ACCESS_TYPE_MODIFY))\n\n    def test_deletes_permission_for_only_given_grantee_on_given_grant_type(self):\n        q = self.factory.create_query()\n        first_user = self.factory.create_user()\n        second_user = self.factory.create_user()\n\n        AccessPermission.grant(\n            obj=q,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=first_user,\n        )\n\n        AccessPermission.grant(\n            obj=q,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=second_user,\n        )\n\n        AccessPermission.grant(\n            obj=q,\n            access_type=ACCESS_TYPE_VIEW,\n            grantor=self.factory.user,\n            grantee=second_user,\n        )\n\n        self.assertEqual(1, AccessPermission.revoke(q, second_user, ACCESS_TYPE_VIEW))\n\n    def test_deletes_all_permissions_if_no_type_given(self):\n        q = self.factory.create_query()\n\n        AccessPermission.grant(\n            obj=q,\n            access_type=ACCESS_TYPE_MODIFY,\n            grantor=self.factory.user,\n            grantee=self.factory.user,\n        )\n\n        AccessPermission.grant(\n            obj=q,\n            access_type=ACCESS_TYPE_VIEW,\n            grantor=self.factory.user,\n            grantee=self.factory.user,\n        )\n\n        self.assertEqual(2, AccessPermission.revoke(q, self.factory.user))\n\n\nclass TestAccessPermissionFind(BaseTestCase):\n    pass\n\n\nclass TestAccessPermissionExists(BaseTestCase):\n    pass\n"
  },
  {
    "path": "tests/models/test_queries.py",
    "content": "import datetime\n\nimport mock\nimport pytest\n\nfrom redash.models import Event, Group, Query, QueryResult, db\nfrom redash.utils import gen_query_hash, utcnow\nfrom tests import BaseTestCase\n\n\nclass QueryTest(BaseTestCase):\n    def test_changing_query_text_changes_hash(self):\n        q = self.factory.create_query()\n        old_hash = q.query_hash\n\n        q.query_text = \"SELECT 2;\"\n        db.session.flush()\n        self.assertNotEqual(old_hash, q.query_hash)\n\n    def create_tagged_query(self, tags):\n        ds = self.factory.create_data_source(group=self.factory.default_group)\n        query = self.factory.create_query(data_source=ds, tags=tags)\n        return query\n\n    def test_all_tags(self):\n        self.create_tagged_query(tags=[\"tag1\"])\n        self.create_tagged_query(tags=[\"tag1\", \"tag2\"])\n        self.create_tagged_query(tags=[\"tag1\", \"tag2\", \"tag3\"])\n\n        self.assertEqual(\n            list(Query.all_tags(self.factory.user)),\n            [(\"tag1\", 3), (\"tag2\", 2), (\"tag3\", 1)],\n        )\n\n    def test_search_finds_in_name(self):\n        q1 = self.factory.create_query(name=\"Testing seåřċħ\")\n        q2 = self.factory.create_query(name=\"Testing seåřċħing\")\n        q3 = self.factory.create_query(name=\"Testing seå řċħ\")\n        queries = list(Query.search(\"seåřċħ\", [self.factory.default_group.id]))\n\n        self.assertIn(q1, queries)\n        self.assertIn(q2, queries)\n        self.assertNotIn(q3, queries)\n\n    def test_search_finds_in_description(self):\n        q1 = self.factory.create_query(description=\"Testing seåřċħ\")\n        q2 = self.factory.create_query(description=\"Testing seåřċħing\")\n        q3 = self.factory.create_query(description=\"Testing seå řċħ\")\n\n        queries = Query.search(\"seåřċħ\", [self.factory.default_group.id])\n\n        self.assertIn(q1, queries)\n        self.assertIn(q2, queries)\n        self.assertNotIn(q3, queries)\n\n    def test_search_finds_in_multi_byte_name_and_description(self):\n        q1 = self.factory.create_query(name=\"日本語の名前テスト\")\n        q2 = self.factory.create_query(description=\"日本語の説明文テスト\")\n        q3 = self.factory.create_query(description=\"Testing search\")\n\n        queries = Query.search(\"テスト\", [self.factory.default_group.id], multi_byte_search=True)\n\n        self.assertIn(q1, queries)\n        self.assertIn(q2, queries)\n        self.assertNotIn(q3, queries)\n\n    def test_search_by_id_returns_query(self):\n        q1 = self.factory.create_query(description=\"Testing search\")\n        q2 = self.factory.create_query(description=\"Testing searching\")\n        q3 = self.factory.create_query(description=\"Testing sea rch\")\n        db.session.flush()\n        queries = Query.search(str(q3.id), [self.factory.default_group.id])\n\n        self.assertIn(q3, queries)\n        self.assertNotIn(q1, queries)\n        self.assertNotIn(q2, queries)\n\n    def test_search_by_number(self):\n        q = self.factory.create_query(description=\"Testing search 12345\")\n        db.session.flush()\n        queries = Query.search(\"12345\", [self.factory.default_group.id])\n\n        self.assertIn(q, queries)\n\n    def test_search_respects_groups(self):\n        other_group = Group(org=self.factory.org, name=\"Other Group\")\n        db.session.add(other_group)\n        ds = self.factory.create_data_source(group=other_group)\n\n        q1 = self.factory.create_query(description=\"Testing search\", data_source=ds)\n        q2 = self.factory.create_query(description=\"Testing searching\")\n        q3 = self.factory.create_query(description=\"Testing sea rch\")\n\n        queries = list(Query.search(\"Testing\", [self.factory.default_group.id]))\n\n        self.assertNotIn(q1, queries)\n        self.assertIn(q2, queries)\n        self.assertIn(q3, queries)\n\n        queries = list(Query.search(\"Testing\", [other_group.id, self.factory.default_group.id]))\n        self.assertIn(q1, queries)\n        self.assertIn(q2, queries)\n        self.assertIn(q3, queries)\n\n        queries = list(Query.search(\"Testing\", [other_group.id]))\n        self.assertIn(q1, queries)\n        self.assertNotIn(q2, queries)\n        self.assertNotIn(q3, queries)\n\n    def test_returns_each_query_only_once(self):\n        other_group = self.factory.create_group()\n        second_group = self.factory.create_group()\n        ds = self.factory.create_data_source(group=other_group)\n        ds.add_group(second_group, False)\n\n        self.factory.create_query(description=\"Testing search\", data_source=ds)\n        db.session.flush()\n        queries = list(\n            Query.search(\n                \"Testing\",\n                [self.factory.default_group.id, other_group.id, second_group.id],\n            )\n        )\n\n        self.assertEqual(1, len(queries))\n\n    def test_save_updates_updated_at_field(self):\n        # This should be a test of ModelTimestampsMixin, but it's easier to test in context of existing model... :-\\\n        one_day_ago = utcnow().date() - datetime.timedelta(days=1)\n        q = self.factory.create_query(created_at=one_day_ago, updated_at=one_day_ago)\n        db.session.flush()\n        q.name = \"x\"\n        db.session.flush()\n        self.assertNotEqual(q.updated_at, one_day_ago)\n\n    def test_search_is_case_insensitive(self):\n        q = self.factory.create_query(name=\"Testing search\")\n\n        self.assertIn(q, Query.search(\"testing\", [self.factory.default_group.id]))\n\n    def test_search_query_parser_or(self):\n        q1 = self.factory.create_query(name=\"Testing\")\n        q2 = self.factory.create_query(name=\"search\")\n\n        queries = list(Query.search(\"testing or search\", [self.factory.default_group.id]))\n        self.assertIn(q1, queries)\n        self.assertIn(q2, queries)\n\n    def test_search_query_parser_negation(self):\n        q1 = self.factory.create_query(name=\"Testing\")\n        q2 = self.factory.create_query(name=\"search\")\n\n        queries = list(Query.search(\"testing -search\", [self.factory.default_group.id]))\n        self.assertIn(q1, queries)\n        self.assertNotIn(q2, queries)\n\n    def test_search_query_parser_parenthesis(self):\n        q1 = self.factory.create_query(name=\"Testing search\")\n        q2 = self.factory.create_query(name=\"Testing searching\")\n        q3 = self.factory.create_query(name=\"Testing finding\")\n\n        queries = list(Query.search(\"testing (search or finding)\", [self.factory.default_group.id]))\n        self.assertIn(q1, queries)\n        self.assertIn(q2, queries)\n        self.assertIn(q3, queries)\n\n    def test_search_query_parser_hyphen(self):\n        q1 = self.factory.create_query(name=\"Testing search\")\n        q2 = self.factory.create_query(name=\"Testing-search\")\n\n        queries = list(Query.search(\"testing search\", [self.factory.default_group.id]))\n        self.assertIn(q1, queries)\n        self.assertIn(q2, queries)\n\n    @pytest.mark.skip(reason=\"sqlalchemy-searchable > 1.0 doesn't support searching for emails\")\n    def test_search_query_parser_emails(self):\n        q1 = self.factory.create_query(name=\"janedoe@example.com\")\n        q2 = self.factory.create_query(name=\"johndoe@example.com\")\n\n        queries = list(Query.search(\"example\", [self.factory.default_group.id]))\n        self.assertIn(q1, queries)\n        self.assertIn(q2, queries)\n\n        queries = list(Query.search(\"com\", [self.factory.default_group.id]))\n        self.assertIn(q1, queries)\n        self.assertIn(q2, queries)\n\n        queries = list(Query.search(\"johndoe\", [self.factory.default_group.id]))\n        self.assertNotIn(q1, queries)\n        self.assertIn(q2, queries)\n\n    def test_past_scheduled_queries(self):\n        query = self.factory.create_query()\n        one_day_ago = (utcnow() - datetime.timedelta(days=1)).strftime(\"%Y-%m-%d\")\n        one_day_later = (utcnow() + datetime.timedelta(days=1)).strftime(\"%Y-%m-%d\")\n        query1 = self.factory.create_query(schedule={\"interval\": \"3600\", \"until\": one_day_ago})\n        query2 = self.factory.create_query(schedule={\"interval\": \"3600\", \"until\": one_day_later})\n        oq = staticmethod(lambda: [query1, query2])\n        with mock.patch.object(query.query.filter(), \"order_by\", oq):\n            res = query.past_scheduled_queries()\n            self.assertTrue(query1 in res)\n            self.assertFalse(query2 in res)\n\n    def test_search_by_user_finds_in_user(self):\n        u1 = self.factory.create_user(name=\"John\")\n        u2 = self.factory.create_user(name=\"Jane\")\n\n        self.factory._user = u1\n        q1 = self.factory.create_query(name=\"Testing search with John\")\n        q2 = self.factory.create_query(description=\"Description search\")\n\n        self.factory._user = u2\n        q3 = self.factory.create_query(name=\"Testing search with Jane\")\n\n        queries = Query.search_by_user(\"search\", u1, multi_byte_search=True)\n\n        self.assertIn(q1, queries)\n        self.assertIn(q2, queries)\n        self.assertNotIn(q3, queries)\n\n    def test_search_by_user_finds_in_multi_byte_user(self):\n        u1 = self.factory.create_user(name=\"大谷\")\n        u2 = self.factory.create_user(name=\"翔平\")\n\n        self.factory._user = u1\n        q1 = self.factory.create_query(name=\"日本語の名前テスト\")\n        q2 = self.factory.create_query(description=\"日本語の説明文テスト\")\n\n        self.factory._user = u2\n        q3 = self.factory.create_query(name=\"日本語の名前テスト\")\n\n        queries = Query.search_by_user(\"名前\", u1, multi_byte_search=True)\n\n        self.assertIn(q1, queries)\n        self.assertNotIn(q2, queries)\n        self.assertNotIn(q3, queries)\n\n\nclass QueryRecentTest(BaseTestCase):\n    def test_global_recent(self):\n        q1 = self.factory.create_query()\n        q2 = self.factory.create_query()\n        db.session.flush()\n        e = Event(\n            org=self.factory.org,\n            user=self.factory.user,\n            action=\"edit\",\n            object_type=\"query\",\n            object_id=q1.id,\n        )\n        db.session.add(e)\n        recent = Query.recent([self.factory.default_group.id])\n        self.assertIn(q1, recent)\n        self.assertNotIn(q2, recent)\n\n    def test_recent_excludes_drafts(self):\n        q1 = self.factory.create_query()\n        q2 = self.factory.create_query(is_draft=True)\n\n        db.session.add_all(\n            [\n                Event(\n                    org=self.factory.org,\n                    user=self.factory.user,\n                    action=\"edit\",\n                    object_type=\"query\",\n                    object_id=q1.id,\n                ),\n                Event(\n                    org=self.factory.org,\n                    user=self.factory.user,\n                    action=\"edit\",\n                    object_type=\"query\",\n                    object_id=q2.id,\n                ),\n            ]\n        )\n        recent = Query.recent([self.factory.default_group.id])\n\n        self.assertIn(q1, recent)\n        self.assertNotIn(q2, recent)\n\n    def test_recent_for_user(self):\n        q1 = self.factory.create_query()\n        q2 = self.factory.create_query()\n        db.session.flush()\n        e = Event(\n            org=self.factory.org,\n            user=self.factory.user,\n            action=\"edit\",\n            object_type=\"query\",\n            object_id=q1.id,\n        )\n        db.session.add(e)\n        recent = Query.recent([self.factory.default_group.id], user_id=self.factory.user.id)\n\n        self.assertIn(q1, recent)\n        self.assertNotIn(q2, recent)\n\n        recent = Query.recent([self.factory.default_group.id], user_id=self.factory.user.id + 1)\n        self.assertNotIn(q1, recent)\n        self.assertNotIn(q2, recent)\n\n    def test_respects_groups(self):\n        q1 = self.factory.create_query()\n        ds = self.factory.create_data_source(group=self.factory.create_group())\n        q2 = self.factory.create_query(data_source=ds)\n        db.session.flush()\n        Event(\n            org=self.factory.org,\n            user=self.factory.user,\n            action=\"edit\",\n            object_type=\"query\",\n            object_id=q1.id,\n        )\n        Event(\n            org=self.factory.org,\n            user=self.factory.user,\n            action=\"edit\",\n            object_type=\"query\",\n            object_id=q2.id,\n        )\n\n        recent = Query.recent([self.factory.default_group.id])\n\n        self.assertIn(q1, recent)\n        self.assertNotIn(q2, recent)\n\n\nclass TestQueryByUser(BaseTestCase):\n    def test_returns_only_users_queries(self):\n        q = self.factory.create_query(user=self.factory.user)\n        q2 = self.factory.create_query(user=self.factory.create_user())\n\n        queries = Query.by_user(self.factory.user)\n\n        # not using self.assertIn/NotIn because otherwise this fails :O\n        self.assertTrue(q in list(queries))\n        self.assertFalse(q2 in list(queries))\n\n    def test_returns_drafts_by_the_user(self):\n        q = self.factory.create_query(is_draft=True)\n        q2 = self.factory.create_query(is_draft=True, user=self.factory.create_user())\n\n        queries = Query.by_user(self.factory.user)\n\n        # not using self.assertIn/NotIn because otherwise this fails :O\n        self.assertTrue(q in queries)\n        self.assertFalse(q2 in queries)\n\n    def test_returns_only_queries_from_groups_the_user_is_member_in(self):\n        q = self.factory.create_query()\n        q2 = self.factory.create_query(data_source=self.factory.create_data_source(group=self.factory.create_group()))\n\n        queries = Query.by_user(self.factory.user)\n\n        # not using self.assertIn/NotIn because otherwise this fails :O\n        self.assertTrue(q in queries)\n        self.assertFalse(q2 in queries)\n\n\nclass TestQueryFork(BaseTestCase):\n    def assert_visualizations(self, origin_q, origin_v, forked_q, forked_v):\n        self.assertEqual(origin_v.options, forked_v.options)\n        self.assertEqual(origin_v.type, forked_v.type)\n        self.assertNotEqual(origin_v.id, forked_v.id)\n        self.assertNotEqual(origin_v.query_rel, forked_v.query_rel)\n        self.assertEqual(forked_q.id, forked_v.query_rel.id)\n\n    def test_fork_with_visualizations(self):\n        # prepare original query and visualizations\n        data_source = self.factory.create_data_source(group=self.factory.create_group())\n        query = self.factory.create_query(data_source=data_source, description=\"this is description\")\n\n        # create default TABLE - query factory does not create it\n        self.factory.create_visualization(query_rel=query, name=\"Table\", description=\"\", type=\"TABLE\", options={})\n\n        visualization_chart = self.factory.create_visualization(\n            query_rel=query,\n            description=\"chart vis\",\n            type=\"CHART\",\n            options={\n                \"yAxis\": [{\"type\": \"linear\"}, {\"type\": \"linear\", \"opposite\": True}],\n                \"series\": {\"stacking\": None},\n                \"globalSeriesType\": \"line\",\n                \"sortX\": True,\n                \"seriesOptions\": {\"count\": {\"zIndex\": 0, \"index\": 0, \"type\": \"line\", \"yAxis\": 0}},\n                \"xAxis\": {\"labels\": {\"enabled\": True}, \"type\": \"datetime\"},\n                \"columnMapping\": {\"count\": \"y\", \"created_at\": \"x\"},\n                \"bottomMargin\": 50,\n                \"legend\": {\"enabled\": True},\n            },\n        )\n        visualization_box = self.factory.create_visualization(\n            query_rel=query, description=\"box vis\", type=\"BOXPLOT\", options={}\n        )\n        fork_user = self.factory.create_user()\n        forked_query = query.fork(fork_user)\n        db.session.flush()\n\n        forked_visualization_chart = None\n        forked_visualization_box = None\n        forked_table = None\n        count_table = 0\n        for v in forked_query.visualizations:\n            if v.description == \"chart vis\":\n                forked_visualization_chart = v\n            if v.description == \"box vis\":\n                forked_visualization_box = v\n            if v.type == \"TABLE\":\n                count_table += 1\n                forked_table = v\n\n        self.assert_visualizations(query, visualization_chart, forked_query, forked_visualization_chart)\n        self.assert_visualizations(query, visualization_box, forked_query, forked_visualization_box)\n\n        self.assertEqual(forked_query.org, query.org)\n        self.assertEqual(forked_query.data_source, query.data_source)\n        self.assertEqual(forked_query.latest_query_data, query.latest_query_data)\n        self.assertEqual(forked_query.description, query.description)\n        self.assertEqual(forked_query.query_text, query.query_text)\n        self.assertEqual(forked_query.query_hash, query.query_hash)\n        self.assertEqual(forked_query.user, fork_user)\n        self.assertEqual(forked_query.description, query.description)\n        self.assertTrue(forked_query.name.startswith(\"Copy\"))\n        # num of TABLE must be 1. default table only\n        self.assertEqual(count_table, 1)\n        self.assertEqual(forked_table.name, \"Table\")\n        self.assertEqual(forked_table.description, \"\")\n        self.assertEqual(forked_table.options, {})\n\n    def test_fork_from_query_that_has_no_visualization(self):\n        # prepare original query and visualizations\n        data_source = self.factory.create_data_source(group=self.factory.create_group())\n        query = self.factory.create_query(data_source=data_source, description=\"this is description\")\n\n        # create default TABLE - query factory does not create it\n        self.factory.create_visualization(query_rel=query, name=\"Table\", description=\"\", type=\"TABLE\", options={})\n\n        fork_user = self.factory.create_user()\n\n        forked_query = query.fork(fork_user)\n\n        count_table = 0\n        count_vis = 0\n        for v in forked_query.visualizations:\n            count_vis += 1\n            if v.type == \"TABLE\":\n                count_table += 1\n\n        self.assertEqual(count_table, 1)\n        self.assertEqual(count_vis, 1)\n\n    def test_fork_keeps_query_tags(self):\n        query = self.factory.create_query(tags=[\"test\", \"query\"])\n\n        forked_query = query.fork(self.factory.user)\n\n        self.assertEqual(query.tags, forked_query.tags)\n\n\nclass TestQueryUpdateLatestResult(BaseTestCase):\n    def setUp(self):\n        super(TestQueryUpdateLatestResult, self).setUp()\n        self.data_source = self.factory.data_source\n        self.query = \"SELECT 1\"\n        self.query_hash = gen_query_hash(self.query)\n        self.runtime = 123\n        self.utcnow = utcnow()\n        self.data = {\"columns\": {}, \"rows\": []}\n\n    def test_updates_existing_queries(self):\n        query1 = self.factory.create_query(query_text=self.query)\n        query2 = self.factory.create_query(query_text=self.query)\n        query3 = self.factory.create_query(query_text=self.query, is_archived=True)\n\n        query_result = QueryResult.store_result(\n            self.data_source.org_id,\n            self.data_source,\n            self.query_hash,\n            self.query,\n            self.data,\n            self.runtime,\n            self.utcnow,\n        )\n\n        Query.update_latest_result(query_result)\n\n        self.assertEqual(query1.latest_query_data, query_result)\n        self.assertEqual(query2.latest_query_data, query_result)\n        self.assertEqual(query3.latest_query_data, None)\n\n    def test_doesnt_update_queries_with_different_hash(self):\n        query1 = self.factory.create_query(query_text=self.query)\n        query2 = self.factory.create_query(query_text=self.query)\n        query3 = self.factory.create_query(query_text=self.query + \"123\")\n\n        query_result = QueryResult.store_result(\n            self.data_source.org_id,\n            self.data_source,\n            self.query_hash,\n            self.query,\n            self.data,\n            self.runtime,\n            self.utcnow,\n        )\n\n        Query.update_latest_result(query_result)\n\n        self.assertEqual(query1.latest_query_data, query_result)\n        self.assertEqual(query2.latest_query_data, query_result)\n        self.assertNotEqual(query3.latest_query_data, query_result)\n\n    def test_doesnt_update_queries_with_different_data_source(self):\n        query1 = self.factory.create_query(query_text=self.query)\n        query2 = self.factory.create_query(query_text=self.query)\n        query3 = self.factory.create_query(query_text=self.query, data_source=self.factory.create_data_source())\n\n        query_result = QueryResult.store_result(\n            self.data_source.org_id,\n            self.data_source,\n            self.query_hash,\n            self.query,\n            self.data,\n            self.runtime,\n            self.utcnow,\n        )\n\n        Query.update_latest_result(query_result)\n\n        self.assertEqual(query1.latest_query_data, query_result)\n        self.assertEqual(query2.latest_query_data, query_result)\n        self.assertNotEqual(query3.latest_query_data, query_result)\n"
  },
  {
    "path": "tests/models/test_query_results.py",
    "content": "import datetime\n\nfrom redash import models\nfrom redash.utils import utcnow\nfrom tests import BaseTestCase\n\n\nclass QueryResultTest(BaseTestCase):\n    def test_get_latest_returns_none_if_not_found(self):\n        found_query_result = models.QueryResult.get_latest(self.factory.data_source, \"SELECT 1\", 60)\n        self.assertIsNone(found_query_result)\n\n    def test_get_latest_returns_when_found(self):\n        qr = self.factory.create_query_result()\n        found_query_result = models.QueryResult.get_latest(qr.data_source, qr.query_text, 60)\n\n        self.assertEqual(qr, found_query_result)\n\n    def test_get_latest_doesnt_return_query_from_different_data_source(self):\n        qr = self.factory.create_query_result()\n        data_source = self.factory.create_data_source()\n        found_query_result = models.QueryResult.get_latest(data_source, qr.query_text, 60)\n\n        self.assertIsNone(found_query_result)\n\n    def test_get_latest_doesnt_return_if_ttl_expired(self):\n        yesterday = utcnow() - datetime.timedelta(days=1)\n        qr = self.factory.create_query_result(retrieved_at=yesterday)\n\n        found_query_result = models.QueryResult.get_latest(qr.data_source, qr.query_text, max_age=60)\n\n        self.assertIsNone(found_query_result)\n\n    def test_get_latest_returns_if_ttl_not_expired(self):\n        yesterday = utcnow() - datetime.timedelta(seconds=30)\n        qr = self.factory.create_query_result(retrieved_at=yesterday)\n\n        found_query_result = models.QueryResult.get_latest(qr.data_source, qr.query_text, max_age=120)\n\n        self.assertEqual(found_query_result, qr)\n\n    def test_get_latest_returns_the_most_recent_result(self):\n        yesterday = utcnow() - datetime.timedelta(seconds=30)\n        self.factory.create_query_result(retrieved_at=yesterday)\n        qr = self.factory.create_query_result()\n\n        found_query_result = models.QueryResult.get_latest(qr.data_source, qr.query_text, 60)\n\n        self.assertEqual(found_query_result.id, qr.id)\n\n    def test_get_latest_returns_the_last_cached_result_for_negative_ttl(self):\n        yesterday = utcnow() + datetime.timedelta(days=-100)\n        self.factory.create_query_result(retrieved_at=yesterday)\n\n        yesterday = utcnow() + datetime.timedelta(days=-1)\n        qr = self.factory.create_query_result(retrieved_at=yesterday)\n        found_query_result = models.QueryResult.get_latest(qr.data_source, qr.query_text, -1)\n\n        self.assertEqual(found_query_result.id, qr.id)\n\n    def test_store_result_does_not_modify_query_update_at(self):\n        original_updated_at = utcnow() - datetime.timedelta(hours=1)\n        query = self.factory.create_query(updated_at=original_updated_at)\n\n        models.QueryResult.store_result(\n            query.org_id,\n            query.data_source,\n            query.query_hash,\n            query.query_text,\n            {},\n            0,\n            utcnow(),\n        )\n\n        self.assertEqual(original_updated_at, query.updated_at)\n"
  },
  {
    "path": "tests/models/test_users.py",
    "content": "from redash import redis_connection\nfrom redash.models import ApiUser, User, db\nfrom redash.models.users import LAST_ACTIVE_KEY, sync_last_active_at\nfrom redash.utils import dt_from_timestamp\nfrom tests import BaseTestCase, authenticated_user\n\n\nclass TestUserUpdateGroupAssignments(BaseTestCase):\n    def test_default_group_always_added(self):\n        user = self.factory.create_user()\n\n        user.update_group_assignments([\"g_unknown\"])\n        db.session.refresh(user)\n\n        self.assertCountEqual([user.org.default_group.id], user.group_ids)\n\n    def test_update_group_assignments(self):\n        user = self.factory.user\n        new_group = self.factory.create_group(name=\"g1\")\n\n        user.update_group_assignments([\"g1\"])\n        db.session.refresh(user)\n\n        self.assertCountEqual([user.org.default_group.id, new_group.id], user.group_ids)\n\n\nclass TestUserFindByEmail(BaseTestCase):\n    def test_finds_users(self):\n        user = self.factory.create_user(email=\"test@example.com\")\n        user2 = self.factory.create_user(email=\"test@example.com\", org=self.factory.create_org())\n\n        users = User.find_by_email(user.email)\n        self.assertIn(user, users)\n        self.assertIn(user2, users)\n\n    def test_finds_users_case_insensitive(self):\n        user = self.factory.create_user(email=\"test@example.com\")\n\n        users = User.find_by_email(\"test@EXAMPLE.com\")\n        self.assertIn(user, users)\n\n\nclass TestUserGetByEmailAndOrg(BaseTestCase):\n    def test_get_user_by_email_and_org(self):\n        user = self.factory.create_user(email=\"test@example.com\")\n\n        found_user = User.get_by_email_and_org(user.email, user.org)\n        self.assertEqual(user, found_user)\n\n    def test_get_user_by_email_and_org_case_insensitive(self):\n        user = self.factory.create_user(email=\"test@example.com\")\n\n        found_user = User.get_by_email_and_org(\"TEST@example.com\", user.org)\n        self.assertEqual(user, found_user)\n\n\nclass TestUserSearch(BaseTestCase):\n    def test_non_unicode_search_string(self):\n        user = self.factory.create_user(name=\"אריק\")\n\n        assert user in User.search(User.all(user.org), term=\"א\")\n\n\nclass TestUserRegenerateApiKey(BaseTestCase):\n    def test_regenerate_api_key(self):\n        user = self.factory.user\n        before_api_key = user.api_key\n        user.regenerate_api_key()\n\n        # check committed by research\n        user = User.query.get(user.id)\n        self.assertNotEqual(user.api_key, before_api_key)\n\n\nclass TestUserDetail(BaseTestCase):\n    # def setUp(self):\n    #     super(TestUserDetail, self).setUp()\n    #     # redis_connection.flushdb()\n\n    def test_userdetail_db_default(self):\n        with authenticated_user(self.client) as user:\n            self.assertEqual(user.details, {})\n            self.assertIsNone(user.active_at)\n\n    def test_userdetail_db_default_save(self):\n        with authenticated_user(self.client) as user:\n            user.details[\"test\"] = 1\n            db.session.commit()\n\n            user_reloaded = User.query.filter_by(id=user.id).first()\n            self.assertEqual(user.details[\"test\"], 1)\n            self.assertEqual(\n                user_reloaded,\n                User.query.filter(User.details[\"test\"].astext.cast(db.Integer) == 1).first(),\n            )\n\n    def test_sync(self):\n        with authenticated_user(self.client) as user:\n            self.client.get(\"/default/\")\n            timestamp = dt_from_timestamp(redis_connection.hget(LAST_ACTIVE_KEY, user.id))\n            sync_last_active_at()\n\n            user_reloaded = User.query.filter(User.id == user.id).first()\n            self.assertIn(\"active_at\", user_reloaded.details)\n            self.assertEqual(user_reloaded.active_at, timestamp)\n\n\nclass TestUserGetActualUser(BaseTestCase):\n    def test_default_user(self):\n        user_email = \"test@example.com\"\n        user = self.factory.create_user(email=user_email)\n        self.assertEqual(user.get_actual_user(), user_email)\n\n    def test_api_user(self):\n        user_email = \"test@example.com\"\n        user = self.factory.create_user(email=user_email)\n        api_user = ApiUser(user.api_key, user.org, user.group_ids)\n        self.assertEqual(api_user.get_actual_user(), repr(api_user))\n"
  },
  {
    "path": "tests/query_runner/__init__.py",
    "content": ""
  },
  {
    "path": "tests/query_runner/test_athena.py",
    "content": "\"\"\"\nSome test cases around the Glue catalog.\n\"\"\"\n\nfrom unittest import TestCase\n\nimport botocore\nimport mock\nfrom botocore.stub import Stubber\n\nfrom redash.query_runner.athena import Athena\n\n\nclass TestGlueSchema(TestCase):\n    def setUp(self):\n        client = botocore.session.get_session().create_client(\n            \"glue\",\n            region_name=\"mars-east-1\",\n            aws_access_key_id=\"foo\",\n            aws_secret_access_key=\"bar\",\n        )\n        self.stubber = Stubber(client)\n\n        self.patcher = mock.patch(\"boto3.client\")\n        mocked_client = self.patcher.start()\n        mocked_client.return_value = client\n\n    def tearDown(self):\n        self.patcher.stop()\n\n    def test_external_table(self):\n        \"\"\"Unpartitioned table crawled through a JDBC connection\"\"\"\n        query_runner = Athena({\"glue\": True, \"region\": \"mars-east-1\"})\n\n        self.stubber.add_response(\"get_databases\", {\"DatabaseList\": [{\"Name\": \"test1\"}]}, {})\n        self.stubber.add_response(\n            \"get_tables\",\n            {\n                \"TableList\": [\n                    {\n                        \"Name\": \"jdbc_table\",\n                        \"StorageDescriptor\": {\n                            \"Columns\": [{\"Name\": \"row_id\", \"Type\": \"int\"}],\n                            \"Location\": \"Database.Schema.Table\",\n                            \"Compressed\": False,\n                            \"NumberOfBuckets\": -1,\n                            \"SerdeInfo\": {\"Parameters\": {}},\n                            \"BucketColumns\": [],\n                            \"SortColumns\": [],\n                            \"Parameters\": {\n                                \"CrawlerSchemaDeserializerVersion\": \"1.0\",\n                                \"CrawlerSchemaSerializerVersion\": \"1.0\",\n                                \"UPDATED_BY_CRAWLER\": \"jdbc\",\n                                \"classification\": \"sqlserver\",\n                                \"compressionType\": \"none\",\n                                \"connectionName\": \"jdbctest\",\n                                \"typeOfData\": \"view\",\n                            },\n                            \"StoredAsSubDirectories\": False,\n                        },\n                        \"PartitionKeys\": [],\n                        \"TableType\": \"EXTERNAL_TABLE\",\n                        \"Parameters\": {\n                            \"CrawlerSchemaDeserializerVersion\": \"1.0\",\n                            \"CrawlerSchemaSerializerVersion\": \"1.0\",\n                            \"UPDATED_BY_CRAWLER\": \"jdbc\",\n                            \"classification\": \"sqlserver\",\n                            \"compressionType\": \"none\",\n                            \"connectionName\": \"jdbctest\",\n                            \"typeOfData\": \"view\",\n                        },\n                    }\n                ]\n            },\n            {\"DatabaseName\": \"test1\"},\n        )\n        with self.stubber:\n            assert query_runner.get_schema() == [\n                {\"columns\": [{\"name\": \"row_id\", \"type\": \"int\"}], \"name\": \"test1.jdbc_table\"}\n            ]\n\n    def test_partitioned_table(self):\n        \"\"\"\n        Partitioned table as created by a GlueContext\n        \"\"\"\n\n        query_runner = Athena({\"glue\": True, \"region\": \"mars-east-1\"})\n\n        self.stubber.add_response(\"get_databases\", {\"DatabaseList\": [{\"Name\": \"test1\"}]}, {})\n        self.stubber.add_response(\n            \"get_tables\",\n            {\n                \"TableList\": [\n                    {\n                        \"Name\": \"partitioned_table\",\n                        \"StorageDescriptor\": {\n                            \"Columns\": [{\"Name\": \"sk\", \"Type\": \"int\"}],\n                            \"Location\": \"s3://bucket/prefix\",\n                            \"InputFormat\": \"org.apache.hadoop.mapred.TextInputFormat\",\n                            \"OutputFormat\": \"org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat\",\n                            \"Compressed\": False,\n                            \"NumberOfBuckets\": -1,\n                            \"SerdeInfo\": {\n                                \"SerializationLibrary\": \"org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe\",\n                                \"Parameters\": {\"serialization.format\": \"1\"},\n                            },\n                            \"BucketColumns\": [],\n                            \"SortColumns\": [],\n                            \"Parameters\": {},\n                            \"SkewedInfo\": {\n                                \"SkewedColumnNames\": [],\n                                \"SkewedColumnValues\": [],\n                                \"SkewedColumnValueLocationMaps\": {},\n                            },\n                            \"StoredAsSubDirectories\": False,\n                        },\n                        \"PartitionKeys\": [{\"Name\": \"category\", \"Type\": \"int\"}],\n                        \"TableType\": \"EXTERNAL_TABLE\",\n                        \"Parameters\": {\n                            \"EXTERNAL\": \"TRUE\",\n                            \"transient_lastDdlTime\": \"1537505313\",\n                        },\n                    }\n                ]\n            },\n            {\"DatabaseName\": \"test1\"},\n        )\n        with self.stubber:\n            assert query_runner.get_schema() == [\n                {\n                    \"columns\": [{\"name\": \"sk\", \"type\": \"int\"}, {\"name\": \"category\", \"type\": \"int\"}],\n                    \"name\": \"test1.partitioned_table\",\n                }\n            ]\n\n    def test_view(self):\n        query_runner = Athena({\"glue\": True, \"region\": \"mars-east-1\"})\n\n        self.stubber.add_response(\"get_databases\", {\"DatabaseList\": [{\"Name\": \"test1\"}]}, {})\n        self.stubber.add_response(\n            \"get_tables\",\n            {\n                \"TableList\": [\n                    {\n                        \"Name\": \"view\",\n                        \"StorageDescriptor\": {\n                            \"Columns\": [{\"Name\": \"sk\", \"Type\": \"int\"}],\n                            \"Location\": \"\",\n                            \"Compressed\": False,\n                            \"NumberOfBuckets\": 0,\n                            \"SerdeInfo\": {},\n                            \"SortColumns\": [],\n                            \"StoredAsSubDirectories\": False,\n                        },\n                        \"PartitionKeys\": [],\n                        \"ViewOriginalText\": \"/* Presto View: ... */\",\n                        \"ViewExpandedText\": \"/* Presto View */\",\n                        \"TableType\": \"VIRTUAL_VIEW\",\n                        \"Parameters\": {\"comment\": \"Presto View\", \"presto_view\": \"true\"},\n                    }\n                ]\n            },\n            {\"DatabaseName\": \"test1\"},\n        )\n        with self.stubber:\n            assert query_runner.get_schema() == [{\"columns\": [{\"name\": \"sk\", \"type\": \"int\"}], \"name\": \"test1.view\"}]\n\n    def test_dodgy_table_does_not_break_schema_listing(self):\n        \"\"\"\n        For some reason, not all Glue tables contain a \"PartitionKeys\" entry.\n\n        This may be a Athena Catalog to Glue catalog migration issue.\n        \"\"\"\n        query_runner = Athena({\"glue\": True, \"region\": \"mars-east-1\"})\n\n        self.stubber.add_response(\"get_databases\", {\"DatabaseList\": [{\"Name\": \"test1\"}]}, {})\n        self.stubber.add_response(\n            \"get_tables\",\n            {\n                \"TableList\": [\n                    {\n                        \"Name\": \"csv\",\n                        \"StorageDescriptor\": {\n                            \"Columns\": [{\"Name\": \"region\", \"Type\": \"string\"}],\n                            \"Location\": \"s3://bucket/files/\",\n                            \"InputFormat\": \"org.apache.hadoop.mapred.TextInputFormat\",\n                            \"Compressed\": False,\n                            \"NumberOfBuckets\": 0,\n                            \"SerdeInfo\": {\n                                \"SerializationLibrary\": \"org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe\",\n                                \"Parameters\": {\n                                    \"field.delim\": \"|\",\n                                    \"skip.header.line.count\": \"1\",\n                                },\n                            },\n                            \"SortColumns\": [],\n                            \"StoredAsSubDirectories\": False,\n                        },\n                        \"Parameters\": {\"classification\": \"csv\"},\n                    }\n                ]\n            },\n            {\"DatabaseName\": \"test1\"},\n        )\n        with self.stubber:\n            assert query_runner.get_schema() == [\n                {\"columns\": [{\"name\": \"region\", \"type\": \"string\"}], \"name\": \"test1.csv\"}\n            ]\n\n    def test_no_storage_descriptor_table(self):\n        \"\"\"\n        For some reason, not all Glue tables contain a \"StorageDescriptor\" entry.\n        \"\"\"\n        query_runner = Athena({\"glue\": True, \"region\": \"mars-east-1\"})\n\n        self.stubber.add_response(\"get_databases\", {\"DatabaseList\": [{\"Name\": \"test1\"}]}, {})\n        self.stubber.add_response(\n            \"get_tables\",\n            {\n                \"TableList\": [\n                    {\n                        \"Name\": \"no_storage_descriptor_table\",\n                        \"PartitionKeys\": [],\n                        \"TableType\": \"EXTERNAL_TABLE\",\n                        \"Parameters\": {\"EXTERNAL\": \"TRUE\"},\n                    }\n                ]\n            },\n            {\"DatabaseName\": \"test1\"},\n        )\n        with self.stubber:\n            assert query_runner.get_schema() == []\n\n    def test_multi_catalog_tables(self):\n        \"\"\"Tables of multi-catalogs\"\"\"\n        query_runner = Athena({\"glue\": True, \"region\": \"mars-east-1\", \"catalog_ids\": \"foo,bar\"})\n\n        self.stubber.add_response(\"get_databases\", {\"DatabaseList\": [{\"Name\": \"test1\"}]}, {\"CatalogId\": \"foo\"})\n        self.stubber.add_response(\n            \"get_tables\",\n            {\n                \"TableList\": [\n                    {\n                        \"Name\": \"jdbc_table\",\n                        \"StorageDescriptor\": {\n                            \"Columns\": [{\"Name\": \"row_id\", \"Type\": \"int\"}],\n                            \"Location\": \"Database.Schema.Table\",\n                            \"Compressed\": False,\n                            \"NumberOfBuckets\": -1,\n                            \"SerdeInfo\": {\"Parameters\": {}},\n                            \"BucketColumns\": [],\n                            \"SortColumns\": [],\n                            \"Parameters\": {\n                                \"CrawlerSchemaDeserializerVersion\": \"1.0\",\n                                \"CrawlerSchemaSerializerVersion\": \"1.0\",\n                                \"UPDATED_BY_CRAWLER\": \"jdbc\",\n                                \"classification\": \"sqlserver\",\n                                \"compressionType\": \"none\",\n                                \"connectionName\": \"jdbctest\",\n                                \"typeOfData\": \"view\",\n                            },\n                            \"StoredAsSubDirectories\": False,\n                        },\n                        \"PartitionKeys\": [],\n                        \"TableType\": \"EXTERNAL_TABLE\",\n                        \"Parameters\": {\n                            \"CrawlerSchemaDeserializerVersion\": \"1.0\",\n                            \"CrawlerSchemaSerializerVersion\": \"1.0\",\n                            \"UPDATED_BY_CRAWLER\": \"jdbc\",\n                            \"classification\": \"sqlserver\",\n                            \"compressionType\": \"none\",\n                            \"connectionName\": \"jdbctest\",\n                            \"typeOfData\": \"view\",\n                        },\n                    }\n                ]\n            },\n            {\"CatalogId\": \"foo\", \"DatabaseName\": \"test1\"},\n        )\n        self.stubber.add_response(\"get_databases\", {\"DatabaseList\": [{\"Name\": \"test2\"}]}, {\"CatalogId\": \"bar\"})\n        self.stubber.add_response(\n            \"get_tables\",\n            {\n                \"TableList\": [\n                    {\n                        \"Name\": \"jdbc_table\",\n                        \"StorageDescriptor\": {\n                            \"Columns\": [{\"Name\": \"row_id\", \"Type\": \"int\"}],\n                            \"Location\": \"Database.Schema.Table\",\n                            \"Compressed\": False,\n                            \"NumberOfBuckets\": -1,\n                            \"SerdeInfo\": {\"Parameters\": {}},\n                            \"BucketColumns\": [],\n                            \"SortColumns\": [],\n                            \"Parameters\": {\n                                \"CrawlerSchemaDeserializerVersion\": \"1.0\",\n                                \"CrawlerSchemaSerializerVersion\": \"1.0\",\n                                \"UPDATED_BY_CRAWLER\": \"jdbc\",\n                                \"classification\": \"sqlserver\",\n                                \"compressionType\": \"none\",\n                                \"connectionName\": \"jdbctest\",\n                                \"typeOfData\": \"view\",\n                            },\n                            \"StoredAsSubDirectories\": False,\n                        },\n                        \"PartitionKeys\": [],\n                        \"TableType\": \"EXTERNAL_TABLE\",\n                        \"Parameters\": {\n                            \"CrawlerSchemaDeserializerVersion\": \"1.0\",\n                            \"CrawlerSchemaSerializerVersion\": \"1.0\",\n                            \"UPDATED_BY_CRAWLER\": \"jdbc\",\n                            \"classification\": \"sqlserver\",\n                            \"compressionType\": \"none\",\n                            \"connectionName\": \"jdbctest\",\n                            \"typeOfData\": \"view\",\n                        },\n                    }\n                ]\n            },\n            {\"CatalogId\": \"bar\", \"DatabaseName\": \"test2\"},\n        )\n        with self.stubber:\n            assert query_runner.get_schema() == [\n                {\"columns\": [{\"name\": \"row_id\", \"type\": \"int\"}], \"name\": \"test1.jdbc_table\"},\n                {\"columns\": [{\"name\": \"row_id\", \"type\": \"int\"}], \"name\": \"test2.jdbc_table\"},\n            ]\n"
  },
  {
    "path": "tests/query_runner/test_azure_kusto.py",
    "content": "from unittest import TestCase\nfrom unittest.mock import patch\n\nfrom redash.query_runner.azure_kusto import AzureKusto\n\n\nclass TestAzureKusto(TestCase):\n    def setUp(self):\n        self.configuration = {\n            \"cluster\": \"https://example.kusto.windows.net\",\n            \"database\": \"sample_db\",\n            \"azure_ad_client_id\": \"client_id\",\n            \"azure_ad_client_secret\": \"client_secret\",\n            \"azure_ad_tenant_id\": \"tenant_id\",\n        }\n        self.kusto = AzureKusto(self.configuration)\n\n    @patch.object(AzureKusto, \"run_query\")\n    def test_get_schema(self, mock_run_query):\n        mock_response = {\n            \"rows\": [\n                {\n                    \"DatabaseSchema\": '{\"Databases\":{\"sample_db\":{\"Tables\":{\"Table1\":{\"Name\":\"Table1\",\"OrderedColumns\":[{\"Name\":\"Column1\",\"Type\":\"System.String\",\"CslType\":\"string\"},{\"Name\":\"Column2\",\"Type\":\"System.DateTime\",\"CslType\":\"datetime\"}]}},\"MaterializedViews\":{\"View1\":{\"Name\":\"View1\",\"OrderedColumns\":[{\"Name\":\"Column1\",\"Type\":\"System.String\",\"CslType\":\"string\"},{\"Name\":\"Column2\",\"Type\":\"System.DateTime\",\"CslType\":\"datetime\"}]}}}}}'\n                }\n            ]\n        }\n        mock_run_query.return_value = (mock_response, None)\n\n        expected_schema = [\n            {\n                \"name\": \"Table1\",\n                \"columns\": [{\"name\": \"Column1\", \"type\": \"string\"}, {\"name\": \"Column2\", \"type\": \"datetime\"}],\n            },\n            {\n                \"name\": \"View1\",\n                \"columns\": [{\"name\": \"Column1\", \"type\": \"string\"}, {\"name\": \"Column2\", \"type\": \"datetime\"}],\n            },\n        ]\n\n        schema = self.kusto.get_schema()\n        print(schema)\n        self.assertEqual(schema, expected_schema)\n"
  },
  {
    "path": "tests/query_runner/test_basequeryrunner.py",
    "content": "import unittest\n\nfrom redash.query_runner import BaseQueryRunner\n\n\nclass TestBaseQueryRunner(unittest.TestCase):\n    def setUp(self):\n        self.query_runner = BaseQueryRunner({})\n\n    def test_duplicate_column_names_assigned_correctly(self):\n        original_column_names = [\n            (\"name\", bool),\n            (\"created_at\", bool),\n            (\"updated_at\", bool),\n            (\"name\", bool),\n            (\"created_at\", bool),\n            (\"updated_at\", bool),\n        ]\n        expected = [\n            {\"name\": \"name\", \"friendly_name\": \"name\", \"type\": bool},\n            {\"name\": \"created_at\", \"friendly_name\": \"created_at\", \"type\": bool},\n            {\"name\": \"updated_at\", \"friendly_name\": \"updated_at\", \"type\": bool},\n            {\"name\": \"name1\", \"friendly_name\": \"name1\", \"type\": bool},\n            {\"name\": \"created_at1\", \"friendly_name\": \"created_at1\", \"type\": bool},\n            {\"name\": \"updated_at1\", \"friendly_name\": \"updated_at1\", \"type\": bool},\n        ]\n\n        new_columns = self.query_runner.fetch_columns(original_column_names)\n\n        self.assertEqual(new_columns, expected)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/query_runner/test_basesql_queryrunner.py",
    "content": "import unittest\n\nfrom redash.query_runner import BaseQueryRunner, BaseSQLQueryRunner\nfrom redash.utils import gen_query_hash\n\n\nclass TestBaseSQLQueryRunner(unittest.TestCase):\n    def setUp(self):\n        self.query_runner = BaseSQLQueryRunner({})\n\n    def test_check_query_limit_no_limit(self):\n        query = \"SELECT *\"\n        self.assertEqual(True, self.query_runner.query_is_select_no_limit(query))\n\n    def test_check_query_limit_non_select(self):\n        query = \"Create Table (PersonID INT)\"\n        self.assertEqual(False, self.query_runner.query_is_select_no_limit(query))\n\n    def test_check_query_limit_invalid_1(self):\n        query = \"OFFSET 5\"\n        self.assertEqual(False, self.query_runner.query_is_select_no_limit(query))\n\n    def test_check_query_limit_invalid_2(self):\n        query = \"TABLE A FROM TABLE B\"\n        self.assertEqual(False, self.query_runner.query_is_select_no_limit(query))\n\n    def test_check_query_with_limit(self):\n        query = \"SELECT * LIMIT 5\"\n        self.assertEqual(False, self.query_runner.query_is_select_no_limit(query))\n\n    def test_check_query_with_offset(self):\n        query = \"SELECT * LIMIT 5 OFFSET 3\"\n        self.assertEqual(False, self.query_runner.query_is_select_no_limit(query))\n\n    def test_add_limit_query_no_limit(self):\n        query = \"SELECT *\"\n        self.assertEqual(\"SELECT * LIMIT 1000\", self.query_runner.add_limit_to_query(query))\n\n    def test_add_limit_query_with_punc(self):\n        query = \"SELECT *;\"\n        self.assertEqual(\"SELECT * LIMIT 1000;\", self.query_runner.add_limit_to_query(query))\n\n    def test_apply_auto_limit_origin_no_limit_1(self):\n        origin_query_text = \"SELECT 2\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(\"SELECT 2 LIMIT 1000\", query_text)\n\n    def test_apply_auto_limit_origin_have_limit_1(self):\n        origin_query_text = \"SELECT 2 LIMIT 100\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(origin_query_text, query_text)\n\n    def test_apply_auto_limit_origin_have_limit_2(self):\n        origin_query_text = \"SELECT * FROM fake WHERE id IN (SELECT id FROM fake_2 LIMIT 200) LIMIT 200\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(origin_query_text, query_text)\n\n    def test_apply_auto_limit_origin_no_limit_2(self):\n        origin_query_text = \"SELECT * FROM fake WHERE id IN (SELECT id FROM fake_2 LIMIT 200)\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(origin_query_text + \" LIMIT 1000\", query_text)\n\n    def test_apply_auto_limit_non_select_query(self):\n        origin_query_text = (\n            \"create table execution_times as \"\n            \"(select id, retrieved_at, data_source_id, query, runtime, query_hash \"\n            \"from query_results order by 1 desc)\"\n        )\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(origin_query_text, query_text)\n\n    def test_apply_auto_limit_error_query(self):\n        origin_query_text = \"dklsk jdhsajhdiwc kkdsakjdwi mdklsjal\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(origin_query_text, query_text)\n\n    def test_apply_auto_limit_multi_query_add_limit_1(self):\n        origin_query_text = (\n            \"insert into execution_times (id, retrieved_at, data_source_id, query, runtime, query_hash) \"\n            \"select id, retrieved_at, data_source_id, query, runtime, query_hash from query_results \"\n            \"where id > (select max(id) from execution_times);\\n\"\n            \"select max(id), 'execution_times' as table_name from execution_times \"\n            \"union all \"\n            \"select max(id), 'query_results' as table_name from query_results\"\n        )\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(origin_query_text + \" LIMIT 1000\", query_text)\n\n    def test_apply_auto_limit_multi_query_add_limit_2(self):\n        origin_query_text = \"use database demo;\\n\" \"select * from data\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(origin_query_text + \" LIMIT 1000\", query_text)\n\n    def test_apply_auto_limit_multi_query_end_with_punc(self):\n        origin_query_text = \"select * from table1;\\n\" \"select * from table2\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(\"select * from table1;\\nselect * from table2 LIMIT 1000\", query_text)\n\n    def test_apply_auto_limit_multi_query_last_not_select(self):\n        origin_query_text = \"select * from table1;\\n\" \"CREATE TABLE Persons (PersonID int)\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(origin_query_text, query_text)\n\n    def test_apply_auto_limit_last_command_comment(self):\n        origin_query_text = \"select * from raw_events; # comment\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(\"select * from raw_events LIMIT 1000\", query_text)\n\n    def test_apply_auto_limit_last_command_comment_2(self):\n        origin_query_text = \"select * from raw_events; -- comment\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(\"select * from raw_events LIMIT 1000\", query_text)\n\n    def test_apply_auto_limit_inline_comment(self):\n        origin_query_text = \"select * from raw_events -- comment\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(\"select * from raw_events LIMIT 1000\", query_text)\n\n    def test_gen_query_hash_baseSQL(self):\n        origin_query_text = \"select *\"\n        expected_query_text = \"select * LIMIT 1000\"\n        base_runner = BaseQueryRunner({})\n        self.assertEqual(\n            base_runner.gen_query_hash(expected_query_text), self.query_runner.gen_query_hash(origin_query_text, True)\n        )\n\n    def test_gen_query_hash_NoneSQL(self):\n        origin_query_text = \"select *\"\n        base_runner = BaseQueryRunner({})\n        self.assertEqual(gen_query_hash(origin_query_text), base_runner.gen_query_hash(origin_query_text, True))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/query_runner/test_bigquery.py",
    "content": "import unittest\n\nfrom redash.query_runner.big_query import BigQuery\n\n\nclass TestBigQueryQueryRunner(unittest.TestCase):\n    def test_annotate_query_with_use_query_annotation_option(self):\n        query_runner = BigQuery({\"useQueryAnnotation\": True})\n\n        self.assertTrue(query_runner.should_annotate_query)\n\n        metadata = {\n            \"Username\": \"username\",\n            \"query_id\": \"adhoc\",\n            \"Job ID\": \"job-id\",\n            \"Query Hash\": \"query-hash\",\n            \"Scheduled\": False,\n        }\n\n        query = \"SELECT a FROM tbl\"\n        expect = (\n            \"/* Username: username, query_id: adhoc, \"\n            \"Query Hash: query-hash, \"\n            \"Scheduled: False */ SELECT a FROM tbl\"\n        )\n\n        self.assertEqual(query_runner.annotate_query(query, metadata), expect)\n\n    def test_annotate_query_without_use_query_annotation_option(self):\n        query_runner = BigQuery({\"useQueryAnnotation\": False})\n\n        self.assertFalse(query_runner.should_annotate_query)\n\n        metadata = {\n            \"Username\": \"user-name\",\n            \"query_id\": \"adhoc\",\n            \"Job ID\": \"job-id\",\n            \"Query Hash\": \"query-hash\",\n            \"Scheduled\": False,\n        }\n\n        query = \"SELECT a FROM tbl\"\n        expect = query\n\n        self.assertEqual(query_runner.annotate_query(query, metadata), expect)\n"
  },
  {
    "path": "tests/query_runner/test_cass.py",
    "content": "import ssl\nfrom unittest import TestCase\n\nfrom redash.query_runner.cass import generate_ssl_options_dict\n\n\nclass TestCassandra(TestCase):\n    def test_generate_ssl_options_dict_creates_plain_protocol_dict(self):\n        expected = {\"ssl_version\": ssl.PROTOCOL_TLSv1_2}\n        actual = generate_ssl_options_dict(\"PROTOCOL_TLSv1_2\")\n        self.assertDictEqual(expected, actual)\n\n    def test_generate_ssl_options_dict_creates_certificate_dict(self):\n        expected = {\n            \"ssl_version\": ssl.PROTOCOL_TLSv1_2,\n            \"ca_certs\": \"some/path\",\n            \"cert_reqs\": ssl.CERT_REQUIRED,\n        }\n        actual = generate_ssl_options_dict(\"PROTOCOL_TLSv1_2\", \"some/path\")\n        self.assertDictEqual(expected, actual)\n"
  },
  {
    "path": "tests/query_runner/test_clickhouse.py",
    "content": "import json\nfrom unittest import TestCase\nfrom unittest.mock import Mock, patch\n\nfrom redash.query_runner import TYPE_INTEGER\nfrom redash.query_runner.clickhouse import ClickHouse, split_multi_query\n\nsplit_multi_query_samples = [\n    # Regular query\n    (\"SELECT 1\", [\"SELECT 1\"]),\n    # Multiple data queries inlined\n    (\"SELECT 1; SELECT 2;\", [\"SELECT 1\", \"SELECT 2\"]),\n    # Multiline data queries\n    (\n        \"\"\"\nSELECT 1;\nSELECT 2;\n\"\"\",\n        [\"SELECT 1\", \"SELECT 2\"],\n    ),\n    # Commented data queries\n    (\n        \"\"\"\n-- First query single-line commentary\nSELECT 1;\n\n/**\n * Second query multi-line commentary\n */\nSELECT 2;\n\n-- Tail single-line commentary\n\n/**\n * Tail multi-line commentary\n */\n\"\"\",\n        [\n            \"-- First query single-line commentary\\nSELECT 1\",\n            \"/**\\n * Second query multi-line commentary\\n */\\nSELECT 2\",\n        ],\n    ),\n    # Should skip empty statements\n    (\n        \"\"\"\n;;;\n;\nSELECT 1;\n\"\"\",\n        [\"SELECT 1\"],\n    ),\n]\n\n\nclass TestClickHouseQueriesSplit(TestCase):\n    def test_split(self):\n        for sample in split_multi_query_samples:\n            query, expected = sample\n\n            self.assertEqual(split_multi_query(query), expected)\n\n\nsimple_query_response = {\n    \"meta\": [\n        {\"name\": \"1\", \"type\": \"UInt8\"},\n    ],\n    \"data\": [\n        {\"1\": 1},\n    ],\n    \"rows\": 1,\n    \"statistics\": {\"elapsed\": 0.0001278, \"rows_read\": 1, \"bytes_read\": 1},\n}\n\n\nclass TestClickHouse(TestCase):\n    @patch(\"requests.post\")\n    def test_send_single_query(self, post_request):\n        query_runner = ClickHouse({\"url\": \"http://clickhouse:8123\", \"dbname\": \"system\", \"timeout\": 60})\n\n        response = Mock()\n        response.status_code = 200\n        response.text = json.dumps(simple_query_response)\n        response.json.return_value = simple_query_response\n        post_request.return_value = response\n\n        data, error = query_runner.run_query(\"SELECT 1\", None)\n\n        self.assertIsNone(error)\n        self.assertEqual(\n            data,\n            {\n                \"columns\": [\n                    {\"name\": \"1\", \"friendly_name\": \"1\", \"type\": TYPE_INTEGER},\n                ],\n                \"rows\": [\n                    {\"1\": 1},\n                ],\n            },\n        )\n\n        (url,), kwargs = post_request.call_args\n        self.assertEqual(url, \"http://clickhouse:8123\")\n        self.assertEqual(kwargs[\"data\"], b\"SELECT 1\\nFORMAT JSON\")\n        self.assertEqual(\n            kwargs[\"params\"],\n            {\n                \"user\": \"default\",\n                \"password\": \"\",\n                \"database\": \"system\",\n                \"default_format\": \"JSON\",\n            },\n        )\n        self.assertEqual(kwargs[\"timeout\"], 60)\n\n    @patch(\"requests.post\")\n    def test_send_multi_query(self, post_request):\n        query_runner = ClickHouse({\"url\": \"http://clickhouse:8123\", \"dbname\": \"system\", \"timeout\": 60})\n\n        create_table_response = Mock()\n        create_table_response.status_code = 200\n        create_table_response.text = \"\"\n\n        select_response = Mock()\n        select_response.status_code = 200\n        select_response.text = json.dumps(simple_query_response)\n        select_response.json.return_value = simple_query_response\n\n        post_request.side_effect = [create_table_response, select_response]\n\n        data, error = query_runner.run_query(\n            \"\"\"\nCREATE\nTEMPORARY TABLE test AS\nSELECT 1;\nSELECT * FROM test;\n        \"\"\",\n            None,\n        )\n\n        self.assertIsNone(error)\n        self.assertEqual(\n            data,\n            {\n                \"columns\": [\n                    {\"name\": \"1\", \"friendly_name\": \"1\", \"type\": TYPE_INTEGER},\n                ],\n                \"rows\": [\n                    {\"1\": 1},\n                ],\n            },\n        )\n\n        (url,), kwargs = post_request.call_args_list[0]\n        self.assertEqual(url, \"http://clickhouse:8123\")\n        self.assertEqual(\n            kwargs[\"data\"],\n            b\"\"\"CREATE\nTEMPORARY TABLE test AS\nSELECT 1\nFORMAT JSON\"\"\",\n        )\n        self.assert_session_params(kwargs, expected_check=\"0\", expected_timeout=60)\n\n        session_id = kwargs[\"params\"][\"session_id\"]\n\n        (url,), kwargs = post_request.call_args_list[1]\n        self.assertEqual(url, \"http://clickhouse:8123\")\n        self.assertEqual(\n            kwargs[\"data\"],\n            b\"\"\"SELECT * FROM test\nFORMAT JSON\"\"\",\n        )\n\n        self.assert_session_params(kwargs, expected_check=\"1\", expected_timeout=60, expected_id=session_id)\n\n    def assert_session_params(self, kwargs, expected_check, expected_timeout, expected_id=None):\n        self.assertEqual(kwargs[\"params\"][\"session_check\"], expected_check)\n        self.assertEqual(kwargs[\"params\"][\"session_timeout\"], expected_timeout)\n\n        session_id = kwargs[\"params\"][\"session_id\"]\n        self.assertRegex(session_id, r\"redash_[a-f0-9]+\")\n\n        if expected_id:\n            self.assertEqual(kwargs[\"params\"][\"session_id\"], session_id)\n"
  },
  {
    "path": "tests/query_runner/test_databricks.py",
    "content": "from unittest import TestCase\n\nfrom redash.query_runner import split_sql_statements\n\n\nclass TestSplitMultipleSQLStatements(TestCase):\n    def _assertSplitSql(self, sql, expected_stmt):\n        stmt = split_sql_statements(sql)\n        # ignore leading and trailing whitespaces when comparing\n        self.assertListEqual([s.strip() for s in stmt], [s.strip() for s in expected_stmt])\n\n    # - it should split statements by semicolon\n    # - it should keep semicolon in string literals\n    # - it should keep semicolon in quoted names (tables, columns, aliases)\n    # - it should keep semicolon in comments\n    # - it should remove semicolon after the statement\n    def test_splits_multiple_statements_by_semicolon(self):\n        self._assertSplitSql(\n            \"\"\"\nselect 1 as \"column\", 'a;b;c' as \"column ; 2\"\nfrom \"table;\";\nselect 2 as column, if(true, x, \"y;z\") from table2 as \"alias ; 2\";\nselect 3 -- comment with ; semicolon\nfrom table3\n            \"\"\",\n            [\n                \"\"\"\nselect 1 as \"column\", 'a;b;c' as \"column ; 2\"\nfrom \"table;\"\n                \"\"\",\n                \"\"\"\nselect 2 as column, if(true, x, \"y;z\") from table2 as \"alias ; 2\"\n                \"\"\",\n                \"\"\"\nselect 3 -- comment with ; semicolon\nfrom table3\n                \"\"\",\n            ],\n        )\n\n    # - it should keep whitespaces\n    # - it should keep letter case\n    # - it should keep all unknown characters/symbols/etc.\n    def test_keeps_original_syntax(self):\n        self._assertSplitSql(\n            \"\"\"\nselECT   #TesT#;\nINSERT LoReM\n    IPSUM %^&*()\n            \"\"\",\n            [\n                \"\"\"\nselECT   #TesT#\n                \"\"\",\n                \"\"\"\nINSERT LoReM\n    IPSUM %^&*()\n                \"\"\",\n            ],\n        )\n\n        self._assertSplitSql(\n            \"\"\"\nset test_var = 'hello';\nselect ${test_var}, 123 from table;\nselect 'qwerty' from ${test_var};\nselect now()\n            \"\"\",\n            [\n                \"set test_var = 'hello'\",\n                \"select ${test_var}, 123 from table\",\n                \"select 'qwerty' from ${test_var}\",\n                \"select now()\",\n            ],\n        )\n\n    # - it should keep all comments to semicolon after statement\n    # - it should remove comments after semicolon after statement\n    def test_keeps_comments(self):\n        self._assertSplitSql(\n            \"\"\"\n-- comment 1\nSELECT x -- comment 2\n-- comment 3\n; -- comment 4\n\n-- comment 5\nDELETE FROM table -- comment 6\n            \"\"\",\n            [\n                \"\"\"\n-- comment 1\nSELECT x -- comment 2\n-- comment 3\n                \"\"\",\n                \"\"\"\n-- comment 5\nDELETE FROM table\n                \"\"\",\n            ],\n        )\n\n    # - it should skip empty statements\n    # - it should skip comment-only statements\n    def test_skips_empty_statements(self):\n        self._assertSplitSql(\n            \"\"\"\n;\n-- comment 1\n;\nSELECT * FROM table;\n-- comment 2\n;\n            \"\"\",\n            [\n                \"\"\"\nSELECT * FROM table\n                \"\"\"\n            ],\n        )\n\n        # special case - if all statements were empty it should return the only empty statement\n        self._assertSplitSql(\";; -- comment 1\", [\"\"])\n"
  },
  {
    "path": "tests/query_runner/test_drill.py",
    "content": "import datetime\nfrom unittest import TestCase\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n)\nfrom redash.query_runner.drill import convert_type, parse_response\n\n\nclass TestConvertType(TestCase):\n    def test_converts_booleans(self):\n        self.assertEqual(convert_type(\"true\", TYPE_BOOLEAN), True)\n        self.assertEqual(convert_type(\"True\", TYPE_BOOLEAN), True)\n        self.assertEqual(convert_type(\"TRUE\", TYPE_BOOLEAN), True)\n        self.assertEqual(convert_type(\"false\", TYPE_BOOLEAN), False)\n        self.assertEqual(convert_type(\"False\", TYPE_BOOLEAN), False)\n        self.assertEqual(convert_type(\"FALSE\", TYPE_BOOLEAN), False)\n\n    def test_converts_strings(self):\n        self.assertEqual(convert_type(\"Текст\", TYPE_STRING), \"Текст\")\n        self.assertEqual(convert_type(None, TYPE_STRING), \"\")\n        self.assertEqual(convert_type(\"\", TYPE_STRING), \"\")\n        self.assertEqual(convert_type(\"redash\", TYPE_STRING), \"redash\")\n\n    def test_converts_integer(self):\n        self.assertEqual(convert_type(\"42\", TYPE_INTEGER), 42)\n\n    def test_converts_float(self):\n        self.assertAlmostEqual(convert_type(\"3.14\", TYPE_FLOAT), 3.14, 2)\n\n    def test_converts_date(self):\n        self.assertEqual(\n            convert_type(\"2018-10-31\", TYPE_DATETIME),\n            datetime.datetime(2018, 10, 31, 0, 0),\n        )\n\n\nempty_response = {\"columns\": [], \"rows\": [{}]}\n\nregular_response = {\n    \"columns\": [\"key\", \"date\", \"count\", \"avg\"],\n    \"rows\": [\n        {\"key\": \"Alpha\", \"date\": \"2018-01-01\", \"count\": \"10\", \"avg\": \"3.14\"},\n        {\"key\": \"Beta\", \"date\": \"2018-02-01\", \"count\": \"20\", \"avg\": \"6.28\"},\n    ],\n}\n\n\nclass TestParseResponse(TestCase):\n    def test_parse_empty_reponse(self):\n        parsed = parse_response(empty_response)\n\n        self.assertIsInstance(parsed, dict)\n        self.assertIsNotNone(parsed[\"columns\"])\n        self.assertIsNotNone(parsed[\"rows\"])\n        self.assertEqual(len(parsed[\"columns\"]), 0)\n        self.assertEqual(len(parsed[\"rows\"]), 0)\n\n    def test_parse_regular_response(self):\n        parsed = parse_response(regular_response)\n\n        self.assertIsInstance(parsed, dict)\n        self.assertIsNotNone(parsed[\"columns\"])\n        self.assertIsNotNone(parsed[\"rows\"])\n        self.assertEqual(len(parsed[\"columns\"]), 4)\n        self.assertEqual(len(parsed[\"rows\"]), 2)\n\n        key_col = parsed[\"columns\"][0]\n        self.assertEqual(key_col[\"name\"], \"key\")\n        self.assertEqual(key_col[\"type\"], TYPE_STRING)\n\n        date_col = parsed[\"columns\"][1]\n        self.assertEqual(date_col[\"name\"], \"date\")\n        self.assertEqual(date_col[\"type\"], TYPE_DATETIME)\n\n        count_col = parsed[\"columns\"][2]\n        self.assertEqual(count_col[\"name\"], \"count\")\n        self.assertEqual(count_col[\"type\"], TYPE_INTEGER)\n\n        avg_col = parsed[\"columns\"][3]\n        self.assertEqual(avg_col[\"name\"], \"avg\")\n        self.assertEqual(avg_col[\"type\"], TYPE_FLOAT)\n\n        row_0 = parsed[\"rows\"][0]\n        self.assertEqual(row_0[\"key\"], \"Alpha\")\n        self.assertEqual(row_0[\"date\"], datetime.datetime(2018, 1, 1, 0, 0))\n        self.assertEqual(row_0[\"count\"], 10)\n        self.assertAlmostEqual(row_0[\"avg\"], 3.14, 2)\n\n        row_1 = parsed[\"rows\"][1]\n        self.assertEqual(row_1[\"key\"], \"Beta\")\n        self.assertEqual(row_1[\"date\"], datetime.datetime(2018, 2, 1, 0, 0))\n        self.assertEqual(row_1[\"count\"], 20)\n        self.assertAlmostEqual(row_1[\"avg\"], 6.28, 2)\n"
  },
  {
    "path": "tests/query_runner/test_duckdb.py",
    "content": "from unittest import TestCase\nfrom unittest.mock import patch\n\nfrom redash.query_runner.duckdb import DuckDB\n\n\nclass TestDuckDBSchema(TestCase):\n    def setUp(self) -> None:\n        self.runner = DuckDB({\"dbpath\": \":memory:\"})\n\n    @patch.object(DuckDB, \"run_query\")\n    def test_simple_schema_build(self, mock_run_query) -> None:\n        # Simulate queries: first for tables, then for DESCRIBE\n        mock_run_query.side_effect = [\n            (\n                {\n                    \"rows\": [\n                        {\n                            \"table_catalog\": \"memory\",\n                            \"table_schema\": \"main\",\n                            \"table_name\": \"users\",\n                        }\n                    ]\n                },\n                None,\n            ),\n            (\n                {\n                    \"rows\": [\n                        {\"column_name\": \"id\", \"column_type\": \"INTEGER\"},\n                        {\"column_name\": \"name\", \"column_type\": \"VARCHAR\"},\n                    ]\n                },\n                None,\n            ),\n        ]\n\n        schema = self.runner.get_schema()\n        self.assertEqual(len(schema), 1)\n        self.assertEqual(schema[0][\"name\"], \"main.users\")\n        self.assertListEqual(\n            schema[0][\"columns\"],\n            [{\"name\": \"id\", \"type\": \"INTEGER\"}, {\"name\": \"name\", \"type\": \"VARCHAR\"}],\n        )\n\n    @patch.object(DuckDB, \"run_query\")\n    def test_struct_column_expansion(self, mock_run_query) -> None:\n        # First call to run_query -> tables list\n        mock_run_query.side_effect = [\n            (\n                {\n                    \"rows\": [\n                        {\n                            \"table_catalog\": \"memory\",\n                            \"table_schema\": \"main\",\n                            \"table_name\": \"events\",\n                        }\n                    ]\n                },\n                None,\n            ),\n            # Second call -> DESCRIBE output\n            (\n                {\n                    \"rows\": [\n                        {\n                            \"column_name\": \"payload\",\n                            \"column_type\": \"STRUCT(a INTEGER, b VARCHAR)\",\n                        }\n                    ]\n                },\n                None,\n            ),\n        ]\n\n        schema_list = self.runner.get_schema()\n        self.assertEqual(len(schema_list), 1)\n        schema = schema_list[0]\n\n        # Ensure both raw and expanded struct fields are present\n        self.assertIn(\"main.events\", schema[\"name\"])\n        self.assertListEqual(\n            schema[\"columns\"],\n            [\n                {\"name\": \"payload\", \"type\": \"STRUCT(a INTEGER, b VARCHAR)\"},\n                {\"name\": \"payload.a\", \"type\": \"INTEGER\"},\n                {\"name\": \"payload.b\", \"type\": \"VARCHAR\"},\n            ],\n        )\n\n    def test_nested_struct_expansion(self) -> None:\n        runner = DuckDB({\"dbpath\": \":memory:\"})\n        runner.con.execute(\n            \"\"\"\n            CREATE TABLE sample_struct_table (\n                id INTEGER,\n                info STRUCT(\n                    name VARCHAR,\n                    metrics STRUCT(score DOUBLE, rank INTEGER),\n                    tags STRUCT(primary_tag VARCHAR, secondary_tag VARCHAR)\n                )\n            );\n        \"\"\"\n        )\n\n        schema = runner.get_schema()\n        table = next(t for t in schema if t[\"name\"] == \"main.sample_struct_table\")\n        colnames = [c[\"name\"] for c in table[\"columns\"]]\n\n        assert \"info\" in colnames\n        assert 'info.\"name\"' in colnames\n        assert \"info.metrics\" in colnames\n        assert \"info.metrics.score\" in colnames\n        assert \"info.metrics.rank\" in colnames\n        assert \"info.tags.primary_tag\" in colnames\n        assert \"info.tags.secondary_tag\" in colnames\n\n    @patch.object(DuckDB, \"run_query\")\n    def test_motherduck_catalog_included(self, mock_run_query) -> None:\n        # Test that non-default catalogs (like MotherDuck) include catalog in name\n        mock_run_query.side_effect = [\n            (\n                {\n                    \"rows\": [\n                        {\n                            \"table_catalog\": \"sample_data\",\n                            \"table_schema\": \"kaggle\",\n                            \"table_name\": \"movies\",\n                        }\n                    ]\n                },\n                None,\n            ),\n            (\n                {\n                    \"rows\": [\n                        {\"column_name\": \"title\", \"column_type\": \"VARCHAR\"},\n                    ]\n                },\n                None,\n            ),\n        ]\n\n        schema = self.runner.get_schema()\n        self.assertEqual(len(schema), 1)\n        # Should include catalog name for non-default catalogs\n        self.assertEqual(schema[0][\"name\"], \"sample_data.kaggle.movies\")\n\n    @patch.object(DuckDB, \"run_query\")\n    def test_error_propagation(self, mock_run_query) -> None:\n        mock_run_query.return_value = (None, \"boom\")\n        with self.assertRaises(Exception) as ctx:\n            self.runner.get_schema()\n        self.assertIn(\"boom\", str(ctx.exception))\n"
  },
  {
    "path": "tests/query_runner/test_e6data.py",
    "content": "from unittest.mock import patch\n\nfrom redash.query_runner import TYPE_INTEGER, TYPE_STRING\nfrom redash.query_runner.e6data import e6data\n\nrunner = e6data(\n    {\n        \"username\": \"test_user\",\n        \"password\": \"test_password\",\n        \"host\": \"test_host\",\n        \"port\": 80,\n        \"catalog\": \"test_catalog\",\n        \"database\": \"test_database\",\n    }\n)\n\n\n@patch(\"e6data_python_connector.e6data_grpc.Cursor\")\ndef test_run_query(mock_cursor):\n    query = \"SELECT * FROM test_table\"\n    user = None\n    mock_cursor.return_value.fetchall.return_value = [[1, \"John\"]]\n    mock_cursor.return_value.description = [\n        (\"id\", \"INT\", None, None, None, None, True),\n        (\"name\", \"STRING\", None, None, None, None, True),\n    ]\n\n    json_data, error = runner.run_query(query, user)\n\n    expected_json_data = {\n        \"columns\": [\n            {\"name\": \"id\", \"type\": TYPE_INTEGER},\n            {\"name\": \"name\", \"type\": TYPE_STRING},\n        ],\n        \"rows\": [{\"id\": 1, \"name\": \"John\"}],\n    }\n\n    assert json_data == expected_json_data\n\n\n@patch(\"e6data_python_connector.e6data_grpc.Cursor\")\ndef test_test_connection(mock_cursor):\n    query = \"SELECT 1\"\n    user = None\n    mock_cursor.return_value.fetchall.return_value = [[1]]\n    mock_cursor.return_value.description = [(\"EXPR$0\", \"INTEGER\", None, None, None, None, True)]\n\n    json_data, error = runner.run_query(query, user)\n\n    expected_json_data = {\"columns\": [{\"name\": \"EXPR$0\", \"type\": TYPE_INTEGER}], \"rows\": [{\"EXPR$0\": 1}]}\n\n    assert json_data == expected_json_data\n\n\n@patch(\"e6data_python_connector.Connection.get_tables\")\n@patch(\"e6data_python_connector.Connection.get_columns\")\ndef test_get_schema(mock_get_columns, mock_get_tables):\n    mock_get_tables.return_value = [\"table1\", \"table2\"]\n    mock_get_columns.side_effect = [\n        [\n            {\"fieldName\": \"id\", \"fieldType\": \"INT\"},\n            {\"fieldName\": \"name\", \"fieldType\": \"STRING\"},\n        ],\n        [\n            {\"fieldName\": \"age\", \"fieldType\": \"INT\"},\n            {\"fieldName\": \"city\", \"fieldType\": \"STRING\"},\n        ],\n    ]\n\n    schema = runner.get_schema()\n\n    expected_schema = [\n        {\n            \"name\": \"table1\",\n            \"columns\": [\n                {\"name\": \"id\", \"type\": TYPE_INTEGER},\n                {\"name\": \"name\", \"type\": TYPE_STRING},\n            ],\n        },\n        {\n            \"name\": \"table2\",\n            \"columns\": [\n                {\"name\": \"age\", \"type\": TYPE_INTEGER},\n                {\"name\": \"city\", \"type\": TYPE_STRING},\n            ],\n        },\n    ]\n\n    assert schema == expected_schema\n"
  },
  {
    "path": "tests/query_runner/test_elasticsearch2.py",
    "content": "from unittest import TestCase, mock\n\nfrom redash.query_runner.elasticsearch2 import (\n    ElasticSearch2,\n    XPackSQLElasticSearch,\n)\n\n\nclass TestElasticSearch(TestCase):\n    def test_parse_mappings(self):\n        mapping_data = {\n            \"bank\": {\n                \"mappings\": {\n                    \"properties\": {\n                        \"account_number\": {\"type\": \"long\"},\n                        \"balance\": {\"type\": \"long\"},\n                        \"city\": {\"fields\": {\"keyword\": {\"ignore_above\": 256, \"type\": \"keyword\"}}, \"type\": \"text\"},\n                        \"geo\": {\"properties\": {\"lat\": {\"type\": \"long\"}, \"long\": {\"type\": \"long\"}}},\n                    }\n                }\n            }\n        }\n        expected = {\n            \"bank\": {\n                \"account_number\": \"integer\",\n                \"balance\": \"integer\",\n                \"city\": \"string\",\n                \"geo.lat\": \"integer\",\n                \"geo.long\": \"integer\",\n            }\n        }\n        self.assertDictEqual(ElasticSearch2._parse_mappings(mapping_data), expected)\n\n    def test_parse_aggregation(self):\n        response = {\n            \"took\": 3,\n            \"timed_out\": False,\n            \"_shards\": {\"total\": 1, \"successful\": 1, \"skipped\": 0, \"failed\": 0},\n            \"hits\": {\"total\": {\"value\": 1001, \"relation\": \"eq\"}, \"max_score\": None, \"hits\": []},\n            \"aggregations\": {\n                \"group_by_state\": {\n                    \"doc_count_error_upper_bound\": 0,\n                    \"sum_other_doc_count\": 743,\n                    \"buckets\": [\n                        {\"key\": \"TX\", \"doc_count\": 30},\n                        {\"key\": \"MD\", \"doc_count\": 28},\n                        {\"key\": \"ID\", \"doc_count\": 27},\n                    ],\n                }\n            },\n        }\n        expected = {\n            \"columns\": [\n                {\"friendly_name\": \"group_by_state\", \"name\": \"group_by_state\", \"type\": \"string\"},\n                {\"friendly_name\": \"group_by_state.doc_count\", \"name\": \"group_by_state.doc_count\", \"type\": \"integer\"},\n            ],\n            \"rows\": [\n                {\n                    \"group_by_state\": \"TX\",\n                    \"group_by_state.doc_count\": 30,\n                },\n                {\n                    \"group_by_state\": \"MD\",\n                    \"group_by_state.doc_count\": 28,\n                },\n                {\n                    \"group_by_state\": \"ID\",\n                    \"group_by_state.doc_count\": 27,\n                },\n            ],\n        }\n        fields = [\"group_by_state\", \"group_by_state.doc_count\"]\n        self.assertDictEqual(ElasticSearch2._parse_results(fields, response), expected)\n\n    def test_parse_sub_aggregation(self):\n        response = {\n            \"took\": 2,\n            \"timed_out\": False,\n            \"_shards\": {\"total\": 1, \"successful\": 1, \"skipped\": 0, \"failed\": 0},\n            \"hits\": {\"total\": {\"value\": 1001, \"relation\": \"eq\"}, \"max_score\": None, \"hits\": []},\n            \"aggregations\": {\n                \"group_by_state\": {\n                    \"doc_count_error_upper_bound\": -1,\n                    \"sum_other_doc_count\": 828,\n                    \"buckets\": [\n                        {\"key\": \"CO\", \"doc_count\": 14, \"average_balance\": {\"value\": 32460.35714285714}},\n                        {\"key\": \"AZ\", \"doc_count\": 14, \"average_balance\": {\"value\": 31634.785714285714}},\n                    ],\n                }\n            },\n        }\n        expected = {\n            \"columns\": [\n                {\"friendly_name\": \"group_by_state\", \"name\": \"group_by_state\", \"type\": \"string\"},\n                {\n                    \"friendly_name\": \"group_by_state.average_balance.value\",\n                    \"name\": \"group_by_state.average_balance.value\",\n                    \"type\": \"float\",\n                },\n            ],\n            \"rows\": [\n                {\n                    \"group_by_state\": \"CO\",\n                    \"group_by_state.average_balance.value\": 32460.35714285714,\n                },\n                {\n                    \"group_by_state\": \"AZ\",\n                    \"group_by_state.average_balance.value\": 31634.785714285714,\n                },\n            ],\n        }\n        fields = [\"group_by_state\", \"group_by_state.average_balance.value\"]\n        self.assertDictEqual(ElasticSearch2._parse_results(fields, response), expected)\n\n\nclass TestXPackSQL(TestCase):\n    def test_parse_results(self):\n        response = {\n            \"columns\": [\n                {\"name\": \"account_number\", \"type\": \"long\"},\n                {\"name\": \"firstname\", \"type\": \"text\"},\n                {\"name\": \"geo.lat\", \"type\": \"long\"},\n                {\"name\": \"geo.long\", \"type\": \"long\"},\n            ],\n            \"rows\": [[1000, \"Nicolas\", 2423, 7654], [999, \"Dorothy\", None, None]],\n        }\n        expected = {\n            \"columns\": [\n                {\"friendly_name\": \"account_number\", \"name\": \"account_number\", \"type\": \"integer\"},\n                {\"friendly_name\": \"firstname\", \"name\": \"firstname\", \"type\": \"string\"},\n                {\"friendly_name\": \"geo.lat\", \"name\": \"geo.lat\", \"type\": \"integer\"},\n                {\"friendly_name\": \"geo.long\", \"name\": \"geo.long\", \"type\": \"integer\"},\n            ],\n            \"rows\": [\n                {\"account_number\": 1000, \"firstname\": \"Nicolas\", \"geo.lat\": 2423, \"geo.long\": 7654},\n                {\"account_number\": 999, \"firstname\": \"Dorothy\", \"geo.lat\": None, \"geo.long\": None},\n            ],\n        }\n        self.assertDictEqual(XPackSQLElasticSearch._parse_results(None, response), expected)\n\n\nclass TestElasticSearch2(TestCase):\n    @mock.patch(\"redash.query_runner.elasticsearch2.ElasticSearch2.__init__\", return_value=None)\n    def test_build_query(self, mock_init):\n        query_runner = ElasticSearch2()\n        query_str = '{\"index\": \"test_index\", \"result_fields\": [\"field1\", \"field2\"]}'\n        query_dict, url, result_fields = query_runner._build_query(query_str)\n        self.assertEqual(query_dict, {})\n        self.assertEqual(url, \"/test_index/_search\")\n        self.assertEqual(result_fields, [\"field1\", \"field2\"])\n"
  },
  {
    "path": "tests/query_runner/test_google_analytics4.py",
    "content": "import datetime\nfrom unittest import TestCase\n\nfrom redash.query_runner.google_analytics4 import (\n    format_column_value,\n    get_formatted_column_json,\n    parse_ga_response,\n)\n\n\nclass TestFormatColumnValue(TestCase):\n    def setUp(self):\n        self.columns = [\n            {\n                \"name\": \"date\",\n                \"friendly_name\": \"date\",\n                \"type\": \"date\",\n            },\n            {\n                \"name\": \"dateHour\",\n                \"friendly_name\": \"dateHour\",\n                \"type\": \"datetime\",\n            },\n            {\n                \"name\": \"dateHourMinute\",\n                \"friendly_name\": \"dateHourMinute\",\n                \"type\": \"datetime\",\n            },\n            {\n                \"name\": \"city\",\n                \"friendly_name\": \"city\",\n                \"type\": \"string\",\n            },\n        ]\n\n    def test_string_value(self):\n        column_name = \"city\"\n        column_value = \"Delhi\"\n\n        value = format_column_value(column_name, column_value, self.columns)\n\n        self.assertEqual(value, column_value)\n\n    def test_for_date(self):\n        column_name = \"date\"\n        column_value = \"20230711\"\n\n        value = format_column_value(column_name, column_value, self.columns)\n\n        self.assertEqual(value, datetime.datetime.strptime(column_value, \"%Y%m%d\"))\n\n    def test_for_date_hour(self):\n        column_name = \"dateHour\"\n        column_value = \"2023071210\"\n\n        value = format_column_value(column_name, column_value, self.columns)\n\n        self.assertEqual(value, datetime.datetime.strptime(column_value, \"%Y%m%d%H\"))\n\n    def test_for_date_hour_minute(self):\n        column_name = \"dateHour\"\n        column_value = \"202307121030\"\n\n        value = format_column_value(column_name, column_value, self.columns)\n\n        self.assertEqual(value, datetime.datetime.strptime(column_value, \"%Y%m%d%H%M\"))\n\n    def test_when_exception_raise(self):\n        column_name = \"dateHour\"\n        column_value = \"20230712103025\"\n\n        with self.assertRaisesRegex(Exception, \"Unknown date/time format in results: '20230712103025'\"):\n            format_column_value(column_name, column_value, self.columns)\n\n\nclass TestGetFormattedColumnJson(TestCase):\n    def test_date_column(self):\n        column_name = \"date\"\n        expected_response = {\n            \"name\": column_name,\n            \"friendly_name\": column_name,\n            \"type\": \"date\",\n        }\n\n        self.assertEqual(get_formatted_column_json(column_name), expected_response)\n\n    def test_date_hour_column(self):\n        column_name = \"dateHour\"\n        expected_response = {\n            \"name\": column_name,\n            \"friendly_name\": column_name,\n            \"type\": \"datetime\",\n        }\n\n        self.assertEqual(get_formatted_column_json(column_name), expected_response)\n\n    def test_other_string(self):\n        column_name = \"city\"\n        expected_response = {\n            \"name\": column_name,\n            \"friendly_name\": column_name,\n            \"type\": \"string\",\n        }\n\n        self.assertEqual(get_formatted_column_json(column_name), expected_response)\n\n\nclass TestParseGaResponse(TestCase):\n    def test_parse_ga_response(self):\n        response = {\n            \"dimensionHeaders\": [{\"name\": \"date\"}],\n            \"metricHeaders\": [{\"name\": \"activeUsers\", \"type\": \"TYPE_INTEGER\"}],\n            \"rows\": [{\"dimensionValues\": [{\"value\": \"20230713\"}], \"metricValues\": [{\"value\": \"50\"}]}],\n            \"rowCount\": 1,\n            \"metadata\": {\"currencyCode\": \"USD\", \"timeZone\": \"Asia/Calcutta\"},\n            \"kind\": \"analyticsData#runReport\",\n        }\n\n        expected_value = {\n            \"columns\": [\n                {\"name\": \"date\", \"friendly_name\": \"date\", \"type\": \"date\"},\n                {\"name\": \"activeUsers\", \"friendly_name\": \"activeUsers\", \"type\": \"string\"},\n            ],\n            \"rows\": [{\"date\": datetime.datetime(2023, 7, 13, 0, 0), \"activeUsers\": \"50\"}],\n        }\n\n        value = parse_ga_response(response)\n\n        self.assertEqual(value, expected_value)\n\n    def test_parse_ga_response_with_date_hour(self):\n        response = {\n            \"dimensionHeaders\": [{\"name\": \"dateHour\"}],\n            \"metricHeaders\": [{\"name\": \"activeUsers\", \"type\": \"TYPE_INTEGER\"}],\n            \"rows\": [\n                {\"dimensionValues\": [{\"value\": \"2023071312\"}], \"metricValues\": [{\"value\": \"7\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071318\"}], \"metricValues\": [{\"value\": \"7\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071317\"}], \"metricValues\": [{\"value\": \"5\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071319\"}], \"metricValues\": [{\"value\": \"5\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071320\"}], \"metricValues\": [{\"value\": \"5\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071314\"}], \"metricValues\": [{\"value\": \"4\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071315\"}], \"metricValues\": [{\"value\": \"4\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071302\"}], \"metricValues\": [{\"value\": \"3\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071305\"}], \"metricValues\": [{\"value\": \"3\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071313\"}], \"metricValues\": [{\"value\": \"3\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071306\"}], \"metricValues\": [{\"value\": \"2\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071310\"}], \"metricValues\": [{\"value\": \"2\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071321\"}], \"metricValues\": [{\"value\": \"2\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071300\"}], \"metricValues\": [{\"value\": \"1\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071304\"}], \"metricValues\": [{\"value\": \"1\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071307\"}], \"metricValues\": [{\"value\": \"1\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071308\"}], \"metricValues\": [{\"value\": \"1\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071309\"}], \"metricValues\": [{\"value\": \"1\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071311\"}], \"metricValues\": [{\"value\": \"1\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071316\"}], \"metricValues\": [{\"value\": \"1\"}]},\n                {\"dimensionValues\": [{\"value\": \"2023071323\"}], \"metricValues\": [{\"value\": \"1\"}]},\n            ],\n            \"rowCount\": 21,\n            \"metadata\": {\"currencyCode\": \"USD\", \"timeZone\": \"Asia/Calcutta\"},\n            \"kind\": \"analyticsData#runReport\",\n        }\n\n        expected_value = {\n            \"columns\": [\n                {\"name\": \"dateHour\", \"friendly_name\": \"dateHour\", \"type\": \"datetime\"},\n                {\"name\": \"activeUsers\", \"friendly_name\": \"activeUsers\", \"type\": \"string\"},\n            ],\n            \"rows\": [\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 12, 0), \"activeUsers\": \"7\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 18, 0), \"activeUsers\": \"7\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 17, 0), \"activeUsers\": \"5\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 19, 0), \"activeUsers\": \"5\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 20, 0), \"activeUsers\": \"5\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 14, 0), \"activeUsers\": \"4\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 15, 0), \"activeUsers\": \"4\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 2, 0), \"activeUsers\": \"3\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 5, 0), \"activeUsers\": \"3\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 13, 0), \"activeUsers\": \"3\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 6, 0), \"activeUsers\": \"2\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 10, 0), \"activeUsers\": \"2\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 21, 0), \"activeUsers\": \"2\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 0, 0), \"activeUsers\": \"1\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 4, 0), \"activeUsers\": \"1\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 7, 0), \"activeUsers\": \"1\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 8, 0), \"activeUsers\": \"1\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 9, 0), \"activeUsers\": \"1\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 11, 0), \"activeUsers\": \"1\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 16, 0), \"activeUsers\": \"1\"},\n                {\"dateHour\": datetime.datetime(2023, 7, 13, 23, 0), \"activeUsers\": \"1\"},\n            ],\n        }\n        value = parse_ga_response(response)\n\n        self.assertEqual(value, expected_value)\n"
  },
  {
    "path": "tests/query_runner/test_google_search_console.py",
    "content": "import datetime\nfrom unittest import TestCase\n\nfrom redash.query_runner.google_search_console import (\n    get_formatted_value,\n    parse_ga_response,\n)\n\n\nclass TestParseGaResponse(TestCase):\n    def test_parse_ga_response(self):\n        response = {\n            \"rows\": [\n                {\n                    \"keys\": [\"example\", \"https://example.com/\"],\n                    \"clicks\": 1400,\n                    \"impressions\": 48844,\n                    \"ctr\": 0.5655737704918032,\n                    \"position\": 1.0163934426229508,\n                },\n                {\n                    \"keys\": [\"second keyword example\", \"https://example.com/example.html\"],\n                    \"clicks\": 12300,\n                    \"impressions\": 41944,\n                    \"ctr\": 0.5417661097852029,\n                    \"position\": 1,\n                },\n            ],\n            \"responseAggregationType\": \"byPage\",\n        }\n\n        dimensions = [\"query\", \"page\"]\n\n        expected_value = {\n            \"columns\": [\n                {\"name\": \"query\", \"friendly_name\": \"query\", \"type\": \"string\"},\n                {\"name\": \"page\", \"friendly_name\": \"page\", \"type\": \"string\"},\n                {\"name\": \"clicks\", \"friendly_name\": \"clicks\", \"type\": \"number\"},\n                {\"name\": \"impressions\", \"friendly_name\": \"impressions\", \"type\": \"number\"},\n                {\"name\": \"ctr\", \"friendly_name\": \"ctr\", \"type\": \"number\"},\n                {\"name\": \"position\", \"friendly_name\": \"position\", \"type\": \"number\"},\n            ],\n            \"rows\": [\n                {\n                    \"query\": \"example\",\n                    \"page\": \"https://example.com/\",\n                    \"clicks\": 1400,\n                    \"impressions\": 48844,\n                    \"ctr\": 0.57,\n                    \"position\": 1.02,\n                },\n                {\n                    \"query\": \"second keyword example\",\n                    \"page\": \"https://example.com/example.html\",\n                    \"clicks\": 12300,\n                    \"impressions\": 41944,\n                    \"ctr\": 0.54,\n                    \"position\": 1,\n                },\n            ],\n        }\n\n        value = parse_ga_response(response, dimensions)\n\n        self.assertEqual(value, expected_value)\n\n    def test_parse_ga_response_with_date(self):\n        response = {\n            \"rows\": [\n                {\n                    \"keys\": [\"example keyword\", \"2022-11-01\"],\n                    \"clicks\": 3964,\n                    \"impressions\": 4954,\n                    \"ctr\": 0.8,\n                    \"position\": 1.0161616161616163,\n                },\n                {\n                    \"keys\": [\"second keyword\", \"2022-11-01\"],\n                    \"clicks\": 35033,\n                    \"impressions\": 42443,\n                    \"ctr\": 0.8254716981132075,\n                    \"position\": 1,\n                },\n            ],\n            \"responseAggregationType\": \"byProperty\",\n        }\n\n        dimensions = [\"query\", \"date\"]\n\n        expected_value = {\n            \"columns\": [\n                {\"name\": \"query\", \"friendly_name\": \"query\", \"type\": \"string\"},\n                {\"name\": \"date\", \"friendly_name\": \"date\", \"type\": \"date\"},\n                {\"name\": \"clicks\", \"friendly_name\": \"clicks\", \"type\": \"number\"},\n                {\"name\": \"impressions\", \"friendly_name\": \"impressions\", \"type\": \"number\"},\n                {\"name\": \"ctr\", \"friendly_name\": \"ctr\", \"type\": \"number\"},\n                {\"name\": \"position\", \"friendly_name\": \"position\", \"type\": \"number\"},\n            ],\n            \"rows\": [\n                {\n                    \"query\": \"example keyword\",\n                    \"date\": datetime.datetime(2022, 11, 1, 0, 0),\n                    \"clicks\": 3964,\n                    \"impressions\": 4954,\n                    \"ctr\": 0.8,\n                    \"position\": 1.02,\n                },\n                {\n                    \"query\": \"second keyword\",\n                    \"date\": datetime.datetime(2022, 11, 1, 0, 0),\n                    \"clicks\": 35033,\n                    \"impressions\": 42443,\n                    \"ctr\": 0.83,\n                    \"position\": 1,\n                },\n            ],\n        }\n        value = parse_ga_response(response, dimensions)\n\n        self.assertEqual(value, expected_value)\n\n\nclass TestFormatColumnValue(TestCase):\n    def test_string_value(self):\n        column_name = \"city\"\n        column_value = \"Delhi\"\n\n        value = get_formatted_value(column_name, column_value)\n\n        self.assertEqual(value, column_value)\n\n    def test_number_value(self):\n        column_name = \"number\"\n        column_value = 25.4145\n\n        value = get_formatted_value(column_name, column_value)\n\n        self.assertEqual(value, 25.41)\n\n    def test_for_date(self):\n        column_name = \"date\"\n        column_value = \"2023-07-11\"\n\n        value = get_formatted_value(column_name, column_value)\n\n        self.assertEqual(value, datetime.datetime.strptime(column_value, \"%Y-%m-%d\"))\n\n    def test_for_date_hour(self):\n        column_name = \"datetime\"\n        column_value = \"2023071210\"\n\n        value = get_formatted_value(column_name, column_value)\n\n        self.assertEqual(value, datetime.datetime.strptime(column_value, \"%Y%m%d%H\"))\n\n    def test_for_date_hour_minute(self):\n        column_name = \"datetime\"\n        column_value = \"202307121030\"\n\n        value = get_formatted_value(column_name, column_value)\n\n        self.assertEqual(value, datetime.datetime.strptime(column_value, \"%Y%m%d%H%M\"))\n\n    def test_when_exception_raise(self):\n        column_name = \"datetime\"\n        column_value = \"20230712103025\"\n\n        with self.assertRaisesRegex(Exception, \"Unknown date/time format in results: '20230712103025'\"):\n            get_formatted_value(column_name, column_value)\n"
  },
  {
    "path": "tests/query_runner/test_google_spreadsheets.py",
    "content": "import datetime\nfrom unittest import TestCase\n\nimport pytest\nfrom google.auth.exceptions import TransportError\nfrom gspread.exceptions import APIError\nfrom mock import MagicMock, patch\n\nfrom redash.query_runner import TYPE_DATETIME, TYPE_FLOAT\nfrom redash.query_runner.google_spreadsheets import (\n    TYPE_BOOLEAN,\n    TYPE_STRING,\n    GoogleSpreadsheet,\n    WorksheetNotFoundByTitleError,\n    WorksheetNotFoundError,\n    _get_columns_and_column_names,\n    _value_eval_list,\n    is_url_key,\n    parse_query,\n    parse_spreadsheet,\n    parse_worksheet,\n)\n\n\nclass TestValueEvalList(TestCase):\n    def test_handles_unicode(self):\n        values = [\"יוניקוד\", \"test\", \"value\"]\n        self.assertEqual(values, _value_eval_list(values, [TYPE_STRING] * len(values)))\n\n    def test_handles_boolean(self):\n        values = [\"true\", \"false\", \"True\", \"False\", \"TRUE\", \"FALSE\"]\n        converted_values = [True, False, True, False, True, False]\n        self.assertEqual(converted_values, _value_eval_list(values, [TYPE_BOOLEAN] * len(values)))\n\n    def test_handles_empty_values(self):\n        values = [\"\", None]\n        converted_values = [None, None]\n        self.assertEqual(converted_values, _value_eval_list(values, [TYPE_STRING, TYPE_STRING]))\n\n    def test_handles_float(self):\n        values = [\"3.14\", \"-273.15\"]\n        converted_values = [3.14, -273.15]\n        self.assertEqual(converted_values, _value_eval_list(values, [TYPE_FLOAT, TYPE_FLOAT]))\n\n    def test_handles_datetime(self):\n        values = [\"2018-06-28\", \"2020-2-29\"]\n        converted_values = [\n            datetime.datetime(2018, 6, 28, 0, 0),\n            datetime.datetime(2020, 2, 29, 0, 0),\n        ]\n        self.assertEqual(converted_values, _value_eval_list(values, [TYPE_DATETIME, TYPE_DATETIME]))\n\n\nclass TestParseSpreadsheet(TestCase):\n    def test_returns_meaningful_error_for_missing_worksheet(self):\n        spreadsheet = MagicMock()\n\n        spreadsheet.worksheets = MagicMock(return_value=[])\n        spreadsheet.get_worksheet_by_index = MagicMock(return_value=None)\n        self.assertRaises(WorksheetNotFoundError, parse_spreadsheet, spreadsheet, 0)\n\n    def test_returns_meaningful_error_for_missing_worksheet_by_title(self):\n        spreadsheet = MagicMock()\n\n        spreadsheet.get_worksheet_by_title = MagicMock(return_value=None)\n        self.assertRaises(WorksheetNotFoundByTitleError, parse_spreadsheet, spreadsheet, \"a\")\n\n\nempty_worksheet = []\nonly_headers_worksheet = [[\"Column A\", \"Column B\"]]\nregular_worksheet = [\n    [\"String Column\", \"Boolean Column\", \"Number Column\"],\n    [\"A\", \"TRUE\", \"1\"],\n    [\"B\", \"FALSE\", \"2\"],\n    [\"C\", \"TRUE\", \"3\"],\n    [\"D\", \"FALSE\", \"4\"],\n]\n\n\n# The following test that the parse function doesn't crash. They don't test correct output.\nclass TestParseWorksheet(TestCase):\n    def test_parse_empty_worksheet(self):\n        parse_worksheet(empty_worksheet)\n\n    def test_parse_only_headers_worksheet(self):\n        parse_worksheet(only_headers_worksheet)\n\n    def test_parse_regular_worksheet(self):\n        parse_worksheet(regular_worksheet)\n\n    def test_parse_worksheet_with_duplicate_column_names(self):\n        worksheet = [\n            [\"Column\", \"Another Column\", \"Column\"],\n            [\"A\", \"TRUE\", \"1\"],\n            [\"B\", \"FALSE\", \"2\"],\n            [\"C\", \"TRUE\", \"3\"],\n            [\"D\", \"FALSE\", \"4\"],\n        ]\n        parsed = parse_worksheet(worksheet)\n\n        columns = [column[\"name\"] for column in parsed[\"columns\"]]\n        self.assertEqual(\"Column\", columns[0])\n        self.assertEqual(\"Another Column\", columns[1])\n        self.assertEqual(\"Column1\", columns[2])\n\n        self.assertEqual(\"A\", parsed[\"rows\"][0][\"Column\"])\n        self.assertEqual(True, parsed[\"rows\"][0][\"Another Column\"])\n        self.assertEqual(1, parsed[\"rows\"][0][\"Column1\"])\n\n\nclass TestParseQuery(TestCase):\n    def test_parse_query(self):\n        parsed = parse_query(\"key|0\")\n        self.assertEqual((\"key\", 0), parsed)\n\n    def test_parse_query_ignored(self):\n        parsed = parse_query(\"key\")\n        self.assertEqual((\"key\", 0), parsed)\n\n        parsed = parse_query(\"key|\")\n        self.assertEqual((\"key\", 0), parsed)\n\n        parsed = parse_query(\"key|1|\")\n        self.assertEqual((\"key\", 0), parsed)\n\n    def test_parse_query_title(self):\n        parsed = parse_query('key|\"\"')\n        self.assertEqual((\"key\", \"\"), parsed)\n\n        parsed = parse_query('key|\"1\"')\n        self.assertEqual((\"key\", \"1\"), parsed)\n\n        parsed = parse_query('key|\"abc\"')\n        self.assertEqual((\"key\", \"abc\"), parsed)\n\n        parsed = parse_query('key|\"あ\"')\n        self.assertEqual((\"key\", \"あ\"), parsed)\n\n        parsed = parse_query('key|\"1\"\"')\n        self.assertEqual((\"key\", '1\"'), parsed)\n\n        parsed = parse_query('key|\"\"')\n        self.assertEqual((\"key\", \"\"), parsed)\n\n    def test_parse_query_failed(self):\n        self.assertRaises(ValueError, parse_query, \"key|0x01\")\n        self.assertRaises(ValueError, parse_query, \"key|a\")\n        self.assertRaises(ValueError, parse_query, 'key|\"\"a')\n\n\nclass TestGetColumnsAndColumnNames(TestCase):\n    def test_get_columns(self):\n        _columns = [\"foo\", \"bar\", \"baz\"]\n        columns, column_names = _get_columns_and_column_names(_columns)\n\n        self.assertEqual(_columns, column_names)\n\n    def test_get_columns_with_duplicated(self):\n        _columns = [\"foo\", \"bar\", \"baz\", \"foo\", \"baz\"]\n        columns, column_names = _get_columns_and_column_names(_columns)\n\n        self.assertEqual([\"foo\", \"bar\", \"baz\", \"foo1\", \"baz2\"], column_names)\n\n    def test_get_columns_with_blank(self):\n        _columns = [\"foo\", \"\", \"baz\", \"\"]\n        columns, column_names = _get_columns_and_column_names(_columns)\n\n        self.assertEqual([\"foo\", \"column_B\", \"baz\", \"column_D\"], column_names)\n\n\nclass TestIsUrlKey(TestCase):\n    def test_is_url_key(self):\n        _key = \"https://docs.google.com/spreadsheets/d/key/edit#gid=12345678\"\n        self.assertTrue(is_url_key(_key))\n\n        _key = \"key|0\"\n        self.assertFalse(is_url_key(_key))\n\n\nclass TestConnection(TestCase):\n    @patch(\"redash.query_runner.google_spreadsheets.google.auth.default\")\n    @patch(\"redash.query_runner.google_spreadsheets.gspread.Client\")\n    def test_connect_succuess(self, mock_client, _mock_auth_default):\n        try:\n            qr_gspread = GoogleSpreadsheet({})\n            qr_gspread.test_connection()\n            mock_client().login.assert_called_once_with()\n            mock_client().open_by_key.assert_called_once()\n        except Exception:\n            self.fail(\"test_connection failed\")\n\n    @patch(\"redash.query_runner.google_spreadsheets.google.auth.default\")\n    def test_connect_fail_with_transport_error(self, mock_auth_default):\n        mock_auth_default.side_effect = TransportError(\"Connection Refused\")\n        qr_gspread = GoogleSpreadsheet({})\n        with pytest.raises(Exception):\n            qr_gspread.test_connection()\n\n    @patch(\"redash.query_runner.google_spreadsheets.google.auth.default\")\n    def test_connect_fail_with_api_error(self, mock_auth_default):\n        mock_response = MagicMock()\n        mock_response.json.return_value = {\"error\": {\"message\": \"Sheet API is disabled\"}}\n        mock_auth_default.side_effect = APIError(mock_response)\n        qr_gspread = GoogleSpreadsheet({})\n        with pytest.raises(Exception):\n            qr_gspread.test_connection()\n"
  },
  {
    "path": "tests/query_runner/test_http.py",
    "content": "from unittest import TestCase\n\nimport mock\n\nfrom redash.query_runner import BaseHTTPQueryRunner\nfrom redash.utils.requests_session import (\n    ConfiguredSession,\n    requests_or_advocate,\n)\n\n\nclass RequiresAuthQueryRunner(BaseHTTPQueryRunner):\n    requires_authentication = True\n\n\nclass TestBaseHTTPQueryRunner(TestCase):\n    def test_requires_authentication_default(self):\n        self.assertFalse(BaseHTTPQueryRunner.requires_authentication)\n        schema = BaseHTTPQueryRunner.configuration_schema()\n        self.assertNotIn(\"username\", schema[\"required\"])\n        self.assertNotIn(\"password\", schema[\"required\"])\n\n    def test_requires_authentication_true(self):\n        schema = RequiresAuthQueryRunner.configuration_schema()\n        self.assertIn(\"username\", schema[\"required\"])\n        self.assertIn(\"password\", schema[\"required\"])\n\n    def test_get_auth_with_values(self):\n        query_runner = BaseHTTPQueryRunner({\"username\": \"username\", \"password\": \"password\"})\n        self.assertEqual(query_runner.get_auth(), (\"username\", \"password\"))\n\n    def test_get_auth_empty(self):\n        query_runner = BaseHTTPQueryRunner({})\n        self.assertIsNone(query_runner.get_auth())\n\n    def test_get_auth_empty_requires_authentication(self):\n        query_runner = RequiresAuthQueryRunner({})\n        self.assertRaisesRegex(ValueError, \"Username and Password required\", query_runner.get_auth)\n\n    @mock.patch.object(ConfiguredSession, \"request\")\n    def test_get_response_success(self, mock_get):\n        mock_response = mock.Mock()\n        mock_response.status_code = 200\n        mock_response.text = \"Success\"\n        mock_get.return_value = mock_response\n\n        url = \"https://example.com/\"\n        query_runner = BaseHTTPQueryRunner({})\n        response, error = query_runner.get_response(url)\n        mock_get.assert_called_once_with(\"get\", url, auth=None)\n        self.assertEqual(response.status_code, 200)\n        self.assertIsNone(error)\n\n    @mock.patch.object(ConfiguredSession, \"request\")\n    def test_get_response_success_custom_auth(self, mock_get):\n        mock_response = mock.Mock()\n        mock_response.status_code = 200\n        mock_response.text = \"Success\"\n        mock_get.return_value = mock_response\n\n        url = \"https://example.com/\"\n        query_runner = BaseHTTPQueryRunner({})\n        auth = (\"username\", \"password\")\n        response, error = query_runner.get_response(url, auth=auth)\n        mock_get.assert_called_once_with(\"get\", url, auth=auth)\n        self.assertEqual(response.status_code, 200)\n        self.assertIsNone(error)\n\n    @mock.patch.object(ConfiguredSession, \"request\")\n    def test_get_response_failure(self, mock_get):\n        mock_response = mock.Mock()\n        mock_response.status_code = 301\n        mock_response.text = \"Redirect\"\n        mock_get.return_value = mock_response\n\n        url = \"https://example.com/\"\n        query_runner = BaseHTTPQueryRunner({})\n        response, error = query_runner.get_response(url)\n        mock_get.assert_called_once_with(\"get\", url, auth=None)\n        self.assertIn(query_runner.response_error, error)\n\n    @mock.patch.object(ConfiguredSession, \"request\")\n    def test_get_response_httperror_exception(self, mock_get):\n        mock_response = mock.Mock()\n        mock_response.status_code = 500\n        mock_response.text = \"Server Error\"\n        http_error = requests_or_advocate.HTTPError()\n        mock_response.raise_for_status.side_effect = http_error\n        mock_get.return_value = mock_response\n\n        url = \"https://example.com/\"\n        query_runner = BaseHTTPQueryRunner({})\n        response, error = query_runner.get_response(url)\n        mock_get.assert_called_once_with(\"get\", url, auth=None)\n        self.assertIsNotNone(error)\n        self.assertIn(\"Failed to execute query\", error)\n\n    @mock.patch.object(ConfiguredSession, \"request\")\n    def test_get_response_requests_exception(self, mock_get):\n        mock_response = mock.Mock()\n        mock_response.status_code = 500\n        mock_response.text = \"Server Error\"\n        exception_message = \"Some requests exception\"\n        requests_exception = requests_or_advocate.RequestException(exception_message)\n        mock_response.raise_for_status.side_effect = requests_exception\n        mock_get.return_value = mock_response\n\n        url = \"https://example.com/\"\n        query_runner = BaseHTTPQueryRunner({})\n        response, error = query_runner.get_response(url)\n        mock_get.assert_called_once_with(\"get\", url, auth=None)\n        self.assertIsNotNone(error)\n        self.assertEqual(exception_message, error)\n\n    @mock.patch.object(ConfiguredSession, \"request\")\n    def test_get_response_generic_exception(self, mock_get):\n        mock_response = mock.Mock()\n        mock_response.status_code = 500\n        mock_response.text = \"Server Error\"\n        exception_message = \"Some generic exception\"\n        exception = ValueError(exception_message)\n        mock_response.raise_for_status.side_effect = exception\n        mock_get.return_value = mock_response\n\n        url = \"https://example.com/\"\n        query_runner = BaseHTTPQueryRunner({})\n        self.assertRaisesRegex(ValueError, exception_message, query_runner.get_response, url)\n"
  },
  {
    "path": "tests/query_runner/test_ignite.py",
    "content": "import datetime\nfrom unittest import TestCase\n\nfrom redash.query_runner.ignite import Ignite\n\n\nclass TestIgnite(TestCase):\n    def test_server_to_connection(self):\n        config = {\n            \"server\": \"localhost,localhost:10801,invalid:port:100\",\n        }\n        ignite = Ignite(config)\n\n        server = ignite.configuration.get(\"server\", \"127.0.0.1:10800\")\n\n        server_list = [ignite.server_to_connection(s) for s in server.split(\",\")]\n\n        self.assertTupleEqual(server_list[0], (\"localhost\", 10800))\n        self.assertTupleEqual(server_list[1], (\"localhost\", 10801))\n        self.assertTupleEqual(server_list[2], (\"unknown\", 10800))\n\n    def test_normalise_row(self):\n        config = {\n            \"server\": \"localhost,localhost:10801,invalid:port:100\",\n        }\n        ignite = Ignite(config)\n\n        row = [1, 1.0, \"string\", True, datetime.datetime(2014, 10, 3, 0, 0), (datetime.datetime(2014, 10, 3, 0, 0), 0)]\n\n        converted = ignite.normalise_row(row)\n\n        self.assertListEqual(\n            converted,\n            [1, 1.0, \"string\", True, datetime.datetime(2014, 10, 3, 0, 0), datetime.datetime(2014, 10, 3, 0, 0)],\n        )\n\n    def test_parse_query_results(self):\n        config = {\n            \"server\": \"localhost,localhost:10801,invalid:port:100\",\n        }\n        ignite = Ignite(config)\n\n        results = ignite._parse_results(\n            iter([[\"col1\", \"col2\", \"col3\", \"col4\"], [1, 2.0, \"three\", (datetime.datetime(2014, 10, 3, 0, 0), 0)]])\n        )\n\n        self.assertListEqual(\n            results[0],\n            [\n                {\"name\": \"col1\", \"friendly_name\": \"col1\"},\n                {\"name\": \"col2\", \"friendly_name\": \"col2\"},\n                {\"name\": \"col3\", \"friendly_name\": \"col3\"},\n                {\"name\": \"col4\", \"friendly_name\": \"col4\"},\n            ],\n        )\n        self.assertListEqual(\n            results[1],\n            [\n                {\"col1\": 1, \"col2\": 2.0, \"col3\": \"three\", \"col4\": datetime.datetime(2014, 10, 3, 0, 0)},\n            ],\n        )\n"
  },
  {
    "path": "tests/query_runner/test_influx_db.py",
    "content": "from influxdb.resultset import ResultSet\n\nfrom redash.query_runner import (\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n)\nfrom redash.query_runner.influx_db import _transform_result\n\nraw = {\n    \"series\": [\n        {\n            \"name\": \"typetest\",\n            \"columns\": [\"time\", \"k1\", \"v1\", \"v2\"],\n            \"values\": [\n                [\"2023-10-06T13:30:51.323358136Z\", \"foo\", 0.5, 2],\n                [\"2023-10-06T13:31:08.882953339Z\", \"bar\", 0.6, 4],\n            ],\n        }\n    ]\n}\n\nraw_no_rows = {\"series\": [{\"name\": \"typetest\", \"columns\": [\"time\", \"k1\", \"v1\", \"v2\"], \"values\": []}]}\n\n\ndef test_influxdb_result_types_with_rows():\n    result = ResultSet(raw)\n    transformed = _transform_result([result])\n    expected = {\n        \"columns\": [\n            {\"name\": \"time\", \"type\": TYPE_STRING},\n            {\"name\": \"k1\", \"type\": TYPE_STRING},\n            {\"name\": \"v1\", \"type\": TYPE_FLOAT},\n            {\"name\": \"v2\", \"type\": TYPE_INTEGER},\n        ],\n        \"rows\": [\n            {\"k1\": \"foo\", \"time\": \"2023-10-06T13:30:51.323358136Z\", \"v1\": 0.5, \"v2\": 2},\n            {\"k1\": \"bar\", \"time\": \"2023-10-06T13:31:08.882953339Z\", \"v1\": 0.6, \"v2\": 4},\n        ],\n    }\n    assert transformed == expected\n\n\ndef test_influxdb_result_types_with_no_rows_are_string():\n    result = ResultSet(raw_no_rows)\n    transformed = _transform_result([result])\n    expected = {\n        \"columns\": [\n            {\"name\": \"time\", \"type\": TYPE_STRING},\n            {\"name\": \"k1\", \"type\": TYPE_STRING},\n            {\"name\": \"v1\", \"type\": TYPE_STRING},\n            {\"name\": \"v2\", \"type\": TYPE_STRING},\n        ],\n        \"rows\": [],\n    }\n    assert transformed == expected\n"
  },
  {
    "path": "tests/query_runner/test_influx_db_v2.py",
    "content": "import mock\nimport pytest\nfrom influxdb_client.client.flux_table import (\n    FluxColumn,\n    FluxRecord,\n    FluxTable,\n    TableList,\n)\n\nfrom redash.query_runner.influx_db_v2 import InfluxDBv2\n\n\n@pytest.fixture()\ndef influx_table_list():\n    tables = TableList()\n    table_1 = FluxTable()\n    table_2 = FluxTable()\n    column_1 = FluxColumn(index=0, label=\"col_1\", data_type=\"string\", group=False, default_value=\"default_value_2\")\n    column_2 = FluxColumn(index=0, label=\"col_2\", data_type=\"integer\", group=False, default_value=\"default_value_2\")\n    column_3 = FluxColumn(index=1, label=\"col_3\", data_type=\"float\", group=False, default_value=3.0)\n\n    record_1 = FluxRecord(table_1, values={\"col_1\": \"col_value_1\", \"col_2\": 1})\n    record_1.table = column_1.index\n    record_1.row = [\"col_value_1\", 1, \"field_1\", \"value_1\"]\n\n    record_2 = FluxRecord(table_1, values={\"col_1\": \"col_value_2\", \"col_2\": 2})\n    record_2.table = column_1.index\n    record_2.row = [\"col_value_2\", 2, \"field_2\", \"value_2\"]\n\n    record_3 = FluxRecord(table_2, values={\"col_3\": 3.0})\n    record_3.table = column_1.index\n    record_3.row = [\"col_value_1\", 1, \"field_1\", \"value_1\"]\n\n    table_1.columns = [column_1, column_2]\n    table_1.records = [record_1, record_2]\n\n    table_2.columns = [column_3]\n    table_2.records = [record_3]\n\n    tables.append(table_1)\n    tables.append(table_2)\n\n    return tables\n\n\nclass TestInfluxDBv2:\n    @mock.patch(\"redash.query_runner.influx_db_v2.InfluxDBv2.\" \"_create_cert_file\")\n    def test_get_influx_kwargs(self, create_cert_file_mock: mock.MagicMock):\n        # 1. case: without ssl attributes\n        influx_db_v2 = InfluxDBv2({\"url\": \"url\", \"token\": \"token\", \"org\": \"org\"})\n\n        create_cert_file_mock.return_value = None\n\n        influx_kwargs = influx_db_v2._get_influx_kwargs()\n\n        assert influx_kwargs == {\n            \"verify_ssl\": None,\n            \"cert_file\": None,\n            \"cert_key_file\": None,\n            \"cert_key_password\": None,\n            \"ssl_ca_cert\": None,\n        }\n\n        create_cert_file_mock.assert_has_calls(\n            [mock.call(\"cert_File\"), mock.call(\"cert_key_File\"), mock.call(\"ssl_ca_cert_File\")]\n        )\n\n        create_cert_file_mock.reset_mock()\n\n        # 2. case: with ssl attributes\n        create_cert_file_return_dict = {\n            \"cert_File\": \"cert_file.crt\",\n            \"cert_key_File\": \"cert_key_file.key\",\n            \"ssl_ca_cert_File\": \"ssl_ca_cert_file.crt\",\n        }\n        create_cert_file_mock.side_effect = lambda key: create_cert_file_return_dict[key]\n\n        influx_db_v2 = InfluxDBv2(\n            {\n                \"url\": \"url\",\n                \"token\": \"token\",\n                \"org\": \"org\",\n                \"verify_ssl\": True,\n                \"cert_File\": \"cert_file\",\n                \"cert_key_File\": \"cert_key_file\",\n                \"cert_key_password\": \"cert_key_password\",\n                \"ssl_ca_cert_File\": \"ssl_ca_cert_file\",\n            }\n        )\n\n        influx_kwargs = influx_db_v2._get_influx_kwargs()\n\n        assert influx_kwargs == {\n            \"verify_ssl\": True,\n            \"cert_file\": \"cert_file.crt\",\n            \"cert_key_file\": \"cert_key_file.key\",\n            \"cert_key_password\": \"cert_key_password\",\n            \"ssl_ca_cert\": \"ssl_ca_cert_file.crt\",\n        }\n        create_cert_file_mock.assert_has_calls(\n            [mock.call(\"cert_File\"), mock.call(\"cert_key_File\"), mock.call(\"ssl_ca_cert_File\")]\n        )\n\n    @mock.patch(\"redash.query_runner.influx_db_v2.NamedTemporaryFile\")\n    def test_create_cert_file(self, named_temporary_file_mock: mock.MagicMock):\n        # 1. case: with none value\n        influx_db_v2 = InfluxDBv2({\"url\": \"url\", \"token\": \"token\", \"org\": \"org\"})\n\n        context_manager_mock = named_temporary_file_mock().__enter__()\n\n        cert_file_name = influx_db_v2._create_cert_file(\"key\")\n\n        assert cert_file_name is None\n        context_manager_mock.write().assert_not_called()\n\n        named_temporary_file_mock.reset_mock()\n\n        # 2. case: with a valid key\n        influx_db_v2 = InfluxDBv2({\"url\": \"url\", \"token\": \"token\", \"org\": \"org\", \"key\": \"dmFsdWU=\"})\n\n        context_manager_mock = named_temporary_file_mock().__enter__()\n        context_manager_mock.name = \"cert_file_name\"\n\n        cert_file_name = influx_db_v2._create_cert_file(\"key\")\n\n        assert cert_file_name == \"cert_file_name\"\n        context_manager_mock.write.assert_called_once_with(\"value\")\n\n    @mock.patch(\"redash.query_runner.influx_db_v2.os\")\n    def test_cleanup_cert_files(self, os_mock: mock.MagicMock):\n        # 1. case: no file found\n        influx_db_v2 = InfluxDBv2(\n            {\n                \"url\": \"url\",\n                \"token\": \"token\",\n                \"org\": \"org\",\n                \"verify_ssl\": True,\n                \"cert_File\": \"cert_file\",\n                \"cert_key_File\": \"cert_key_file\",\n                \"cert_key_password\": \"cert_key_password\",\n                \"ssl_ca_cert_File\": \"ssl_ca_cert_file\",\n            }\n        )\n\n        influx_db_v2._cleanup_cert_files({\"any_file\": \"any_file\"})\n\n        os_mock.path.exists.assert_not_called()\n        os_mock.remove.assert_not_called()\n\n        # 2. case: file found and deleted\n        os_mock.path.exists.return_value = True\n        influx_db_v2._cleanup_cert_files({\"cert_file\": \"cert_file\"})\n\n        os_mock.path.exists.assert_called_once_with(\"cert_file\")\n        os_mock.remove.assert_called_once_with(\"cert_file\")\n\n    def test_configuration_schema(self):\n        configuration_schema = InfluxDBv2.configuration_schema()\n        assert configuration_schema == {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\", \"title\": \"URL\"},\n                \"org\": {\"type\": \"string\", \"title\": \"Organization\"},\n                \"token\": {\"type\": \"string\", \"title\": \"Token\"},\n                \"verify_ssl\": {\"type\": \"boolean\", \"title\": \"Verify SSL\", \"default\": False},\n                \"cert_File\": {\"type\": \"string\", \"title\": \"SSL Client Certificate\", \"default\": None},\n                \"cert_key_File\": {\"type\": \"string\", \"title\": \"SSL Client Key\", \"default\": None},\n                \"cert_key_password\": {\"type\": \"string\", \"title\": \"Password for SSL Client Key\", \"default\": None},\n                \"ssl_ca_cert_File\": {\"type\": \"string\", \"title\": \"SSL Root Certificate\", \"default\": None},\n            },\n            \"order\": [\"url\", \"org\", \"token\", \"cert_File\", \"cert_key_File\", \"cert_key_password\", \"ssl_ca_cert_File\"],\n            \"required\": [\"url\", \"org\", \"token\"],\n            \"secret\": [\"token\", \"cert_File\", \"cert_key_File\", \"cert_key_password\", \"ssl_ca_cert_File\"],\n            \"extra_options\": [\"verify_ssl\", \"cert_File\", \"cert_key_File\", \"cert_key_password\", \"ssl_ca_cert_File\"],\n        }\n\n    def test_enabled(self):\n        assert InfluxDBv2.enabled() is True\n\n    @mock.patch(\"redash.query_runner.influx_db_v2.InfluxDBClient\")\n    @mock.patch(\"redash.query_runner.influx_db_v2.InfluxDBv2.\" \"_cleanup_cert_files\")\n    @mock.patch(\"redash.query_runner.influx_db_v2.logger\")\n    def test_test_connection(\n        self,\n        logger_mock: mock.MagicMock,\n        cleanup_cert_files_mock: mock.MagicMock,\n        influx_db_client_mock: mock.MagicMock,\n    ):\n        # 1. case: successful test connection\n        influx_db_v2 = InfluxDBv2({\"url\": \"url\", \"token\": \"token\", \"org\": \"org\"})\n        influx_kwargs = {\n            \"verify_ssl\": None,\n            \"cert_file\": None,\n            \"cert_key_file\": None,\n            \"cert_key_password\": None,\n            \"ssl_ca_cert\": None,\n        }\n\n        health_mock = influx_db_client_mock.return_value.__enter__().health\n        health_mock.return_value = mock.MagicMock(status=\"pass\")\n\n        influx_db_v2.test_connection()\n\n        influx_db_client_mock.assert_called_once_with(url=\"url\", token=\"token\", org=\"org\", **influx_kwargs)\n        health_mock.assert_called_once()\n        cleanup_cert_files_mock.assert_called_once_with(influx_kwargs)\n        logger_mock.error.assert_not_called()\n\n        cleanup_cert_files_mock.reset_mock()\n        influx_db_client_mock.reset_mock()\n\n        # 2. case: unsuccessful test connection\n        influx_db_v2 = InfluxDBv2({\"url\": \"url\", \"token\": \"token\", \"org\": \"org\"})\n        influx_kwargs = {\n            \"verify_ssl\": None,\n            \"cert_file\": None,\n            \"cert_key_file\": None,\n            \"cert_key_password\": None,\n            \"ssl_ca_cert\": None,\n        }\n\n        health_mock = influx_db_client_mock.return_value.__enter__().health\n        health_mock.return_value = mock.MagicMock(status=\"fail\", message=\"Connection failed.\")\n\n        with pytest.raises(Exception) as exp:\n            influx_db_v2.test_connection()\n\n        assert str(exp.value) == \"InfluxDB is not healthy. Check logs for more information.\"\n        influx_db_client_mock.assert_called_once_with(url=\"url\", token=\"token\", org=\"org\", **influx_kwargs)\n        health_mock.assert_called_once()\n        cleanup_cert_files_mock.assert_called_once_with(influx_kwargs)\n        logger_mock.error.assert_called_once_with(\"Connection test failed, due to: 'Connection failed.'.\")\n\n    def test_get_type(self):\n        influx_db_v2 = InfluxDBv2(\n            {\n                \"url\": \"url\",\n                \"token\": \"token\",\n                \"org\": \"org\",\n            }\n        )\n\n        assert influx_db_v2._get_type(\"integer\") == \"integer\"\n        assert influx_db_v2._get_type(\"long\") == \"integer\"\n        assert influx_db_v2._get_type(\"float\") == \"float\"\n        assert influx_db_v2._get_type(\"double\") == \"float\"\n        assert influx_db_v2._get_type(\"boolean\") == \"boolean\"\n        assert influx_db_v2._get_type(\"string\") == \"string\"\n        assert influx_db_v2._get_type(\"datetime:RFC3339\") == \"datetime\"\n\n    def test_get_data_from_tables(self, influx_table_list: TableList):\n        # 1. case: get object with coulmns and rows\n        influx_db_v2 = InfluxDBv2(\n            {\n                \"url\": \"url\",\n                \"token\": \"token\",\n                \"org\": \"org\",\n            }\n        )\n\n        data = influx_db_v2._get_data_from_tables(influx_table_list)\n        assert data == {\n            \"columns\": [\n                {\"friendly_name\": \"Col_1\", \"name\": \"col_1\", \"type\": \"string\"},\n                {\"friendly_name\": \"Col_2\", \"name\": \"col_2\", \"type\": \"integer\"},\n                {\"friendly_name\": \"Col_3\", \"name\": \"col_3\", \"type\": \"float\"},\n            ],\n            \"rows\": [{\"col_1\": \"col_value_1\", \"col_2\": 1}, {\"col_1\": \"col_value_2\", \"col_2\": 2}, {\"col_3\": 3.0}],\n        }\n\n        # 2. case: get empty object without coulmns and rows\n        data = influx_db_v2._get_data_from_tables(TableList())\n        assert data == {\"columns\": [], \"rows\": []}\n\n    @mock.patch(\"redash.query_runner.influx_db_v2.InfluxDBClient\")\n    @mock.patch(\"redash.query_runner.influx_db_v2.InfluxDBv2.\" \"_cleanup_cert_files\")\n    @mock.patch(\"redash.query_runner.influx_db_v2.logger\")\n    def test_run_query(\n        self,\n        logger_mock: mock.MagicMock,\n        cleanup_cert_files_mock: mock.MagicMock,\n        influx_db_client_mock: mock.MagicMock,\n        influx_table_list: TableList,\n    ):\n        influx_db_v2 = InfluxDBv2(\n            {\n                \"url\": \"url\",\n                \"token\": \"token\",\n                \"org\": \"org\",\n            }\n        )\n        influx_kwargs = {\n            \"verify_ssl\": None,\n            \"cert_file\": None,\n            \"cert_key_file\": None,\n            \"cert_key_password\": None,\n            \"ssl_ca_cert\": None,\n        }\n        query = 'from(bucket: \"test\")' \"|> range(start: 2023-12-04T09:00:00.000Z, \" \"stop: 2023-12-04T15:00:00.000Z)\"\n\n        result_data = {\n            \"columns\": [\n                {\"friendly_name\": \"Col_1\", \"name\": \"col_1\", \"type\": \"string\"},\n                {\"friendly_name\": \"Col_2\", \"name\": \"col_2\", \"type\": \"integer\"},\n                {\"friendly_name\": \"Col_3\", \"name\": \"col_3\", \"type\": \"float\"},\n            ],\n            \"rows\": [{\"col_1\": \"col_value_1\", \"col_2\": 1}, {\"col_1\": \"col_value_2\", \"col_2\": 2}, {\"col_3\": 3.0}],\n        }\n\n        query_mock = influx_db_client_mock.return_value.__enter__().query_api().query\n        query_mock.return_value = influx_table_list\n\n        # 1. case: successful query data\n        data, error = influx_db_v2.run_query(query, \"user\")\n\n        assert data == result_data\n        assert error is None\n\n        influx_db_client_mock.assert_called_once_with(url=\"url\", token=\"token\", org=\"org\", **influx_kwargs)\n        logger_mock.debug.assert_called_once_with(f\"InfluxDB got query: {query!r}\")\n        query_mock.assert_called_once_with(query)\n        cleanup_cert_files_mock.assert_called_once_with(influx_kwargs)\n\n        influx_db_client_mock.reset_mock()\n        logger_mock.reset_mock()\n        query_mock.reset_mock()\n        cleanup_cert_files_mock.reset_mock()\n\n        # 2. case: unsuccessful query data\n        query_mock.side_effect = Exception(\"test error\")\n        data, error = influx_db_v2.run_query(query, \"user\")\n\n        assert data is None\n        assert error == \"test error\"\n\n        influx_db_client_mock.assert_called_once_with(url=\"url\", token=\"token\", org=\"org\", **influx_kwargs)\n        logger_mock.debug.assert_called_once_with(f\"InfluxDB got query: {query!r}\")\n        query_mock.assert_called_once_with(query)\n        cleanup_cert_files_mock.assert_called_once_with(influx_kwargs)\n"
  },
  {
    "path": "tests/query_runner/test_jql.py",
    "content": "from unittest import TestCase\n\nfrom redash.query_runner.jql import FieldMapping, parse_issue\n\n\nclass TestFieldMapping(TestCase):\n    def test_empty(self):\n        field_mapping = FieldMapping({})\n\n        self.assertEqual(field_mapping.get_output_field_name(\"field1\"), \"field1\")\n        self.assertEqual(field_mapping.get_dict_output_field_name(\"field1\", \"member1\"), None)\n        self.assertEqual(field_mapping.get_dict_members(\"field1\"), [])\n\n    def test_with_mappings(self):\n        field_mapping = FieldMapping(\n            {\n                \"field1\": \"output_name_1\",\n                \"field2.member1\": \"output_name_2\",\n                \"field2.member2\": \"output_name_3\",\n            }\n        )\n\n        self.assertEqual(field_mapping.get_output_field_name(\"field1\"), \"output_name_1\")\n        self.assertEqual(field_mapping.get_dict_output_field_name(\"field1\", \"member1\"), None)\n        self.assertEqual(field_mapping.get_dict_members(\"field1\"), [])\n\n        self.assertEqual(field_mapping.get_output_field_name(\"field2\"), \"field2\")\n        self.assertEqual(\n            field_mapping.get_dict_output_field_name(\"field2\", \"member1\"),\n            \"output_name_2\",\n        )\n        self.assertEqual(\n            field_mapping.get_dict_output_field_name(\"field2\", \"member2\"),\n            \"output_name_3\",\n        )\n        self.assertEqual(field_mapping.get_dict_output_field_name(\"field2\", \"member3\"), None)\n        self.assertEqual(field_mapping.get_dict_members(\"field2\"), [\"member1\", \"member2\"])\n\n\nclass TestParseIssue(TestCase):\n    issue = {\n        \"key\": \"KEY-1\",\n        \"fields\": {\n            \"string_field\": \"value1\",\n            \"int_field\": 123,\n            \"string_list_field\": [\"value1\", \"value2\"],\n            \"dict_field\": {\"member1\": \"value1\", \"member2\": \"value2\"},\n            \"dict_list_field\": [\n                {\"member1\": \"value1a\", \"member2\": \"value2a\"},\n                {\"member1\": \"value1b\", \"member2\": \"value2b\"},\n            ],\n            \"dict_legacy\": {\n                \"key\": \"legacyKey\",\n                \"name\": \"legacyName\",\n                \"dict_legacy\": \"legacyValue\",\n            },\n            \"watchers\": {\"watchCount\": 10},\n        },\n    }\n\n    def test_no_mapping(self):\n        result = parse_issue(self.issue, FieldMapping({}))\n\n        self.assertEqual(result[\"key\"], \"KEY-1\")\n        self.assertEqual(result[\"string_field\"], \"value1\")\n        self.assertEqual(result[\"int_field\"], 123)\n        self.assertEqual(result[\"string_list_field\"], \"value1,value2\")\n        self.assertEqual(\"dict_field\" in result, False)\n        self.assertEqual(\"dict_list_field\" in result, False)\n        self.assertEqual(result[\"dict_legacy\"], \"legacyValue\")\n        self.assertEqual(result[\"dict_legacy_key\"], \"legacyKey\")\n        self.assertEqual(result[\"dict_legacy_name\"], \"legacyName\")\n        self.assertEqual(result[\"watchers\"], 10)\n\n    def test_mapping(self):\n        result = parse_issue(\n            self.issue,\n            FieldMapping(\n                {\n                    \"string_field\": \"string_output_field\",\n                    \"string_list_field\": \"string_output_list_field\",\n                    \"dict_field.member1\": \"dict_field_1\",\n                    \"dict_field.member2\": \"dict_field_2\",\n                    \"dict_list_field.member1\": \"dict_list_field_1\",\n                    \"dict_legacy.key\": \"dict_legacy\",\n                    \"watchers.watchCount\": \"watchCount\",\n                }\n            ),\n        )\n\n        self.assertEqual(result[\"key\"], \"KEY-1\")\n        self.assertEqual(result[\"string_output_field\"], \"value1\")\n        self.assertEqual(result[\"int_field\"], 123)\n        self.assertEqual(result[\"string_output_list_field\"], \"value1,value2\")\n        self.assertEqual(result[\"dict_field_1\"], \"value1\")\n        self.assertEqual(result[\"dict_field_2\"], \"value2\")\n        self.assertEqual(result[\"dict_list_field_1\"], \"value1a,value1b\")\n        self.assertEqual(result[\"dict_legacy\"], \"legacyKey\")\n        self.assertEqual(\"dict_legacy_key\" in result, False)\n        self.assertEqual(\"dict_legacy_name\" in result, False)\n        self.assertEqual(\"watchers\" in result, False)\n        self.assertEqual(result[\"watchCount\"], 10)\n\n    def test_mapping_nonexisting_field(self):\n        result = parse_issue(\n            self.issue,\n            FieldMapping(\n                {\n                    \"non_existing_field\": \"output_name1\",\n                    \"dict_field.non_existing_member\": \"output_name2\",\n                    \"dict_list_field.non_existing_member\": \"output_name3\",\n                }\n            ),\n        )\n\n        self.assertEqual(result[\"key\"], \"KEY-1\")\n        self.assertEqual(result[\"string_field\"], \"value1\")\n        self.assertEqual(result[\"int_field\"], 123)\n        self.assertEqual(result[\"string_list_field\"], \"value1,value2\")\n        self.assertEqual(\"dict_field\" in result, False)\n        self.assertEqual(\"dict_list_field\" in result, False)\n        self.assertEqual(result[\"dict_legacy\"], \"legacyValue\")\n        self.assertEqual(result[\"dict_legacy_key\"], \"legacyKey\")\n        self.assertEqual(result[\"dict_legacy_name\"], \"legacyName\")\n        self.assertEqual(result[\"watchers\"], 10)\n"
  },
  {
    "path": "tests/query_runner/test_json_ds.py",
    "content": "\"\"\"\nSome test cases for JSON api runner\n\"\"\"\n\nfrom unittest import TestCase\nfrom urllib.parse import urlencode, urljoin\n\nfrom redash.query_runner.json_ds import JSON\n\n\ndef mock_api(url, method, **request_options):\n    if \"params\" in request_options:\n        qs = urlencode(request_options[\"params\"])\n        url = urljoin(url, \"?{}\".format(qs))\n\n    data, error = None, None\n\n    if url == \"http://localhost/basics\":\n        data = [{\"id\": 1}, {\"id\": 2}]\n    elif url == \"http://localhost/token-test\":\n        data = {\"next_page_token\": \"2\", \"records\": [{\"id\": 1}, {\"id\": 2}]}\n    elif url == \"http://localhost/token-test?page_token=2\":\n        data = {\"next_page_token\": \"3\", \"records\": [{\"id\": 3}, {\"id\": 4}]}\n    elif url == \"http://localhost/token-test?page_token=3\":\n        data = {\"records\": [{\"id\": 5}]}\n    elif url == \"http://localhost/hateoas\":\n        data = {\n            \"_embedded\": {\"records\": [{\"id\": 10}, {\"id\": 11}]},\n            \"_links\": {\n                \"first\": {\"href\": \"http://localhost/hateoas\"},\n                \"self\": {\"href\": \"http://localhost/hateoas\"},\n                \"next\": {\"href\": \"http://localhost/hateoas?page=2\"},\n                \"last\": {\"href\": \"http://localhost/hateoas?page=2\"},\n            },\n            \"page\": {\"size\": 2, \"totalElements\": 3, \"totalPages\": 2},\n        }\n    elif url == \"http://localhost/hateoas?page=2\":\n        data = {\n            \"_embedded\": {\"records\": [{\"id\": 12}]},\n            \"_links\": {\n                \"first\": {\"href\": \"http://localhost/hateoas\"},\n                \"self\": {\"href\": \"http://localhost/hateoas?page=2\"},\n                \"prev\": {\"href\": \"http://localhost/hateoas\"},\n                \"last\": {\"href\": \"http://localhost/hateoas?page=2\"},\n            },\n            \"page\": {\"size\": 2, \"totalElements\": 3, \"totalPages\": 2},\n        }\n    else:\n        error = \"404: {} not found\".format(url)\n\n    return data, error\n\n\nclass TestJSON(TestCase):\n    def setUp(self):\n        self.runner = JSON({\"base_url\": \"http://localhost/\"})\n        self.runner._get_json_response = mock_api\n\n    def test_basics(self):\n        q = {\"url\": \"basics\"}\n        results, error = self.runner._run_json_query(q)\n\n        expected = [{\"id\": 1}, {\"id\": 2}]\n        self.assertEqual(results[\"rows\"], expected)\n\n    def test_token_pagination(self):\n        q = {\n            \"url\": \"token-test\",\n            \"pagination\": {\"type\": \"token\", \"fields\": [\"next_page_token\", \"page_token\"]},\n            \"path\": \"records\",\n        }\n        results, error = self.runner._run_json_query(q)\n        self.assertIsNone(error)\n\n        expected = [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}, {\"id\": 4}, {\"id\": 5}]\n        self.assertEqual(results[\"rows\"], expected)\n\n    def test_url_pagination(self):\n        q = {\n            \"url\": \"hateoas\",\n            \"pagination\": {\"type\": \"url\", \"path\": \"_links.next.href\"},\n            \"path\": \"_embedded.records\",\n            \"fields\": [\"id\"],\n        }\n        results, error = self.runner._run_json_query(q)\n        self.assertIsNone(error)\n\n        expected = [{\"id\": 10}, {\"id\": 11}, {\"id\": 12}]\n        self.assertEqual(results[\"rows\"], expected)\n"
  },
  {
    "path": "tests/query_runner/test_mongodb.py",
    "content": "import datetime\nfrom unittest import TestCase\n\nfrom freezegun import freeze_time\nfrom mock import patch\nfrom pytz import utc\n\nfrom redash.query_runner import TYPE_INTEGER, TYPE_STRING\nfrom redash.query_runner.mongodb import (\n    MongoDB,\n    _get_column_by_name,\n    parse_query_json,\n    parse_results,\n)\nfrom redash.utils import json_dumps, parse_human_time\n\n\n@patch(\"redash.query_runner.mongodb.pymongo.MongoClient\")\nclass TestMongoDB(TestCase):\n    def test_username_password_present_overrides_username_from_uri(self, mongo_client):\n        config = {\n            \"connectionString\": \"mongodb://localhost:27017/test\",\n            \"username\": \"test_user\",\n            \"password\": \"test_pass\",\n            \"dbName\": \"test\",\n        }\n        mongo_qr = MongoDB(config)\n        _ = mongo_qr._get_db()\n\n        self.assertIn(\"username\", mongo_client.call_args.kwargs)\n        self.assertIn(\"password\", mongo_client.call_args.kwargs)\n\n    def test_username_password_absent_does_not_pass_args(self, mongo_client):\n        config = {\"connectionString\": \"mongodb://user:pass@localhost:27017/test\", \"dbName\": \"test\"}\n        mongo_qr = MongoDB(config)\n        _ = mongo_qr._get_db()\n\n        self.assertNotIn(\"username\", mongo_client.call_args.kwargs)\n        self.assertNotIn(\"password\", mongo_client.call_args.kwargs)\n\n    def test_run_query_with_fields(self, mongo_client):\n        query = {\"collection\": \"test\", \"query\": {\"age\": 10}, \"fields\": {\"_id\": 1, \"name\": 2}}\n        return_value = [{\"_id\": \"6569ee53d53db7930aaa0cc0\", \"name\": \"test2\"}]\n        expected = {\n            \"columns\": [\n                {\"name\": \"_id\", \"friendly_name\": \"_id\", \"type\": TYPE_STRING},\n                {\"name\": \"name\", \"friendly_name\": \"name\", \"type\": TYPE_STRING},\n            ],\n            \"rows\": return_value,\n        }\n\n        mongo_client().__getitem__().__getitem__().find.return_value = return_value\n        self._test_query(query, return_value, expected)\n\n    def test_run_query_with_func(self, mongo_client):\n        query = {\n            \"collection\": \"test\",\n            \"query\": {\"age\": 10},\n            \"fields\": {\"_id\": 1, \"name\": 4, \"link\": {\"$concat\": [\"hoge_\", \"$name\"]}},\n        }\n        return_value = [{\"_id\": \"6569ee53d53db7930aaa0cc0\", \"name\": \"test2\", \"link\": \"hoge_test2\"}]\n        expected = {\n            \"columns\": [\n                {\"name\": \"_id\", \"friendly_name\": \"_id\", \"type\": TYPE_STRING},\n                {\"name\": \"link\", \"friendly_name\": \"link\", \"type\": TYPE_STRING},\n                {\"name\": \"name\", \"friendly_name\": \"name\", \"type\": TYPE_STRING},\n            ],\n            \"rows\": return_value,\n        }\n\n        mongo_client().__getitem__().__getitem__().find.return_value = return_value\n        self._test_query(query, return_value, expected)\n\n    def test_run_query_with_aggregate(self, mongo_client):\n        query = {\n            \"collection\": \"test\",\n            \"aggregate\": [\n                {\"$unwind\": \"$tags\"},\n                {\"$group\": {\"_id\": \"$tags\", \"count\": {\"$sum\": 1}}},\n                {\"$sort\": [{\"name\": \"count\", \"direction\": -1}, {\"name\": \"_id\", \"direction\": -1}]},\n            ],\n        }\n        return_value = [{\"_id\": \"foo\", \"count\": 10}, {\"_id\": \"bar\", \"count\": 9}]\n        expected = {\n            \"columns\": [\n                {\"name\": \"_id\", \"friendly_name\": \"_id\", \"type\": TYPE_STRING},\n                {\"name\": \"count\", \"friendly_name\": \"count\", \"type\": TYPE_INTEGER},\n            ],\n            \"rows\": return_value,\n        }\n\n        mongo_client().__getitem__().__getitem__().aggregate.return_value = return_value\n        self._test_query(query, return_value, expected)\n\n    def _test_query(self, query, return_value, expected):\n        config = {\n            \"connectionString\": \"mongodb://localhost:27017/test\",\n            \"username\": \"test_user\",\n            \"password\": \"test_pass\",\n            \"dbName\": \"test\",\n        }\n        mongo_qr = MongoDB(config)\n\n        result, err = mongo_qr.run_query(json_dumps(query), None)\n        self.assertIsNone(err)\n        self.assertEqual(expected, result)\n\n\nclass TestParseQueryJson(TestCase):\n    def test_ignores_non_isodate_fields(self):\n        query = {\"test\": 1, \"test_list\": [\"a\", \"b\", \"c\"], \"test_dict\": {\"a\": 1, \"b\": 2}}\n\n        query_data = parse_query_json(json_dumps(query))\n        self.assertDictEqual(query_data, query)\n\n    def test_parses_isodate_fields(self):\n        query = {\n            \"test\": 1,\n            \"test_list\": [\"a\", \"b\", \"c\"],\n            \"test_dict\": {\"a\": 1, \"b\": 2},\n            \"testIsoDate\": 'ISODate(\"2014-10-03T00:00\")',\n        }\n\n        query_data = parse_query_json(json_dumps(query))\n\n        self.assertEqual(query_data[\"testIsoDate\"], datetime.datetime(2014, 10, 3, 0, 0))\n\n    def test_parses_isodate_in_nested_fields(self):\n        query = {\n            \"test\": 1,\n            \"test_list\": [\"a\", \"b\", \"c\"],\n            \"test_dict\": {\"a\": 1, \"b\": {\"date\": 'ISODate(\"2014-10-04T00:00\")'}},\n            \"testIsoDate\": 'ISODate(\"2014-10-03T00:00\")',\n        }\n\n        query_data = parse_query_json(json_dumps(query))\n\n        self.assertEqual(query_data[\"testIsoDate\"], datetime.datetime(2014, 10, 3, 0, 0))\n        self.assertEqual(query_data[\"test_dict\"][\"b\"][\"date\"], datetime.datetime(2014, 10, 4, 0, 0))\n\n    def test_handles_nested_fields(self):\n        # https://github.com/getredash/redash/issues/597\n        query = {\n            \"collection\": \"bus\",\n            \"aggregate\": [\n                {\n                    \"$geoNear\": {\n                        \"near\": {\n                            \"type\": \"Point\",\n                            \"coordinates\": [-22.910079, -43.205161],\n                        },\n                        \"maxDistance\": 100000000,\n                        \"distanceField\": \"dist.calculated\",\n                        \"includeLocs\": \"dist.location\",\n                        \"spherical\": True,\n                    }\n                }\n            ],\n        }\n\n        query_data = parse_query_json(json_dumps(query))\n\n        self.assertDictEqual(query, query_data)\n\n    def test_supports_extended_json_types(self):\n        query = {\n            \"test\": 1,\n            \"test_list\": [\"a\", \"b\", \"c\"],\n            \"test_dict\": {\"a\": 1, \"b\": 2},\n            \"testIsoDate\": 'ISODate(\"2014-10-03T00:00\")',\n            \"test$date\": {\"$date\": \"2014-10-03T00:00:00.0\"},\n            \"test$undefined\": {\"$undefined\": None},\n        }\n        query_data = parse_query_json(json_dumps(query))\n        self.assertEqual(query_data[\"test$undefined\"], None)\n        self.assertEqual(\n            query_data[\"test$date\"],\n            datetime.datetime(2014, 10, 3, 0, 0).replace(tzinfo=utc),\n        )\n\n    @freeze_time(\"2019-01-01 12:00:00\")\n    def test_supports_relative_timestamps(self):\n        query = {\"ts\": {\"$humanTime\": \"1 hour ago\"}}\n\n        one_hour_ago = parse_human_time(\"1 hour ago\")\n        query_data = parse_query_json(json_dumps(query))\n        self.assertEqual(query_data[\"ts\"], one_hour_ago)\n\n\nclass TestMongoResults(TestCase):\n    def test_parses_regular_results(self):\n        raw_results = [\n            {\"column\": 1, \"column2\": \"test\"},\n            {\"column\": 2, \"column2\": \"test\", \"column3\": \"hello\"},\n        ]\n        rows, columns = parse_results(raw_results)\n\n        for i, row in enumerate(rows):\n            self.assertDictEqual(row, raw_results[i])\n\n        self.assertEqual(3, len(columns))\n        self.assertIsNotNone(_get_column_by_name(columns, \"column\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"column2\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"column3\"))\n\n    def test_parses_nested_results(self):\n        raw_results = [\n            {\"column\": 1, \"column2\": \"test\", \"nested\": {\"a\": 1, \"b\": \"str\"}},\n            {\n                \"column\": 2,\n                \"column2\": \"test\",\n                \"column3\": \"hello\",\n                \"nested\": {\n                    \"a\": 2,\n                    \"b\": \"str2\",\n                    \"c\": \"c\",\n                    \"d\": {\"e\": 3},\n                    \"f\": {\"h\": {\"i\": [\"j\", \"k\", \"l\"]}},\n                },\n            },\n        ]\n\n        rows, columns = parse_results(raw_results)\n\n        self.assertDictEqual(rows[0], {\"column\": 1, \"column2\": \"test\", \"nested.a\": 1, \"nested.b\": \"str\"})\n        self.assertDictEqual(\n            rows[1],\n            {\n                \"column\": 2,\n                \"column2\": \"test\",\n                \"column3\": \"hello\",\n                \"nested.a\": 2,\n                \"nested.b\": \"str2\",\n                \"nested.c\": \"c\",\n                \"nested.d.e\": 3,\n                \"nested.f.h.i\": [\"j\", \"k\", \"l\"],\n            },\n        )\n\n        self.assertIsNotNone(_get_column_by_name(columns, \"column\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"column2\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"column3\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.a\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.b\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.c\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.d.e\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.f.h.i\"))\n\n    def test_parses_flatten_nested_results(self):\n        raw_results = [\n            {\n                \"column\": 2,\n                \"column2\": \"test\",\n                \"column3\": \"hello\",\n                \"nested\": {\n                    \"a\": 2,\n                    \"b\": \"str2\",\n                    \"c\": \"c\",\n                    \"d\": {\"e\": 3},\n                    \"f\": {\"h\": {\"i\": [\"j\", \"k\", \"l\"]}},\n                },\n            }\n        ]\n\n        rows, columns = parse_results(raw_results, flatten=True)\n        print(rows)\n        self.assertDictEqual(\n            rows[0],\n            {\n                \"column\": 2,\n                \"column2\": \"test\",\n                \"column3\": \"hello\",\n                \"nested.a\": 2,\n                \"nested.b\": \"str2\",\n                \"nested.c\": \"c\",\n                \"nested.d.e\": 3,\n                \"nested.f.h.i.0\": \"j\",\n                \"nested.f.h.i.1\": \"k\",\n                \"nested.f.h.i.2\": \"l\",\n            },\n        )\n\n        self.assertIsNotNone(_get_column_by_name(columns, \"column\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"column2\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"column3\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.a\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.b\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.c\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.d.e\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.f.h.i.0\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.f.h.i.1\"))\n        self.assertIsNotNone(_get_column_by_name(columns, \"nested.f.h.i.2\"))\n"
  },
  {
    "path": "tests/query_runner/test_oracle.py",
    "content": "import unittest\n\nfrom redash.query_runner.oracle import Oracle\n\n\nclass TestOracle(unittest.TestCase):\n    def setUp(self):\n        self.query_runner = Oracle({})\n\n    def test_add_limit_query_no_limit(self):\n        query = \"SELECT *\"\n        self.assertEqual(\"SELECT * FETCH NEXT 1000 ROWS ONLY\", self.query_runner.add_limit_to_query(query))\n\n    def test_add_limit_query_with_punc(self):\n        query = \"SELECT *;\"\n        self.assertEqual(\"SELECT * FETCH NEXT 1000 ROWS ONLY;\", self.query_runner.add_limit_to_query(query))\n\n    def test_apply_auto_limit_origin_no_limit_1(self):\n        origin_query_text = \"SELECT 2\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(\"SELECT 2 FETCH NEXT 1000 ROWS ONLY\", query_text)\n\n    def test_apply_auto_limit_origin_have_limit_1(self):\n        origin_query_text = \"SELECT 2 LIMIT 100 FETCH NEXT 1000 ROWS ONLY\"\n        query_text = self.query_runner.apply_auto_limit(origin_query_text, True)\n        self.assertEqual(origin_query_text, query_text)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/query_runner/test_pg.py",
    "content": "from unittest import TestCase\n\nfrom redash.query_runner.pg import _parse_dsn, build_schema\n\n\nclass TestParameters(TestCase):\n    def test_parse_dsn(self):\n        configuration = {\"dsn\": \"application_name=redash connect_timeout=5\"}\n        self.assertDictEqual(_parse_dsn(configuration), {\"application_name\": \"redash\", \"connect_timeout\": \"5\"})\n\n    def test_parse_dsn_not_permitted(self):\n        configuration = {\"dsn\": \"password=xyz\"}\n        self.assertRaises(ValueError, _parse_dsn, configuration)\n\n\nclass TestBuildSchema(TestCase):\n    def test_handles_dups_between_public_and_other_schemas(self):\n        results = {\n            \"rows\": [\n                {\n                    \"table_schema\": \"public\",\n                    \"table_name\": \"main.users\",\n                    \"column_name\": \"id\",\n                },\n                {\"table_schema\": \"main\", \"table_name\": \"users\", \"column_name\": \"id\"},\n                {\"table_schema\": \"main\", \"table_name\": \"users\", \"column_name\": \"name\"},\n            ]\n        }\n\n        schema = {}\n\n        build_schema(results, schema)\n\n        self.assertIn(\"main.users\", schema.keys())\n        self.assertListEqual(schema[\"main.users\"][\"columns\"], [\"id\", \"name\"])\n        self.assertIn('public.\"main.users\"', schema.keys())\n        self.assertListEqual(schema['public.\"main.users\"'][\"columns\"], [\"id\"])\n\n    def test_build_schema_with_data_types(self):\n        results = {\n            \"rows\": [\n                {\"table_schema\": \"main\", \"table_name\": \"users\", \"column_name\": \"id\", \"data_type\": \"integer\"},\n                {\"table_schema\": \"main\", \"table_name\": \"users\", \"column_name\": \"name\", \"data_type\": \"varchar\"},\n            ]\n        }\n\n        schema = {}\n\n        build_schema(results, schema)\n\n        self.assertListEqual(\n            schema[\"main.users\"][\"columns\"], [{\"name\": \"id\", \"type\": \"integer\"}, {\"name\": \"name\", \"type\": \"varchar\"}]\n        )\n"
  },
  {
    "path": "tests/query_runner/test_prometheus.py",
    "content": "import time\nfrom datetime import datetime\nfrom unittest import TestCase\n\nimport mock\n\nfrom redash.query_runner.prometheus import Prometheus, get_instant_rows, get_range_rows\n\n\nclass TestPrometheus(TestCase):\n    def setUp(self):\n        self.instant_query_result = [\n            {\n                \"metric\": {\"name\": \"example_metric_name\", \"foo_bar\": \"foo\"},\n                \"value\": [1516937400.781, \"7400_foo\"],\n            },\n            {\n                \"metric\": {\"name\": \"example_metric_name\", \"foo_bar\": \"bar\"},\n                \"value\": [1516937400.781, \"7400_bar\"],\n            },\n        ]\n\n        self.range_query_result = [\n            {\n                \"metric\": {\"name\": \"example_metric_name\", \"foo_bar\": \"foo\"},\n                \"values\": [[1516937400.781, \"7400_foo\"], [1516938000.781, \"8000_foo\"]],\n            },\n            {\n                \"metric\": {\"name\": \"example_metric_name\", \"foo_bar\": \"bar\"},\n                \"values\": [[1516937400.781, \"7400_bar\"], [1516938000.781, \"8000_bar\"]],\n            },\n        ]\n\n    def test_get_instant_rows(self):\n        instant_rows = [\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"foo\",\n                \"timestamp\": datetime.fromtimestamp(1516937400.781),\n                \"value\": \"7400_foo\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"bar\",\n                \"timestamp\": datetime.fromtimestamp(1516937400.781),\n                \"value\": \"7400_bar\",\n            },\n        ]\n\n        rows = get_instant_rows(self.instant_query_result)\n        self.assertEqual(instant_rows, rows)\n\n    def test_get_range_rows(self):\n        range_rows = [\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"foo\",\n                \"timestamp\": datetime.fromtimestamp(1516937400.781),\n                \"value\": \"7400_foo\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"foo\",\n                \"timestamp\": datetime.fromtimestamp(1516938000.781),\n                \"value\": \"8000_foo\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"bar\",\n                \"timestamp\": datetime.fromtimestamp(1516937400.781),\n                \"value\": \"7400_bar\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"bar\",\n                \"timestamp\": datetime.fromtimestamp(1516938000.781),\n                \"value\": \"8000_bar\",\n            },\n        ]\n\n        rows = get_range_rows(self.range_query_result)\n        self.assertEqual(range_rows, rows)\n\n    @mock.patch(\"redash.query_runner.prometheus.datetime\")\n    def test_get_datetime_now(self, datetime_mock: mock.MagicMock):\n        prometheus = Prometheus({\"url\": \"url\"})\n        datetime_mock.now.return_value = datetime(2023, 12, 12, 11, 00)\n        now = prometheus._get_datetime_now()\n        self.assertEqual(now, datetime(2023, 12, 12, 11, 00))\n\n    @mock.patch(\"redash.query_runner.prometheus.Prometheus._create_cert_file\")\n    def test_get_prometheus_kwargs(self, create_cert_file_mock: mock.MagicMock):\n        # 1. case: without ssl attributes\n        prometheus = Prometheus({\"url\": \"url\"})\n\n        create_cert_file_mock.return_value = None\n\n        prometheus_kwargs = prometheus._get_prometheus_kwargs()\n\n        assert prometheus_kwargs == {\n            \"verify\": True,\n            \"cert\": (),\n        }\n\n        create_cert_file_mock.assert_has_calls(\n            [mock.call(\"ca_cert_File\"), mock.call(\"cert_File\"), mock.call(\"cert_key_File\")]\n        )\n\n        create_cert_file_mock.reset_mock()\n\n        # 2. case: with ssl attributes\n        create_cert_file_return_dict = {\n            \"ca_cert_File\": \"ca_cert_file.crt\",\n            \"cert_File\": \"cert_file.crt\",\n            \"cert_key_File\": \"cert_key_file.key\",\n        }\n        create_cert_file_mock.side_effect = lambda key: create_cert_file_return_dict[key]\n\n        prometheus = Prometheus(\n            {\n                \"url\": \"url\",\n                \"verify_ssl\": True,\n                \"ca_cert_File\": \"ca_cert_file\",\n                \"cert_File\": \"cert_file\",\n                \"cert_key_File\": \"cert_key_file\",\n            }\n        )\n\n        prometheus_kwargs = prometheus._get_prometheus_kwargs()\n\n        assert prometheus_kwargs == {\n            \"verify\": \"ca_cert_file.crt\",\n            \"cert\": (\"cert_file.crt\", \"cert_key_file.key\"),\n        }\n        create_cert_file_mock.assert_has_calls(\n            [mock.call(\"ca_cert_File\"), mock.call(\"cert_File\"), mock.call(\"cert_key_File\")]\n        )\n\n    @mock.patch(\"redash.query_runner.prometheus.NamedTemporaryFile\")\n    def test_create_cert_file(self, named_temporary_file_mock: mock.MagicMock):\n        # 1. case: with none value\n        prometheus = Prometheus({\"url\": \"url\"})\n\n        context_manager_mock = named_temporary_file_mock().__enter__()\n\n        cert_file_name = prometheus._create_cert_file(\"key\")\n\n        assert cert_file_name is None\n        context_manager_mock.write().assert_not_called()\n\n        named_temporary_file_mock.reset_mock()\n\n        # 2. case: with a valid key\n        prometheus = Prometheus({\"url\": \"url\", \"key\": \"dmFsdWU=\"})\n\n        context_manager_mock = named_temporary_file_mock().__enter__()\n        context_manager_mock.name = \"cert_file_name\"\n\n        cert_file_name = prometheus._create_cert_file(\"key\")\n\n        assert cert_file_name == \"cert_file_name\"\n        context_manager_mock.write.assert_called_once_with(\"value\")\n\n    @mock.patch(\"redash.query_runner.prometheus.os\")\n    def test_cleanup_cert_files(self, os_mock: mock.MagicMock):\n        # 1. case: no file found or verify is bool\n        prometheus = Prometheus(\n            {\n                \"url\": \"url\",\n                \"verify_ssl\": True,\n                \"ca_cert_File\": \"ca_cert_file\",\n                \"cert_File\": \"cert_file\",\n                \"cert_key_File\": \"cert_key_file\",\n            }\n        )\n\n        prometheus._cleanup_cert_files({\"verify\": True, \"cert\": ()})\n\n        os_mock.path.exists.assert_not_called()\n        os_mock.remove.assert_not_called()\n\n        # 2. case: files found and deleted\n        os_mock.path.exists.return_value = True\n        prometheus._cleanup_cert_files({\"verify\": \"ca_cert_file\", \"cert\": (\"cert_file\", \"cert_key_file\")})\n\n        os_mock.path.exists.assert_has_calls(\n            [mock.call(\"ca_cert_file\"), mock.call(\"cert_file\"), mock.call(\"cert_key_file\")]\n        )\n        os_mock.remove.assert_has_calls(\n            [mock.call(\"ca_cert_file\"), mock.call(\"cert_file\"), mock.call(\"cert_key_file\")]\n        )\n\n    def test_configuration_schema(self):\n        configuration_schema = Prometheus.configuration_schema()\n        assert configuration_schema == {\n            \"type\": \"object\",\n            \"properties\": {\n                \"url\": {\"type\": \"string\", \"title\": \"Prometheus API URL\"},\n                \"verify_ssl\": {\n                    \"type\": \"boolean\",\n                    \"title\": \"Verify SSL (Ignored, if SSL Root Certificate is given)\",\n                    \"default\": True,\n                },\n                \"cert_File\": {\"type\": \"string\", \"title\": \"SSL Client Certificate\", \"default\": None},\n                \"cert_key_File\": {\"type\": \"string\", \"title\": \"SSL Client Key\", \"default\": None},\n                \"ca_cert_File\": {\"type\": \"string\", \"title\": \"SSL Root Certificate\", \"default\": None},\n            },\n            \"required\": [\"url\"],\n            \"secret\": [\"cert_File\", \"cert_key_File\", \"ca_cert_File\"],\n            \"extra_options\": [\"verify_ssl\", \"cert_File\", \"cert_key_File\", \"ca_cert_File\"],\n        }\n\n    def test_enabled(self):\n        assert Prometheus.enabled() is True\n\n    @mock.patch(\"redash.query_runner.prometheus.requests.get\")\n    @mock.patch(\"redash.query_runner.prometheus.Prometheus._cleanup_cert_files\")\n    def test_test_connection(\n        self,\n        cleanup_cert_files_mock: mock.MagicMock,\n        requests_get_mock: mock.MagicMock,\n    ):\n        # 1. case: successful test connection\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n\n        requests_get_mock.get.return_value = mock.Mock(ok=True)\n\n        connected = prometheus.test_connection()\n\n        self.assertTrue(connected)\n        requests_get_mock.assert_called_once_with(\"url\", **prometheus_kwargs)\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n\n        cleanup_cert_files_mock.reset_mock()\n        requests_get_mock.reset_mock()\n\n        # 2. case: unsuccessful test connection\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n\n        requests_get_mock.return_value = mock.Mock(ok=False)\n\n        connected = prometheus.test_connection()\n\n        self.assertFalse(connected)\n        requests_get_mock.assert_called_once_with(\"url\", **prometheus_kwargs)\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n\n        cleanup_cert_files_mock.reset_mock()\n        requests_get_mock.reset_mock()\n\n        # 3. case: unsuccessful test connection with raised exception\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n\n        requests_get_mock.side_effect = Exception(\"test exception\")\n\n        with self.assertRaises(Exception) as exception_obj:\n            connected = prometheus.test_connection()\n\n        self.assertFalse(connected)\n        self.assertEqual(str(exception_obj.exception), \"test exception\")\n        requests_get_mock.assert_called_once_with(\"url\", **prometheus_kwargs)\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n\n    @mock.patch(\"redash.query_runner.prometheus.requests.get\")\n    @mock.patch(\"redash.query_runner.prometheus.Prometheus._cleanup_cert_files\")\n    def test_get_schema(\n        self,\n        cleanup_cert_files_mock: mock.MagicMock,\n        requests_get_mock: mock.MagicMock,\n    ):\n        # 1. case: successful get schema\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n\n        requests_get_mock.return_value = mock.Mock(json=mock.Mock(return_value={\"data\": [\"name1\", \"name2\"]}))\n\n        schema = prometheus.get_schema()\n\n        self.assertEqual(schema, [{\"name\": \"name1\", \"columns\": []}, {\"name\": \"name2\", \"columns\": []}])\n        requests_get_mock.assert_called_once_with(\"url/api/v1/label/__name__/values\", **prometheus_kwargs)\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n\n        cleanup_cert_files_mock.reset_mock()\n        requests_get_mock.reset_mock()\n\n        # 2. case: successful get empty schema\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n\n        requests_get_mock.return_value = mock.Mock(json=mock.Mock(return_value={\"data\": []}))\n\n        schema = prometheus.get_schema()\n\n        self.assertEqual(schema, [])\n        requests_get_mock.assert_called_once_with(\"url/api/v1/label/__name__/values\", **prometheus_kwargs)\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n\n        cleanup_cert_files_mock.reset_mock()\n        requests_get_mock.reset_mock()\n\n        # 3. case: unsuccessful get schema with an exception\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n\n        requests_get_mock.side_effect = Exception(\"test exception\")\n\n        with self.assertRaises(Exception) as exception_obj:\n            schema = prometheus.get_schema()\n\n        self.assertEqual(schema, [])\n        self.assertEqual(str(exception_obj.exception), \"test exception\")\n        requests_get_mock.assert_called_once_with(\"url/api/v1/label/__name__/values\", **prometheus_kwargs)\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n\n    @mock.patch(\"redash.query_runner.prometheus.requests.get\")\n    @mock.patch(\"redash.query_runner.prometheus.Prometheus._cleanup_cert_files\")\n    def test_run_query(\n        self,\n        cleanup_cert_files_mock: mock.MagicMock,\n        requests_get_mock: mock.MagicMock,\n    ):\n        # 1. case: successful run instant query\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n\n        timestamp_expected_7400 = datetime.fromtimestamp(1516937400.781)\n\n        rows = [\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"foo\",\n                \"timestamp\": timestamp_expected_7400,\n                \"value\": \"7400_foo\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"bar\",\n                \"timestamp\": timestamp_expected_7400,\n                \"value\": \"7400_bar\",\n            },\n        ]\n        columns = [\n            {\"friendly_name\": \"timestamp\", \"type\": \"datetime\", \"name\": \"timestamp\"},\n            {\"friendly_name\": \"value\", \"type\": \"string\", \"name\": \"value\"},\n            {\"friendly_name\": \"name\", \"type\": \"string\", \"name\": \"name\"},\n            {\"friendly_name\": \"foo_bar\", \"type\": \"string\", \"name\": \"foo_bar\"},\n        ]\n\n        data_expected = {\"rows\": rows, \"columns\": columns}\n\n        requests_get_mock.return_value = mock.Mock(\n            json=mock.Mock(return_value={\"data\": {\"result\": self.instant_query_result}})\n        )\n\n        data, error = prometheus.run_query(\"http_requests_total\", \"user\")\n\n        self.assertEqual(data, data_expected)\n        self.assertIsNone(error)\n        requests_get_mock.assert_called_once_with(\n            \"url/api/v1/query\", params={\"query\": [\"http_requests_total\"]}, **prometheus_kwargs\n        )\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n\n        cleanup_cert_files_mock.reset_mock()\n        requests_get_mock.reset_mock()\n\n        # 2. case: successful run instant query with empty result\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n\n        requests_get_mock.return_value = mock.Mock(json=mock.Mock(return_value={\"data\": {\"result\": []}}))\n\n        data, error = prometheus.run_query(\"http_requests_total\", \"user\")\n\n        self.assertIsNone(data)\n        self.assertEqual(error, \"query result is empty.\")\n        requests_get_mock.assert_called_once_with(\n            \"url/api/v1/query\", params={\"query\": [\"http_requests_total\"]}, **prometheus_kwargs\n        )\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n\n        cleanup_cert_files_mock.reset_mock()\n        requests_get_mock.reset_mock()\n\n        # 3. case: successful run range query with start and end\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n        timestamp_expected_8000 = datetime.fromtimestamp(1516938000.781)\n\n        rows = [\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"foo\",\n                \"timestamp\": timestamp_expected_7400,\n                \"value\": \"7400_foo\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"foo\",\n                \"timestamp\": timestamp_expected_8000,\n                \"value\": \"8000_foo\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"bar\",\n                \"timestamp\": timestamp_expected_7400,\n                \"value\": \"7400_bar\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"bar\",\n                \"timestamp\": timestamp_expected_8000,\n                \"value\": \"8000_bar\",\n            },\n        ]\n        columns = [\n            {\"friendly_name\": \"timestamp\", \"type\": \"datetime\", \"name\": \"timestamp\"},\n            {\"friendly_name\": \"value\", \"type\": \"string\", \"name\": \"value\"},\n            {\"friendly_name\": \"name\", \"type\": \"string\", \"name\": \"name\"},\n            {\"friendly_name\": \"foo_bar\", \"type\": \"string\", \"name\": \"foo_bar\"},\n        ]\n\n        data_expected = {\"rows\": rows, \"columns\": columns}\n\n        requests_get_mock.return_value = mock.Mock(\n            json=mock.Mock(return_value={\"data\": {\"result\": self.range_query_result}})\n        )\n\n        start_timestamp_expected = int(time.mktime(datetime(2018, 1, 26).timetuple()))\n        end_timestamp_expected = int(time.mktime(datetime(2018, 1, 27).timetuple()))\n        data, error = prometheus.run_query(\n            \"http_requests_total&start=2018-01-26T00:00:00.000Z&end=2018-01-27T00:00:00.000Z&step=60s\", \"user\"\n        )\n\n        self.assertEqual(data, data_expected)\n        self.assertIsNone(error)\n        requests_get_mock.assert_called_once_with(\n            \"url/api/v1/query_range\",\n            params={\n                \"query\": [\"http_requests_total\"],\n                \"start\": [start_timestamp_expected],\n                \"end\": [end_timestamp_expected],\n                \"step\": [\"60s\"],\n            },\n            **prometheus_kwargs,\n        )\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n\n        cleanup_cert_files_mock.reset_mock()\n        requests_get_mock.reset_mock()\n\n        # 4. case: successful run range query with start and without end\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n\n        rows = [\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"foo\",\n                \"timestamp\": timestamp_expected_7400,\n                \"value\": \"7400_foo\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"foo\",\n                \"timestamp\": timestamp_expected_8000,\n                \"value\": \"8000_foo\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"bar\",\n                \"timestamp\": timestamp_expected_7400,\n                \"value\": \"7400_bar\",\n            },\n            {\n                \"name\": \"example_metric_name\",\n                \"foo_bar\": \"bar\",\n                \"timestamp\": timestamp_expected_8000,\n                \"value\": \"8000_bar\",\n            },\n        ]\n        columns = [\n            {\"friendly_name\": \"timestamp\", \"type\": \"datetime\", \"name\": \"timestamp\"},\n            {\"friendly_name\": \"value\", \"type\": \"string\", \"name\": \"value\"},\n            {\"friendly_name\": \"name\", \"type\": \"string\", \"name\": \"name\"},\n            {\"friendly_name\": \"foo_bar\", \"type\": \"string\", \"name\": \"foo_bar\"},\n        ]\n\n        data_expected = {\"rows\": rows, \"columns\": columns}\n\n        now_datetime = datetime(2023, 12, 12, 11, 00, 00)\n        end_timestamp_expected = int(time.mktime(now_datetime.timetuple()))\n\n        requests_get_mock.return_value = mock.Mock(\n            json=mock.Mock(return_value={\"data\": {\"result\": self.range_query_result}})\n        )\n\n        with mock.patch(\"redash.query_runner.prometheus.Prometheus._get_datetime_now\") as get_datetime_now_mock:\n            get_datetime_now_mock.return_value = now_datetime\n            data, error = prometheus.run_query(\"http_requests_total&start=2018-01-26T00:00:00.000Z&step=60s\", \"user\")\n\n        self.assertEqual(data, data_expected)\n        self.assertIsNone(error)\n        requests_get_mock.assert_called_once_with(\n            \"url/api/v1/query_range\",\n            params={\n                \"query\": [\"http_requests_total\"],\n                \"start\": [start_timestamp_expected],\n                \"end\": [end_timestamp_expected],\n                \"step\": [\"60s\"],\n            },\n            **prometheus_kwargs,\n        )\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n\n        cleanup_cert_files_mock.reset_mock()\n        requests_get_mock.reset_mock()\n        data = None\n        error = None\n\n        # 5. case: run query with exception\n        prometheus = Prometheus({\"url\": \"url\"})\n        prometheus_kwargs = {\"verify\": True, \"cert\": ()}\n\n        requests_get_mock.side_effect = Exception(\"test exception\")\n\n        with self.assertRaises(Exception) as exception_obj:\n            data, error = prometheus.run_query(\"http_requests_total\", \"user\")\n\n        self.assertIsNone(data)\n        self.assertIsNone(error)\n        self.assertEqual(str(exception_obj.exception), \"test exception\")\n        requests_get_mock.assert_called_once_with(\n            \"url/api/v1/query\", params={\"query\": [\"http_requests_total\"]}, **prometheus_kwargs\n        )\n        cleanup_cert_files_mock.assert_called_once_with(prometheus_kwargs)\n"
  },
  {
    "path": "tests/query_runner/test_python.py",
    "content": "from datetime import datetime\nfrom unittest import TestCase\n\nimport mock\n\nfrom redash.query_runner.python import Python\n\n\nclass TestPythonQueryRunner(TestCase):\n    def setUp(self):\n        self.python = Python({})\n\n    @mock.patch(\"datetime.datetime\")\n    def test_print_in_query_string_success(self, mock_dt):\n        query_string = \"print('test')\"\n        mock_dt.utcnow = mock.Mock(return_value=datetime(1901, 12, 21))\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(result[0], {\"rows\": [], \"columns\": [], \"log\": [\"[1901-12-21T00:00:00] test\"]})\n\n    def test_empty_result(self):\n        query_string = \"result={}\"\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(result[0], None)\n\n    def test_none_result(self):\n        query_string = \"result=None\"\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(result[0], None)\n\n    def test_invalid_result_type_string(self):\n        query_string = \"result='string'\"\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(result[0], None)\n\n    def test_invalid_result_type_int(self):\n        query_string = \"result=100\"\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(result[0], None)\n\n    def test_invalid_result_missing_rows(self):\n        query_string = \"result={'columns': []}\"\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(result[0], None)\n\n    def test_invalid_result_not_list_rows(self):\n        query_string = \"result={'rows': {}, 'columns': []}\"\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(result[0], None)\n\n    def test_invalid_result_missing_columns(self):\n        query_string = \"result={'rows': []}\"\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(result[0], None)\n\n    def test_invalid_result_not_list_columns(self):\n        query_string = \"result={'rows': [], 'columns': {}}\"\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(result[0], None)\n\n    def test_valid_result_type(self):\n        query_string = (\n            \"result=\"\n            '{\"columns\": [{\"name\": \"col1\", \"type\": TYPE_STRING},'\n            '{\"name\": \"col2\", \"type\": TYPE_INTEGER}],'\n            '\"rows\": [{\"col1\": \"foo\", \"col2\": 100},'\n            '{\"col1\": \"bar\", \"col2\": 200}]}'\n        )\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(\n            result[0],\n            {\n                \"columns\": [{\"name\": \"col1\", \"type\": \"string\"}, {\"name\": \"col2\", \"type\": \"integer\"}],\n                \"rows\": [{\"col1\": \"foo\", \"col2\": 100}, {\"col1\": \"bar\", \"col2\": 200}],\n                \"log\": [],\n            },\n        )\n\n    @mock.patch(\"datetime.datetime\")\n    def test_valid_result_type_with_print(self, mock_dt):\n        mock_dt.utcnow = mock.Mock(return_value=datetime(1901, 12, 21))\n        query_string = (\n            'print(\"test\")\\n'\n            \"result=\"\n            '{\"columns\": [{\"name\": \"col1\", \"type\": TYPE_STRING},'\n            '{\"name\": \"col2\", \"type\": TYPE_INTEGER}],'\n            '\"rows\": [{\"col1\": \"foo\", \"col2\": 100},'\n            '{\"col1\": \"bar\", \"col2\": 200}]}'\n        )\n        result = self.python.run_query(query_string, \"user\")\n        self.assertEqual(\n            result[0],\n            {\n                \"columns\": [{\"name\": \"col1\", \"type\": \"string\"}, {\"name\": \"col2\", \"type\": \"integer\"}],\n                \"rows\": [{\"col1\": \"foo\", \"col2\": 100}, {\"col1\": \"bar\", \"col2\": 200}],\n                \"log\": [\"[1901-12-21T00:00:00] test\"],\n            },\n        )\n\n\nclass TestPython(TestCase):\n    def test_sorted_safe_builtins(self):\n        src = list(Python.safe_builtins)\n        assert src == sorted(src), \"Python safe_builtins package not sorted.\"\n"
  },
  {
    "path": "tests/query_runner/test_query_results.py",
    "content": "import datetime\nimport decimal\nimport sqlite3\nfrom unittest import TestCase\n\nimport mock\nimport pytest\n\nfrom redash.query_runner.query_results import (\n    CreateTableError,\n    PermissionError,\n    _load_query,\n    create_table,\n    extract_cached_query_ids,\n    extract_query_ids,\n    extract_query_params,\n    fix_column_name,\n    get_query_results,\n    prepare_parameterized_query,\n    replace_query_parameters,\n)\nfrom tests import BaseTestCase\n\n\nclass TestExtractQueryIds(TestCase):\n    def test_works_with_simple_query(self):\n        query = \"SELECT 1\"\n        self.assertEqual([], extract_query_ids(query))\n\n    def test_finds_queries_to_load(self):\n        query = \"SELECT * FROM query_123\"\n        self.assertEqual([123], extract_query_ids(query))\n\n    def test_finds_queries_in_joins(self):\n        query = \"SELECT * FROM query_123 JOIN query_4566\"\n        self.assertEqual([123, 4566], extract_query_ids(query))\n\n    def test_finds_queries_with_whitespace_characters(self):\n        query = \"SELECT * FROM    query_123 a JOIN\\tquery_4566 b ON a.id=b.parent_id JOIN\\r\\nquery_78 c ON b.id=c.parent_id\"\n        self.assertEqual([123, 4566, 78], extract_query_ids(query))\n\n\nclass TestCreateTable(TestCase):\n    def test_creates_table_with_colons_in_column_name(self):\n        connection = sqlite3.connect(\":memory:\")\n        results = {\n            \"columns\": [{\"name\": \"ga:newUsers\"}, {\"name\": \"test2\"}],\n            \"rows\": [{\"ga:newUsers\": 123, \"test2\": 2}],\n        }\n        table_name = \"query_123\"\n        create_table(connection, table_name, results)\n        connection.execute(\"SELECT 1 FROM query_123\")\n\n    def test_creates_table_with_double_quotes_in_column_name(self):\n        connection = sqlite3.connect(\":memory:\")\n        results = {\n            \"columns\": [{\"name\": \"ga:newUsers\"}, {\"name\": '\"test2\"'}],\n            \"rows\": [{\"ga:newUsers\": 123, '\"test2\"': 2}],\n        }\n        table_name = \"query_123\"\n        create_table(connection, table_name, results)\n        connection.execute(\"SELECT 1 FROM query_123\")\n\n    def test_creates_table(self):\n        connection = sqlite3.connect(\":memory:\")\n        results = {\"columns\": [{\"name\": \"test1\"}, {\"name\": \"test2\"}], \"rows\": []}\n        table_name = \"query_123\"\n        create_table(connection, table_name, results)\n        connection.execute(\"SELECT 1 FROM query_123\")\n\n    def test_creates_table_with_missing_columns(self):\n        connection = sqlite3.connect(\":memory:\")\n        results = {\n            \"columns\": [{\"name\": \"test1\"}, {\"name\": \"test2\"}],\n            \"rows\": [{\"test1\": 1, \"test2\": 2}, {\"test1\": 3}],\n        }\n        table_name = \"query_123\"\n        create_table(connection, table_name, results)\n        connection.execute(\"SELECT 1 FROM query_123\")\n\n    def test_creates_table_with_spaces_in_column_name(self):\n        connection = sqlite3.connect(\":memory:\")\n        results = {\n            \"columns\": [{\"name\": \"two words\"}, {\"name\": \"test2\"}],\n            \"rows\": [{\"two words\": 1, \"test2\": 2}, {\"test1\": 3}],\n        }\n        table_name = \"query_123\"\n        create_table(connection, table_name, results)\n        connection.execute(\"SELECT 1 FROM query_123\")\n\n    def test_creates_table_with_dashes_in_column_name(self):\n        connection = sqlite3.connect(\":memory:\")\n        results = {\n            \"columns\": [{\"name\": \"two-words\"}, {\"name\": \"test2\"}],\n            \"rows\": [{\"two-words\": 1, \"test2\": 2}],\n        }\n        table_name = \"query_123\"\n        create_table(connection, table_name, results)\n        connection.execute(\"SELECT 1 FROM query_123\")\n        connection.execute('SELECT \"two-words\" FROM query_123')\n\n    def test_creates_table_with_non_ascii_in_column_name(self):\n        connection = sqlite3.connect(\":memory:\")\n        results = {\n            \"columns\": [{\"name\": \"\\xe4\"}, {\"name\": \"test2\"}],\n            \"rows\": [{\"\\xe4\": 1, \"test2\": 2}],\n        }\n        table_name = \"query_123\"\n        create_table(connection, table_name, results)\n        connection.execute(\"SELECT 1 FROM query_123\")\n\n    def test_creates_table_with_decimal_and_timedelta_in_column_value(self):\n        connection = sqlite3.connect(\":memory:\")\n        results = {\n            \"columns\": [{\"name\": \"test1\"}, {\"name\": \"test2\"}, {\"name\": \"test3\"}],\n            \"rows\": [{\"test1\": 1, \"test2\": decimal.Decimal(2), \"test3\": datetime.timedelta(seconds=3)}],\n        }\n        table_name = \"query_123\"\n        create_table(connection, table_name, results)\n        connection.execute(\"SELECT 1 FROM query_123\")\n\n    def test_shows_meaningful_error_on_failure_to_create_table(self):\n        connection = sqlite3.connect(\":memory:\")\n        results = {\"columns\": [], \"rows\": []}\n        table_name = \"query_123\"\n        with pytest.raises(CreateTableError):\n            create_table(connection, table_name, results)\n\n    def test_loads_results(self):\n        connection = sqlite3.connect(\":memory:\")\n        rows = [{\"test1\": 1, \"test2\": \"test\"}, {\"test1\": 2, \"test2\": \"test2\"}]\n        results = {\"columns\": [{\"name\": \"test1\"}, {\"name\": \"test2\"}], \"rows\": rows}\n        table_name = \"query_123\"\n        create_table(connection, table_name, results)\n        self.assertEqual(len(list(connection.execute(\"SELECT * FROM query_123\"))), 2)\n\n    def test_loads_list_and_dict_results(self):\n        connection = sqlite3.connect(\":memory:\")\n        rows = [{\"test1\": [1, 2, 3]}, {\"test2\": {\"a\": \"b\"}}]\n        results = {\"columns\": [{\"name\": \"test1\"}, {\"name\": \"test2\"}], \"rows\": rows}\n        table_name = \"query_123\"\n        create_table(connection, table_name, results)\n        self.assertEqual(len(list(connection.execute(\"SELECT * FROM query_123\"))), 2)\n\n\nclass TestGetQuery(BaseTestCase):\n    # test query from different account\n    def test_raises_exception_for_query_from_different_account(self):\n        query = self.factory.create_query()\n        user = self.factory.create_user(org=self.factory.create_org())\n\n        self.assertRaises(PermissionError, lambda: _load_query(user, query.id))\n\n    def test_raises_exception_for_query_with_different_groups(self):\n        ds = self.factory.create_data_source(group=self.factory.create_group())\n        query = self.factory.create_query(data_source=ds)\n        user = self.factory.create_user()\n\n        self.assertRaises(PermissionError, lambda: _load_query(user, query.id))\n\n    def test_returns_query(self):\n        query = self.factory.create_query()\n        user = self.factory.create_user()\n\n        loaded = _load_query(user, query.id)\n        self.assertEqual(query, loaded)\n\n    def test_returns_query_when_user_has_view_only_access(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)\n        query = self.factory.create_query(data_source=ds)\n        user = self.factory.create_user()\n\n        loaded = _load_query(user, query.id)\n        self.assertEqual(query, loaded)\n\n\nclass TestExtractCachedQueryIds(TestCase):\n    def test_works_with_simple_query(self):\n        query = \"SELECT 1\"\n        self.assertEqual([], extract_cached_query_ids(query))\n\n    def test_finds_queries_to_load(self):\n        query = \"SELECT * FROM cached_query_123\"\n        self.assertEqual([123], extract_cached_query_ids(query))\n\n    def test_finds_queries_in_joins(self):\n        query = \"SELECT * FROM cached_query_123 JOIN cached_query_4566\"\n        self.assertEqual([123, 4566], extract_cached_query_ids(query))\n\n    def test_finds_queries_with_whitespace_characters(self):\n        query = \"SELECT * FROM    cached_query_123 a JOIN\\tcached_query_4566 b ON a.id=b.parent_id JOIN\\r\\ncached_query_78 c ON b.id=c.parent_id\"\n        self.assertEqual([123, 4566, 78], extract_cached_query_ids(query))\n\n\nclass TestExtractParamQueryIds(TestCase):\n    def test_works_with_simple_query(self):\n        query = \"SELECT 1\"\n        self.assertEqual([], extract_query_params(query))\n\n    def test_ignores_non_param_queries(self):\n        query = \"SELECT * FROM query_123\"\n        self.assertEqual([], extract_query_params(query))\n\n    def test_ignores_cached_queries_to_load(self):\n        query = \"SELECT * FROM cached_query_123\"\n        self.assertEqual([], extract_query_params(query))\n\n    def test_finds_queries_to_load(self):\n        query = \"SELECT * FROM param_query_123_{token=test}\"\n        self.assertEqual([(\"123\", \"token=test\")], extract_query_params(query))\n\n    def test_finds_queries_in_joins(self):\n        query = \"SELECT * FROM param_query_123_{token1=test1} JOIN param_query_456_{token2=test2}\"\n        self.assertEqual([(\"123\", \"token1=test1\"), (\"456\", \"token2=test2\")], extract_query_params(query))\n\n\nclass TestPrepareParameterizedQuery(TestCase):\n    def test_param_query_replacement(self):\n        result = prepare_parameterized_query(\"SELECT * FROM param_query_123_{token=test}\", [(\"123\", \"token=test\")])\n        self.assertEqual(\"SELECT * FROM query_123_1c5f1acad40f99b968836273d74baa89\", result)\n\n\nclass TestReplaceQueryParameters(TestCase):\n    def test_replace_query_params(self):\n        result = replace_query_parameters(\"SELECT '{{token1}}', '{{token2}}'\", \"token1=test1&token2=test2\")\n        self.assertEqual(\"SELECT 'test1', 'test2'\", result)\n\n\nclass TestFixColumnName(TestCase):\n    def test_fix_column_name(self):\n        self.assertEqual('\"a_b_c_d\"', fix_column_name(\"a:b.c d\"))\n\n\nclass TestGetQueryResult(BaseTestCase):\n    def test_cached_query_result(self):\n        query_result = self.factory.create_query_result()\n        query = self.factory.create_query(latest_query_data=query_result)\n\n        self.assertEqual(query_result.data, get_query_results(self.factory.user, query.id, True))\n\n    def test_non_cached_query_result(self):\n        query_result = self.factory.create_query_result()\n        query = self.factory.create_query(latest_query_data=query_result)\n\n        from redash.query_runner.pg import PostgreSQL\n\n        with mock.patch.object(PostgreSQL, \"run_query\") as qr:\n            query_result_data = {\"columns\": [], \"rows\": []}\n            qr.return_value = (query_result_data, None)\n            self.assertEqual(query_result_data, get_query_results(self.factory.user, query.id, False))\n"
  },
  {
    "path": "tests/query_runner/test_script.py",
    "content": "import os\nimport subprocess\n\nfrom _pytest.monkeypatch import MonkeyPatch\n\nfrom redash.query_runner.script import query_to_script_path, run_script\nfrom tests import BaseTestCase\n\n\nclass TestQueryToScript(BaseTestCase):\n    monkeypatch = MonkeyPatch()\n\n    def test_unspecified(self):\n        self.assertEqual(\"/foo/bar/baz.sh\", query_to_script_path(\"*\", \"/foo/bar/baz.sh\"))\n\n    def test_specified(self):\n        self.assertRaises(IOError, lambda: query_to_script_path(\"/foo/bar\", \"baz.sh\"))\n\n        self.monkeypatch.setattr(os.path, \"exists\", lambda x: True)\n        self.assertEqual([\"/foo/bar/baz.sh\"], query_to_script_path(\"/foo/bar\", \"baz.sh\"))\n\n\nclass TestRunScript(BaseTestCase):\n    monkeypatch = MonkeyPatch()\n\n    def test_success(self):\n        self.monkeypatch.setattr(subprocess, \"check_output\", lambda script, shell: \"test\")\n        self.assertEqual((\"test\", None), run_script(\"/foo/bar/baz.sh\", True))\n\n    def test_failure(self):\n        self.monkeypatch.setattr(subprocess, \"check_output\", lambda script, shell: None)\n        self.assertEqual((None, \"Error reading output\"), run_script(\"/foo/bar/baz.sh\", True))\n        self.monkeypatch.setattr(subprocess, \"check_output\", lambda script, shell: \"\")\n        self.assertEqual((None, \"Empty output from script\"), run_script(\"/foo/bar/baz.sh\", True))\n        self.monkeypatch.setattr(subprocess, \"check_output\", lambda script, shell: \" \")\n        self.assertEqual((None, \"Empty output from script\"), run_script(\"/foo/bar/baz.sh\", True))\n"
  },
  {
    "path": "tests/query_runner/test_tinybird.py",
    "content": "import json\nfrom unittest import TestCase\nfrom unittest.mock import Mock, patch\n\nfrom redash.query_runner import TYPE_DATETIME, TYPE_INTEGER, TYPE_STRING\nfrom redash.query_runner.tinybird import Tinybird\n\nDATASOURCES_RESPONSE = {\n    \"datasources\": [\n        {\n            \"id\": \"t_datasource_id\",\n            \"name\": \"test_datasource\",\n            \"columns\": [\n                {\n                    \"name\": \"string_attribute\",\n                    \"type\": \"String\",\n                    \"nullable\": False,\n                    \"normalized_name\": \"string_attribute\",\n                },\n                {\n                    \"name\": \"number_attribute\",\n                    \"type\": \"Int32\",\n                    \"nullable\": False,\n                    \"normalized_name\": \"number_attribute\",\n                },\n                {\"name\": \"date_attribute\", \"type\": \"DateTime\", \"nullable\": False, \"normalized_name\": \"date_attribute\"},\n            ],\n        }\n    ]\n}\n\nPIPES_RESPONSE = {\"pipes\": [{\"id\": \"t_pipe_id\", \"name\": \"test_pipe\", \"endpoint\": \"t_endpoint_id\", \"type\": \"endpoint\"}]}\n\nSCHEMA_RESPONSE = {\n    \"meta\": [\n        {\"name\": \"string_attribute\", \"type\": \"String\"},\n        {\"name\": \"number_attribute\", \"type\": \"Int32\"},\n        {\"name\": \"date_attribute\", \"type\": \"DateTime\"},\n    ]\n}\n\nQUERY_RESPONSE = {\n    **SCHEMA_RESPONSE,\n    \"data\": [\n        {\"string_attribute\": \"hello world\", \"number_attribute\": 123, \"date_attribute\": \"2023-01-01 00:00:03.001000\"},\n    ],\n    \"rows\": 1,\n    \"statistics\": {\"elapsed\": 0.011556914, \"rows_read\": 87919, \"bytes_read\": 17397219},\n}\n\n\nclass TestTinybird(TestCase):\n    @patch(\"requests.get\")\n    def test_get_schema_scans_pipes_and_datasources(self, get_request):\n        query_runner = self._build_query_runner()\n\n        get_request.side_effect = self._mock_tinybird_schema_requests\n\n        schema = query_runner.get_schema()\n\n        self.assertEqual(\n            schema,\n            [\n                {\"name\": \"test_datasource\", \"columns\": [\"string_attribute\", \"number_attribute\", \"date_attribute\"]},\n                {\"name\": \"test_pipe\", \"columns\": [\"string_attribute\", \"number_attribute\", \"date_attribute\"]},\n            ],\n        )\n\n        (url,), kwargs = get_request.call_args\n\n        self.assertEqual(kwargs[\"timeout\"], 60)\n        self.assertEqual(kwargs[\"headers\"], {\"Authorization\": \"Bearer p.test.token\"})\n\n    @patch(\"requests.get\")\n    def test_run_query(self, get_request):\n        query_runner = self._build_query_runner()\n\n        get_request.return_value = Mock(\n            status_code=200, text=json.dumps(QUERY_RESPONSE), json=Mock(return_value=QUERY_RESPONSE)\n        )\n\n        data, error = query_runner.run_query(\"SELECT * FROM test_datasource LIMIT 1\", None)\n\n        self.assertIsNone(error)\n        self.assertEqual(\n            data,\n            {\n                \"columns\": [\n                    {\"name\": \"string_attribute\", \"friendly_name\": \"string_attribute\", \"type\": TYPE_STRING},\n                    {\"name\": \"number_attribute\", \"friendly_name\": \"number_attribute\", \"type\": TYPE_INTEGER},\n                    {\"name\": \"date_attribute\", \"friendly_name\": \"date_attribute\", \"type\": TYPE_DATETIME},\n                ],\n                \"rows\": [\n                    {\n                        \"string_attribute\": \"hello world\",\n                        \"number_attribute\": 123,\n                        \"date_attribute\": \"2023-01-01 00:00:03.001000\",\n                    }\n                ],\n            },\n        )\n\n        (url,), kwargs = get_request.call_args\n\n        self.assertEqual(url, \"https://api.tinybird.co/v0/sql\")\n        self.assertEqual(kwargs[\"timeout\"], 60)\n        self.assertEqual(kwargs[\"headers\"], {\"Authorization\": \"Bearer p.test.token\"})\n        self.assertEqual(kwargs[\"params\"], {\"q\": b\"SELECT * FROM test_datasource LIMIT 1\\nFORMAT JSON\"})\n\n    def _mock_tinybird_schema_requests(self, endpoint, **kwargs):\n        response = {}\n\n        if endpoint.endswith(Tinybird.PIPES_ENDPOINT):\n            response = PIPES_RESPONSE\n        if endpoint.endswith(Tinybird.DATASOURCES_ENDPOINT):\n            response = DATASOURCES_RESPONSE\n        if endpoint.endswith(Tinybird.SQL_ENDPOINT):\n            response = SCHEMA_RESPONSE\n\n        return Mock(status_code=200, text=json.dumps(response), json=Mock(return_value=response))\n\n    def _build_query_runner(self):\n        return Tinybird({\"url\": \"https://api.tinybird.co\", \"token\": \"p.test.token\", \"timeout\": 60})\n"
  },
  {
    "path": "tests/query_runner/test_trino.py",
    "content": "\"\"\"\nSome test cases for Trino.\n\"\"\"\n\nfrom unittest import TestCase\nfrom unittest.mock import patch\n\nfrom trino.types import NamedRowTuple\n\nfrom redash.query_runner.trino import Trino, _convert_row_types\n\n\nclass TestTrino(TestCase):\n    catalog_name = \"memory\"\n    schema_name = \"default\"\n    table_name = \"users\"\n    column_name = \"id\"\n    column_type = \"integer\"\n\n    @patch.object(Trino, \"_get_catalogs\")\n    @patch.object(Trino, \"run_query\")\n    def test_get_schema_no_catalog_set(self, mock_run_query, mock__get_catalogs):\n        runner = Trino({})\n        self._assert_schema_catalog(mock_run_query, mock__get_catalogs, runner)\n\n    @patch.object(Trino, \"_get_catalogs\")\n    @patch.object(Trino, \"run_query\")\n    def test_get_schema_catalog_set(self, mock_run_query, mock__get_catalogs):\n        runner = Trino({\"catalog\": TestTrino.catalog_name})\n        self._assert_schema_catalog(mock_run_query, mock__get_catalogs, runner)\n\n    def _assert_schema_catalog(self, mock_run_query, mock__get_catalogs, runner):\n        mock_run_query.return_value = (\n            {\n                \"rows\": [\n                    {\n                        \"table_schema\": TestTrino.schema_name,\n                        \"table_name\": TestTrino.table_name,\n                        \"column_name\": TestTrino.column_name,\n                        \"data_type\": TestTrino.column_type,\n                    }\n                ]\n            },\n            None,\n        )\n        mock__get_catalogs.return_value = [TestTrino.catalog_name]\n        schema = runner.get_schema()\n        expected_schema = [\n            {\n                \"name\": f\"{TestTrino.catalog_name}.{TestTrino.schema_name}.{TestTrino.table_name}\",\n                \"columns\": [{\"name\": TestTrino.column_name, \"type\": TestTrino.column_type}],\n            }\n        ]\n        self.assertEqual(schema, expected_schema)\n\n    @patch.object(Trino, \"run_query\")\n    def test__get_catalogs(self, mock_run_query):\n        mock_run_query.return_value = ({\"rows\": [{\"Catalog\": TestTrino.catalog_name}]}, None)\n        runner = Trino({})\n        catalogs = runner._get_catalogs()\n        expected_catalogs = [TestTrino.catalog_name]\n        self.assertEqual(catalogs, expected_catalogs)\n\n    def test_get_client_tags_parses_comma_separated_values(self):\n        runner = Trino({\"client_tags\": \"finance,  redash  , ,analytics\"})\n        self.assertEqual(runner._get_client_tags(), [\"finance\", \"redash\", \"analytics\"])\n\n    def test_get_client_tags_returns_none_when_empty(self):\n        runner = Trino({\"client_tags\": \" ,  , \"})\n        self.assertIsNone(runner._get_client_tags())\n\n\nclass TestConvertRowTypes(TestCase):\n    def test_plain_values_unchanged(self):\n        self.assertEqual(_convert_row_types(42), 42)\n        self.assertEqual(_convert_row_types(\"hello\"), \"hello\")\n        self.assertIsNone(_convert_row_types(None))\n\n    def test_named_row_tuple_to_dict(self):\n        row = NamedRowTuple([1, \"alice\"], [\"id\", \"name\"], [\"integer\", \"varchar\"])\n        result = _convert_row_types(row)\n        self.assertEqual(result, {\"id\": 1, \"name\": \"alice\"})\n\n    def test_nested_row_tuple(self):\n        inner = NamedRowTuple([10, 20], [\"x\", \"y\"], [\"integer\", \"integer\"])\n        outer = NamedRowTuple([1, inner], [\"id\", \"point\"], [\"integer\", \"row\"])\n        result = _convert_row_types(outer)\n        self.assertEqual(result, {\"id\": 1, \"point\": {\"x\": 10, \"y\": 20}})\n\n    def test_row_tuple_inside_list(self):\n        row = NamedRowTuple([1, \"a\"], [\"id\", \"val\"], [\"integer\", \"varchar\"])\n        result = _convert_row_types([row, row])\n        self.assertEqual(result, [{\"id\": 1, \"val\": \"a\"}, {\"id\": 1, \"val\": \"a\"}])\n\n    def test_unnamed_fields_get_positional_names(self):\n        row = NamedRowTuple([1, 2], [None, None], [\"integer\", \"integer\"])\n        result = _convert_row_types(row)\n        self.assertEqual(result, {\"_field0\": 1, \"_field1\": 2})\n"
  },
  {
    "path": "tests/query_runner/test_utils.py",
    "content": "from unittest import TestCase\n\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n    guess_type,\n)\n\n\nclass TestGuessType(TestCase):\n    def test_handles_unicode(self):\n        self.assertEqual(guess_type(\"Текст\"), TYPE_STRING)\n\n    def test_detects_booleans(self):\n        self.assertEqual(guess_type(\"true\"), TYPE_BOOLEAN)\n        self.assertEqual(guess_type(\"True\"), TYPE_BOOLEAN)\n        self.assertEqual(guess_type(\"TRUE\"), TYPE_BOOLEAN)\n        self.assertEqual(guess_type(\"false\"), TYPE_BOOLEAN)\n        self.assertEqual(guess_type(\"False\"), TYPE_BOOLEAN)\n        self.assertEqual(guess_type(\"FALSE\"), TYPE_BOOLEAN)\n        self.assertEqual(guess_type(False), TYPE_BOOLEAN)\n\n    def test_detects_strings(self):\n        self.assertEqual(guess_type(None), TYPE_STRING)\n        self.assertEqual(guess_type(\"\"), TYPE_STRING)\n        self.assertEqual(guess_type(\"redash\"), TYPE_STRING)\n\n    def test_detects_integer(self):\n        self.assertEqual(guess_type(\"42\"), TYPE_INTEGER)\n        self.assertEqual(guess_type(42), TYPE_INTEGER)\n\n    def test_detects_float(self):\n        self.assertEqual(guess_type(\"3.14\"), TYPE_FLOAT)\n        self.assertEqual(guess_type(3.14), TYPE_FLOAT)\n\n    def test_detects_date(self):\n        self.assertEqual(guess_type(\"2018-10-31\"), TYPE_DATETIME)\n"
  },
  {
    "path": "tests/query_runner/test_yandex_disk.py",
    "content": "from io import BytesIO\nfrom unittest import mock\n\nimport yaml\n\nfrom redash.query_runner.yandex_disk import enabled\n\nif enabled:\n    import pandas as pd\n\n    from redash.query_runner.yandex_disk import EXTENSIONS_READERS, YandexDisk\n\n    test_df = pd.DataFrame(\n        [\n            {\"id\": 1, \"name\": \"Alice\", \"age\": 20},\n            {\"id\": 2, \"name\": \"Bob\", \"age\": 21},\n            {\"id\": 3, \"name\": \"Charlie\", \"age\": 22},\n            {\"id\": 4, \"name\": \"Dave\", \"age\": 23},\n            {\"id\": 5, \"name\": \"Eve\", \"age\": 24},\n        ]\n    )\n\n\nimport pytest\n\ntest_token = \"AAAAQAA\"\nskip_condition = pytest.mark.skipif(not enabled, reason=\"pandas and/or openpyxl are not installed\")\n\n\n@pytest.fixture\ndef mock_yandex_disk():\n    return YandexDisk(configuration={\"token\": test_token})\n\n\n@skip_condition\ndef test_yandex_disk_type():\n    assert YandexDisk.type() == \"yandex_disk\"\n\n\n@skip_condition\ndef test_yandex_disk_name():\n    assert YandexDisk.name() == \"Yandex Disk\"\n\n\n@skip_condition\n@mock.patch(\"requests.get\")\ndef test__send_query(mock_requests_get):\n    mock_requests_get.return_value.ok = True\n    mock_requests_get.return_value.json.return_value = {\"foo\": \"bar\"}\n\n    configuration = {\"token\": test_token}\n    disk = YandexDisk(configuration)\n    response = disk._send_query(\"test_url\")\n\n    assert response == {\"foo\": \"bar\"}\n    mock_requests_get.assert_called_once()\n\n\n@skip_condition\n@pytest.mark.parametrize(\n    \"configuration, error_message\",\n    [({\"token\": test_token}, None), ({\"token\": \"\"}, \"Code: 400, message: Unauthorized\")],\n)\n@mock.patch(\"requests.get\")\ndef test_test_connection(mock_requests_get, configuration, error_message):\n    if error_message:\n        mock_requests_get.return_value.ok = False\n        mock_requests_get.return_value.status_code = 400\n        mock_requests_get.return_value.text = \"Unauthorized\"\n    else:\n        mock_requests_get.return_value.ok = True\n\n    disk = YandexDisk(configuration)\n    if error_message:\n        with pytest.raises(Exception, match=error_message):\n            disk.test_connection()\n    else:\n        assert disk.test_connection() is None\n\n\n@skip_condition\ndef test_get_tables(mock_yandex_disk):\n    mock_files = {\n        \"items\": [\n            {\"name\": \"test_file.csv\", \"path\": \"disk:/test_path/test_file.csv\"},\n            {\"name\": \"invalid_file.txt\", \"path\": \"disk:/test_path/invalid_file.txt\"},\n        ]\n    }\n    mock_yandex_disk._send_query = mock.MagicMock(return_value=mock_files)\n\n    tables = mock_yandex_disk._get_tables({})\n    assert len(tables) == 1\n    assert tables[0][\"name\"] == \"test_file.csv\"\n    assert tables[0][\"columns\"] == [\"/test_path/test_file.csv\"]\n\n\ndef mock_ext_readers_return(url, **params):\n    return test_df\n\n\ndef mock_ext_readers_return_multiple_sheets(url, **params):\n    return {\"sheet1\": test_df}\n\n\n@skip_condition\n@mock.patch(\"requests.get\")\ndef test_run_query(mocked_requests, mock_yandex_disk):\n    mocked_response = mock.MagicMock()\n    mocked_response.ok = True\n    mocked_response.json.return_value = {\"href\": \"test_file.csv\"}\n    mocked_requests.return_value = mocked_response\n\n    mock_readers = EXTENSIONS_READERS.copy()\n    mock_readers[\"csv\"] = mock_ext_readers_return\n\n    expected_data = {\n        \"columns\": [\n            {\"name\": \"id\", \"friendly_name\": \"id\", \"type\": \"integer\"},\n            {\"name\": \"name\", \"friendly_name\": \"name\", \"type\": \"string\"},\n            {\"name\": \"age\", \"friendly_name\": \"age\", \"type\": \"integer\"},\n        ],\n        \"rows\": [\n            {\"id\": 1, \"name\": \"Alice\", \"age\": 20},\n            {\"id\": 2, \"name\": \"Bob\", \"age\": 21},\n            {\"id\": 3, \"name\": \"Charlie\", \"age\": 22},\n            {\"id\": 4, \"name\": \"Dave\", \"age\": 23},\n            {\"id\": 5, \"name\": \"Eve\", \"age\": 24},\n        ],\n    }\n\n    with mock.patch.dict(\"redash.query_runner.yandex_disk.EXTENSIONS_READERS\", mock_readers, clear=True):\n        data, error = mock_yandex_disk.run_query(yaml.dump({\"path\": \"/tmp/file.csv\"}), \"user\")\n\n    assert error is None\n    assert data == expected_data\n\n\n@skip_condition\ndef test_run_query_with_empty_query(mock_yandex_disk):\n    result = mock_yandex_disk.run_query(\"\", \"user\")\n    assert result == (None, \"Query is empty\")\n\n\n@skip_condition\ndef test_run_query_nonstring_yaml(mock_yandex_disk):\n    bad_yaml_query = [0, 1]\n    data, error = mock_yandex_disk.run_query(bad_yaml_query, \"user\")\n    assert data is None\n    assert error.startswith(\"YAML read error: \")\n\n\n@skip_condition\ndef test_run_query_bad_yaml(mock_yandex_disk):\n    bad_yaml_query = \"unparseable = yaml\"\n    result = mock_yandex_disk.run_query(bad_yaml_query, \"user\")\n    assert result == (None, \"The query format must be JSON or YAML\")\n\n\n@skip_condition\ndef test_run_query_without_path(mock_yandex_disk):\n    bad_yaml_query = \"without: path\"\n    result = mock_yandex_disk.run_query(bad_yaml_query, \"user\")\n    assert result == (None, \"The query must contain path\")\n\n\n@skip_condition\ndef test_run_query_unsupported_extension(mock_yandex_disk):\n    bad_yaml_query = \"path: /tmp/file.txt\"\n    result = mock_yandex_disk.run_query(bad_yaml_query, \"user\")\n    assert result == (None, \"Unsupported file extension: txt\")\n\n\n@skip_condition\ndef test_run_query_read_file_error(mock_yandex_disk):\n    mock_yandex_disk._send_query = mock.MagicMock(return_value={\"href\": \"test_file.csv\"})\n    mock_yandex_disk._get_tables = mock.MagicMock(return_value=[{\"name\": \"test_file.csv\", \"columns\": []}])\n    mock_yandex_disk._read_file = mock.MagicMock(side_effect=Exception(\"Read file error\"))\n\n    data, error = mock_yandex_disk.run_query(yaml.dump({\"path\": \"/tmp/file.csv\"}), \"user\")\n    assert data is None\n    assert error is not None and error.startswith(\"Read file error\")\n\n\n@skip_condition\n@mock.patch(\"requests.get\")\ndef test_run_query_multiple_sheets(mocked_requests, mock_yandex_disk):\n    mocked_response = mock.MagicMock()\n    mocked_response.ok = True\n    mocked_response.json.return_value = {\"href\": \"test_file.xlsx\"}\n    mocked_requests.return_value = mocked_response\n\n    query = \"\"\"\n    path: /tmp/file.xlsx\n    sheet_name: null\n    \"\"\"\n\n    mock_readers = EXTENSIONS_READERS.copy()\n    mock_readers[\"xlsx\"] = mock_ext_readers_return_multiple_sheets\n\n    with mock.patch.dict(\"redash.query_runner.yandex_disk.EXTENSIONS_READERS\", mock_readers, clear=True):\n        data, error = mock_yandex_disk.run_query(query, \"user\")\n\n    assert error is None\n    assert data == {\n        \"columns\": [\n            {\"name\": \"id\", \"friendly_name\": \"id\", \"type\": \"integer\"},\n            {\"name\": \"name\", \"friendly_name\": \"name\", \"type\": \"string\"},\n            {\"name\": \"age\", \"friendly_name\": \"age\", \"type\": \"integer\"},\n            {\"name\": \"sheet_name\", \"friendly_name\": \"sheet_name\", \"type\": \"string\"},\n        ],\n        \"rows\": [\n            {\"id\": 1, \"name\": \"Alice\", \"age\": 20, \"sheet_name\": \"sheet1\"},\n            {\"id\": 2, \"name\": \"Bob\", \"age\": 21, \"sheet_name\": \"sheet1\"},\n            {\"id\": 3, \"name\": \"Charlie\", \"age\": 22, \"sheet_name\": \"sheet1\"},\n            {\"id\": 4, \"name\": \"Dave\", \"age\": 23, \"sheet_name\": \"sheet1\"},\n            {\"id\": 5, \"name\": \"Eve\", \"age\": 24, \"sheet_name\": \"sheet1\"},\n        ],\n    }\n\n\n@skip_condition\ndef test_read_xlsx():\n    output = BytesIO()\n    writer = pd.ExcelWriter(output)\n    test_df.to_excel(writer, index=False)\n    writer.close()\n    assert test_df.equals(EXTENSIONS_READERS[\"xlsx\"](output))\n\n\n@skip_condition\ndef test_read_csv():\n    output = BytesIO()\n    test_df.to_csv(output, index=False)\n    output.seek(0)\n\n    assert test_df.equals(EXTENSIONS_READERS[\"csv\"](output))\n\n\n@skip_condition\ndef test_tsv():\n    output = BytesIO()\n    test_df.to_csv(output, index=False, sep=\"\\t\")\n    output.seek(0)\n\n    assert test_df.equals(EXTENSIONS_READERS[\"tsv\"](output))\n"
  },
  {
    "path": "tests/query_runner/test_yandex_metrica.py",
    "content": "import json\n\nimport pytest\nimport requests\n\nfrom redash.query_runner import TYPE_FLOAT, TYPE_STRING\nfrom redash.query_runner.yandex_metrica import YandexMetrica\n\nexample_query = \"\"\"id: 1234567\ndate1: '2018-07-01'\ndate2: '2018-07-01'\ndimensions: 'ym:pv:month'\nmetrics: 'ym:pv:pageviews'\"\"\"\n\n# an example of a real API metric return\nexample_response = {\n    \"query\": {\n        \"ids\": [1234567],\n        \"dimensions\": [\"ym:pv:month\"],\n        \"metrics\": [\"ym:pv:pageviews\"],\n        \"sort\": [\"-ym:pv:pageviews\"],\n        \"date1\": \"2018-07-01\",\n        \"date2\": \"2018-07-01\",\n        \"limit\": 100,\n        \"offset\": 1,\n        \"group\": \"Week\",\n        \"auto_group_size\": \"1\",\n        \"attr_name\": \"\",\n        \"quantile\": \"50\",\n        \"offline_window\": \"21\",\n        \"attribution\": \"LastSign\",\n        \"currency\": \"RUB\",\n        \"adfox_event_id\": \"0\",\n    },\n    \"data\": [{\"dimensions\": [{\"name\": \"7\"}], \"metrics\": [1000.0]}],\n    \"total_rows\": 1,\n    \"total_rows_rounded\": False,\n    \"sampled\": True,\n    \"contains_sensitive_data\": False,\n    \"sample_share\": 0.1,\n    \"sample_size\": 651081,\n    \"sample_space\": 6510809,\n    \"data_lag\": 0,\n    \"totals\": [1000.0],\n    \"min\": [1000.0],\n    \"max\": [1000.0],\n}\n\nexpected_data = {\n    \"columns\": [\n        {\"name\": \"ym:pv:month\", \"friendly_name\": \"month\", \"type\": TYPE_STRING},\n        {\"name\": \"ym:pv:pageviews\", \"friendly_name\": \"pageviews\", \"type\": TYPE_FLOAT},\n    ],\n    \"rows\": [\n        {\"ym:pv:month\": \"7\", \"ym:pv:pageviews\": 1000.0},\n    ],\n}\n\nN_API_CALLS = 3\n\n\n@pytest.fixture\ndef mock_yandex_response():\n    class MockResponse:\n        def __init__(self, status=\"passing\"):\n            if status == \"passing\":\n                self.status_code = 200\n                self.text = json.dumps(example_response)\n                self.json = lambda *args, **kwargs: example_response\n                self.ok = True\n            elif status == \"failing\":\n                self.status_code = 429\n                self.text = json.dumps(example_response)\n                self.json = lambda *args, **kwargs: example_response\n                self.ok = False\n\n            # for mocking 429's\n            self.count = 0\n\n        def __call__(self, *args, **kwargs):\n            self.count += 1\n            if self.count == N_API_CALLS:\n                # return a failing response on N_API_CALLS\n                return MockResponse(\"failing\")\n            # before/after we get to the Nth call, just return success\n            return self\n\n    return MockResponse(\"passing\")\n\n\n@pytest.fixture\ndef mocked_requests_get(monkeypatch, mock_yandex_response):\n    monkeypatch.setattr(requests, \"get\", mock_yandex_response)\n\n\ndef test_yandex_metrica_query(mocked_requests_get):\n    query_runner = YandexMetrica({\"token\": \"example_token\"})\n    data, error = query_runner.run_query(example_query, None)\n\n    assert error is None\n    assert data == expected_data\n\n\ndef test_yandex_metrica_429(mocked_requests_get):\n    query_runner = YandexMetrica({\"token\": \"example_token\"})\n    # run 3 times in a row to simulate \"too many requests\"\n    for _ in range(N_API_CALLS):\n        data, error = query_runner.run_query(example_query, None)\n    # we expect to have called `requests.get` 1 more time than we call `run_query`\n    assert requests.get.count == N_API_CALLS + 1\n"
  },
  {
    "path": "tests/serializers/__init__.py",
    "content": ""
  },
  {
    "path": "tests/serializers/test_query_results.py",
    "content": "import csv\nimport io\n\nfrom redash.serializers import (\n    serialize_query_result,\n    serialize_query_result_to_dsv,\n)\nfrom tests import BaseTestCase\n\ndata = {\n    \"rows\": [\n        {\"datetime\": \"2019-05-26T12:39:23.026Z\", \"bool\": True, \"date\": \"2019-05-26\"},\n        {\"datetime\": \"\", \"bool\": False, \"date\": \"\"},\n        {\"datetime\": None, \"bool\": None, \"date\": None},\n        {\"datetime\": 459, \"bool\": None, \"date\": 123},\n        {\"datetime\": \"459\", \"bool\": None, \"date\": \"123\"},\n    ],\n    \"columns\": [\n        {\"friendly_name\": \"bool\", \"type\": \"boolean\", \"name\": \"bool\"},\n        {\"friendly_name\": \"date\", \"type\": \"datetime\", \"name\": \"datetime\"},\n        {\"friendly_name\": \"date\", \"type\": \"date\", \"name\": \"date\"},\n    ],\n}\n\n\nclass QueryResultSerializationTest(BaseTestCase):\n    def test_serializes_all_keys_for_authenticated_users(self):\n        query_result = self.factory.create_query_result(data={})\n        serialized = serialize_query_result(query_result, False)\n        self.assertSetEqual(set(query_result.to_dict().keys()), set(serialized.keys()))\n\n    def test_doesnt_serialize_sensitive_keys_for_unauthenticated_users(self):\n        query_result = self.factory.create_query_result(data={})\n        serialized = serialize_query_result(query_result, True)\n        self.assertSetEqual(set([\"data\", \"retrieved_at\"]), set(serialized.keys()))\n\n\nclass DsvSerializationTest(BaseTestCase):\n    def delimited_content(self, delimiter):\n        query_result = self.factory.create_query_result(data=data)\n        return serialize_query_result_to_dsv(query_result, delimiter)\n\n    def test_serializes_booleans_correctly(self):\n        with self.app.test_request_context(\"/\"):\n            parsed = csv.DictReader(io.StringIO(self.delimited_content(\",\")))\n        rows = list(parsed)\n\n        self.assertEqual(rows[0][\"bool\"], \"true\")\n        self.assertEqual(rows[1][\"bool\"], \"false\")\n        self.assertEqual(rows[2][\"bool\"], \"\")\n\n    def test_serializes_datatime_with_correct_format(self):\n        with self.app.test_request_context(\"/\"):\n            parsed = csv.DictReader(io.StringIO(self.delimited_content(\",\")))\n        rows = list(parsed)\n\n        self.assertEqual(rows[0][\"datetime\"], \"26/05/19 12:39\")\n        self.assertEqual(rows[1][\"datetime\"], \"\")\n        self.assertEqual(rows[2][\"datetime\"], \"\")\n        self.assertEqual(rows[0][\"date\"], \"26/05/19\")\n        self.assertEqual(rows[1][\"date\"], \"\")\n        self.assertEqual(rows[2][\"date\"], \"\")\n\n    def test_serializes_datatime_as_is_in_case_of_error(self):\n        with self.app.test_request_context(\"/\"):\n            parsed = csv.DictReader(io.StringIO(self.delimited_content(\",\")))\n        rows = list(parsed)\n\n        self.assertEqual(rows[3][\"datetime\"], \"459\")\n        self.assertEqual(rows[3][\"date\"], \"123\")\n\n    def test_serializes_tsv_format(self):\n        delimiter = \"\\t\"\n        with self.app.test_request_context(\"/\"):\n            parsed = csv.DictReader(io.StringIO(self.delimited_content(delimiter)), delimiter=delimiter)\n        rows = list(parsed)\n\n        self.assertEqual(rows[0][\"datetime\"], \"26/05/19 12:39\")\n        self.assertEqual(rows[1][\"bool\"], \"false\")\n        self.assertEqual(rows[2][\"date\"], \"\")\n        self.assertEqual(rows[3][\"datetime\"], \"459\")\n"
  },
  {
    "path": "tests/tasks/__init__.py",
    "content": ""
  },
  {
    "path": "tests/tasks/test_alerts.py",
    "content": "from mock import ANY, MagicMock\n\nimport redash.tasks.alerts\nfrom redash.models import Alert\nfrom redash.tasks.alerts import check_alerts_for_query, notify_subscriptions\nfrom tests import BaseTestCase\n\n\nclass TestCheckAlertsForQuery(BaseTestCase):\n    def test_notifies_subscribers_when_should(self):\n        redash.tasks.alerts.notify_subscriptions = MagicMock()\n        Alert.evaluate = MagicMock(return_value=Alert.TRIGGERED_STATE)\n\n        alert = self.factory.create_alert()\n        check_alerts_for_query(alert.query_id, metadata={\"Scheduled\": False})\n\n        self.assertTrue(redash.tasks.alerts.notify_subscriptions.called)\n\n    def test_doesnt_notify_when_nothing_changed(self):\n        redash.tasks.alerts.notify_subscriptions = MagicMock()\n        Alert.evaluate = MagicMock(return_value=Alert.OK_STATE)\n\n        alert = self.factory.create_alert()\n        check_alerts_for_query(alert.query_id, metadata={\"Scheduled\": False})\n\n        self.assertFalse(redash.tasks.alerts.notify_subscriptions.called)\n\n    def test_doesnt_notify_when_muted(self):\n        redash.tasks.alerts.notify_subscriptions = MagicMock()\n        Alert.evaluate = MagicMock(return_value=Alert.TRIGGERED_STATE)\n\n        alert = self.factory.create_alert(options={\"muted\": True})\n        check_alerts_for_query(alert.query_id, metadata={\"Scheduled\": False})\n\n        self.assertFalse(redash.tasks.alerts.notify_subscriptions.called)\n\n\nclass TestNotifySubscriptions(BaseTestCase):\n    def test_calls_notify_for_subscribers(self):\n        subscription = self.factory.create_alert_subscription()\n        subscription.notify = MagicMock()\n        notify_subscriptions(subscription.alert, Alert.OK_STATE, metadata={\"Scheduled\": False})\n        subscription.notify.assert_called_with(\n            subscription.alert,\n            subscription.alert.query_rel,\n            subscription.user,\n            Alert.OK_STATE,\n            ANY,\n            ANY,\n            ANY,\n        )\n"
  },
  {
    "path": "tests/tasks/test_empty_schedule.py",
    "content": "import datetime\n\nfrom mock import patch\n\nfrom redash.models import Query\nfrom redash.tasks import empty_schedules\nfrom redash.utils import utcnow\nfrom tests import BaseTestCase\n\n\nclass TestEmptyScheduleQuery(BaseTestCase):\n    def test_empty_schedules(self):\n        one_day_ago = (utcnow() - datetime.timedelta(days=1)).strftime(\"%Y-%m-%d\")\n        query = self.factory.create_query(schedule={\"interval\": \"3600\", \"until\": one_day_ago})\n        oq = staticmethod(lambda: [query])\n        with patch.object(Query, \"past_scheduled_queries\", oq):\n            empty_schedules()\n            self.assertEqual(query.schedule, None)\n"
  },
  {
    "path": "tests/tasks/test_failure_report.py",
    "content": "import dateutil\nimport mock\nfrom freezegun import freeze_time\n\nfrom redash import redis_connection, settings\nfrom redash.tasks.failure_report import (\n    key,\n    notify_of_failure,\n    send_failure_report,\n)\nfrom tests import BaseTestCase\n\n\nclass TestSendAggregatedErrorsTask(BaseTestCase):\n    def setUp(self):\n        super(TestSendAggregatedErrorsTask, self).setUp()\n        redis_connection.flushall()\n        self.factory.org.set_setting(\"send_email_on_failed_scheduled_queries\", True)\n\n    def notify(self, message=\"Oh no, I failed!\", query=None, **kwargs):\n        if query is None:\n            query = self.factory.create_query(**kwargs)\n\n        notify_of_failure(message, query)\n        return key(query.user.id)\n\n    @mock.patch(\"redash.tasks.failure_report.render_template\", return_value=\"\")\n    def send_email(self, user, render_template):\n        send_failure_report(user.id)\n\n        _, context = render_template.call_args[0]\n        return context[\"failures\"]\n\n    def test_schedules_email_if_failure_count_is_beneath_limit(self):\n        key = self.notify(schedule_failures=settings.MAX_FAILURE_REPORTS_PER_QUERY - 1)\n        email_pending = redis_connection.exists(key)\n        self.assertTrue(email_pending)\n\n    def test_does_not_report_if_failure_count_is_beyond_limit(self):\n        key = self.notify(schedule_failures=settings.MAX_FAILURE_REPORTS_PER_QUERY)\n        email_pending = redis_connection.exists(key)\n        self.assertFalse(email_pending)\n\n    def test_does_not_report_if_organization_is_not_subscribed(self):\n        self.factory.org.set_setting(\"send_email_on_failed_scheduled_queries\", False)\n        key = self.notify()\n        email_pending = redis_connection.exists(key)\n        self.assertFalse(email_pending)\n\n    def test_does_not_report_if_query_owner_is_disabled(self):\n        self.factory.user.disable()\n        key = self.notify()\n        email_pending = redis_connection.exists(key)\n        self.assertFalse(email_pending)\n\n    def test_does_not_indicate_when_not_near_limit_for_a_query(self):\n        self.notify(schedule_failures=settings.MAX_FAILURE_REPORTS_PER_QUERY / 2)\n        failures = self.send_email(self.factory.user)\n\n        self.assertFalse(failures[0][\"comment\"])\n\n    def test_indicates_when_near_limit_for_a_query(self):\n        self.notify(schedule_failures=settings.MAX_FAILURE_REPORTS_PER_QUERY - 1)\n        failures = self.send_email(self.factory.user)\n\n        self.assertTrue(failures[0][\"comment\"])\n\n    def test_aggregates_different_queries_in_a_single_report(self):\n        key1 = self.notify(message=\"I'm a failure\")\n        key2 = self.notify(message=\"I'm simply not a success\")\n\n        self.assertEqual(key1, key2)\n\n    def test_counts_failures_for_each_reason(self):\n        query = self.factory.create_query()\n\n        self.notify(message=\"I'm a failure\", query=query)\n        self.notify(message=\"I'm a failure\", query=query)\n        self.notify(message=\"I'm a different type of failure\", query=query)\n        self.notify(message=\"I'm a totally different query\")\n\n        failures = self.send_email(query.user)\n\n        f1 = next(f for f in failures if f[\"failure_reason\"] == \"I'm a failure\")\n        self.assertEqual(2, f1[\"failure_count\"])\n        f2 = next(f for f in failures if f[\"failure_reason\"] == \"I'm a different type of failure\")\n        self.assertEqual(1, f2[\"failure_count\"])\n        f3 = next(f for f in failures if f[\"failure_reason\"] == \"I'm a totally different query\")\n        self.assertEqual(1, f3[\"failure_count\"])\n\n    def test_shows_latest_failure_time(self):\n        query = self.factory.create_query()\n\n        with freeze_time(\"2000-01-01\"):\n            self.notify(query=query)\n\n        self.notify(query=query)\n\n        failures = self.send_email(query.user)\n        latest_failure = dateutil.parser.parse(failures[0][\"failed_at\"])\n        self.assertNotEqual(2000, latest_failure.year)\n"
  },
  {
    "path": "tests/tasks/test_queries.py",
    "content": "from mock import Mock, patch\nfrom rq import Connection\nfrom rq.exceptions import NoSuchJobError\n\nfrom redash import models, rq_redis_connection\nfrom redash.query_runner.pg import PostgreSQL\nfrom redash.tasks import Job\nfrom redash.tasks.queries.execution import (\n    QueryExecutionError,\n    enqueue_query,\n    execute_query,\n)\nfrom tests import BaseTestCase\n\n\ndef fetch_job(*args, **kwargs):\n    if any(args):\n        job_id = args[0] if isinstance(args[0], str) else args[0].id\n    else:\n        job_id = create_job().id\n\n    result = Mock()\n    result.id = job_id\n    result.is_cancelled = False\n\n    return result\n\n\ndef create_job(*args, **kwargs):\n    return Job(connection=rq_redis_connection)\n\n\n@patch(\"redash.tasks.queries.execution.Job.fetch\", side_effect=fetch_job)\n@patch(\"redash.tasks.queries.execution.Queue.enqueue\", side_effect=create_job)\nclass TestEnqueueTask(BaseTestCase):\n    def test_multiple_enqueue_of_same_query(self, enqueue, _):\n        query = self.factory.create_query()\n\n        with Connection(rq_redis_connection):\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                query,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                query,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                query,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n\n        self.assertEqual(1, enqueue.call_count)\n\n    def test_multiple_enqueue_of_expired_job(self, enqueue, fetch_job):\n        query = self.factory.create_query()\n\n        with Connection(rq_redis_connection):\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                query,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n\n            # \"expire\" the previous job\n            fetch_job.side_effect = NoSuchJobError\n\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                query,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n\n        self.assertEqual(2, enqueue.call_count)\n\n    def test_reenqueue_during_job_cancellation(self, enqueue, my_fetch_job):\n        query = self.factory.create_query()\n\n        with Connection(rq_redis_connection):\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                query,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n\n            # \"cancel\" the previous job\n            def cancel_job(*args, **kwargs):\n                job = fetch_job(*args, **kwargs)\n                job.is_cancelled = True\n                return job\n\n            my_fetch_job.side_effect = cancel_job\n\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                query,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n\n        self.assertEqual(2, enqueue.call_count)\n\n    @patch(\"redash.settings.dynamic_settings.query_time_limit\", return_value=60)\n    def test_limits_query_time(self, _, enqueue, __):\n        query = self.factory.create_query()\n\n        with Connection(rq_redis_connection):\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                query,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n\n        _, kwargs = enqueue.call_args\n        self.assertEqual(60, kwargs.get(\"job_timeout\"))\n\n    def test_multiple_enqueue_of_different_query(self, enqueue, _):\n        query = self.factory.create_query()\n\n        with Connection(rq_redis_connection):\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                None,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n            enqueue_query(\n                query.query_text + \"2\",\n                query.data_source,\n                query.user_id,\n                False,\n                None,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n            enqueue_query(\n                query.query_text + \"3\",\n                query.data_source,\n                query.user_id,\n                False,\n                None,\n                {\"Username\": \"Arik\", \"query_id\": query.id},\n            )\n\n        self.assertEqual(3, enqueue.call_count)\n\n\n@patch(\"redash.tasks.queries.execution.get_current_job\", side_effect=fetch_job)\nclass QueryExecutorTests(BaseTestCase):\n    def test_success(self, _):\n        \"\"\"\n        ``execute_query`` invokes the query runner and stores a query result.\n        \"\"\"\n        with patch.object(PostgreSQL, \"run_query\") as qr:\n            query_result_data = {\"columns\": [], \"rows\": []}\n            qr.return_value = (query_result_data, None)\n            result_id = execute_query(\"SELECT 1, 2\", self.factory.data_source.id, {})\n            self.assertEqual(1, qr.call_count)\n            result = models.QueryResult.query.get(result_id)\n            self.assertEqual(result.data, query_result_data)\n\n    def test_success_scheduled(self, _):\n        \"\"\"\n        Scheduled queries remember their latest results.\n        \"\"\"\n        q = self.factory.create_query(query_text=\"SELECT 1, 2\", schedule={\"interval\": 300})\n        with patch.object(PostgreSQL, \"run_query\") as qr:\n            qr.return_value = (\n                {\n                    \"columns\": [\n                        {\"name\": \"_col0\", \"friendly_name\": \"_col0\", \"type\": \"integer\"},\n                        {\"name\": \"_col1\", \"friendly_name\": \"_col1\", \"type\": \"integer\"},\n                    ],\n                    \"rows\": [{\"_col0\": 1, \"_col1\": 2}],\n                },\n                None,\n            )\n            result_id = execute_query(\n                \"SELECT 1, 2\",\n                self.factory.data_source.id,\n                {\"query_id\": q.id},\n                scheduled_query_id=q.id,\n            )\n            q = models.Query.get_by_id(q.id)\n            self.assertEqual(q.schedule_failures, 0)\n            result = models.QueryResult.query.get(result_id)\n            self.assertEqual(q.latest_query_data, result)\n\n    def test_failure_scheduled(self, _):\n        \"\"\"\n        Scheduled queries that fail have their failure recorded.\n        \"\"\"\n        q = self.factory.create_query(query_text=\"SELECT 1, 2\", schedule={\"interval\": 300})\n        with patch.object(PostgreSQL, \"run_query\") as qr:\n            qr.side_effect = ValueError(\"broken\")\n\n            result = execute_query(\n                \"SELECT 1, 2\",\n                self.factory.data_source.id,\n                {\"query_id\": q.id},\n                scheduled_query_id=q.id,\n            )\n            self.assertTrue(isinstance(result, QueryExecutionError))\n            q = models.Query.get_by_id(q.id)\n            self.assertEqual(q.schedule_failures, 1)\n\n            result = execute_query(\n                \"SELECT 1, 2\",\n                self.factory.data_source.id,\n                {\"query_id\": q.id},\n                scheduled_query_id=q.id,\n            )\n            self.assertTrue(isinstance(result, QueryExecutionError))\n            q = models.Query.get_by_id(q.id)\n            self.assertEqual(q.schedule_failures, 2)\n\n    def test_success_after_failure(self, _):\n        \"\"\"\n        Query execution success resets the failure counter.\n        \"\"\"\n        q = self.factory.create_query(query_text=\"SELECT 1, 2\", schedule={\"interval\": 300})\n        with patch.object(PostgreSQL, \"run_query\") as qr:\n            qr.side_effect = ValueError(\"broken\")\n            result = execute_query(\n                \"SELECT 1, 2\",\n                self.factory.data_source.id,\n                {\"query_id\": q.id},\n                scheduled_query_id=q.id,\n            )\n            self.assertTrue(isinstance(result, QueryExecutionError))\n            q = models.Query.get_by_id(q.id)\n            self.assertEqual(q.schedule_failures, 1)\n\n        with patch.object(PostgreSQL, \"run_query\") as qr:\n            qr.return_value = (\n                {\n                    \"columns\": [\n                        {\"name\": \"_col0\", \"friendly_name\": \"_col0\", \"type\": \"integer\"},\n                        {\"name\": \"_col1\", \"friendly_name\": \"_col1\", \"type\": \"integer\"},\n                    ],\n                    \"rows\": [{\"_col0\": 1, \"_col1\": 2}],\n                },\n                None,\n            )\n            execute_query(\n                \"SELECT 1, 2\",\n                self.factory.data_source.id,\n                {\"query_id\": q.id},\n                scheduled_query_id=q.id,\n            )\n            q = models.Query.get_by_id(q.id)\n            self.assertEqual(q.schedule_failures, 0)\n\n    def test_adhoc_success_after_scheduled_failure(self, _):\n        \"\"\"\n        Query execution success resets the failure counter, even if it runs as an adhoc query.\n        \"\"\"\n        q = self.factory.create_query(query_text=\"SELECT 1, 2\", schedule={\"interval\": 300})\n        with patch.object(PostgreSQL, \"run_query\") as qr:\n            qr.side_effect = ValueError(\"broken\")\n            result = execute_query(\n                \"SELECT 1, 2\",\n                self.factory.data_source.id,\n                {\"query_id\": q.id},\n                scheduled_query_id=q.id,\n                user_id=self.factory.user.id,\n            )\n            self.assertTrue(isinstance(result, QueryExecutionError))\n            q = models.Query.get_by_id(q.id)\n            self.assertEqual(q.schedule_failures, 1)\n\n        with patch.object(PostgreSQL, \"run_query\") as qr:\n            qr.return_value = (\n                {\n                    \"columns\": [\n                        {\"name\": \"_col0\", \"friendly_name\": \"_col0\", \"type\": \"integer\"},\n                        {\"name\": \"_col1\", \"friendly_name\": \"_col1\", \"type\": \"integer\"},\n                    ],\n                    \"rows\": [{\"_col0\": 1, \"_col1\": 2}],\n                },\n                None,\n            )\n            execute_query(\n                \"SELECT 1, 2\",\n                self.factory.data_source.id,\n                {\"query_id\": q.id},\n                user_id=self.factory.user.id,\n            )\n            q = models.Query.get_by_id(q.id)\n            self.assertEqual(q.schedule_failures, 0)\n"
  },
  {
    "path": "tests/tasks/test_refresh_queries.py",
    "content": "from mock import ANY, call, patch\n\nfrom redash.models import Query\nfrom redash.tasks.queries.maintenance import refresh_queries\nfrom tests import BaseTestCase\n\nENQUEUE_QUERY = \"redash.tasks.queries.maintenance.enqueue_query\"\n\n\nclass TestRefreshQuery(BaseTestCase):\n    def test_enqueues_outdated_queries_for_sqlquery(self):\n        \"\"\"\n        refresh_queries() launches an execution task for each query returned\n        from Query.outdated_queries().\n        \"\"\"\n        query1 = self.factory.create_query(options={\"apply_auto_limit\": True})\n        query2 = self.factory.create_query(\n            query_text=\"select 42;\",\n            data_source=self.factory.create_data_source(),\n            options={\"apply_auto_limit\": True},\n        )\n        oq = staticmethod(lambda: [query1, query2])\n        with patch(ENQUEUE_QUERY) as add_job_mock, patch.object(Query, \"outdated_queries\", oq):\n            refresh_queries()\n            self.assertEqual(add_job_mock.call_count, 2)\n            add_job_mock.assert_has_calls(\n                [\n                    call(\n                        query1.query_text + \" LIMIT 1000\",\n                        query1.data_source,\n                        query1.user_id,\n                        scheduled_query=query1,\n                        metadata={\"query_id\": query1.id, \"Username\": query1.user.get_actual_user()},\n                    ),\n                    call(\n                        \"select 42 LIMIT 1000\",\n                        query2.data_source,\n                        query2.user_id,\n                        scheduled_query=query2,\n                        metadata={\"query_id\": query2.id, \"Username\": query2.user.get_actual_user()},\n                    ),\n                ],\n                any_order=True,\n            )\n\n    def test_enqueues_outdated_queries_for_non_sqlquery(self):\n        \"\"\"\n        refresh_queries() launches an execution task for each query returned\n        from Query.outdated_queries().\n        \"\"\"\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, type=\"prometheus\")\n        query1 = self.factory.create_query(data_source=ds, options={\"apply_auto_limit\": True})\n        query2 = self.factory.create_query(query_text=\"select 42;\", data_source=ds, options={\"apply_auto_limit\": True})\n        oq = staticmethod(lambda: [query1, query2])\n        with patch(ENQUEUE_QUERY) as add_job_mock, patch.object(Query, \"outdated_queries\", oq):\n            refresh_queries()\n            self.assertEqual(add_job_mock.call_count, 2)\n            add_job_mock.assert_has_calls(\n                [\n                    call(\n                        query1.query_text,\n                        query1.data_source,\n                        query1.user_id,\n                        scheduled_query=query1,\n                        metadata={\"query_id\": query1.id, \"Username\": query1.user.get_actual_user()},\n                    ),\n                    call(\n                        query2.query_text,\n                        query2.data_source,\n                        query2.user_id,\n                        scheduled_query=query2,\n                        metadata={\"query_id\": query2.id, \"Username\": query2.user.get_actual_user()},\n                    ),\n                ],\n                any_order=True,\n            )\n\n    def test_doesnt_enqueue_outdated_queries_for_paused_data_source_for_sqlquery(self):\n        \"\"\"\n        refresh_queries() does not launch execution tasks for queries whose\n        data source is paused.\n        \"\"\"\n        query = self.factory.create_query(options={\"apply_auto_limit\": True})\n        oq = staticmethod(lambda: [query])\n        query.data_source.pause()\n        with patch.object(Query, \"outdated_queries\", oq):\n            with patch(ENQUEUE_QUERY) as add_job_mock:\n                refresh_queries()\n                add_job_mock.assert_not_called()\n\n            query.data_source.resume()\n\n            with patch(ENQUEUE_QUERY) as add_job_mock:\n                refresh_queries()\n                add_job_mock.assert_called_with(\n                    query.query_text + \" LIMIT 1000\",\n                    query.data_source,\n                    query.user_id,\n                    scheduled_query=query,\n                    metadata=ANY,\n                )\n\n    def test_doesnt_enqueue_outdated_queries_for_paused_data_source_for_non_sqlquery(\n        self,\n    ):\n        \"\"\"\n        refresh_queries() does not launch execution tasks for queries whose\n        data source is paused.\n        \"\"\"\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, type=\"prometheus\")\n        query = self.factory.create_query(data_source=ds, options={\"apply_auto_limit\": True})\n        oq = staticmethod(lambda: [query])\n        query.data_source.pause()\n        with patch.object(Query, \"outdated_queries\", oq):\n            with patch(ENQUEUE_QUERY) as add_job_mock:\n                refresh_queries()\n                add_job_mock.assert_not_called()\n\n            query.data_source.resume()\n\n            with patch(ENQUEUE_QUERY) as add_job_mock:\n                refresh_queries()\n                add_job_mock.assert_called_with(\n                    query.query_text,\n                    query.data_source,\n                    query.user_id,\n                    scheduled_query=query,\n                    metadata=ANY,\n                )\n\n    def test_enqueues_parameterized_queries_for_sqlquery(self):\n        \"\"\"\n        Scheduled queries with parameters use saved values.\n        \"\"\"\n        query = self.factory.create_query(\n            query_text=\"select {{n}}\",\n            options={\n                \"parameters\": [\n                    {\n                        \"global\": False,\n                        \"type\": \"text\",\n                        \"name\": \"n\",\n                        \"value\": \"42\",\n                        \"title\": \"n\",\n                    }\n                ],\n                \"apply_auto_limit\": True,\n            },\n        )\n        oq = staticmethod(lambda: [query])\n        with patch(ENQUEUE_QUERY) as add_job_mock, patch.object(Query, \"outdated_queries\", oq):\n            refresh_queries()\n            add_job_mock.assert_called_with(\n                \"select 42 LIMIT 1000\",\n                query.data_source,\n                query.user_id,\n                scheduled_query=query,\n                metadata=ANY,\n            )\n\n    def test_enqueues_parameterized_queries_for_non_sqlquery(self):\n        \"\"\"\n        Scheduled queries with parameters use saved values.\n        \"\"\"\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, type=\"prometheus\")\n        query = self.factory.create_query(\n            query_text=\"select {{n}}\",\n            options={\n                \"parameters\": [\n                    {\n                        \"global\": False,\n                        \"type\": \"text\",\n                        \"name\": \"n\",\n                        \"value\": \"42\",\n                        \"title\": \"n\",\n                    }\n                ],\n                \"apply_auto_limit\": True,\n            },\n            data_source=ds,\n        )\n        oq = staticmethod(lambda: [query])\n        with patch(ENQUEUE_QUERY) as add_job_mock, patch.object(Query, \"outdated_queries\", oq):\n            refresh_queries()\n            add_job_mock.assert_called_with(\n                \"select 42\",\n                query.data_source,\n                query.user_id,\n                scheduled_query=query,\n                metadata=ANY,\n            )\n\n    def test_doesnt_enqueue_parameterized_queries_with_invalid_parameters(self):\n        \"\"\"\n        Scheduled queries with invalid parameters are skipped.\n        \"\"\"\n        query = self.factory.create_query(\n            query_text=\"select {{n}}\",\n            options={\n                \"parameters\": [\n                    {\n                        \"global\": False,\n                        \"type\": \"text\",\n                        \"name\": \"n\",\n                        \"value\": 42,  # <-- should be text!\n                        \"title\": \"n\",\n                    }\n                ],\n                \"apply_auto_limit\": True,\n            },\n        )\n        oq = staticmethod(lambda: [query])\n        with patch(ENQUEUE_QUERY) as add_job_mock, patch.object(Query, \"outdated_queries\", oq):\n            refresh_queries()\n            add_job_mock.assert_not_called()\n\n    def test_doesnt_enqueue_parameterized_queries_with_dropdown_queries_that_are_detached_from_data_source(\n        self,\n    ):\n        \"\"\"\n        Scheduled queries with a dropdown parameter which points to a query that is detached from its data source are skipped.\n        \"\"\"\n        query = self.factory.create_query(\n            query_text=\"select {{n}}\",\n            options={\n                \"parameters\": [\n                    {\n                        \"global\": False,\n                        \"type\": \"query\",\n                        \"name\": \"n\",\n                        \"queryId\": 100,\n                        \"title\": \"n\",\n                    }\n                ],\n                \"apply_auto_limit\": True,\n            },\n        )\n\n        self.factory.create_query(id=100, data_source=None)\n\n        oq = staticmethod(lambda: [query])\n        with patch(ENQUEUE_QUERY) as add_job_mock, patch.object(Query, \"outdated_queries\", oq):\n            refresh_queries()\n            add_job_mock.assert_not_called()\n"
  },
  {
    "path": "tests/tasks/test_refresh_schemas.py",
    "content": "from mock import patch\n\nfrom redash.tasks import refresh_schemas\nfrom tests import BaseTestCase\n\n\nclass TestRefreshSchemas(BaseTestCase):\n    def test_calls_refresh_of_all_data_sources(self):\n        self.factory.data_source  # trigger creation\n        with patch(\"redash.tasks.queries.maintenance.refresh_schema.delay\") as refresh_job:\n            refresh_schemas()\n            refresh_job.assert_called()\n\n    def test_skips_paused_data_sources(self):\n        self.factory.data_source.pause()\n\n        with patch(\"redash.tasks.queries.maintenance.refresh_schema.delay\") as refresh_job:\n            refresh_schemas()\n            refresh_job.assert_not_called()\n\n        self.factory.data_source.resume()\n\n        with patch(\"redash.tasks.queries.maintenance.refresh_schema.delay\") as refresh_job:\n            refresh_schemas()\n            refresh_job.assert_called()\n"
  },
  {
    "path": "tests/tasks/test_schedule.py",
    "content": "from unittest import TestCase\n\nfrom mock import patch\n\nfrom redash.tasks.schedule import rq_scheduler, schedule_periodic_jobs\n\n\nclass TestSchedule(TestCase):\n    def setUp(self):\n        for job in rq_scheduler.get_jobs():\n            rq_scheduler.cancel(job)\n            job.delete()\n\n    def test_schedules_a_new_job(self):\n        def foo():\n            pass\n\n        schedule_periodic_jobs([{\"func\": foo, \"interval\": 60}])\n\n        jobs = [job for job in rq_scheduler.get_jobs()]\n\n        self.assertEqual(len(jobs), 1)\n        self.assertTrue(jobs[0].func_name.endswith(\"foo\"))\n        self.assertEqual(jobs[0].meta[\"interval\"], 60)\n\n    def test_doesnt_reschedule_an_existing_job(self):\n        def foo():\n            pass\n\n        schedule_periodic_jobs([{\"func\": foo, \"interval\": 60}])\n        with patch(\"redash.tasks.rq_scheduler.schedule\") as schedule:\n            schedule_periodic_jobs([{\"func\": foo, \"interval\": 60}])\n            schedule.assert_not_called()\n\n    def test_reschedules_a_modified_job(self):\n        def foo():\n            pass\n\n        schedule_periodic_jobs([{\"func\": foo, \"interval\": 60}])\n        schedule_periodic_jobs([{\"func\": foo, \"interval\": 120}])\n\n        jobs = [job for job in rq_scheduler.get_jobs()]\n\n        self.assertEqual(len(jobs), 1)\n        self.assertTrue(jobs[0].func_name.endswith(\"foo\"))\n        self.assertEqual(jobs[0].meta[\"interval\"], 120)\n\n    def test_removes_jobs_that_are_no_longer_defined(self):\n        def foo():\n            pass\n\n        def bar():\n            pass\n\n        schedule_periodic_jobs([{\"func\": foo, \"interval\": 60}, {\"func\": bar, \"interval\": 90}])\n        schedule_periodic_jobs([{\"func\": foo, \"interval\": 60}])\n\n        jobs = [job for job in rq_scheduler.get_jobs()]\n\n        self.assertEqual(len(jobs), 1)\n        self.assertTrue(jobs[0].func_name.endswith(\"foo\"))\n        self.assertEqual(jobs[0].meta[\"interval\"], 60)\n\n\nclass TestSchedulerMetrics(TestCase):\n    def setUp(self):\n        for job in rq_scheduler.get_jobs():\n            rq_scheduler.cancel(job)\n            job.delete()\n\n    def test_scheduler_enqueue_job_metric(self):\n        def foo():\n            pass\n\n        schedule_periodic_jobs([{\"func\": foo, \"interval\": 60}])\n\n        with patch(\"statsd.StatsClient.incr\") as incr:\n            rq_scheduler.enqueue_jobs()\n            incr.assert_called_once_with(\"rq.jobs.created.periodic\")\n"
  },
  {
    "path": "tests/tasks/test_worker.py",
    "content": "from mock import call, patch\nfrom rq import Connection\nfrom rq.job import JobStatus\n\nfrom redash import rq_redis_connection\nfrom redash.tasks import Queue, Worker\nfrom redash.tasks.queries.execution import enqueue_query\nfrom redash.worker import default_queues, job\nfrom tests import BaseTestCase\n\n\n@patch(\"statsd.StatsClient.incr\")\nclass TestWorkerMetrics(BaseTestCase):\n    def tearDown(self):\n        with Connection(rq_redis_connection):\n            for queue_name in default_queues:\n                Queue(queue_name).empty()\n\n    def test_worker_records_success_metrics(self, incr):\n        query = self.factory.create_query()\n\n        with Connection(rq_redis_connection):\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                None,\n                {\"Username\": \"Patrick\", \"query_id\": query.id},\n            )\n\n            Worker([\"queries\"]).work(max_jobs=1)\n\n        calls = [\n            call(\"rq.jobs.running.queries\"),\n            call(\"rq.jobs.started.queries\"),\n            call(\"rq.jobs.running.queries\", -1, 1),\n            call(\"rq.jobs.finished.queries\"),\n        ]\n        incr.assert_has_calls(calls)\n\n    @patch(\"rq.Worker.execute_job\")\n    def test_worker_records_failure_metrics(self, _, incr):\n        \"\"\"\n        Force superclass execute_job to do nothing and set status to JobStatus.Failed to simulate query failure\n        \"\"\"\n        query = self.factory.create_query()\n\n        with Connection(rq_redis_connection):\n            job = enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                None,\n                {\"Username\": \"Patrick\", \"query_id\": query.id},\n            )\n            job.set_status(JobStatus.FAILED)\n\n            Worker([\"queries\"]).work(max_jobs=1)\n\n        calls = [\n            call(\"rq.jobs.running.queries\"),\n            call(\"rq.jobs.started.queries\"),\n            call(\"rq.jobs.running.queries\", -1, 1),\n            call(\"rq.jobs.failed.queries\"),\n        ]\n        incr.assert_has_calls(calls)\n\n\n@patch(\"statsd.StatsClient.incr\")\nclass TestQueueMetrics(BaseTestCase):\n    def tearDown(self):\n        with Connection(rq_redis_connection):\n            for queue_name in default_queues:\n                Queue(queue_name).empty()\n\n    def test_enqueue_query_records_created_metric(self, incr):\n        query = self.factory.create_query()\n\n        with Connection(rq_redis_connection):\n            enqueue_query(\n                query.query_text,\n                query.data_source,\n                query.user_id,\n                False,\n                None,\n                {\"Username\": \"Patrick\", \"query_id\": query.id},\n            )\n\n        incr.assert_called_with(\"rq.jobs.created.queries\")\n\n    def test_job_delay_records_created_metric(self, incr):\n        @job(\"default\", timeout=300)\n        def foo():\n            pass\n\n        foo.delay()\n        incr.assert_called_with(\"rq.jobs.created.default\")\n"
  },
  {
    "path": "tests/test_authentication.py",
    "content": "import importlib\nimport json\nimport os\nimport subprocess\nimport time\n\nimport jwcrypto.jwk\nimport jwt\nimport requests\nfrom flask import request\nfrom mock import Mock, patch\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom redash import models, settings\nfrom redash.authentication import (\n    api_key_load_user_from_request,\n    get_login_url,\n    hmac_load_user_from_request,\n    jwt_auth,\n    org_settings,\n    sign,\n)\nfrom redash.authentication.google_oauth import (\n    create_and_login_user,\n    verify_profile,\n)\nfrom tests import BaseTestCase\n\n\nclass TestApiKeyAuthentication(BaseTestCase):\n    #\n    # This is a bad way to write these tests, but the way Flask works doesn't make it easy to write them properly...\n    #\n    def setUp(self):\n        super(TestApiKeyAuthentication, self).setUp()\n        self.api_key = \"10\"\n        self.query = self.factory.create_query(api_key=self.api_key)\n        models.db.session.flush()\n        self.query_url = \"/{}/api/queries/{}\".format(self.factory.org.slug, self.query.id)\n        self.queries_url = \"/{}/api/queries\".format(self.factory.org.slug)\n\n    def test_no_api_key(self):\n        with self.app.test_client() as c:\n            c.get(self.query_url)\n            self.assertIsNone(api_key_load_user_from_request(request))\n\n    def test_wrong_api_key(self):\n        with self.app.test_client() as c:\n            c.get(self.query_url, query_string={\"api_key\": \"whatever\"})\n            self.assertIsNone(api_key_load_user_from_request(request))\n\n    def test_correct_api_key(self):\n        with self.app.test_client() as c:\n            c.get(self.query_url, query_string={\"api_key\": self.api_key})\n            self.assertIsNotNone(api_key_load_user_from_request(request))\n\n    def test_no_query_id(self):\n        with self.app.test_client() as c:\n            c.get(self.queries_url, query_string={\"api_key\": self.api_key})\n            self.assertIsNone(api_key_load_user_from_request(request))\n\n    def test_user_api_key(self):\n        user = self.factory.create_user(api_key=\"user_key\")\n        models.db.session.flush()\n        with self.app.test_client() as c:\n            c.get(self.queries_url, query_string={\"api_key\": user.api_key})\n            self.assertEqual(user.id, api_key_load_user_from_request(request).id)\n\n    def test_disabled_user_api_key(self):\n        user = self.factory.create_user(api_key=\"user_key\")\n        user.disable()\n        models.db.session.flush()\n        with self.app.test_client() as c:\n            c.get(self.queries_url, query_string={\"api_key\": user.api_key})\n            self.assertEqual(None, api_key_load_user_from_request(request))\n\n    def test_api_key_header(self):\n        with self.app.test_client() as c:\n            c.get(self.query_url, headers={\"Authorization\": \"Key {}\".format(self.api_key)})\n            self.assertIsNotNone(api_key_load_user_from_request(request))\n\n    def test_api_key_header_with_wrong_key(self):\n        with self.app.test_client() as c:\n            c.get(self.query_url, headers={\"Authorization\": \"Key oops\"})\n            self.assertIsNone(api_key_load_user_from_request(request))\n\n    def test_api_key_for_wrong_org(self):\n        other_user = self.factory.create_admin(org=self.factory.create_org())\n\n        with self.app.test_client() as c:\n            rv = c.get(\n                self.query_url,\n                headers={\"Authorization\": \"Key {}\".format(other_user.api_key)},\n            )\n            self.assertEqual(404, rv.status_code)\n\n\nclass TestHMACAuthentication(BaseTestCase):\n    #\n    # This is a bad way to write these tests, but the way Flask works doesn't make it easy to write them properly...\n    #\n    def setUp(self):\n        super(TestHMACAuthentication, self).setUp()\n        self.api_key = \"10\"\n        self.query = self.factory.create_query(api_key=self.api_key)\n        models.db.session.flush()\n        self.path = \"/{}/api/queries/{}\".format(self.query.org.slug, self.query.id)\n        self.expires = time.time() + 1800\n\n    def signature(self, expires):\n        return sign(self.query.api_key, self.path, expires)\n\n    def test_no_signature(self):\n        with self.app.test_client() as c:\n            c.get(self.path)\n            self.assertIsNone(hmac_load_user_from_request(request))\n\n    def test_wrong_signature(self):\n        with self.app.test_client() as c:\n            c.get(\n                self.path,\n                query_string={\"signature\": \"whatever\", \"expires\": self.expires},\n            )\n            self.assertIsNone(hmac_load_user_from_request(request))\n\n    def test_correct_signature(self):\n        with self.app.test_client() as c:\n            c.get(\n                self.path,\n                query_string={\n                    \"signature\": self.signature(self.expires),\n                    \"expires\": self.expires,\n                },\n            )\n            self.assertIsNotNone(hmac_load_user_from_request(request))\n\n    def test_no_query_id(self):\n        with self.app.test_client() as c:\n            c.get(\n                \"/{}/api/queries\".format(self.query.org.slug),\n                query_string={\"api_key\": self.api_key},\n            )\n            self.assertIsNone(hmac_load_user_from_request(request))\n\n    def test_user_api_key(self):\n        user = self.factory.create_user(api_key=\"user_key\")\n        path = \"/api/queries/\"\n        models.db.session.flush()\n\n        signature = sign(user.api_key, path, self.expires)\n        with self.app.test_client() as c:\n            c.get(\n                path,\n                query_string={\n                    \"signature\": signature,\n                    \"expires\": self.expires,\n                    \"user_id\": user.id,\n                },\n            )\n            self.assertEqual(user.id, hmac_load_user_from_request(request).id)\n\n\nclass TestSessionAuthentication(BaseTestCase):\n    def test_prefers_api_key_over_session_user_id(self):\n        user = self.factory.create_user()\n        query = self.factory.create_query(user=user)\n\n        other_org = self.factory.create_org()\n        other_user = self.factory.create_user(org=other_org)\n        models.db.session.flush()\n\n        rv = self.make_request(\n            \"get\",\n            \"/api/queries/{}?api_key={}\".format(query.id, query.api_key),\n            user=other_user,\n        )\n        self.assertEqual(rv.status_code, 200)\n\n\nclass TestCreateAndLoginUser(BaseTestCase):\n    def test_logins_valid_user(self):\n        user = self.factory.create_user(email=\"test@example.com\")\n\n        with patch(\"redash.authentication.login_user\") as login_user_mock:\n            create_and_login_user(self.factory.org, user.name, user.email)\n            login_user_mock.assert_called_once_with(user, remember=True)\n\n    def test_creates_vaild_new_user(self):\n        email = \"test@example.com\"\n        name = \"Test User\"\n\n        with patch(\"redash.authentication.login_user\") as login_user_mock:\n            create_and_login_user(self.factory.org, name, email)\n\n            self.assertTrue(login_user_mock.called)\n            user = models.User.query.filter(models.User.email == email).one()\n            self.assertEqual(user.email, email)\n\n    def test_updates_user_name(self):\n        user = self.factory.create_user(email=\"test@example.com\")\n\n        with patch(\"redash.authentication.login_user\") as login_user_mock:\n            create_and_login_user(self.factory.org, \"New Name\", user.email)\n            login_user_mock.assert_called_once_with(user, remember=True)\n\n\nclass TestVerifyProfile(BaseTestCase):\n    def test_no_domain_allowed_for_org(self):\n        profile = dict(email=\"arik@example.com\")\n        self.assertFalse(verify_profile(self.factory.org, profile))\n\n    def test_domain_not_in_org_domains_list(self):\n        profile = dict(email=\"arik@example.com\")\n        self.factory.org.settings[models.Organization.SETTING_GOOGLE_APPS_DOMAINS] = [\"example.org\"]\n        self.assertFalse(verify_profile(self.factory.org, profile))\n\n    def test_domain_in_org_domains_list(self):\n        profile = dict(email=\"arik@example.com\")\n        self.factory.org.settings[models.Organization.SETTING_GOOGLE_APPS_DOMAINS] = [\"example.com\"]\n        self.assertTrue(verify_profile(self.factory.org, profile))\n\n        self.factory.org.settings[models.Organization.SETTING_GOOGLE_APPS_DOMAINS] = [\n            \"example.org\",\n            \"example.com\",\n        ]\n        self.assertTrue(verify_profile(self.factory.org, profile))\n\n    def test_org_in_public_mode_accepts_any_domain(self):\n        profile = dict(email=\"arik@example.com\")\n        self.factory.org.settings[models.Organization.SETTING_IS_PUBLIC] = True\n        self.factory.org.settings[models.Organization.SETTING_GOOGLE_APPS_DOMAINS] = []\n        self.assertTrue(verify_profile(self.factory.org, profile))\n\n    def test_user_not_in_domain_but_account_exists(self):\n        profile = dict(email=\"arik@example.com\")\n        self.factory.create_user(email=\"arik@example.com\")\n        self.factory.org.settings[models.Organization.SETTING_GOOGLE_APPS_DOMAINS] = [\"example.org\"]\n        self.assertTrue(verify_profile(self.factory.org, profile))\n\n\nclass TestGetLoginUrl(BaseTestCase):\n    def test_when_multi_org_enabled_and_org_exists(self):\n        with self.app.test_request_context(\"/{}/\".format(self.factory.org.slug)):\n            self.assertEqual(get_login_url(next=None), \"/{}/login\".format(self.factory.org.slug))\n\n    def test_when_multi_org_enabled_and_org_doesnt_exist(self):\n        with self.app.test_request_context(\"/{}_notexists/\".format(self.factory.org.slug)):\n            self.assertEqual(get_login_url(next=None), \"/\")\n\n\nclass TestRedirectToUrlAfterLoggingIn(BaseTestCase):\n    def setUp(self):\n        super(TestRedirectToUrlAfterLoggingIn, self).setUp()\n        self.user = self.factory.user\n        self.password = \"test1234\"\n\n    def test_no_next_param(self):\n        response = self.post_request(\n            \"/login\",\n            data={\"email\": self.user.email, \"password\": self.password},\n            org=self.factory.org,\n        )\n        self.assertEqual(response.location, \"/{}/\".format(self.user.org.slug))\n\n    def test_simple_path_in_next_param(self):\n        response = self.post_request(\n            \"/login?next=queries\",\n            data={\"email\": self.user.email, \"password\": self.password},\n            org=self.factory.org,\n        )\n        self.assertEqual(response.location, \"queries\")\n\n    def test_starts_scheme_url_in_next_param(self):\n        response = self.post_request(\n            \"/login?next=https://redash.io\",\n            data={\"email\": self.user.email, \"password\": self.password},\n            org=self.factory.org,\n        )\n        self.assertEqual(response.location, \"./\")\n\n    def test_without_scheme_url_in_next_param(self):\n        response = self.post_request(\n            \"/login?next=//redash.io\",\n            data={\"email\": self.user.email, \"password\": self.password},\n            org=self.factory.org,\n        )\n        self.assertEqual(response.location, \"./\")\n\n    def test_without_scheme_with_path_url_in_next_param(self):\n        response = self.post_request(\n            \"/login?next=//localhost/queries\",\n            data={\"email\": self.user.email, \"password\": self.password},\n            org=self.factory.org,\n        )\n        self.assertEqual(response.location, \"/queries\")\n\n\nclass TestRemoteUserAuth(BaseTestCase):\n    DEFAULT_SETTING_OVERRIDES = {\"REDASH_REMOTE_USER_LOGIN_ENABLED\": \"true\"}\n\n    def setUp(self):\n        # Apply default setting overrides to every test\n        self.override_settings(None)\n\n        super(TestRemoteUserAuth, self).setUp()\n\n    def override_settings(self, overrides):\n        \"\"\"Override settings for testing purposes.\n\n        This helper method can be used to override specific environmental\n        variables to enable / disable Re:Dash features for the duration\n        of the test.\n\n        Note that these overrides only affect code that checks the value of\n        the setting at runtime. It doesn't affect code that only checks the\n        value during program initialization.\n\n        :param dict overrides: a dict of environmental variables to override\n            when the settings are reloaded\n        \"\"\"\n        variables = self.DEFAULT_SETTING_OVERRIDES.copy()\n        variables.update(overrides or {})\n        with patch.dict(os.environ, variables):\n            importlib.reload(settings)\n\n        # Queue a cleanup routine that reloads the settings without overrides\n        # once the test ends\n        self.addCleanup(lambda: importlib.reload(settings))\n\n    def assert_correct_user_attributes(\n        self,\n        user,\n        email=\"test@example.com\",\n        name=\"test@example.com\",\n        groups=None,\n        org=None,\n    ):\n        \"\"\"Helper to assert that the user attributes are correct.\"\"\"\n        groups = groups or []\n        if self.factory.org.default_group.id not in groups:\n            groups.append(self.factory.org.default_group.id)\n\n        self.assertIsNotNone(user)\n        self.assertEqual(user.email, email)\n        self.assertEqual(user.name, name)\n        self.assertEqual(user.org, org or self.factory.org)\n        self.assertCountEqual(user.group_ids, groups)\n\n    def get_test_user(self, email=\"test@example.com\", org=None):\n        \"\"\"Helper to fetch an user from the database.\"\"\"\n\n        # Expire all cached objects to ensure these values are read directly\n        # from the database.\n        models.db.session.expire_all()\n\n        return models.User.get_by_email_and_org(email, org or self.factory.org)\n\n    def test_remote_login_disabled(self):\n        self.override_settings({\"REDASH_REMOTE_USER_LOGIN_ENABLED\": \"false\"})\n\n        self.get_request(\n            \"/remote_user/login\",\n            org=self.factory.org,\n            headers={\"X-Forwarded-Remote-User\": \"test@example.com\"},\n        )\n\n        with self.assertRaises(NoResultFound):\n            self.get_test_user()\n\n    def test_remote_login_default_header(self):\n        self.get_request(\n            \"/remote_user/login\",\n            org=self.factory.org,\n            headers={\"X-Forwarded-Remote-User\": \"test@example.com\"},\n        )\n\n        self.assert_correct_user_attributes(self.get_test_user())\n\n    def test_remote_login_custom_header(self):\n        self.override_settings({\"REDASH_REMOTE_USER_HEADER\": \"X-Custom-User\"})\n\n        self.get_request(\n            \"/remote_user/login\",\n            org=self.factory.org,\n            headers={\"X-Custom-User\": \"test@example.com\"},\n        )\n\n        self.assert_correct_user_attributes(self.get_test_user())\n\n\nclass TestUserForgotPassword(BaseTestCase):\n    def test_user_should_receive_password_reset_link(self):\n        user = self.factory.create_user()\n\n        with patch(\"redash.handlers.authentication.send_password_reset_email\") as send_password_reset_email_mock:\n            response = self.post_request(\"/forgot\", org=user.org, data={\"email\": user.email})\n            self.assertEqual(response.status_code, 200)\n            send_password_reset_email_mock.assert_called_with(user)\n\n    def test_disabled_user_should_not_receive_password_reset_link(self):\n        user = self.factory.create_user()\n        user.disable()\n        self.db.session.add(user)\n        self.db.session.commit()\n\n        with patch(\n            \"redash.handlers.authentication.send_password_reset_email\"\n        ) as send_password_reset_email_mock, patch(\n            \"redash.handlers.authentication.send_user_disabled_email\"\n        ) as send_user_disabled_email_mock:\n            response = self.post_request(\"/forgot\", org=user.org, data={\"email\": user.email})\n            self.assertEqual(response.status_code, 200)\n            send_password_reset_email_mock.assert_not_called()\n            send_user_disabled_email_mock.assert_called_with(user)\n\n\nclass TestJWTAuthentication(BaseTestCase):\n    def setUp(self):\n        super(TestJWTAuthentication, self).setUp()\n        self.auth_audience = \"My Org\"\n        self.auth_issuer = \"Admin\"\n        self.token_name = \"jwt-token\"\n        self.rsa_private_key = \"/tmp/jwtRS256.key\"\n        self.rsa_public_key = \"/tmp/jwtRS256.pem\"\n\n        if not os.path.exists(self.rsa_public_key):\n            subprocess.check_output([\"openssl\", \"genrsa\", \"-out\", self.rsa_private_key, \"4096\"])\n            subprocess.check_output(\n                [\"openssl\", \"rsa\", \"-pubout\", \"-in\", self.rsa_private_key, \"-out\", self.rsa_public_key]\n            )\n\n        org_settings[\"auth_jwt_login_enabled\"] = True\n        org_settings[\"auth_jwt_auth_public_certs_url\"] = \"file://{}\".format(self.rsa_public_key)\n        org_settings[\"auth_jwt_auth_issuer\"] = self.auth_issuer\n        org_settings[\"auth_jwt_auth_audience\"] = self.auth_audience\n        org_settings[\"auth_jwt_auth_header_name\"] = self.token_name\n\n    def tearDown(self):\n        org_settings[\"auth_jwt_login_enabled\"] = False\n        org_settings[\"auth_jwt_auth_public_certs_url\"] = \"\"\n        org_settings[\"auth_jwt_auth_issuer\"] = \"\"\n        org_settings[\"auth_jwt_auth_audience\"] = \"\"\n        org_settings[\"auth_jwt_auth_header_name\"] = \"\"\n\n    def test_jwt_no_token(self):\n        response = self.get_request(\"/data_sources\", org=self.factory.org)\n        self.assertEqual(response.status_code, 302)\n\n    def test_jwt_from_pem_file(self):\n        user = self.factory.create_user()\n\n        issued_at_timestamp = time.time()\n        expiration_timestamp = issued_at_timestamp + 60\n\n        data = {\n            \"aud\": self.auth_audience,\n            \"email\": user.email,\n            \"exp\": expiration_timestamp,\n            \"iat\": issued_at_timestamp,\n            \"iss\": self.auth_issuer,\n        }\n        with open(self.rsa_private_key) as keyfile:\n            sign_key = keyfile.read().strip()\n        token_data = jwt.encode(data, sign_key, algorithm=\"RS256\")\n\n        response = self.get_request(\"/data_sources\", org=self.factory.org, headers={self.token_name: token_data})\n        self.assertEqual(response.status_code, 200)\n\n    @patch.object(requests, \"get\")\n    def test_jwk_decode(self, mock_get):\n        with open(self.rsa_public_key, \"rb\") as keyfile:\n            public_key = jwcrypto.jwk.JWK.from_pem(keyfile.read())\n            jwk_keys = {\"keys\": [json.loads(public_key.export())]}\n\n        mockresponse = Mock()\n        mockresponse.json = lambda: jwk_keys\n        mock_get.return_value = mockresponse\n\n        keys = jwt_auth.get_public_keys(\"http://localhost/key.jwt\")\n        self.assertEqual(keys[0].key_size, 4096)\n"
  },
  {
    "path": "tests/test_cli.py",
    "content": "import textwrap\n\nimport mock\nfrom click.testing import CliRunner\n\nfrom redash.cli import manager\nfrom redash.models import DataSource, Group, Organization, User, db\nfrom redash.query_runner import query_runners\nfrom redash.utils.configuration import ConfigurationContainer\nfrom tests import BaseTestCase\n\n\nclass DataSourceCommandTests(BaseTestCase):\n    def test_interactive_new(self):\n        runner = CliRunner()\n        pg_i = list(query_runners.keys()).index(\"pg\") + 1\n        result = runner.invoke(\n            manager,\n            [\"ds\", \"new\"],\n            input=\"test\\n%s\\n\\n\\nexample.com\\n\\n\\ntestdb\\n\" % (pg_i,),\n        )\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        self.assertEqual(DataSource.query.count(), 1)\n        ds = DataSource.query.first()\n        self.assertEqual(ds.name, \"test\")\n        self.assertEqual(ds.type, \"pg\")\n        self.assertEqual(ds.options[\"dbname\"], \"testdb\")\n\n    def test_options_new(self):\n        runner = CliRunner()\n        result = runner.invoke(\n            manager,\n            [\n                \"ds\",\n                \"new\",\n                \"test\",\n                \"--options\",\n                '{\"host\": \"example.com\", \"dbname\": \"testdb\"}',\n                \"--type\",\n                \"pg\",\n            ],\n        )\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        self.assertEqual(DataSource.query.count(), 1)\n        ds = DataSource.query.first()\n        self.assertEqual(ds.name, \"test\")\n        self.assertEqual(ds.type, \"pg\")\n        self.assertEqual(ds.options[\"host\"], \"example.com\")\n        self.assertEqual(ds.options[\"dbname\"], \"testdb\")\n\n    def test_bad_type_new(self):\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"ds\", \"new\", \"test\", \"--type\", \"wrong\"])\n        self.assertTrue(result.exception)\n        self.assertEqual(result.exit_code, 1)\n        self.assertIn(\"not supported\", result.output)\n        self.assertEqual(DataSource.query.count(), 0)\n\n    def test_bad_options_new(self):\n        runner = CliRunner()\n        result = runner.invoke(\n            manager,\n            [\n                \"ds\",\n                \"new\",\n                \"test\",\n                \"--options\",\n                '{\"host\": 12345, \"dbname\": \"testdb\"}',\n                \"--type\",\n                \"pg\",\n            ],\n        )\n        self.assertTrue(result.exception)\n        self.assertEqual(result.exit_code, 1)\n        self.assertIn(\"invalid configuration\", result.output)\n        self.assertEqual(DataSource.query.count(), 0)\n\n    def test_list(self):\n        self.factory.create_data_source(\n            name=\"test1\",\n            type=\"pg\",\n            options=ConfigurationContainer({\"host\": \"example.com\", \"dbname\": \"testdb1\"}),\n        )\n        self.factory.create_data_source(\n            name=\"test2\",\n            type=\"sqlite\",\n            options=ConfigurationContainer({\"dbpath\": \"/tmp/test.db\"}),\n        )\n\n        self.factory.create_data_source(\n            name=\"Atest\",\n            type=\"sqlite\",\n            options=ConfigurationContainer({\"dbpath\": \"/tmp/test.db\"}),\n        )\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"ds\", \"list\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        expected_output = \"\"\"\n        Id: 3\n        Name: Atest\n        Type: sqlite\n        Options: {\"dbpath\": \"/tmp/test.db\"}\n        --------------------\n        Id: 1\n        Name: test1\n        Type: pg\n        Options: {\"dbname\": \"testdb1\", \"host\": \"example.com\"}\n        --------------------\n        Id: 2\n        Name: test2\n        Type: sqlite\n        Options: {\"dbpath\": \"/tmp/test.db\"}\n        \"\"\"\n        self.assertMultiLineEqual(result.output, textwrap.dedent(expected_output).lstrip())\n\n    def test_connection_test(self):\n        self.factory.create_data_source(\n            name=\"test1\",\n            type=\"sqlite\",\n            options=ConfigurationContainer({\"dbpath\": \"/tmp/test.db\"}),\n        )\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"ds\", \"test\", \"test1\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        self.assertIn(\"Success\", result.output)\n\n    def test_connection_bad_test(self):\n        self.factory.create_data_source(\n            name=\"test1\",\n            type=\"sqlite\",\n            options=ConfigurationContainer({\"dbpath\": \"/notexist.db\"}),\n        )\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"ds\", \"test\", \"test1\"])\n        self.assertTrue(result.exception)\n        self.assertEqual(result.exit_code, 1)\n        self.assertIn(\"Failure\", result.output)\n\n    def test_connection_delete(self):\n        self.factory.create_data_source(\n            name=\"test1\",\n            type=\"sqlite\",\n            options=ConfigurationContainer({\"dbpath\": \"/tmp/test.db\"}),\n        )\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"ds\", \"delete\", \"test1\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        self.assertIn(\"Deleting\", result.output)\n        self.assertEqual(DataSource.query.count(), 0)\n\n    def test_connection_bad_delete(self):\n        self.factory.create_data_source(\n            name=\"test1\",\n            type=\"sqlite\",\n            options=ConfigurationContainer({\"dbpath\": \"/tmp/test.db\"}),\n        )\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"ds\", \"delete\", \"wrong\"])\n        self.assertTrue(result.exception)\n        self.assertEqual(result.exit_code, 1)\n        self.assertIn(\"Couldn't find\", result.output)\n        self.assertEqual(DataSource.query.count(), 1)\n\n    def test_options_edit(self):\n        self.factory.create_data_source(\n            name=\"test1\",\n            type=\"sqlite\",\n            options=ConfigurationContainer({\"dbpath\": \"/tmp/test.db\"}),\n        )\n        runner = CliRunner()\n        result = runner.invoke(\n            manager,\n            [\n                \"ds\",\n                \"edit\",\n                \"test1\",\n                \"--options\",\n                '{\"host\": \"example.com\", \"dbname\": \"testdb\"}',\n                \"--name\",\n                \"test2\",\n                \"--type\",\n                \"pg\",\n            ],\n        )\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        self.assertEqual(DataSource.query.count(), 1)\n        ds = DataSource.query.first()\n        self.assertEqual(ds.name, \"test2\")\n        self.assertEqual(ds.type, \"pg\")\n        self.assertEqual(ds.options[\"host\"], \"example.com\")\n        self.assertEqual(ds.options[\"dbname\"], \"testdb\")\n\n    def test_bad_type_edit(self):\n        self.factory.create_data_source(\n            name=\"test1\",\n            type=\"sqlite\",\n            options=ConfigurationContainer({\"dbpath\": \"/tmp/test.db\"}),\n        )\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"ds\", \"edit\", \"test\", \"--type\", \"wrong\"])\n        self.assertTrue(result.exception)\n        self.assertEqual(result.exit_code, 1)\n        self.assertIn(\"not supported\", result.output)\n        ds = DataSource.query.first()\n        self.assertEqual(ds.type, \"sqlite\")\n\n    def test_bad_options_edit(self):\n        ds = self.factory.create_data_source(\n            name=\"test1\",\n            type=\"sqlite\",\n            options=ConfigurationContainer({\"dbpath\": \"/tmp/test.db\"}),\n        )\n        runner = CliRunner()\n        result = runner.invoke(\n            manager,\n            [\n                \"ds\",\n                \"new\",\n                \"test\",\n                \"--options\",\n                '{\"host\": 12345, \"dbname\": \"testdb\"}',\n                \"--type\",\n                \"pg\",\n            ],\n        )\n        self.assertTrue(result.exception)\n        self.assertEqual(result.exit_code, 1)\n        self.assertIn(\"invalid configuration\", result.output)\n        ds = DataSource.query.first()\n        self.assertEqual(ds.type, \"sqlite\")\n        self.assertEqual(ds.options._config, {\"dbpath\": \"/tmp/test.db\"})\n\n\nclass GroupCommandTests(BaseTestCase):\n    def test_create(self):\n        gcount = Group.query.count()\n        perms = [\"create_query\", \"edit_query\", \"view_query\"]\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"groups\", \"create\", \"test\", \"--permissions\", \",\".join(perms)])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        self.assertEqual(Group.query.count(), gcount + 1)\n        g = Group.query.order_by(Group.id.desc()).first()\n        db.session.add(self.factory.org)\n        self.assertEqual(g.org_id, self.factory.org.id)\n        self.assertEqual(g.permissions, perms)\n\n    def test_change_permissions(self):\n        g = self.factory.create_group(permissions=[\"list_dashboards\"])\n        db.session.flush()\n        g_id = g.id\n        perms = [\"create_query\", \"edit_query\", \"view_query\"]\n        runner = CliRunner()\n        result = runner.invoke(\n            manager,\n            [\n                \"groups\",\n                \"change_permissions\",\n                str(g_id),\n                \"--permissions\",\n                \",\".join(perms),\n            ],\n        )\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        g = Group.query.filter(Group.id == g_id).first()\n        self.assertEqual(g.permissions, perms)\n\n    def test_list(self):\n        self.factory.create_group(name=\"test\", permissions=[\"list_dashboards\"])\n        self.factory.create_group(name=\"agroup\", permissions=[\"list_dashboards\"])\n        self.factory.create_group(name=\"bgroup\", permissions=[\"list_dashboards\"])\n\n        self.factory.create_user(\n            name=\"Fred Foobar\",\n            email=\"foobar@example.com\",\n            org=self.factory.org,\n            group_ids=[self.factory.default_group.id],\n        )\n\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"groups\", \"list\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        output = \"\"\"\n        Id: 1\n        Name: admin\n        Type: builtin\n        Organization: default\n        Permissions: [admin,super_admin]\n        Users:\n        --------------------\n        Id: 4\n        Name: agroup\n        Type: regular\n        Organization: default\n        Permissions: [list_dashboards]\n        Users:\n        --------------------\n        Id: 5\n        Name: bgroup\n        Type: regular\n        Organization: default\n        Permissions: [list_dashboards]\n        Users:\n        --------------------\n        Id: 2\n        Name: default\n        Type: builtin\n        Organization: default\n        Permissions: [create_dashboard,create_query,edit_dashboard,edit_query,view_query,view_source,execute_query,list_users,schedule_query,list_dashboards,list_alerts,list_data_sources]\n        Users: Fred Foobar\n        --------------------\n        Id: 3\n        Name: test\n        Type: regular\n        Organization: default\n        Permissions: [list_dashboards]\n        Users:\n        \"\"\"\n        self.assertMultiLineEqual(result.output, textwrap.dedent(output).lstrip())\n\n\nclass OrganizationCommandTests(BaseTestCase):\n    def test_set_google_apps_domains(self):\n        domains = [\"example.org\", \"example.com\"]\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"org\", \"set_google_apps_domains\", \",\".join(domains)])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        db.session.add(self.factory.org)\n        self.assertEqual(self.factory.org.google_apps_domains, domains)\n\n    def test_show_google_apps_domains(self):\n        self.factory.org.settings[Organization.SETTING_GOOGLE_APPS_DOMAINS] = [\n            \"example.org\",\n            \"example.com\",\n        ]\n        db.session.add(self.factory.org)\n        db.session.commit()\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"org\", \"show_google_apps_domains\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        output = \"\"\"\n        Current list of Google Apps domains: example.org, example.com\n        \"\"\"\n        self.assertMultiLineEqual(result.output, textwrap.dedent(output).lstrip())\n\n    def test_create(self):\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"org\", \"create\", \"test\", \"--slug\", \"test\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n\n        ucount = Organization.query.count()\n\n        self.assertEqual(ucount, 2)\n\n    def test_list(self):\n        self.factory.create_org(name=\"test\", slug=\"test_org\")\n        self.factory.create_org(name=\"Borg\", slug=\"B_org\")\n        self.factory.create_org(name=\"Aorg\", slug=\"A_org\")\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"org\", \"list\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        output = \"\"\"\n        Id: 4\n        Name: Aorg\n        Slug: A_org\n        --------------------\n        Id: 3\n        Name: Borg\n        Slug: B_org\n        --------------------\n        Id: 1\n        Name: Default\n        Slug: default\n        --------------------\n        Id: 2\n        Name: test\n        Slug: test_org\n        \"\"\"\n        self.assertMultiLineEqual(result.output, textwrap.dedent(output).lstrip())\n\n\nclass UserCommandTests(BaseTestCase):\n    def test_create_basic(self):\n        runner = CliRunner()\n        result = runner.invoke(\n            manager,\n            [\"users\", \"create\", \"foobar@example.com\", \"Fred Foobar\"],\n            input=\"password1\\npassword1\\n\",\n        )\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        u = User.query.filter(User.email == \"foobar@example.com\").first()\n        self.assertEqual(u.name, \"Fred Foobar\")\n        self.assertTrue(u.verify_password(\"password1\"))\n        self.assertEqual(u.group_ids, [u.org.default_group.id])\n\n    def test_create_admin(self):\n        runner = CliRunner()\n        result = runner.invoke(\n            manager,\n            [\n                \"users\",\n                \"create\",\n                \"foobar@example.com\",\n                \"Fred Foobar\",\n                \"--password\",\n                \"password1\",\n                \"--admin\",\n            ],\n        )\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        u = User.query.filter(User.email == \"foobar@example.com\").first()\n        self.assertEqual(u.name, \"Fred Foobar\")\n        self.assertTrue(u.verify_password(\"password1\"))\n        self.assertEqual(u.group_ids, [u.org.default_group.id, u.org.admin_group.id])\n\n    def test_create_googleauth(self):\n        runner = CliRunner()\n        result = runner.invoke(\n            manager,\n            [\"users\", \"create\", \"foobar@example.com\", \"Fred Foobar\", \"--google\"],\n        )\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        u = User.query.filter(User.email == \"foobar@example.com\").first()\n        self.assertEqual(u.name, \"Fred Foobar\")\n        self.assertIsNone(u.password_hash)\n        self.assertEqual(u.group_ids, [u.org.default_group.id])\n\n    def test_create_bad(self):\n        self.factory.create_user(email=\"foobar@example.com\")\n        runner = CliRunner()\n        result = runner.invoke(\n            manager,\n            [\"users\", \"create\", \"foobar@example.com\", \"Fred Foobar\"],\n            input=\"password1\\npassword1\\n\",\n        )\n        self.assertTrue(result.exception)\n        self.assertEqual(result.exit_code, 1)\n        self.assertIn(\"Failed\", result.output)\n\n    def test_delete(self):\n        self.factory.create_user(email=\"foobar@example.com\")\n        ucount = User.query.count()\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"users\", \"delete\", \"foobar@example.com\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        self.assertEqual(User.query.filter(User.email == \"foobar@example.com\").count(), 0)\n        self.assertEqual(User.query.count(), ucount - 1)\n\n    def test_delete_bad(self):\n        ucount = User.query.count()\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"users\", \"delete\", \"foobar@example.com\"])\n        self.assertIn(\"Deleted 0 users\", result.output)\n        self.assertEqual(User.query.count(), ucount)\n\n    def test_password(self):\n        self.factory.create_user(email=\"foobar@example.com\")\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"users\", \"password\", \"foobar@example.com\", \"xyzzy\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        u = User.query.filter(User.email == \"foobar@example.com\").first()\n        self.assertTrue(u.verify_password(\"xyzzy\"))\n\n    def test_password_bad(self):\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"users\", \"password\", \"foobar@example.com\", \"xyzzy\"])\n        self.assertTrue(result.exception)\n        self.assertEqual(result.exit_code, 1)\n        self.assertIn(\"not found\", result.output)\n\n    def test_password_bad_org(self):\n        runner = CliRunner()\n        result = runner.invoke(\n            manager,\n            [\"users\", \"password\", \"foobar@example.com\", \"xyzzy\", \"--org\", \"default\"],\n        )\n        self.assertTrue(result.exception)\n        self.assertEqual(result.exit_code, 1)\n        self.assertIn(\"not found\", result.output)\n\n    def test_invite(self):\n        admin = self.factory.create_user(email=\"redash-admin@example.com\")\n        runner = CliRunner()\n        with mock.patch(\"redash.cli.users.invite_user\") as iu:\n            result = runner.invoke(\n                manager,\n                [\n                    \"users\",\n                    \"invite\",\n                    \"foobar@example.com\",\n                    \"Fred Foobar\",\n                    \"redash-admin@example.com\",\n                ],\n            )\n            self.assertFalse(result.exception)\n            self.assertEqual(result.exit_code, 0)\n            self.assertTrue(iu.called)\n            c = iu.call_args[0]\n            db.session.add_all(c)\n            self.assertEqual(c[0].id, self.factory.org.id)\n            self.assertEqual(c[1].id, admin.id)\n            self.assertEqual(c[2].email, \"foobar@example.com\")\n\n    def test_list(self):\n        self.factory.create_user(name=\"Fred Foobar\", email=\"foobar@example.com\", org=self.factory.org)\n\n        self.factory.create_user(name=\"William Foobar\", email=\"william@example.com\", org=self.factory.org)\n\n        self.factory.create_user(name=\"Andrew Foobar\", email=\"andrew@example.com\", org=self.factory.org)\n\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"users\", \"list\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        output = \"\"\"\n        Id: 3\n        Name: Andrew Foobar\n        Email: andrew@example.com\n        Organization: Default\n        Active: True\n        Groups: default\n        --------------------\n        Id: 1\n        Name: Fred Foobar\n        Email: foobar@example.com\n        Organization: Default\n        Active: True\n        Groups: default\n        --------------------\n        Id: 2\n        Name: William Foobar\n        Email: william@example.com\n        Organization: Default\n        Active: True\n        Groups: default\n        \"\"\"\n        self.assertMultiLineEqual(result.output, textwrap.dedent(output).lstrip())\n\n    def test_grant_admin(self):\n        u = self.factory.create_user(\n            name=\"Fred Foobar\",\n            email=\"foobar@example.com\",\n            org=self.factory.org,\n            group_ids=[self.factory.default_group.id],\n        )\n        runner = CliRunner()\n        result = runner.invoke(manager, [\"users\", \"grant_admin\", \"foobar@example.com\"])\n        self.assertFalse(result.exception)\n        self.assertEqual(result.exit_code, 0)\n        db.session.add(u)\n        self.assertEqual(u.group_ids, [u.org.default_group.id, u.org.admin_group.id])\n"
  },
  {
    "path": "tests/test_configuration.py",
    "content": "from unittest import TestCase\n\nfrom jsonschema import ValidationError\n\nfrom redash.utils.configuration import ConfigurationContainer\n\nconfiguration_schema = {\n    \"type\": \"object\",\n    \"properties\": {\n        \"a\": {\"type\": \"integer\"},\n        \"e\": {\"type\": \"integer\"},\n        \"b\": {\"type\": \"string\"},\n    },\n    \"required\": [\"a\"],\n    \"secret\": [\"b\"],\n}\n\n\nclass TestConfigurationToJson(TestCase):\n    def setUp(self):\n        self.config = {\"a\": 1, \"b\": \"test\"}\n        self.container = ConfigurationContainer(self.config, configuration_schema)\n\n    def test_returns_plain_dict(self):\n        self.assertDictEqual(self.config, self.container.to_dict())\n\n    def test_raises_exception_when_no_schema_set(self):\n        self.container.set_schema(None)\n        self.assertRaises(RuntimeError, lambda: self.container.to_dict(mask_secrets=True))\n\n    def test_returns_dict_with_masked_secrets(self):\n        d = self.container.to_dict(mask_secrets=True)\n\n        self.assertEqual(d[\"a\"], self.config[\"a\"])\n        self.assertNotEqual(d[\"b\"], self.config[\"b\"])\n\n        self.assertEqual(self.config[\"b\"], self.container[\"b\"])\n\n\nclass TestConfigurationUpdate(TestCase):\n    def setUp(self):\n        self.config = {\"a\": 1, \"b\": \"test\"}\n        self.container = ConfigurationContainer(self.config, configuration_schema)\n\n    def test_rejects_invalid_new_config(self):\n        self.assertRaises(ValidationError, lambda: self.container.update({\"c\": 3}))\n\n    def test_fails_if_no_schema_set(self):\n        self.container.set_schema(None)\n        self.assertRaises(RuntimeError, lambda: self.container.update({\"c\": 3}))\n\n    def test_ignores_secret_placehodler(self):\n        self.container.update(self.container.to_dict(mask_secrets=True))\n        self.assertEqual(self.container[\"b\"], self.config[\"b\"])\n\n    def test_updates_secret(self):\n        new_config = {\"a\": 2, \"b\": \"new\"}\n        self.container.update(new_config)\n        self.assertDictEqual(self.container._config, new_config)\n\n    def test_doesnt_leave_leftovers(self):\n        container = ConfigurationContainer({\"a\": 1, \"b\": \"test\", \"e\": 3}, configuration_schema)\n        new_config = container.to_dict(mask_secrets=True)\n        new_config.pop(\"e\")\n        container.update(new_config)\n\n        self.assertEqual(container[\"a\"], 1)\n        self.assertEqual(\"test\", container[\"b\"])\n        self.assertNotIn(\"e\", container)\n\n    def test_works_for_schema_without_secret(self):\n        secretless = configuration_schema.copy()\n        secretless.pop(\"secret\")\n        container = ConfigurationContainer({\"a\": 1, \"b\": \"test\", \"e\": 3}, secretless)\n        container.update({\"a\": 2})\n        self.assertEqual(container[\"a\"], 2)\n"
  },
  {
    "path": "tests/test_handlers.py",
    "content": "from flask_login import current_user\nfrom funcy import project\nfrom mock import patch\n\nfrom redash import models, settings\nfrom tests import BaseTestCase, authenticated_user\n\n\nclass AuthenticationTestMixin:\n    def test_returns_404_when_not_unauthenticated(self):\n        for path in self.paths:\n            rv = self.client.get(path)\n            self.assertEqual(404, rv.status_code)\n\n    def test_returns_content_when_authenticated(self):\n        for path in self.paths:\n            rv = self.make_request(\"get\", path, is_json=False)\n            self.assertEqual(200, rv.status_code)\n\n\nclass TestAuthentication(BaseTestCase):\n    def test_responds_with_success_for_signed_in_user(self):\n        with self.client as c:\n            with c.session_transaction() as sess:\n                sess[\"_user_id\"] = self.factory.user.get_id()\n            rv = self.client.get(\"/default/\")\n\n            self.assertEqual(200, rv.status_code)\n\n    def test_redirects_for_nonsigned_in_user(self):\n        rv = self.client.get(\"/default/\")\n        self.assertEqual(302, rv.status_code)\n\n    def test_redirects_for_invalid_session_identifier(self):\n        with self.client as c:\n            with c.session_transaction() as sess:\n                sess[\"_user_id\"] = 100\n            rv = self.client.get(\"/default/\")\n\n            self.assertEqual(302, rv.status_code)\n\n\nclass PingTest(BaseTestCase):\n    def test_ping(self):\n        rv = self.client.get(\"/ping\")\n        self.assertEqual(200, rv.status_code)\n        self.assertEqual(b\"PONG.\", rv.data)\n\n\nclass IndexTest(BaseTestCase):\n    def setUp(self):\n        self.paths = [\n            \"/default/\",\n            \"/default/dashboard/example\",\n            \"/default/queries/1\",\n            \"/default/admin/status\",\n        ]\n        super(IndexTest, self).setUp()\n\n    def test_redirect_to_login_when_not_authenticated(self):\n        for path in self.paths:\n            rv = self.client.get(path)\n            self.assertEqual(302, rv.status_code)\n\n    def test_returns_content_when_authenticated(self):\n        for path in self.paths:\n            rv = self.make_request(\"get\", path, org=False, is_json=False)\n            self.assertEqual(200, rv.status_code)\n\n\nclass StatusTest(BaseTestCase):\n    def test_returns_data_for_super_admin(self):\n        admin = self.factory.create_admin()\n        models.db.session.commit()\n        rv = self.make_request(\"get\", \"/status.json\", org=False, user=admin, is_json=False)\n        self.assertEqual(rv.status_code, 200)\n\n    def test_returns_403_for_non_admin(self):\n        rv = self.make_request(\"get\", \"/status.json\", org=False, is_json=False)\n        self.assertEqual(rv.status_code, 403)\n\n    def test_redirects_non_authenticated_user(self):\n        rv = self.client.get(\"/status.json\")\n        self.assertEqual(rv.status_code, 302)\n\n\nclass JobAPITest(BaseTestCase, AuthenticationTestMixin):\n    def setUp(self):\n        self.paths = []\n        super(JobAPITest, self).setUp()\n\n\nclass TestLogin(BaseTestCase):\n    def setUp(self):\n        super(TestLogin, self).setUp()\n        self.factory.org.set_setting(\"auth_password_login_enabled\", True)\n\n    def test_get_login_form(self):\n        rv = self.client.get(\"/default/login\")\n        self.assertEqual(rv.status_code, 200)\n\n    def test_get_login_form_remote_auth(self):\n        \"\"\"Make sure the remote auth link can be rendered correctly on the\n        login page when the remote user login feature is enabled\"\"\"\n        old_remote_user_enabled = settings.REMOTE_USER_LOGIN_ENABLED\n        old_ldap_login_enabled = settings.LDAP_LOGIN_ENABLED\n        try:\n            settings.REMOTE_USER_LOGIN_ENABLED = True\n            settings.LDAP_LOGIN_ENABLED = True\n            rv = self.client.get(\"/default/login\")\n            self.assertEqual(rv.status_code, 200)\n            self.assertIn(\"/{}/remote_user/login\".format(self.factory.org.slug), rv.data.decode())\n            self.assertIn(\"/{}/ldap/login\".format(self.factory.org.slug), rv.data.decode())\n        finally:\n            settings.REMOTE_USER_LOGIN_ENABLED = old_remote_user_enabled\n            settings.LDAP_LOGIN_ENABLED = old_ldap_login_enabled\n\n    def test_submit_non_existing_user(self):\n        with patch(\"redash.handlers.authentication.login_user\") as login_user_mock:\n            rv = self.client.post(\"/default/login\", data={\"email\": \"arik\", \"password\": \"password\"})\n            self.assertEqual(rv.status_code, 200)\n            self.assertFalse(login_user_mock.called)\n\n    def test_submit_correct_user_and_password(self):\n        user = self.factory.user\n        user.hash_password(\"password\")\n\n        self.db.session.add(user)\n        self.db.session.commit()\n\n        with patch(\"redash.handlers.authentication.login_user\") as login_user_mock:\n            rv = self.client.post(\"/default/login\", data={\"email\": user.email, \"password\": \"password\"})\n            self.assertEqual(rv.status_code, 302)\n            login_user_mock.assert_called_with(user, remember=False)\n\n    def test_submit_case_insensitive_user_and_password(self):\n        user = self.factory.user\n        user.hash_password(\"password\")\n\n        self.db.session.add(user)\n        self.db.session.commit()\n\n        with patch(\"redash.handlers.authentication.login_user\") as login_user_mock:\n            rv = self.client.post(\n                \"/default/login\",\n                data={\"email\": user.email.upper(), \"password\": \"password\"},\n            )\n            self.assertEqual(rv.status_code, 302)\n            login_user_mock.assert_called_with(user, remember=False)\n\n    def test_submit_correct_user_and_password_and_remember_me(self):\n        user = self.factory.user\n        user.hash_password(\"password\")\n\n        self.db.session.add(user)\n        self.db.session.commit()\n\n        with patch(\"redash.handlers.authentication.login_user\") as login_user_mock:\n            rv = self.client.post(\n                \"/default/login\",\n                data={\"email\": user.email, \"password\": \"password\", \"remember\": True},\n            )\n            self.assertEqual(rv.status_code, 302)\n            login_user_mock.assert_called_with(user, remember=True)\n\n    def test_submit_correct_user_and_password_with_next(self):\n        user = self.factory.user\n        user.hash_password(\"password\")\n\n        self.db.session.add(user)\n        self.db.session.commit()\n\n        with patch(\"redash.handlers.authentication.login_user\") as login_user_mock:\n            rv = self.client.post(\n                \"/default/login?next=/test\",\n                data={\"email\": user.email, \"password\": \"password\"},\n            )\n            self.assertEqual(rv.status_code, 302)\n            self.assertEqual(rv.location, \"/test\")\n            login_user_mock.assert_called_with(user, remember=False)\n\n    def test_submit_incorrect_user(self):\n        with patch(\"redash.handlers.authentication.login_user\") as login_user_mock:\n            rv = self.client.post(\"/default/login\", data={\"email\": \"non-existing\", \"password\": \"password\"})\n            self.assertEqual(rv.status_code, 200)\n            self.assertFalse(login_user_mock.called)\n\n    def test_submit_incorrect_password(self):\n        user = self.factory.user\n        user.hash_password(\"password\")\n\n        self.db.session.add(user)\n        self.db.session.commit()\n\n        with patch(\"redash.handlers.authentication.login_user\") as login_user_mock:\n            rv = self.client.post(\n                \"/default/login\",\n                data={\"email\": user.email, \"password\": \"badbadpassword\"},\n            )\n            self.assertEqual(rv.status_code, 200)\n            self.assertFalse(login_user_mock.called)\n\n    def test_submit_empty_password(self):\n        user = self.factory.user\n\n        with patch(\"redash.handlers.authentication.login_user\") as login_user_mock:\n            rv = self.client.post(\"/default/login\", data={\"email\": user.email, \"password\": \"\"})\n            self.assertEqual(rv.status_code, 200)\n            self.assertFalse(login_user_mock.called)\n\n    def test_user_already_loggedin(self):\n        with authenticated_user(self.client), patch(\"redash.handlers.authentication.login_user\") as login_user_mock:\n            rv = self.client.get(\"/default/login\")\n            self.assertEqual(rv.status_code, 302)\n            self.assertFalse(login_user_mock.called)\n\n    def test_correct_user_and_password_when_password_login_disabled(self):\n        user = self.factory.user\n        user.hash_password(\"password\")\n\n        self.db.session.add(user)\n        self.db.session.commit()\n\n        self.factory.org.set_setting(\"auth_password_login_enabled\", False)\n\n        with patch(\"redash.handlers.authentication.login_user\"):\n            rv = self.client.post(\"/default/login\", data={\"email\": user.email, \"password\": \"password\"})\n            self.assertEqual(rv.status_code, 200)\n            self.assertIn(\"Password login is not enabled for your organization\", str(rv.data))\n\n\nclass TestLogout(BaseTestCase):\n    def test_logout_when_not_loggedin(self):\n        with self.app.test_client() as c:\n            rv = c.get(\"/default/logout\")\n            self.assertEqual(rv.status_code, 302)\n            self.assertFalse(current_user.is_authenticated)\n\n    def test_logout_when_loggedin(self):\n        with self.app.test_client() as c, authenticated_user(c, user=self.factory.user):\n            rv = c.get(\"/default/\")\n            self.assertTrue(current_user.is_authenticated)\n            rv = c.get(\"/default/logout\")\n            self.assertEqual(rv.status_code, 302)\n            self.assertFalse(current_user.is_authenticated)\n\n\nclass TestQuerySnippet(BaseTestCase):\n    def test_create(self):\n        res = self.make_request(\n            \"post\",\n            \"/api/query_snippets\",\n            data={\"trigger\": \"x\", \"description\": \"y\", \"snippet\": \"z\"},\n            user=self.factory.user,\n        )\n        self.assertEqual(\n            project(res.json, [\"id\", \"trigger\", \"description\", \"snippet\"]),\n            {\"id\": 1, \"trigger\": \"x\", \"description\": \"y\", \"snippet\": \"z\"},\n        )\n        qs = models.QuerySnippet.query.one()\n        self.assertEqual(qs.trigger, \"x\")\n        self.assertEqual(qs.description, \"y\")\n        self.assertEqual(qs.snippet, \"z\")\n\n    def test_edit(self):\n        qs = models.QuerySnippet(\n            trigger=\"a\",\n            description=\"b\",\n            snippet=\"c\",\n            user=self.factory.user,\n            org=self.factory.org,\n        )\n        models.db.session.add(qs)\n        models.db.session.commit()\n        res = self.make_request(\n            \"post\",\n            \"/api/query_snippets/1\",\n            data={\"trigger\": \"x\", \"description\": \"y\", \"snippet\": \"z\"},\n            user=self.factory.user,\n        )\n        self.assertEqual(\n            project(res.json, [\"id\", \"trigger\", \"description\", \"snippet\"]),\n            {\"id\": 1, \"trigger\": \"x\", \"description\": \"y\", \"snippet\": \"z\"},\n        )\n        self.assertEqual(qs.trigger, \"x\")\n        self.assertEqual(qs.description, \"y\")\n        self.assertEqual(qs.snippet, \"z\")\n\n    def test_list(self):\n        qs = models.QuerySnippet(\n            trigger=\"x\",\n            description=\"y\",\n            snippet=\"z\",\n            user=self.factory.user,\n            org=self.factory.org,\n        )\n        models.db.session.add(qs)\n        models.db.session.commit()\n        res = self.make_request(\"get\", \"/api/query_snippets\", user=self.factory.user)\n        self.assertEqual(res.status_code, 200)\n        data = res.json\n        self.assertEqual(len(data), 1)\n        self.assertEqual(\n            project(data[0], [\"id\", \"trigger\", \"description\", \"snippet\"]),\n            {\"id\": 1, \"trigger\": \"x\", \"description\": \"y\", \"snippet\": \"z\"},\n        )\n        self.assertEqual(qs.trigger, \"x\")\n        self.assertEqual(qs.description, \"y\")\n        self.assertEqual(qs.snippet, \"z\")\n\n    def test_delete(self):\n        qs = models.QuerySnippet(\n            trigger=\"a\",\n            description=\"b\",\n            snippet=\"c\",\n            user=self.factory.user,\n            org=self.factory.org,\n        )\n        models.db.session.add(qs)\n        models.db.session.commit()\n        self.make_request(\"delete\", \"/api/query_snippets/1\", user=self.factory.user)\n        self.assertEqual(models.QuerySnippet.query.count(), 0)\n"
  },
  {
    "path": "tests/test_migrations.py",
    "content": "import os\n\nfrom alembic.config import Config\nfrom alembic.script import ScriptDirectory\n\n\ndef test_only_single_head_revision_in_migrations():\n    \"\"\"\n    If multiple developers are working on migrations and one of them is merged before the\n    other you might end up with multiple heads (multiple revisions with the same down_revision).\n\n    This makes sure that there is only a single head revision in the migrations directory.\n\n    Adopted from https://blog.jerrycodes.com/multiple-heads-in-alembic-migrations/.\n    \"\"\"\n    config = Config(os.path.join(\"migrations\", \"alembic.ini\"))\n    config.set_main_option(\"script_location\", \"migrations\")\n    script = ScriptDirectory.from_config(config)\n\n    # This will raise if there are multiple heads\n    script.get_current_head()\n"
  },
  {
    "path": "tests/test_models.py",
    "content": "import calendar\nimport datetime\nfrom unittest import TestCase\n\nfrom dateutil.parser import parse as date_parse\n\nfrom redash import models\nfrom redash.models import db\nfrom redash.utils import gen_query_hash, utcnow\nfrom tests import BaseTestCase\n\n\nclass DashboardTest(BaseTestCase):\n    def test_appends_suffix_to_slug_when_duplicate(self):\n        d1 = self.factory.create_dashboard()\n        db.session.flush()\n        self.assertEqual(d1.slug, \"test\")\n\n        d2 = self.factory.create_dashboard(user=d1.user)\n        db.session.flush()\n        self.assertNotEqual(d1.slug, d2.slug)\n\n        d3 = self.factory.create_dashboard(user=d1.user)\n        db.session.flush()\n        self.assertNotEqual(d1.slug, d3.slug)\n        self.assertNotEqual(d2.slug, d3.slug)\n\n\nclass ShouldScheduleNextTest(TestCase):\n    def test_interval_schedule_that_needs_reschedule(self):\n        now = utcnow()\n        two_hours_ago = now - datetime.timedelta(hours=2)\n        self.assertTrue(models.should_schedule_next(two_hours_ago, now, \"3600\"))\n\n    def test_interval_schedule_that_doesnt_need_reschedule(self):\n        now = utcnow()\n        half_an_hour_ago = now - datetime.timedelta(minutes=30)\n        self.assertFalse(models.should_schedule_next(half_an_hour_ago, now, \"3600\"))\n\n    def test_exact_time_that_needs_reschedule(self):\n        now = utcnow()\n        yesterday = now - datetime.timedelta(days=1)\n        scheduled_datetime = now - datetime.timedelta(hours=3)\n        scheduled_time = \"{:02d}:00\".format(scheduled_datetime.hour)\n        self.assertTrue(models.should_schedule_next(yesterday, now, \"86400\", scheduled_time))\n\n    def test_exact_time_that_doesnt_need_reschedule(self):\n        now = date_parse(\"2015-10-16 20:10\")\n        yesterday = date_parse(\"2015-10-15 23:07\")\n        schedule = \"23:00\"\n        self.assertFalse(models.should_schedule_next(yesterday, now, \"86400\", schedule))\n\n    def test_exact_time_with_day_change(self):\n        now = utcnow().replace(hour=0, minute=1)\n        previous = (now - datetime.timedelta(days=2)).replace(hour=23, minute=59)\n        schedule = \"23:59\"\n        self.assertTrue(models.should_schedule_next(previous, now, \"86400\", schedule))\n\n    def test_exact_time_every_x_days_that_needs_reschedule(self):\n        now = utcnow()\n        four_days_ago = now - datetime.timedelta(days=4)\n        three_day_interval = \"259200\"\n        scheduled_datetime = now - datetime.timedelta(hours=3)\n        scheduled_time = \"{:02d}:00\".format(scheduled_datetime.hour)\n        self.assertTrue(models.should_schedule_next(four_days_ago, now, three_day_interval, scheduled_time))\n\n    def test_exact_time_every_x_days_that_doesnt_need_reschedule(self):\n        now = utcnow()\n        four_days_ago = now - datetime.timedelta(days=2)\n        three_day_interval = \"259200\"\n        scheduled_datetime = now - datetime.timedelta(hours=3)\n        scheduled_time = \"{:02d}:00\".format(scheduled_datetime.hour)\n        self.assertFalse(models.should_schedule_next(four_days_ago, now, three_day_interval, scheduled_time))\n\n    def test_exact_time_every_x_days_with_day_change(self):\n        now = utcnow().replace(hour=23, minute=59)\n        previous = (now - datetime.timedelta(days=2)).replace(hour=0, minute=1)\n        schedule = \"23:58\"\n        three_day_interval = \"259200\"\n        self.assertTrue(models.should_schedule_next(previous, now, three_day_interval, schedule))\n\n    def test_exact_time_every_x_weeks_that_needs_reschedule(self):\n        # Setup:\n        #\n        # 1) The query should run every 3 weeks on Tuesday\n        # 2) The last time it ran was 3 weeks ago from this week's Thursday\n        # 3) It is now Wednesday of this week\n        #\n        # Expectation: Even though less than 3 weeks have passed since the\n        #              last run 3 weeks ago on Thursday, it's overdue since\n        #              it should be running on Tuesdays.\n        this_thursday = utcnow() + datetime.timedelta(\n            days=list(calendar.day_name).index(\"Thursday\") - utcnow().weekday()\n        )\n        three_weeks_ago = this_thursday - datetime.timedelta(weeks=3)\n        now = this_thursday - datetime.timedelta(days=1)\n        three_week_interval = \"1814400\"\n        scheduled_datetime = now - datetime.timedelta(hours=3)\n        scheduled_time = \"{:02d}:00\".format(scheduled_datetime.hour)\n        self.assertTrue(\n            models.should_schedule_next(three_weeks_ago, now, three_week_interval, scheduled_time, \"Tuesday\")\n        )\n\n    def test_exact_time_every_x_weeks_that_doesnt_need_reschedule(self):\n        # Setup:\n        #\n        # 1) The query should run every 3 weeks on Thurday\n        # 2) The last time it ran was 3 weeks ago from this week's Tuesday\n        # 3) It is now Wednesday of this week\n        #\n        # Expectation: Even though more than 3 weeks have passed since the\n        #              last run 3 weeks ago on Tuesday, it's not overdue since\n        #              it should be running on Thursdays.\n        this_tuesday = utcnow() + datetime.timedelta(\n            days=list(calendar.day_name).index(\"Tuesday\") - utcnow().weekday()\n        )\n        three_weeks_ago = this_tuesday - datetime.timedelta(weeks=3)\n        now = this_tuesday + datetime.timedelta(days=1)\n        three_week_interval = \"1814400\"\n        scheduled_datetime = now - datetime.timedelta(hours=3)\n        scheduled_time = \"{:02d}:00\".format(scheduled_datetime.hour)\n        self.assertFalse(\n            models.should_schedule_next(three_weeks_ago, now, three_week_interval, scheduled_time, \"Thursday\")\n        )\n\n    def test_backoff(self):\n        now = utcnow()\n        two_hours_ago = now - datetime.timedelta(hours=2)\n        self.assertTrue(models.should_schedule_next(two_hours_ago, now, \"3600\", failures=5))\n        self.assertFalse(models.should_schedule_next(two_hours_ago, now, \"3600\", failures=10))\n\n    def test_next_iteration_overflow(self):\n        now = utcnow()\n        two_hours_ago = now - datetime.timedelta(hours=2)\n        self.assertFalse(models.should_schedule_next(two_hours_ago, now, \"3600\", failures=32))\n\n\nclass QueryOutdatedQueriesTest(BaseTestCase):\n    def schedule(self, **kwargs):\n        schedule = {\"interval\": None, \"time\": None, \"until\": None, \"day_of_week\": None}\n        schedule.update(**kwargs)\n        return schedule\n\n    def create_scheduled_query(self, **kwargs):\n        return self.factory.create_query(schedule=self.schedule(**kwargs))\n\n    def fake_previous_execution(self, query, **kwargs):\n        retrieved_at = utcnow() - datetime.timedelta(**kwargs)\n        query_result = self.factory.create_query_result(\n            retrieved_at=retrieved_at,\n            query_text=query.query_text,\n            query_hash=query.query_hash,\n        )\n        query.latest_query_data = query_result\n\n    # TODO: this test can be refactored to use mock version of should_schedule_next to simplify it.\n    def test_outdated_queries_skips_unscheduled_queries(self):\n        query = self.create_scheduled_query()\n        query_with_none = self.factory.create_query(schedule=None)\n\n        queries = models.Query.outdated_queries()\n\n        self.assertNotIn(query, queries)\n        self.assertNotIn(query_with_none, queries)\n\n    def test_outdated_queries_works_with_ttl_based_schedule(self):\n        query = self.create_scheduled_query(interval=\"3600\")\n        self.fake_previous_execution(query, hours=2)\n\n        queries = models.Query.outdated_queries()\n\n        self.assertIn(query, queries)\n\n    def test_outdated_queries_works_scheduled_queries_tracker(self):\n        query = self.create_scheduled_query(interval=\"3600\")\n        self.fake_previous_execution(query, hours=2)\n        models.scheduled_queries_executions.update(query.id)\n\n        queries = models.Query.outdated_queries()\n\n        self.assertNotIn(query, queries)\n\n    def test_skips_fresh_queries(self):\n        query = self.create_scheduled_query(interval=\"3600\")\n        self.fake_previous_execution(query, minutes=30)\n\n        queries = models.Query.outdated_queries()\n\n        self.assertNotIn(query, queries)\n\n    def test_outdated_queries_works_with_specific_time_schedule(self):\n        half_an_hour_ago = utcnow() - datetime.timedelta(minutes=30)\n        query = self.create_scheduled_query(interval=\"86400\", time=half_an_hour_ago.strftime(\"%H:%M\"))\n        query_result = self.factory.create_query_result(\n            query=query.query_text,\n            retrieved_at=half_an_hour_ago - datetime.timedelta(days=1),\n        )\n        query.latest_query_data = query_result\n\n        queries = models.Query.outdated_queries()\n        self.assertIn(query, queries)\n\n    def test_enqueues_query_only_once(self):\n        \"\"\"\n        Only one query per data source with the same text will be reported by\n        Query.outdated_queries().\n        \"\"\"\n        query = self.create_scheduled_query(interval=\"60\")\n        query2 = self.factory.create_query(\n            schedule=self.schedule(interval=\"60\"),\n            query_text=query.query_text,\n            query_hash=query.query_hash,\n        )\n        self.fake_previous_execution(query, minutes=10)\n        self.fake_previous_execution(query2, minutes=10)\n\n        self.assertEqual(list(models.Query.outdated_queries()), [query2])\n\n    def test_enqueues_scheduled_query_without_latest_query_data(self):\n        \"\"\"\n        Queries with a schedule but no latest_query_data will still be reported by Query.outdated_queries()\n        \"\"\"\n        query = self.factory.create_query(\n            schedule=self.schedule(interval=\"60\"),\n            data_source=self.factory.create_data_source(),\n        )\n\n        outdated_queries = models.Query.outdated_queries()\n        self.assertEqual(query.latest_query_data, None)\n        self.assertEqual(len(outdated_queries), 1)\n        self.assertIn(query, outdated_queries)\n\n    def test_enqueues_query_with_correct_data_source(self):\n        \"\"\"\n        Queries from different data sources will be reported by\n        Query.outdated_queries() even if they have the same query text.\n        \"\"\"\n        query = self.factory.create_query(\n            schedule=self.schedule(interval=\"60\"),\n            data_source=self.factory.create_data_source(),\n        )\n        query2 = self.factory.create_query(\n            schedule=self.schedule(interval=\"60\"),\n            query_text=query.query_text,\n            query_hash=query.query_hash,\n        )\n        self.fake_previous_execution(query, minutes=10)\n        self.fake_previous_execution(query2, minutes=10)\n\n        outdated_queries = models.Query.outdated_queries()\n        self.assertEqual(len(outdated_queries), 2)\n        self.assertIn(query, outdated_queries)\n        self.assertIn(query2, outdated_queries)\n\n    def test_enqueues_only_for_relevant_data_source(self):\n        \"\"\"\n        If multiple queries with the same text exist, only ones that are\n        scheduled to be refreshed are reported by Query.outdated_queries().\n        \"\"\"\n        query = self.create_scheduled_query(interval=\"60\")\n        query2 = self.factory.create_query(\n            schedule=self.schedule(interval=\"3600\"),\n            query_text=query.query_text,\n            query_hash=query.query_hash,\n        )\n        self.fake_previous_execution(query, minutes=10)\n        self.fake_previous_execution(query2, minutes=10)\n\n        self.assertEqual(list(models.Query.outdated_queries()), [query])\n\n    def test_failure_extends_schedule(self):\n        \"\"\"\n        Execution failures recorded for a query result in exponential backoff\n        for scheduling future execution.\n        \"\"\"\n        query = self.factory.create_query(\n            schedule=self.schedule(interval=\"60\"),\n            schedule_failures=4,\n        )\n        self.fake_previous_execution(query, minutes=16)\n\n        self.assertEqual(list(models.Query.outdated_queries()), [])\n\n        self.fake_previous_execution(query, minutes=17)\n        self.assertEqual(list(models.Query.outdated_queries()), [query])\n\n    def test_schedule_until_after(self):\n        \"\"\"\n        Queries with non-null ``schedule['until']`` are not reported by\n        Query.outdated_queries() after the given time is past.\n        \"\"\"\n        one_day_ago = (utcnow() - datetime.timedelta(days=1)).strftime(\"%Y-%m-%d\")\n        query = self.create_scheduled_query(interval=\"3600\", until=one_day_ago)\n        self.fake_previous_execution(query, hours=2)\n\n        queries = models.Query.outdated_queries()\n\n        self.assertNotIn(query, queries)\n\n    def test_schedule_until_before(self):\n        \"\"\"\n        Queries with non-null ``schedule['until']`` are reported by\n        Query.outdated_queries() before the given time is past.\n        \"\"\"\n        one_day_from_now = (utcnow() + datetime.timedelta(days=1)).strftime(\"%Y-%m-%d\")\n        query = self.create_scheduled_query(interval=\"3600\", until=one_day_from_now)\n        self.fake_previous_execution(query, hours=2)\n\n        queries = models.Query.outdated_queries()\n\n        self.assertIn(query, queries)\n\n    def test_skips_and_disables_faulty_queries(self):\n        faulty_query = self.create_scheduled_query(until=\"pigs fly\")\n        valid_query = self.create_scheduled_query(interval=\"60\")\n        self.fake_previous_execution(valid_query, minutes=10)\n\n        models.Query.outdated_queries()\n\n        self.assertEqual(list(models.Query.outdated_queries()), [valid_query])\n        self.assertTrue(faulty_query.schedule.get(\"disabled\"))\n\n    def test_skips_disabled_schedules(self):\n        query = self.create_scheduled_query(disabled=True)\n        queries = models.Query.outdated_queries()\n        self.assertNotIn(query, queries)\n\n\nclass QueryArchiveTest(BaseTestCase):\n    def test_archive_query_sets_flag(self):\n        query = self.factory.create_query()\n        db.session.flush()\n        query.archive()\n\n        self.assertEqual(query.is_archived, True)\n\n    def test_archived_query_doesnt_return_in_all(self):\n        query = self.factory.create_query(schedule={\"interval\": \"1\", \"until\": None, \"time\": None, \"day_of_week\": None})\n        yesterday = utcnow() - datetime.timedelta(days=1)\n        query_result = models.QueryResult.store_result(\n            query.org_id,\n            query.data_source,\n            query.query_hash,\n            query.query_text,\n            {\"columns\": {}, \"rows\": []},\n            123,\n            yesterday,\n        )\n\n        query.latest_query_data = query_result\n        groups = list(models.Group.query.filter(models.Group.id.in_(query.groups)))\n        self.assertIn(query, list(models.Query.all_queries([g.id for g in groups])))\n        self.assertIn(query, models.Query.outdated_queries())\n        db.session.flush()\n        query.archive()\n\n        self.assertNotIn(query, list(models.Query.all_queries([g.id for g in groups])))\n        self.assertNotIn(query, models.Query.outdated_queries())\n\n    def test_removes_associated_widgets_from_dashboards(self):\n        widget = self.factory.create_widget()\n        query = widget.visualization.query_rel\n        db.session.commit()\n        query.archive()\n        db.session.flush()\n        self.assertEqual(models.Widget.query.get(widget.id), None)\n\n    def test_removes_scheduling(self):\n        query = self.factory.create_query(schedule={\"interval\": \"1\", \"until\": None, \"time\": None, \"day_of_week\": None})\n\n        query.archive()\n\n        self.assertIsNone(query.schedule)\n\n    def test_deletes_alerts(self):\n        subscription = self.factory.create_alert_subscription()\n        query = subscription.alert.query_rel\n        db.session.commit()\n        query.archive()\n        db.session.flush()\n        self.assertEqual(models.Alert.query.get(subscription.alert.id), None)\n        self.assertEqual(models.AlertSubscription.query.get(subscription.id), None)\n\n\nclass TestUnusedQueryResults(BaseTestCase):\n    def test_returns_only_unused_query_results(self):\n        two_weeks_ago = utcnow() - datetime.timedelta(days=14)\n        qr = self.factory.create_query_result()\n        self.factory.create_query(latest_query_data=qr)\n        db.session.flush()\n        unused_qr = self.factory.create_query_result(retrieved_at=two_weeks_ago)\n        self.assertIn(unused_qr, list(models.QueryResult.unused()))\n        self.assertNotIn(qr, list(models.QueryResult.unused()))\n\n    def test_returns_only_over_a_week_old_results(self):\n        two_weeks_ago = utcnow() - datetime.timedelta(days=14)\n        unused_qr = self.factory.create_query_result(retrieved_at=two_weeks_ago)\n        db.session.flush()\n        new_unused_qr = self.factory.create_query_result()\n\n        self.assertIn(unused_qr, list(models.QueryResult.unused()))\n        self.assertNotIn(new_unused_qr, list(models.QueryResult.unused()))\n\n\nclass TestQueryAll(BaseTestCase):\n    def test_returns_only_queries_in_given_groups(self):\n        ds1 = self.factory.create_data_source()\n        ds2 = self.factory.create_data_source()\n\n        group1 = models.Group(name=\"g1\", org=ds1.org, permissions=[\"create\", \"view\"])\n        group2 = models.Group(name=\"g2\", org=ds1.org, permissions=[\"create\", \"view\"])\n\n        q1 = self.factory.create_query(data_source=ds1)\n        q2 = self.factory.create_query(data_source=ds2)\n\n        db.session.add_all(\n            [\n                ds1,\n                ds2,\n                group1,\n                group2,\n                q1,\n                q2,\n                models.DataSourceGroup(group=group1, data_source=ds1),\n                models.DataSourceGroup(group=group2, data_source=ds2),\n            ]\n        )\n        db.session.flush()\n        self.assertIn(q1, list(models.Query.all_queries([group1.id])))\n        self.assertNotIn(q2, list(models.Query.all_queries([group1.id])))\n        self.assertIn(q1, list(models.Query.all_queries([group1.id, group2.id])))\n        self.assertIn(q2, list(models.Query.all_queries([group1.id, group2.id])))\n\n    def test_skips_drafts(self):\n        q = self.factory.create_query(is_draft=True)\n        self.assertNotIn(q, models.Query.all_queries([self.factory.default_group.id]))\n\n    def test_includes_drafts_of_given_user(self):\n        q = self.factory.create_query(is_draft=True)\n        self.assertIn(\n            q,\n            models.Query.all_queries([self.factory.default_group.id], user_id=q.user_id),\n        )\n\n    def test_order_by_relationship(self):\n        u1 = self.factory.create_user(name=\"alice\")\n        u2 = self.factory.create_user(name=\"bob\")\n        self.factory.create_query(user=u1)\n        self.factory.create_query(user=u2)\n        db.session.commit()\n        # have to reset the order here with None since all_queries orders by\n        # created_at by default\n        base = models.Query.all_queries([self.factory.default_group.id]).order_by(None)\n        qs1 = base.order_by(models.User.name)\n        self.assertEqual([\"alice\", \"bob\"], [q.user.name for q in qs1])\n        qs2 = base.order_by(models.User.name.desc())\n        self.assertEqual([\"bob\", \"alice\"], [q.user.name for q in qs2])\n\n    def test_update_query_hash_basesql_with_options(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, type=\"pg\")\n        query = self.factory.create_query(query_text=\"SELECT 2\", data_source=ds)\n        query.options = {\"apply_auto_limit\": True}\n        origin_hash = query.query_hash\n        query.update_query_hash()\n        self.assertNotEqual(origin_hash, query.query_hash)\n\n    def test_update_query_hash_basesql_no_options(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, type=\"pg\")\n        query = self.factory.create_query(query_text=\"SELECT 2\", data_source=ds)\n        query.options = {}\n        origin_hash = query.query_hash\n        query.update_query_hash()\n        self.assertEqual(origin_hash, query.query_hash)\n\n    def test_update_query_hash_non_basesql(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, type=\"prometheus\")\n        query = self.factory.create_query(query_text=\"SELECT 2\", data_source=ds)\n        query.options = {\"apply_auto_limit\": True}\n        origin_hash = query.query_hash\n        query.update_query_hash()\n        self.assertEqual(origin_hash, query.query_hash)\n\n    def test_update_query_hash_basesql_with_parameters(self):\n        ds = self.factory.create_data_source(group=self.factory.org.default_group, type=\"pg\")\n        query = self.factory.create_query(query_text=\"SELECT {{num}}\", data_source=ds)\n        query.options = {\"parameters\": [{\"type\": \"number\", \"name\": \"num\", \"value\": 5}]}\n        origin_hash = query.query_hash\n        query.update_query_hash()\n        self.assertNotEqual(origin_hash, query.query_hash)\n\n\nclass TestGroup(BaseTestCase):\n    def test_returns_groups_with_specified_names(self):\n        org1 = self.factory.create_org()\n        org2 = self.factory.create_org()\n\n        matching_group1 = models.Group(id=999, name=\"g1\", org=org1)\n        matching_group2 = models.Group(id=888, name=\"g2\", org=org1)\n        non_matching_group = models.Group(id=777, name=\"g1\", org=org2)\n\n        groups = models.Group.find_by_name(org1, [\"g1\", \"g2\"])\n        self.assertIn(matching_group1, groups)\n        self.assertIn(matching_group2, groups)\n        self.assertNotIn(non_matching_group, groups)\n\n    def test_returns_no_groups(self):\n        org1 = self.factory.create_org()\n\n        models.Group(id=999, name=\"g1\", org=org1)\n        self.assertEqual([], models.Group.find_by_name(org1, [\"non-existing\"]))\n\n\nclass TestQueryResultStoreResult(BaseTestCase):\n    def setUp(self):\n        super(TestQueryResultStoreResult, self).setUp()\n        self.data_source = self.factory.data_source\n        self.query = \"SELECT 1\"\n        self.query_hash = gen_query_hash(self.query)\n        self.runtime = 123\n        self.utcnow = utcnow()\n        self.data = {\"a\": 1}\n\n    def test_stores_the_result(self):\n        query_result = models.QueryResult.store_result(\n            self.data_source.org_id,\n            self.data_source,\n            self.query_hash,\n            self.query,\n            self.data,\n            self.runtime,\n            self.utcnow,\n        )\n\n        self.assertEqual(query_result.data, self.data)\n        self.assertEqual(query_result.runtime, self.runtime)\n        self.assertEqual(query_result.retrieved_at, self.utcnow)\n        self.assertEqual(query_result.query_text, self.query)\n        self.assertEqual(query_result.query_hash, self.query_hash)\n        self.assertEqual(query_result.data_source, self.data_source)\n\n\nclass TestEvents(BaseTestCase):\n    def raw_event(self):\n        timestamp = 1411778709.791\n        user = self.factory.user\n        created_at = datetime.datetime.utcfromtimestamp(timestamp)\n        db.session.flush()\n        raw_event = {\n            \"action\": \"view\",\n            \"timestamp\": timestamp,\n            \"object_type\": \"dashboard\",\n            \"user_id\": user.id,\n            \"object_id\": 1,\n            \"org_id\": 1,\n        }\n\n        return raw_event, user, created_at\n\n    def test_records_event(self):\n        raw_event, user, created_at = self.raw_event()\n\n        event = models.Event.record(raw_event)\n        db.session.flush()\n        self.assertEqual(event.user, user)\n        self.assertEqual(event.action, \"view\")\n        self.assertEqual(event.object_type, \"dashboard\")\n        self.assertEqual(event.object_id, 1)\n        self.assertEqual(event.created_at, created_at)\n\n    def test_records_additional_properties(self):\n        raw_event, _, _ = self.raw_event()\n        additional_properties = {\"test\": 1, \"test2\": 2, \"whatever\": \"abc\"}\n        raw_event.update(additional_properties)\n\n        event = models.Event.record(raw_event)\n\n        self.assertDictEqual(event.additional_properties, additional_properties)\n\n\ndef _set_up_dashboard_test(d):\n    d.g1 = d.factory.create_group(name=\"First\", permissions=[\"create\", \"view\"])\n    d.g2 = d.factory.create_group(name=\"Second\", permissions=[\"create\", \"view\"])\n    d.ds1 = d.factory.create_data_source()\n    d.ds2 = d.factory.create_data_source()\n    db.session.flush()\n    d.u1 = d.factory.create_user(group_ids=[d.g1.id])\n    d.u2 = d.factory.create_user(group_ids=[d.g2.id])\n    db.session.add_all(\n        [\n            models.DataSourceGroup(group=d.g1, data_source=d.ds1),\n            models.DataSourceGroup(group=d.g2, data_source=d.ds2),\n        ]\n    )\n    d.q1 = d.factory.create_query(data_source=d.ds1)\n    d.q2 = d.factory.create_query(data_source=d.ds2)\n    d.v1 = d.factory.create_visualization(query_rel=d.q1)\n    d.v2 = d.factory.create_visualization(query_rel=d.q2)\n    d.w1 = d.factory.create_widget(visualization=d.v1)\n    d.w2 = d.factory.create_widget(visualization=d.v2)\n    d.w3 = d.factory.create_widget(visualization=d.v2, dashboard=d.w2.dashboard)\n    d.w4 = d.factory.create_widget(visualization=d.v2)\n    d.w5 = d.factory.create_widget(visualization=d.v1, dashboard=d.w4.dashboard)\n    d.w1.dashboard.is_draft = False\n    d.w2.dashboard.is_draft = False\n    d.w4.dashboard.is_draft = False\n\n\nclass TestDashboardAll(BaseTestCase):\n    def setUp(self):\n        super(TestDashboardAll, self).setUp()\n        _set_up_dashboard_test(self)\n\n    def test_requires_group_or_user_id(self):\n        d1 = self.factory.create_dashboard()\n        self.assertNotIn(d1, list(models.Dashboard.all(d1.user.org, d1.user.group_ids, None)))\n        l2 = list(models.Dashboard.all(d1.user.org, [0], d1.user.id))\n        self.assertIn(d1, l2)\n\n    def test_returns_dashboards_based_on_groups(self):\n        self.assertIn(\n            self.w1.dashboard,\n            list(models.Dashboard.all(self.u1.org, self.u1.group_ids, None)),\n        )\n        self.assertIn(\n            self.w2.dashboard,\n            list(models.Dashboard.all(self.u2.org, self.u2.group_ids, None)),\n        )\n        self.assertNotIn(\n            self.w1.dashboard,\n            list(models.Dashboard.all(self.u2.org, self.u2.group_ids, None)),\n        )\n        self.assertNotIn(\n            self.w2.dashboard,\n            list(models.Dashboard.all(self.u1.org, self.u1.group_ids, None)),\n        )\n\n    def test_returns_each_dashboard_once(self):\n        dashboards = list(models.Dashboard.all(self.u2.org, self.u2.group_ids, None))\n        self.assertEqual(len(dashboards), 2)\n\n    def test_returns_dashboard_you_have_partial_access_to(self):\n        self.assertIn(\n            self.w5.dashboard,\n            models.Dashboard.all(self.u1.org, self.u1.group_ids, None),\n        )\n\n    def test_returns_dashboards_created_by_user(self):\n        d1 = self.factory.create_dashboard(user=self.u1)\n        db.session.flush()\n        self.assertIn(d1, list(models.Dashboard.all(self.u1.org, self.u1.group_ids, self.u1.id)))\n        self.assertIn(d1, list(models.Dashboard.all(self.u1.org, [0], self.u1.id)))\n        self.assertNotIn(d1, list(models.Dashboard.all(self.u2.org, self.u2.group_ids, self.u2.id)))\n\n    def test_returns_dashboards_with_text_widgets_to_creator(self):\n        w1 = self.factory.create_widget(visualization=None)\n\n        self.assertEqual(w1.dashboard.user, self.factory.user)\n        self.assertIn(\n            w1.dashboard,\n            list(\n                models.Dashboard.all(\n                    self.factory.user.org,\n                    self.factory.user.group_ids,\n                    self.factory.user.id,\n                )\n            ),\n        )\n        self.assertNotIn(\n            w1.dashboard,\n            list(models.Dashboard.all(self.u1.org, self.u1.group_ids, self.u1.id)),\n        )\n\n    def test_returns_dashboards_from_current_org_only(self):\n        w1 = self.factory.create_widget()\n\n        user = self.factory.create_user(org=self.factory.create_org())\n\n        self.assertIn(\n            w1.dashboard,\n            list(models.Dashboard.all(self.factory.user.org, self.factory.user.group_ids, None)),\n        )\n        self.assertNotIn(w1.dashboard, list(models.Dashboard.all(user.org, user.group_ids, user.id)))\n"
  },
  {
    "path": "tests/test_monitor.py",
    "content": "from unittest.mock import MagicMock, patch\n\nfrom redash import rq_redis_connection\nfrom redash.monitor import rq_job_ids\n\n\ndef test_rq_job_ids_uses_rq_redis_connection():\n    mock_queue = MagicMock()\n    mock_queue.job_ids = []\n\n    mock_registry = MagicMock()\n    mock_registry.get_job_ids.return_value = []\n\n    with patch(\"redash.monitor.Queue\") as mock_Queue, patch(\n        \"redash.monitor.StartedJobRegistry\"\n    ) as mock_StartedJobRegistry:\n        mock_Queue.all.return_value = [mock_queue]\n        mock_StartedJobRegistry.return_value = mock_registry\n\n        rq_job_ids()\n\n        mock_Queue.all.assert_called_once_with(connection=rq_redis_connection)\n        mock_StartedJobRegistry.assert_called_once_with(queue=mock_queue)\n"
  },
  {
    "path": "tests/test_permissions.py",
    "content": "from collections import namedtuple\n\nfrom redash import models\nfrom redash.permissions import has_access\nfrom tests import BaseTestCase\n\nMockUser = namedtuple(\"MockUser\", [\"permissions\", \"group_ids\"])\nview_only = True\n\n\nclass TestHasAccess(BaseTestCase):\n    def test_allows_admin_regardless_of_groups(self):\n        user = MockUser([\"admin\"], [])\n\n        self.assertTrue(has_access({}, user, view_only))\n        self.assertTrue(has_access({}, user, not view_only))\n\n    def test_allows_if_user_member_in_group_with_view_access(self):\n        user = MockUser([], [1])\n\n        self.assertTrue(has_access({1: view_only}, user, view_only))\n\n    def test_allows_if_user_member_in_group_with_full_access(self):\n        user = MockUser([], [1])\n\n        self.assertTrue(has_access({1: not view_only}, user, not view_only))\n\n    def test_allows_if_user_member_in_multiple_groups(self):\n        user = MockUser([], [1, 2, 3])\n\n        self.assertTrue(has_access({1: not view_only, 2: view_only}, user, not view_only))\n        self.assertFalse(has_access({1: view_only, 2: view_only}, user, not view_only))\n        self.assertTrue(has_access({1: view_only, 2: view_only}, user, view_only))\n        self.assertTrue(has_access({1: not view_only, 2: not view_only}, user, view_only))\n\n    def test_not_allows_if_not_enough_permission(self):\n        user = MockUser([], [1])\n\n        self.assertFalse(has_access({1: view_only}, user, not view_only))\n        self.assertFalse(has_access({2: view_only}, user, not view_only))\n        self.assertFalse(has_access({2: view_only}, user, view_only))\n        self.assertFalse(has_access({2: not view_only, 1: view_only}, user, not view_only))\n\n    def test_allows_access_to_query_by_query_api_key(self):\n        query = self.factory.create_query()\n        user = models.ApiUser(query.api_key, None, [])\n\n        self.assertTrue(has_access(query, user, view_only))\n\n    def test_doesnt_allow_access_to_query_by_different_api_key(self):\n        query = self.factory.create_query()\n        other_query = self.factory.create_query()\n        user = models.ApiUser(other_query.api_key, None, [])\n\n        self.assertFalse(has_access(query, user, view_only))\n\n    def test_allows_access_to_query_by_dashboard_api_key(self):\n        dashboard = self.factory.create_dashboard()\n        visualization = self.factory.create_visualization()\n        self.factory.create_widget(dashboard=dashboard, visualization=visualization)\n        query = self.factory.create_query(visualizations=[visualization])\n\n        api_key = self.factory.create_api_key(object=dashboard).api_key\n        user = models.ApiUser(api_key, None, [])\n\n        self.assertTrue(has_access(query, user, view_only))\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "from collections import namedtuple\nfrom unittest import TestCase\n\nimport pytest\n\nfrom redash import create_app\nfrom redash.query_runner import (\n    TYPE_BOOLEAN,\n    TYPE_DATE,\n    TYPE_DATETIME,\n    TYPE_FLOAT,\n    TYPE_INTEGER,\n    TYPE_STRING,\n)\nfrom redash.utils import (\n    build_url,\n    collect_parameters_from_request,\n    filter_none,\n    generate_token,\n    json_dumps,\n    render_template,\n)\nfrom redash.utils.pandas import pandas_installed\n\nDummyRequest = namedtuple(\"DummyRequest\", [\"host\", \"scheme\"])\n\nskip_condition = pytest.mark.skipif(not pandas_installed, reason=\"pandas is not installed\")\n\nif pandas_installed:\n    import numpy as np\n    import pandas as pd\n\n    from redash.utils.pandas import get_column_types_from_dataframe, pandas_to_result\n\n\nclass TestBuildUrl(TestCase):\n    def test_simple_case(self):\n        self.assertEqual(\n            \"http://example.com/test\",\n            build_url(DummyRequest(\"\", \"http\"), \"example.com\", \"/test\"),\n        )\n\n    def test_uses_current_request_port(self):\n        self.assertEqual(\n            \"http://example.com:5000/test\",\n            build_url(DummyRequest(\"example.com:5000\", \"http\"), \"example.com\", \"/test\"),\n        )\n\n    def test_uses_current_request_schema(self):\n        self.assertEqual(\n            \"https://example.com/test\",\n            build_url(DummyRequest(\"example.com\", \"https\"), \"example.com\", \"/test\"),\n        )\n\n    def test_skips_port_for_default_ports(self):\n        self.assertEqual(\n            \"https://example.com/test\",\n            build_url(DummyRequest(\"example.com:443\", \"https\"), \"example.com\", \"/test\"),\n        )\n        self.assertEqual(\n            \"http://example.com/test\",\n            build_url(DummyRequest(\"example.com:80\", \"http\"), \"example.com\", \"/test\"),\n        )\n        self.assertEqual(\n            \"https://example.com:80/test\",\n            build_url(DummyRequest(\"example.com:80\", \"https\"), \"example.com\", \"/test\"),\n        )\n        self.assertEqual(\n            \"http://example.com:443/test\",\n            build_url(DummyRequest(\"example.com:443\", \"http\"), \"example.com\", \"/test\"),\n        )\n\n\nclass TestCollectParametersFromRequest(TestCase):\n    def test_ignores_non_prefixed_values(self):\n        self.assertEqual({}, collect_parameters_from_request({\"test\": 1}))\n\n    def test_takes_prefixed_values(self):\n        self.assertDictEqual(\n            {\"test\": 1, \"something_else\": \"test\"},\n            collect_parameters_from_request({\"p_test\": 1, \"p_something_else\": \"test\"}),\n        )\n\n\nclass TestSkipNones(TestCase):\n    def test_skips_nones(self):\n        d = {\"a\": 1, \"b\": None}\n\n        self.assertDictEqual(filter_none(d), {\"a\": 1})\n\n\nclass TestJsonDumps(TestCase):\n    def test_handles_binary(self):\n        self.assertEqual(json_dumps(memoryview(b\"test\")), '\"74657374\"')\n\n\nclass TestGenerateToken(TestCase):\n    def test_format(self):\n        token = generate_token(40)\n        self.assertRegex(token, r\"[a-zA-Z0-9]{40}\")\n\n\nclass TestRenderTemplate(TestCase):\n    def test_render(self):\n        app = create_app()\n        with app.app_context():\n            d = {\n                \"failures\": [\n                    {\n                        \"id\": 1,\n                        \"name\": \"Failure Unit Test\",\n                        \"failed_at\": \"May 04, 2021 02:07PM UTC\",\n                        \"failure_reason\": \"\",\n                        \"failure_count\": 1,\n                        \"comment\": None,\n                    }\n                ]\n            }\n            html, text = [render_template(\"emails/failures.{}\".format(f), d) for f in [\"html\", \"txt\"]]\n            self.assertIn(\"Failure Unit Test\", html)\n            self.assertIn(\"Failure Unit Test\", text)\n\n\n@pytest.fixture\n@skip_condition\ndef mock_dataframe():\n    df = pd.DataFrame(\n        {\n            \"boolean_col\": [True, False],\n            \"integer_col\": [1, 2],\n            \"float_col\": [1.1, 2.2],\n            \"date_col\": [np.datetime64(\"2020-01-01\"), np.datetime64(\"2020-05-05\")],\n            \"datetime_col\": [np.datetime64(\"2020-01-01 12:00:00\"), np.datetime64(\"2020-05-05 14:30:00\")],\n            \"string_col\": [\"A\", \"B\"],\n        }\n    )\n    return df\n\n\n@skip_condition\ndef test_get_column_types_from_dataframe(mock_dataframe):\n    result = get_column_types_from_dataframe(mock_dataframe)\n    expected_output = [\n        {\"name\": \"boolean_col\", \"friendly_name\": \"boolean_col\", \"type\": TYPE_BOOLEAN},\n        {\"name\": \"integer_col\", \"friendly_name\": \"integer_col\", \"type\": TYPE_INTEGER},\n        {\"name\": \"float_col\", \"friendly_name\": \"float_col\", \"type\": TYPE_FLOAT},\n        {\"name\": \"date_col\", \"friendly_name\": \"date_col\", \"type\": TYPE_DATE},\n        {\"name\": \"datetime_col\", \"friendly_name\": \"datetime_col\", \"type\": TYPE_DATETIME},\n        {\"name\": \"string_col\", \"friendly_name\": \"string_col\", \"type\": TYPE_STRING},\n    ]\n\n    assert result == expected_output\n\n\n@skip_condition\ndef test_pandas_to_result(mock_dataframe):\n    result = pandas_to_result(mock_dataframe)\n\n    assert \"columns\" in result\n    assert \"rows\" in result\n\n    assert mock_dataframe.equals(pd.DataFrame(result[\"rows\"]))\n"
  },
  {
    "path": "tests/utils/__init__.py",
    "content": ""
  },
  {
    "path": "tests/utils/test_json_dumps.py",
    "content": "from redash.utils import json_dumps, json_loads\nfrom tests import BaseTestCase\n\n\nclass TestJsonDumps(BaseTestCase):\n    \"\"\"\n    NaN, Inf, and -Inf are sanitized to None.\n    \"\"\"\n\n    def test_data_with_nan_is_sanitized(self):\n        input_data = {\n            \"columns\": [\n                {\"name\": \"_col0\", \"friendly_name\": \"_col0\", \"type\": \"float\"},\n                {\"name\": \"_col1\", \"friendly_name\": \"_col1\", \"type\": \"float\"},\n                {\"name\": \"_col2\", \"friendly_name\": \"_col1\", \"type\": \"float\"},\n                {\"name\": \"_col3\", \"friendly_name\": \"_col1\", \"type\": \"float\"},\n            ],\n            \"rows\": [{\"_col0\": 1.0, \"_col1\": float(\"nan\"), \"_col2\": float(\"inf\"), \"_col3\": float(\"-inf\")}],\n        }\n        expected_output_data = {\n            \"columns\": [\n                {\"name\": \"_col0\", \"friendly_name\": \"_col0\", \"type\": \"float\"},\n                {\"name\": \"_col1\", \"friendly_name\": \"_col1\", \"type\": \"float\"},\n                {\"name\": \"_col2\", \"friendly_name\": \"_col1\", \"type\": \"float\"},\n                {\"name\": \"_col3\", \"friendly_name\": \"_col1\", \"type\": \"float\"},\n            ],\n            \"rows\": [{\"_col0\": 1.0, \"_col1\": None, \"_col2\": None, \"_col3\": None}],\n        }\n        json_data = json_dumps(input_data)\n        actual_output_data = json_loads(json_data)\n        self.assertEqual(actual_output_data, expected_output_data)\n"
  },
  {
    "path": "viz-lib/.babelrc",
    "content": "{\n  \"presets\": [\"@babel/preset-env\", \"@babel/preset-react\", \"@babel/preset-typescript\"],\n  \"plugins\": [\n    \"@babel/plugin-transform-class-properties\",\n    [\n      \"module-resolver\",\n      {\n        \"root\": [\"./src\"],\n        \"alias\": {\n          \"@\": \"./src\"\n        }\n      }\n    ]\n  ],\n  \"env\": {\n    \"test\": {\n      \"plugins\": [\"istanbul\"]\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/.gitignore",
    "content": "# dependencies\nnode_modules\n\n# builds\n/build\n/dist\n/lib\n.rpt2_cache\n\n# misc\n.DS_Store\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\npnpm-debug.log*"
  },
  {
    "path": "viz-lib/CHANGELOG.md",
    "content": "# Change Log\n\n## v0.1.0 - 2020-05-05\n\n- Created the library from Redash codebase\n"
  },
  {
    "path": "viz-lib/README.md",
    "content": "# @redash/viz\n\n![Version](https://img.shields.io/npm/v/@redash/viz)\n\n`@redash/viz` is a library containing the visualizations used by [Redash](https://redash.io).\n\n## Installation\n\nRequired libraries:\n\n- react (`>=16.8.0`)\n- react-dom (`>=16.8.0`)\n- antd (`>=4.0.0`)\n\nUsing npm:\n\n```bash\nnpm install @redash/viz\n```\n\nUsing pnpm:\n\n```bash\npnpm add @redash/viz\n```\n\n## Usage\n\n### Basic Usage\n\nYou can check [our live example](https://codesandbox.io/s/redashviz-v9odv) or follow the code below:\n\n```jsx\nimport React, { useState } from \"react\";\nimport { Renderer, Editor } from \"@redash/viz\";\n\nconst exampleData = {\n  columns: [\n    { type: null, name: \"Country\" },\n    { type: null, name: \"Amount\" },\n  ],\n  rows: [\n    { Amount: 37.620000000000005, Country: \"Argentina\" },\n    { Amount: 37.620000000000005, Country: \"Australia\" },\n    { Amount: 42.62, Country: \"Austria\" },\n    { Amount: 37.62, Country: \"Belgium\" },\n    { Amount: 190.09999999999997, Country: \"Brazil\" },\n    { Amount: 303.9599999999999, Country: \"Canada\" },\n    { Amount: 46.62, Country: \"Chile\" },\n    { Amount: 90.24000000000001, Country: \"Czech Republic\" },\n    { Amount: 37.620000000000005, Country: \"Denmark\" },\n    { Amount: 41.620000000000005, Country: \"Finland\" },\n    { Amount: 195.09999999999994, Country: \"France\" },\n  ],\n};\n\nfunction Example() {\n  const [options, setOptions] = useState({ countRows: true });\n\n  return (\n    <div>\n      <Editor\n        type=\"COUNTER\"\n        visualizationName=\"Example Visualization\"\n        options={options}\n        data={exampleData}\n        onChange={setOptions}\n      />\n      <Renderer type=\"COUNTER\" visualizationName=\"Example Visualization\" options={options} data={exampleData} />\n    </div>\n  );\n}\n```\n\n### Available Types\n\n- Chart: `CHART`\n- Cohort: `COHORT`\n- Counter: `COUNTER`\n- Details View: `DETAILS`\n- Funnel: `FUNNEL`\n- Map (Choropleth): `CHOROPLETH`\n- Map (Markers): `MAP`\n- Pivot Table: `PIVOT`\n- Sankey: `SANKEY`\n- Sunburst Sequence: `SUNBURST_SEQUENCE`\n- Table: `TABLE`\n- Word Cloud: `WORD_CLOUD`\n\n### Column Types\n\nTypes used for the `columns` property in the `data` object. Currently used to determine the default column view for the Table Visualization. This field is not mandatory and can receive a `null` value.\n\nExample:\n\n```js\nconst data = {\n  columns: [\n    { type: \"string\", name: \"Country\" },\n    { type: \"float\", name: \"Amount\" },\n  ],\n  rows: [],\n};\n```\n\nAvailable types:\n\n- `integer`\n- `float`\n- `boolean`\n- `string`\n- `datetime`\n- `date`\n\n### Customizable Settings\n\n| Option                     | Description                                                                                     | Type                                                           | Default                                                                                                                                                                        |\n| -------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| dateFormat                 | Date Format                                                                                     | `string`                                                       | `\"DD/MM/YYYY\"`                                                                                                                                                                 |\n| dateTimeFormat             | DateTime Format                                                                                 | `string`                                                       | `\"DD/MM/YYYY HH:mm\"`                                                                                                                                                           |\n| integerFormat              | Integer Format                                                                                  | `string`                                                       | `\"0,0\"`                                                                                                                                                                        |\n| floatFormat                | Float Format                                                                                    | `string`                                                       | `\"0,0.00\"`                                                                                                                                                                     |\n| booleanValues              | Boolean names                                                                                   | `Array [string, string]` (correspond to false and true values) | `[\"false\", \"true\"]`                                                                                                                                                            |\n| tableCellMaxJSONSize       | Max json size that will be parsed and rendered in a Table Cell                                  | `integer`                                                      | `50000`                                                                                                                                                                        |\n| allowCustomJSVisualization | Whether to allow the `Custom` chart type                                                        | `boolean`                                                      | `false`                                                                                                                                                                        |\n| hidePlotlyModeBar          | Whether to hide the Plotly Mode Bar on charts                                                   | `boolean`                                                      | `false`                                                                                                                                                                        |\n| choroplethAvailableMaps    | Configure the JSONs used for Choropleth maps (Note: Choropleth won't work without this setting) | `Object` (see example below)                                   | `{}`                                                                                                                                                                           |\n| HelpTriggerComponent       | Component used to render helper links on the Editor                                             | React component with `title` and `href` props                  | Renders a [tooltip with a link](https://github.com/getredash/redash/blob/fc246aafc445bdfc3ad2b82560141ef51f8753a9/viz-lib/src/visualizations/visualizationsSettings.js#L6-L33) |\n\nExample:\n\n```jsx\nimport React from \"react\";\nimport { Renderer, Editor, updateVisualizationsSettings } from \"@redash/viz\";\n\nimport countriesDataUrl from \"@redash/viz/lib/visualizations/choropleth/maps/countries.geo.json\";\nimport subdivJapanDataUrl from \"@redash/viz/lib/visualizations/choropleth/maps/japan.prefectures.geo.json\";\n\nfunction wrapComponentWithSettings(WrappedComponent) {\n  return function VisualizationComponent(props) {\n    updateVisualizationsSettings({\n      choroplethAvailableMaps: {\n        countries: {\n          name: \"Countries\",\n          url: countriesDataUrl,\n        },\n        subdiv_japan: {\n          name: \"Japan/Prefectures\",\n          url: subdivJapanDataUrl,\n        },\n      },\n      dateFormat: \"YYYY/MM/DD\",\n      booleanValues: [\"False\", \"True\"],\n      hidePlotlyModeBar: true,\n    });\n\n    return <WrappedComponent {...props} />;\n  };\n}\n\nexport const ConfiguredRenderer = wrapComponentWithSettings(Renderer);\nexport const ConfiguredEditor = wrapComponentWithSettings(Editor);\n```\n\n### Specific File Imports\n\nThere is a transpiled only build aimed for specific file imports.\n\n**Note:** Currently requires Less.\n\nUsage:\n\n```jsx\nimport React from \"react\";\nimport JsonViewInteractive from \"@redash/viz/lib/components/json-view-interactive/JsonViewInteractive\";\n\nconst example = { list: [\"value1\", \"value2\", \"value3\"], obj: { prop: \"value\" } };\n\nexport default function App() {\n  return <JsonViewInteractive value={example} />;\n}\n```\n\n## License\n\nBSD-2-Clause.\n"
  },
  {
    "path": "viz-lib/__tests__/enzyme_setup.js",
    "content": "const { configure } = require(\"enzyme\");\nconst Adapter = require(\"enzyme-adapter-react-16\");\n\nconfigure({ adapter: new Adapter() });\n"
  },
  {
    "path": "viz-lib/__tests__/mocks.js",
    "content": "const MockDate = require(\"mockdate\");\n\nconst date = new Date(\"2000-01-01T02:00:00.000\");\n\nMockDate.set(date);\n\nObject.defineProperty(window, \"matchMedia\", {\n  writable: true,\n  value: jest.fn().mockImplementation(query => ({\n    matches: false,\n    media: query,\n    onchange: null,\n    addListener: jest.fn(), // deprecated\n    removeListener: jest.fn(), // deprecated\n    addEventListener: jest.fn(),\n    removeEventListener: jest.fn(),\n    dispatchEvent: jest.fn(),\n  })),\n});\n"
  },
  {
    "path": "viz-lib/package.json",
    "content": "{\n  \"name\": \"@redash/viz\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Redash visualizations\",\n  \"main\": \"dist/redash-visualizations.js\",\n  \"scripts\": {\n    \"clean\": \"rm -rf lib dist\",\n    \"type-check\": \"tsc --noEmit\",\n    \"type-gen\": \"tsc --emitDeclarationOnly\",\n    \"build:babel:base\": \"babel src --out-dir lib --source-maps --ignore 'src/**/*.test.js' --copy-files --no-copy-ignored --extensions .ts,.tsx,.js,.jsx\",\n    \"build:babel\": \"pnpm run type-gen && pnpm run build:babel:base\",\n    \"build:webpack\": \"webpack\",\n    \"build\": \" NODE_ENV=production npm-run-all clean build:babel build:webpack\",\n    \"watch:babel\": \"pnpm run build:babel:base --watch\",\n    \"watch:webpack\": \"webpack --watch\",\n    \"watch\": \"npm-run-all --parallel watch:*\",\n    \"version\": \"pnpm run build\",\n    \"prettier\": \"prettier --write 'src/**/*.{ts,tsx}'\",\n    \"test\": \"jest\",\n    \"test:watch\": \"jest --watch\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/getredash/redash.git\"\n  },\n  \"author\": \"Redash\",\n  \"license\": \"BSD-2-Clause\",\n  \"peerDependencies\": {\n    \"@ant-design/icons\": \">=4.0.0\",\n    \"antd\": \">=4.0.0\",\n    \"react-dom\": \">=16.14.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.28.6\",\n    \"@babel/core\": \"^7.29.0\",\n    \"@babel/plugin-transform-class-properties\": \"^7.28.5\",\n    \"@babel/preset-env\": \"^7.29.0\",\n    \"@babel/preset-react\": \"^7.28.5\",\n    \"@babel/preset-typescript\": \"^7.28.5\",\n    \"@types/chroma-js\": \"^2.1.2\",\n    \"@types/d3\": \"^6.2.0\",\n    \"@types/d3-cloud\": \"^1.2.3\",\n    \"@types/debug\": \"^4.1.5\",\n    \"@types/dompurify\": \"^2.0.4\",\n    \"@types/enzyme\": \"^3.10.8\",\n    \"@types/jest\": \"^26.0.18\",\n    \"@types/leaflet\": \"^1.5.19\",\n    \"@types/numeral\": \"0.0.28\",\n    \"@types/plotly.js\": \"^3.0.10\",\n    \"@types/react\": \"^17.0.0\",\n    \"@types/react-dom\": \"^17.0.0\",\n    \"@types/react-pivottable\": \"^0.11.6\",\n    \"@types/react-plotly.js\": \"^2.6.4\",\n    \"@types/tinycolor2\": \"^1.4.2\",\n    \"babel-loader\": \"^10.0.0\",\n    \"babel-plugin-istanbul\": \"^6.1.1\",\n    \"babel-plugin-module-resolver\": \"^5.0.0\",\n    \"css-loader\": \"^7.1.4\",\n    \"babel-jest\": \"^30.2.0\",\n    \"enzyme\": \"^3.8.0\",\n    \"enzyme-adapter-react-16\": \"^1.15.7\",\n    \"enzyme-to-json\": \"^3.3.5\",\n    \"jest\": \"^30.2.0\",\n    \"less\": \"^4.5.1\",\n    \"less-loader\": \"^12.3.1\",\n    \"less-plugin-autoprefix\": \"^2.0.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prettier\": \"3.3.2\",\n    \"prop-types\": \"^15.7.2\",\n    \"style-loader\": \"^4.0.0\",\n    \"ts-migrate\": \"^0.1.35\",\n    \"typescript\": \"^5.9.3\",\n    \"webpack\": \"^5.105.3\",\n    \"webpack-cli\": \"^6.0.1\"\n  },\n  \"files\": [\n    \"dist\",\n    \"lib\"\n  ],\n  \"dependencies\": {\n    \"axios\": \"0.28.0\",\n    \"axios-auth-refresh\": \"3.3.6\",\n    \"beautifymarker\": \"^1.0.7\",\n    \"chroma-js\": \"^1.3.6\",\n    \"classnames\": \"^2.2.6\",\n    \"d3\": \"^3.5.17\",\n    \"d3-cloud\": \"^1.2.4\",\n    \"debug\": \"^3.1.0\",\n    \"dompurify\": \"^2.0.7\",\n    \"font-awesome\": \"^4.7.0\",\n    \"hoist-non-react-statics\": \"^3.3.0\",\n    \"leaflet\": \"~1.3.1\",\n    \"leaflet-fullscreen\": \"^1.0.2\",\n    \"leaflet.markercluster\": \"^1.1.0\",\n    \"lodash\": \"^4.17.10\",\n    \"numeral\": \"^2.0.6\",\n    \"plotly.js\": \"3.3.1\",\n    \"react-pivottable\": \"^0.11.0\",\n    \"react-plotly.js\": \"^2.6.0\",\n    \"react-sortable-hoc\": \"^1.10.1\",\n    \"tinycolor2\": \"^1.4.1\",\n    \"use-debounce\": \"^3.4.1\",\n    \"use-media\": \"^1.4.0\"\n  },\n  \"jest\": {\n    \"rootDir\": \"./src\",\n    \"testEnvironment\": \"jsdom\",\n    \"transform\": {\n      \"\\\\.[jt]sx?$\": \"babel-jest\"\n    },\n    \"setupFiles\": [\n      \"../__tests__/enzyme_setup.js\",\n      \"../__tests__/mocks.js\"\n    ],\n    \"snapshotSerializers\": [\n      \"enzyme-to-json/serializer\"\n    ],\n    \"moduleNameMapper\": {\n      \"^@/(.*)\": \"<rootDir>/$1\",\n      \"\\\\.(css|less)$\": \"identity-obj-proxy\"\n    }\n  },\n  \"browser\": {\n    \"fs\": false,\n    \"path\": false\n  }\n}\n"
  },
  {
    "path": "viz-lib/prettier.config.js",
    "content": "module.exports = {\n  printWidth: 120,\n  jsxBracketSameLine: true,\n  tabWidth: 2,\n  trailingComma: 'es5',\n};\n"
  },
  {
    "path": "viz-lib/src/components/ColorPicker/Input.tsx",
    "content": "import { isNil, isArray, chunk, map, filter, toPairs } from \"lodash\";\nimport React, { useState, useEffect } from \"react\";\nimport tinycolor from \"tinycolor2\";\nimport TextInput from \"antd/lib/input\";\nimport Typography from \"antd/lib/typography\";\nimport Swatch from \"./Swatch\";\n\nimport \"./input.less\";\n\nfunction preparePresets(presetColors: any, presetColumns: any) {\n  presetColors = isArray(presetColors) ? map(presetColors, v => [null, v]) : toPairs(presetColors);\n  presetColors = map(presetColors, ([title, value]) => {\n    if (isNil(value)) {\n      return [title, null];\n    }\n    value = tinycolor(value);\n    if (value.isValid()) {\n      return [title, \"#\" + value.toHex().toUpperCase()];\n    }\n    return null;\n  });\n  return chunk(filter(presetColors), presetColumns);\n}\n\nfunction validateColor(value: any, callback: any, prefix = \"#\") {\n  if (isNil(value)) {\n    callback(null);\n  }\n  value = tinycolor(value);\n  if (value.isValid()) {\n    callback(prefix + value.toHex().toUpperCase());\n  }\n}\n\ntype OwnProps = {\n  color?: string;\n  presetColors?:\n    | string[]\n    | {\n        [key: string]: string;\n      };\n  presetColumns?: number;\n  onChange?: (...args: any[]) => any;\n  onPressEnter?: (...args: any[]) => any;\n};\n\nconst inputDefaultProps = {\n  color: \"#FFFFFF\",\n  presetColors: null,\n  presetColumns: 8,\n  onChange: () => {},\n  onPressEnter: () => {},\n};\n\ntype Props = OwnProps & typeof inputDefaultProps;\n\nexport default function Input({ color, presetColors, presetColumns, onChange, onPressEnter }: Props) {\n  const [inputValue, setInputValue] = useState(\"\");\n  const [isInputFocused, setIsInputFocused] = useState(false);\n\n  const presets = preparePresets(presetColors, presetColumns);\n\n  function handleInputChange(value: any) {\n    setInputValue(value);\n    validateColor(value, onChange);\n  }\n\n  useEffect(() => {\n    if (!isInputFocused) {\n      validateColor(color, setInputValue, \"\");\n    }\n  }, [color, isInputFocused]);\n\n  return (\n    <React.Fragment>\n      {map(presets, (group, index) => (\n        <div className=\"color-picker-input-swatches\" key={`preset-row-${index}`}>\n          {map(group, ([title, value]) => (\n            // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n            <Swatch key={value} color={value} title={title} size={30} onClick={() => validateColor(value, onChange)} />\n          ))}\n        </div>\n      ))}\n      <div className=\"color-picker-input\">\n        <TextInput\n          data-test=\"ColorPicker.CustomColor\"\n          addonBefore={<Typography.Text type=\"secondary\">#</Typography.Text>}\n          value={inputValue}\n          onChange={e => handleInputChange(e.target.value)}\n          onFocus={() => setIsInputFocused(true)}\n          onBlur={() => setIsInputFocused(false)}\n          onPressEnter={onPressEnter}\n        />\n      </div>\n    </React.Fragment>\n  );\n}\n\nInput.defaultProps = inputDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/components/ColorPicker/Label.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport cx from \"classnames\";\n\nimport { validateColor, getColorName } from \"./utils\";\nimport \"./label.less\";\n\ntype OwnProps = {\n  className?: string;\n  color?: string;\n  presetColors?:\n    | string[]\n    | {\n        [key: string]: string;\n      };\n};\n\nconst labelDefaultProps = {\n  className: null,\n  color: \"#FFFFFF\",\n  presetColors: null,\n};\n\ntype Props = OwnProps & typeof labelDefaultProps;\n\n// @ts-expect-error ts-migrate(2700) FIXME: Rest types may only be created from object types.\nexport default function Label({ className, color, presetColors, ...props }: Props) {\n  const name = useMemo(() => getColorName(validateColor(color), presetColors), [color, presetColors]);\n\n  return (\n    <span className={cx(\"color-label\", className)} {...props}>\n      {name}\n    </span>\n  );\n}\n\nLabel.defaultProps = labelDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/components/ColorPicker/Swatch.tsx",
    "content": "import { isString } from \"lodash\";\nimport React from \"react\";\nimport cx from \"classnames\";\nimport Tooltip from \"antd/lib/tooltip\";\n\nimport \"./swatch.less\";\n\ntype OwnProps = {\n  className?: string;\n  style?: any;\n  title?: string;\n  color?: string;\n  size?: number;\n};\n\nconst swatchDefaultProps = {\n  className: null,\n  style: null,\n  title: null,\n  color: \"transparent\",\n  size: 12,\n};\n\ntype Props = OwnProps & typeof swatchDefaultProps;\n\n// @ts-expect-error ts-migrate(2700) FIXME: Rest types may only be created from object types.\nexport default function Swatch({ className, color, title, size, style, ...props }: Props) {\n  const result = (\n    <span\n      className={cx(\"color-swatch\", className)}\n      // @ts-expect-error ts-migrate(2698) FIXME: Spread types may only be created from object types... Remove this comment to see the full error message\n      style={{ backgroundColor: color, width: size, ...style }}\n      {...props}\n    />\n  );\n\n  if (isString(title) && title !== \"\") {\n    return (\n      <Tooltip title={title} mouseEnterDelay={0} mouseLeaveDelay={0}>\n        {result}\n      </Tooltip>\n    );\n  }\n  return result;\n}\n\nSwatch.defaultProps = swatchDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/components/ColorPicker/index.less",
    "content": ".color-picker {\n  &.color-picker-with-actions {\n    &.ant-popover-placement-top,\n    &.ant-popover-placement-topLeft,\n    &.ant-popover-placement-topRight,\n    &.ant-popover-placement-leftBottom,\n    &.ant-popover-placement-rightBottom {\n      > .ant-popover-content > .ant-popover-arrow {\n        border-color: #fafafa; // same as card actions\n      }\n    }\n  }\n\n  &.ant-popover-placement-bottom,\n  &.ant-popover-placement-bottomLeft,\n  &.ant-popover-placement-bottomRight,\n  &.ant-popover-placement-leftTop,\n  &.ant-popover-placement-rightTop {\n    > .ant-popover-content > .ant-popover-arrow {\n      border-color: var(--color-picker-selected-color);\n    }\n  }\n\n  .ant-popover-inner-content {\n    padding: 0;\n  }\n\n  .ant-card-head {\n    text-align: center;\n    border-bottom-color: rgba(0, 0, 0, 0.1);\n  }\n\n  .ant-card-body {\n    padding: 10px;\n  }\n}\n\n.color-picker-trigger {\n  cursor: pointer;\n}\n\n.color-picker-wrapper {\n  white-space: nowrap;\n}\n"
  },
  {
    "path": "viz-lib/src/components/ColorPicker/index.tsx",
    "content": "import { toString } from \"lodash\";\nimport React, { useState, useEffect, useMemo } from \"react\";\nimport cx from \"classnames\";\nimport Popover from \"antd/lib/popover\";\nimport Card from \"antd/lib/card\";\nimport Tooltip from \"antd/lib/tooltip\";\nimport chooseTextColorForBackground from \"@/lib/chooseTextColorForBackground\";\n\nimport CloseOutlinedIcon from \"@ant-design/icons/CloseOutlined\";\nimport CheckOutlinedIcon from \"@ant-design/icons/CheckOutlined\";\n\nimport ColorInput from \"./Input\";\nimport Swatch from \"./Swatch\";\nimport Label from \"./Label\";\nimport { validateColor } from \"./utils\";\n\nimport \"./index.less\";\n\ntype OwnProps = {\n  color?: string;\n  placement?:\n    | \"top\"\n    | \"left\"\n    | \"right\"\n    | \"bottom\"\n    | \"topLeft\"\n    | \"topRight\"\n    | \"bottomLeft\"\n    | \"bottomRight\"\n    | \"leftTop\"\n    | \"leftBottom\"\n    | \"rightTop\"\n    | \"rightBottom\";\n  presetColors?:\n    | string[]\n    | {\n        [key: string]: string;\n      };\n  presetColumns?: number;\n  interactive?: boolean;\n  triggerProps?: any;\n  children?: React.ReactNode;\n  addonBefore?: React.ReactNode;\n  addonAfter?: React.ReactNode;\n  onChange?: (...args: any[]) => any;\n};\n\nconst colorPickerDefaultProps = {\n  color: \"#FFFFFF\",\n  placement: \"top\",\n  presetColors: null,\n  presetColumns: 8,\n  interactive: false,\n  triggerProps: {} as Record<string, any>,\n  children: null,\n  addonBefore: null,\n  addonAfter: null,\n  onChange: () => {},\n};\n\ntype Props = OwnProps & typeof colorPickerDefaultProps;\n\nexport default function ColorPicker({\n  color,\n  placement,\n  presetColors,\n  presetColumns,\n  interactive,\n  children,\n  onChange,\n  triggerProps,\n  addonBefore,\n  addonAfter,\n}: Props) {\n  const [visible, setVisible] = useState(false);\n  const validatedColor = useMemo(() => validateColor(color), [color]);\n  const [currentColor, setCurrentColor] = useState(\"\");\n\n  function handleApply() {\n    setVisible(false);\n    if (!interactive) {\n      // @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.\n      onChange(currentColor);\n    }\n  }\n\n  function handleCancel() {\n    setVisible(false);\n  }\n\n  const actions = [];\n  if (!interactive) {\n    actions.push(\n      <Tooltip key=\"cancel\" title=\"Cancel\">\n        <CloseOutlinedIcon onClick={handleCancel} />\n      </Tooltip>\n    );\n    actions.push(\n      <Tooltip key=\"apply\" title=\"Apply\">\n        <CheckOutlinedIcon onClick={handleApply} />\n      </Tooltip>\n    );\n  }\n\n  function handleInputChange(newColor: any) {\n    setCurrentColor(newColor);\n    if (interactive) {\n      // @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.\n      onChange(newColor);\n    }\n  }\n\n  useEffect(() => {\n    if (visible) {\n      // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string | null' is not assignable... Remove this comment to see the full error message\n      setCurrentColor(validatedColor);\n    }\n  }, [validatedColor, visible]);\n\n  return (\n    <span className=\"color-picker-wrapper\">\n      {addonBefore}\n      <Popover\n        arrowPointAtCenter\n        overlayClassName={`color-picker ${interactive ? \"color-picker-interactive\" : \"color-picker-with-actions\"}`}\n        // @ts-expect-error ts-migrate(2322) FIXME: Type '{ \"--color-picker-selected-color\": string; }... Remove this comment to see the full error message\n        overlayStyle={{ \"--color-picker-selected-color\": currentColor }}\n        content={\n          <Card\n            data-test=\"ColorPicker\"\n            className=\"color-picker-panel\"\n            bordered={false}\n            title={toString(currentColor).toUpperCase()}\n            headStyle={{\n              backgroundColor: currentColor,\n              // @ts-expect-error ts-migrate(2322) FIXME: Type 'string | null | undefined' is not assignable... Remove this comment to see the full error message\n              color: chooseTextColorForBackground(currentColor),\n            }}\n            actions={actions}>\n            <ColorInput\n              // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n              color={currentColor}\n              presetColors={presetColors}\n              presetColumns={presetColumns}\n              // @ts-expect-error ts-migrate(2322) FIXME: Type '(newColor: any) => void' is not assignable t... Remove this comment to see the full error message\n              onChange={handleInputChange}\n              // @ts-expect-error ts-migrate(2322) FIXME: Type '() => void' is not assignable to type 'never... Remove this comment to see the full error message\n              onPressEnter={handleApply}\n            />\n          </Card>\n        }\n        trigger=\"click\"\n        placement={placement}\n        visible={visible}\n        onVisibleChange={setVisible}>\n        {children || (\n          <Swatch\n            color={validatedColor}\n            size={30}\n            {...((triggerProps as any) || {})}\n            className={cx(\"color-picker-trigger\", (triggerProps as any)?.className)}\n          />\n        )}\n      </Popover>\n      {addonAfter}\n    </span>\n  );\n}\n\nColorPicker.defaultProps = colorPickerDefaultProps;\n\nColorPicker.Input = ColorInput;\nColorPicker.Swatch = Swatch;\nColorPicker.Label = Label;\n"
  },
  {
    "path": "viz-lib/src/components/ColorPicker/input.less",
    "content": ".color-picker-input-swatches {\n  margin: 0 0 10px 0;\n  text-align: left;\n  white-space: nowrap;\n\n  .color-swatch {\n    cursor: pointer;\n    margin: 0 10px 0 0;\n\n    &:last-child {\n      margin-right: 0;\n    }\n  }\n}\n\n.color-picker-input {\n  text-align: left;\n  white-space: nowrap;\n}\n"
  },
  {
    "path": "viz-lib/src/components/ColorPicker/label.less",
    "content": ".color-label {\n  vertical-align: middle;\n\n  .color-swatch + & {\n    margin-left: 7px;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/components/ColorPicker/swatch.less",
    "content": ".color-swatch {\n  display: inline-block;\n  box-sizing: border-box;\n  vertical-align: middle;\n  border-radius: 2px;\n  overflow: hidden;\n  width: 12px;\n\n  @cell-size: 12px;\n  @cell-color: rgba(0, 0, 0, 0.1);\n\n  background-color: transparent;\n  background-image:\n    linear-gradient(45deg, @cell-color 25%, transparent 25%),\n    linear-gradient(-45deg, @cell-color 25%, transparent 25%),\n    linear-gradient(45deg, transparent 75%, @cell-color 75%),\n    linear-gradient(-45deg, transparent 75%, @cell-color 75%);\n  background-size: @cell-size @cell-size;\n  background-position: 0 0, 0 @cell-size/2, @cell-size/2 -@cell-size/2, -@cell-size/2 0px;\n\n  &:before {\n    content: \"\";\n    display: block;\n    padding-top: ~\"calc(100% - 2px)\";\n    background-color: inherit;\n    border: 1px solid rgba(0, 0, 0, 0.1);\n    border-radius: 2px;\n    overflow: hidden;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/components/ColorPicker/utils.ts",
    "content": "import { isArray, findKey } from \"lodash\";\nimport tinycolor from \"tinycolor2\";\n\nexport function validateColor(value: any, fallback = null) {\n  value = tinycolor(value);\n  return value.isValid() ? \"#\" + value.toHex().toUpperCase() : fallback;\n}\n\nexport function getColorName(color: any, presetColors: any) {\n  if (isArray(presetColors)) {\n    return color;\n  }\n  return findKey(presetColors, v => validateColor(v) === color) || color;\n}\n"
  },
  {
    "path": "viz-lib/src/components/ErrorBoundary.tsx",
    "content": "import { isFunction } from \"lodash\";\nimport React from \"react\";\nimport debug from \"debug\";\nimport Alert from \"antd/lib/alert\";\n\nconst logger = debug(\"redash:errors\");\n\nexport const ErrorBoundaryContext = React.createContext({\n  handleError: (error: any) => {\n    // Allow calling chain to roll up, and then throw the error in global context\n    setTimeout(() => {\n      throw error;\n    });\n  },\n  reset: () => {},\n});\n\ntype OwnErrorMessageProps = {\n  children?: React.ReactNode;\n};\n\nconst errorMessageDefaultProps = {\n  children: \"Something went wrong.\",\n};\n\ntype ErrorMessageProps = OwnErrorMessageProps & typeof errorMessageDefaultProps;\n\nexport function ErrorMessage({ children }: ErrorMessageProps) {\n  return <Alert message={children} type=\"error\" showIcon />;\n}\n\nErrorMessage.defaultProps = errorMessageDefaultProps;\n\ntype OwnErrorBoundaryProps = {\n  renderError?: (...args: any[]) => any;\n};\n\ntype ErrorBoundaryState = any;\n\ntype ErrorBoundaryProps = OwnErrorBoundaryProps & typeof ErrorBoundary.defaultProps;\n\nexport default class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n  static defaultProps = {\n    children: null,\n    renderError: null,\n  };\n\n  state = { error: null };\n\n  handleError = (error: any) => {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'getDerivedStateFromError' does not exist... Remove this comment to see the full error message\n    this.setState(this.constructor.getDerivedStateFromError(error));\n    this.componentDidCatch(error, null);\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'handleException' does not exist on type ... Remove this comment to see the full error message\n    if (isFunction(window.handleException)) {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'handleException' does not exist on type ... Remove this comment to see the full error message\n      window.handleException(error);\n    }\n  };\n\n  reset = () => {\n    this.setState({ error: null });\n  };\n\n  static getDerivedStateFromError(error: any) {\n    return { error };\n  }\n\n  componentDidCatch(error: any, errorInfo: any) {\n    logger(error, errorInfo);\n  }\n\n  render() {\n    const { renderError, children } = this.props;\n    const { error } = this.state;\n\n    if (error) {\n      if (isFunction(renderError)) {\n        // @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.\n        return renderError(error);\n      }\n      return <ErrorMessage />;\n    }\n\n    return <ErrorBoundaryContext.Provider value={this}>{children}</ErrorBoundaryContext.Provider>;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/components/HtmlContent.tsx",
    "content": "import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport sanitize from \"@/services/sanitize\";\n\nconst HtmlContent = React.memo(function HtmlContent({ children, ...props }) {\n  return (\n    <div\n      {...props}\n      // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'ReactNode' is not assignable to ... Remove this comment to see the full error message\n      dangerouslySetInnerHTML={{ __html: sanitize(children) }} // eslint-disable-line react/no-danger\n    />\n  );\n});\n\n// @ts-expect-error ts-migrate(2339) FIXME: Property 'propTypes' does not exist on type 'Named... Remove this comment to see the full error message\nHtmlContent.propTypes = {\n  children: PropTypes.string,\n};\n\n// @ts-expect-error ts-migrate(2339) FIXME: Property 'defaultProps' does not exist on type 'Na... Remove this comment to see the full error message\nHtmlContent.defaultProps = {\n  children: \"\",\n};\n\nexport default HtmlContent;\n"
  },
  {
    "path": "viz-lib/src/components/TextAlignmentSelect/index.less",
    "content": ".ant-radio-group.text-alignment-select {\n  display: flex;\n  align-items: stretch;\n  justify-content: stretch;\n\n  .ant-radio-button-wrapper {\n    flex-grow: 1;\n    text-align: center;\n    // fit <Input> height\n    height: 35px;\n    line-height: 33px;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/components/TextAlignmentSelect/index.tsx",
    "content": "import { pickBy, startsWith } from \"lodash\";\nimport React from \"react\";\nimport cx from \"classnames\";\nimport Radio from \"antd/lib/radio\";\nimport Tooltip from \"antd/lib/tooltip\";\n\nimport AlignLeftOutlinedIcon from \"@ant-design/icons/AlignLeftOutlined\";\nimport AlignCenterOutlinedIcon from \"@ant-design/icons/AlignCenterOutlined\";\nimport AlignRightOutlinedIcon from \"@ant-design/icons/AlignRightOutlined\";\n\nimport \"./index.less\";\n\ntype OwnProps = {\n  className?: string;\n};\n\nconst textAlignmentSelectDefaultProps = {\n  className: null,\n};\n\ntype Props = OwnProps & typeof textAlignmentSelectDefaultProps;\n\n// @ts-expect-error ts-migrate(2700) FIXME: Rest types may only be created from object types.\nexport default function TextAlignmentSelect({ className, ...props }: Props) {\n  return (\n    // Antd RadioGroup does not use any custom attributes\n    <div {...pickBy(props, (v, k) => startsWith(k, \"data-\"))}>\n      <Radio.Group className={cx(\"text-alignment-select\", className)} {...props}>\n        <Tooltip title=\"Align left\" mouseEnterDelay={0} mouseLeaveDelay={0}>\n          <Radio.Button value=\"left\" data-test=\"TextAlignmentSelect.Left\">\n            <AlignLeftOutlinedIcon />\n          </Radio.Button>\n        </Tooltip>\n        <Tooltip title=\"Align center\" mouseEnterDelay={0} mouseLeaveDelay={0}>\n          <Radio.Button value=\"center\" data-test=\"TextAlignmentSelect.Center\">\n            <AlignCenterOutlinedIcon />\n          </Radio.Button>\n        </Tooltip>\n        <Tooltip title=\"Align right\" mouseEnterDelay={0} mouseLeaveDelay={0}>\n          <Radio.Button value=\"right\" data-test=\"TextAlignmentSelect.Right\">\n            <AlignRightOutlinedIcon />\n          </Radio.Button>\n        </Tooltip>\n      </Radio.Group>\n    </div>\n  );\n}\n\nTextAlignmentSelect.defaultProps = textAlignmentSelectDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/components/json-view-interactive/JsonViewInteractive.tsx",
    "content": "/* eslint-disable react/prop-types */\n\nimport { isFinite, isString, isArray, isObject, keys, map } from \"lodash\";\nimport React, { useState } from \"react\";\nimport cx from \"classnames\";\n\nimport \"./json-view-interactive.less\";\n\nfunction JsonBlock({ value, children, openingBrace, closingBrace, withKeys }: any) {\n  const [isExpanded, setIsExpanded] = useState(false);\n\n  const objectKeys = keys(value);\n  const count = objectKeys.length;\n\n  return (\n    <React.Fragment>\n      {count > 0 && (\n        <span className=\"jvi-toggle\" onClick={() => setIsExpanded(!isExpanded)}>\n          <i className={cx(\"fa\", { \"fa-caret-right\": !isExpanded, \"fa-caret-down\": isExpanded })} />\n        </span>\n      )}\n      <span className=\"jvi-punctuation jvi-braces\">{openingBrace}</span>\n      {!isExpanded && count > 0 && (\n        <span className=\"jvi-punctuation jvi-ellipsis\" onClick={() => setIsExpanded(true)}>\n          &hellip;\n        </span>\n      )}\n      {isExpanded && (\n        <span className=\"jvi-block\">\n          {map(objectKeys, (key, index) => {\n            const isFirst = index === 0;\n            const isLast = index === count - 1;\n            const comma = isLast ? null : <span className=\"jvi-punctuation jvi-comma\">,</span>;\n            return (\n              <span\n                key={\"item-\" + key}\n                className={cx(\"jvi-item\", { \"jvi-nested-first\": isFirst, \"jvi-nested-last\": isLast })}>\n                {withKeys && (\n                  <span className=\"jvi-object-key\">\n                    <JsonValue value={key}>\n                      <span className=\"jvi-punctuation\">: </span>\n                    </JsonValue>\n                  </span>\n                )}\n                <JsonValue value={value[key]}>{comma}</JsonValue>\n              </span>\n            );\n          })}\n        </span>\n      )}\n      <span className=\"jvi-punctuation jvi-braces\">{closingBrace}</span>\n      {children}\n      {!isExpanded && <span className=\"jvi-comment\">{\" // \" + count + \" \" + (count === 1 ? \"item\" : \"items\")}</span>}\n    </React.Fragment>\n  );\n}\n\nfunction JsonValue({ value, children }: any) {\n  if (value === null || value === false || value === true || isFinite(value)) {\n    return (\n      <span className=\"jvi-value jvi-primitive\">\n        {\"\" + value}\n        {children}\n      </span>\n    );\n  }\n  if (isString(value)) {\n    return (\n      <React.Fragment>\n        <span className=\"jvi-punctuation jvi-string\">&quot;</span>\n        <span className=\"jvi-value jvi-string\">{value}</span>\n        <span className=\"jvi-punctuation jvi-string\">&quot;</span>\n        {children}\n      </React.Fragment>\n    );\n  }\n  if (isArray(value)) {\n    return (\n      <JsonBlock value={value} openingBrace=\"[\" closingBrace=\"]\">\n        {children}\n      </JsonBlock>\n    );\n  }\n  if (isObject(value)) {\n    return (\n      <JsonBlock value={value} openingBrace=\"{\" closingBrace=\"}\" withKeys>\n        {children}\n      </JsonBlock>\n    );\n  }\n  return null;\n}\n\ntype OwnJsonViewInteractiveProps = {\n  value?: any;\n};\n\nconst jsonViewInteractiveDefaultProps = {\n  // `null` will be rendered as \"null\" because it is a valid JSON value, so use `undefined` for no value\n  value: undefined,\n};\n\ntype JsonViewInteractiveProps = OwnJsonViewInteractiveProps & typeof jsonViewInteractiveDefaultProps;\n\nexport default function JsonViewInteractive({ value }: JsonViewInteractiveProps) {\n  return (\n    <span className=\"jvi-item jvi-root\">\n      <JsonValue value={value} />\n    </span>\n  );\n}\n\nJsonViewInteractive.defaultProps = jsonViewInteractiveDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/components/json-view-interactive/json-view-interactive.less",
    "content": "@import \"../../visualizations/variables\";\n\n@jvi-gutter: 20px;\n@jvi-spacing: 2px;\n\n.jvi-root {\n  display: block;\n  font-family: @font-family-monospace;\n}\n\n.jvi-block {\n  display: block;\n  border-left: 1px dotted @table-border-color;\n  margin: 0 0 0 2px;\n}\n\n.jvi-item {\n  display: block;\n  position: relative;\n  padding: 0 0 0 @jvi-gutter;\n  white-space: nowrap;\n\n  .jvi-item {\n    margin: @jvi-spacing 0;\n  }\n\n  &.jvi-nested-last > span > .jvi-punctuation.jvi-comma {\n    display: none;\n  }\n}\n\n.jvi-toggle {\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: @jvi-gutter;\n  height: @jvi-gutter;\n  line-height: @jvi-gutter;\n  text-align: center;\n  cursor: pointer;\n  z-index: 1;\n  color: @text-color;\n  opacity: 0.5;\n\n  &:hover {\n    opacity: 0.8;\n  }\n\n  i {\n    vertical-align: middle;\n  }\n\n  &.hidden {\n    display: none;\n  }\n}\n\n.jvi-punctuation {\n  color: @text-color;\n\n  &.jvi-string {\n    color: @green;\n  }\n\n  &.jvi-braces {\n    margin: 0 @jvi-spacing;\n  }\n\n  &.jvi-ellipsis {\n    padding: 0 @jvi-spacing;\n    cursor: pointer;\n\n    &:hover {\n      text-decoration: underline;\n    }\n  }\n\n  &.hidden {\n    display: none;\n  }\n}\n\n.jvi-value {\n  color: @green;\n\n  &.jvi-primitive {\n    color: @orange;\n  }\n\n  &.jvi-string {\n    white-space: normal;\n  }\n}\n\n.jvi-object-key {\n  .jvi-value,\n  .jvi-punctuation {\n    color: @blue;\n  }\n}\n\n.jvi-comment {\n  color: @text-muted;\n  font-family: @visualizations-font;\n  font-style: italic;\n  margin: 0 0 0 2 * @jvi-spacing;\n  opacity: 0.5;\n\n  &.hidden {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/components/sortable/index.tsx",
    "content": "import { isFunction, wrap } from \"lodash\";\nimport React, { useRef, useState } from \"react\";\nimport cx from \"classnames\";\n// @ts-expect-error ts-migrate(2724) FIXME: Module '\"../../../node_modules/react-sortable-hoc/... Remove this comment to see the full error message\nimport { sortableContainer, sortableElement, sortableHandle } from \"react-sortable-hoc\";\n\nimport \"./style.less\";\n\nexport const DragHandle = sortableHandle(({ className, ...restProps }: any) => (\n  <div className={cx(\"drag-handle\", className)} {...restProps} />\n));\n\nexport const SortableContainerWrapper = sortableContainer(({ children }: any) => children);\n\nexport const SortableElement = sortableElement(({ children }: any) => children);\n\ntype OwnProps = {\n  disabled?: boolean;\n  containerComponent?: React.ReactElement;\n  containerProps?: any;\n  children?: React.ReactNode;\n};\n\nconst sortableContainerDefaultProps = {\n  disabled: false,\n  containerComponent: \"div\",\n  containerProps: {},\n  children: null,\n};\n\ntype Props = OwnProps & typeof sortableContainerDefaultProps;\n\nexport function SortableContainer({ disabled, containerComponent, containerProps, children, ...wrapperProps }: Props) {\n  const containerRef = useRef();\n  const [isDragging, setIsDragging] = useState(false);\n\n  wrapperProps = { ...wrapperProps };\n  containerProps = { ...containerProps };\n\n  if (disabled) {\n    // Disabled state:\n    // - forbid drag'n'drop (and therefore no need to hook events\n    // - don't override anything on container element\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'shouldCancelStart' does not exist on typ... Remove this comment to see the full error message\n    wrapperProps.shouldCancelStart = () => true;\n  } else {\n    // Enabled state:\n\n    // - use container element as a default helper element\n    // @ts-expect-error\n    wrapperProps.helperContainer = wrap(wrapperProps.helperContainer, helperContainer =>\n      isFunction(helperContainer) ? helperContainer(containerRef.current) : containerRef.current\n    );\n\n    // - hook drag start/end events\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'updateBeforeSortStart' does not exist on... Remove this comment to see the full error message\n    wrapperProps.updateBeforeSortStart = wrap(wrapperProps.updateBeforeSortStart, (updateBeforeSortStart, ...args) => {\n      setIsDragging(true);\n      if (isFunction(updateBeforeSortStart)) {\n        updateBeforeSortStart(...args);\n      }\n    });\n    // @ts-expect-error\n    wrapperProps.onSortStart = wrap(wrapperProps.onSortStart, (onSortStart, ...args) => {\n      if (isFunction(onSortStart)) {\n        onSortStart(...args);\n      } else {\n        const event = args[1] as DragEvent;\n        event.preventDefault();\n      }\n    });\n\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'onSortEnd' does not exist on type '{}'.\n    wrapperProps.onSortEnd = wrap(wrapperProps.onSortEnd, (onSortEnd, ...args) => {\n      setIsDragging(false);\n      if (isFunction(onSortEnd)) {\n        onSortEnd(...args);\n      }\n    });\n\n    // - update container element: add classes and take a ref\n    containerProps.className = cx(\n      \"sortable-container\",\n      { \"sortable-container-dragging\": isDragging },\n      containerProps.className\n    );\n    containerProps.ref = containerRef;\n  }\n\n  const ContainerComponent = containerComponent;\n  return (\n    <SortableContainerWrapper {...wrapperProps}>\n      {/* @ts-expect-error ts-migrate(2604) FIXME: JSX element type 'ContainerComponent' does not hav... Remove this comment to see the full error message */}\n      <ContainerComponent {...containerProps}>{children}</ContainerComponent>\n    </SortableContainerWrapper>\n  );\n}\n\nSortableContainer.defaultProps = sortableContainerDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/components/sortable/style.less",
    "content": ".drag-handle {\n  vertical-align: bottom;\n  cursor: move;\n\n  display: inline-flex;\n  align-items: stretch;\n  justify-content: center;\n\n  &:before {\n    content: '';\n    display: block;\n    width: 6px;\n\n    background:\n      linear-gradient(90deg, transparent 0px, white 1px, white 2px) center,\n      linear-gradient(transparent 0px, white 1px, white 2px) center,\n      #111111;\n    background-size: 2px 2px;\n  }\n}\n\n.sortable-container {\n  transition: background-color 200ms ease-out;\n  transition-delay: 300ms; // short pause before returning to original bgcolor\n\n  &.sortable-container-dragging {\n    transition-delay: 0s;\n    background-color: #f6f8f9;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/ContextHelp.tsx",
    "content": "import React from \"react\";\nimport Popover from \"antd/lib/popover\";\nimport QuestionCircleFilledIcon from \"@ant-design/icons/QuestionCircleFilled\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\n\nimport \"./context-help.less\";\n\ntype OwnContextHelpProps = {\n  icon?: React.ReactNode;\n  children?: React.ReactNode;\n};\n\nconst contextHelpDefaultProps = {\n  icon: null,\n  children: null,\n};\n\ntype ContextHelpProps = OwnContextHelpProps & typeof contextHelpDefaultProps;\n\nexport default function ContextHelp({ icon, children, ...props }: ContextHelpProps) {\n  return (\n    <Popover {...props} content={children}>\n      {icon || ContextHelp.defaultIcon}\n    </Popover>\n  );\n}\n\nContextHelp.defaultProps = contextHelpDefaultProps;\n\nContextHelp.defaultIcon = <QuestionCircleFilledIcon className=\"context-help-default-icon\" />;\n\nfunction NumberFormatSpecs() {\n  const { HelpTriggerComponent } = visualizationsSettings;\n  return (\n    <HelpTriggerComponent\n      // @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: Element; type: string; title: st... Remove this comment to see the full error message\n      type=\"NUMBER_FORMAT_SPECS\"\n      title=\"Formatting Numbers\"\n      href=\"https://redash.io/help/user-guide/visualizations/formatting-numbers\"\n      className=\"visualization-editor-context-help\">\n      {ContextHelp.defaultIcon}\n    </HelpTriggerComponent>\n  );\n}\n\nfunction DateTimeFormatSpecs() {\n  const { HelpTriggerComponent } = visualizationsSettings;\n  return (\n    <HelpTriggerComponent\n      title=\"Formatting Dates and Times\"\n      href=\"https://momentjs.com/docs/#/displaying/format/\"\n      className=\"visualization-editor-context-help\">\n      {ContextHelp.defaultIcon}\n    </HelpTriggerComponent>\n  );\n}\n\nfunction TickFormatSpecs() {\n  const { HelpTriggerComponent } = visualizationsSettings;\n  return (\n    <HelpTriggerComponent\n      title=\"Tick Formatting\"\n      href=\"https://redash.io/help/user-guide/visualizations/formatting-axis\"\n      className=\"visualization-editor-context-help\">\n      {ContextHelp.defaultIcon}\n    </HelpTriggerComponent>\n  );\n}\n\nContextHelp.NumberFormatSpecs = NumberFormatSpecs;\nContextHelp.DateTimeFormatSpecs = DateTimeFormatSpecs;\nContextHelp.TickFormatSpecs = TickFormatSpecs;\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/Section.less",
    "content": ".visualization-editor-section-title {\n  margin-top: 0px;\n  margin-bottom: 15px;\n}\n\n.visualization-editor-section {\n  margin-bottom: 15px;\n}\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/Section.tsx",
    "content": "import React from \"react\";\nimport cx from \"classnames\";\n\nimport \"./Section.less\";\n\ntype OwnSectionTitleProps = {\n  className?: string;\n  children?: React.ReactNode;\n};\n\nconst sectionTitleDefaultProps = {\n  className: null,\n  children: null,\n};\n\ntype SectionTitleProps = OwnSectionTitleProps & typeof sectionTitleDefaultProps;\n\n// @ts-expect-error ts-migrate(2700) FIXME: Rest types may only be created from object types.\nfunction SectionTitle({ className, children, ...props }: SectionTitleProps) {\n  if (!children) {\n    return null;\n  }\n\n  return (\n    <h4 className={cx(\"visualization-editor-section-title\", className)} {...props}>\n      {children}\n    </h4>\n  );\n}\n\nSectionTitle.defaultProps = sectionTitleDefaultProps;\n\ntype OwnSectionProps = {\n  className?: string;\n  children?: React.ReactNode;\n};\n\nconst sectionDefaultProps = {\n  className: null,\n  children: null,\n};\n\ntype SectionProps = OwnSectionProps & typeof sectionDefaultProps;\n\n// @ts-expect-error ts-migrate(2700) FIXME: Rest types may only be created from object types.\nexport default function Section({ className, children, ...props }: SectionProps) {\n  return (\n    <div className={cx(\"visualization-editor-section\", className)} {...props}>\n      {children}\n    </div>\n  );\n}\n\nSection.defaultProps = sectionDefaultProps;\n\nSection.Title = SectionTitle;\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/Switch.less",
    "content": ".switch-with-label {\n  display: flex;\n  align-items: center;\n\n  .switch-text {\n    margin-left: 10px;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/Switch.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport AntSwitch from \"antd/lib/switch\";\nimport Typography from \"antd/lib/typography\";\n\nimport \"./Switch.less\";\n\ntype OwnProps = {\n  id?: string;\n  disabled?: boolean;\n  children?: React.ReactNode;\n};\n\nconst switchDefaultProps = {\n  id: null,\n  disabled: false,\n  children: null,\n};\n\ntype Props = OwnProps & typeof switchDefaultProps;\n\n// @ts-expect-error ts-migrate(2700) FIXME: Rest types may only be created from object types.\nexport default function Switch({ id, children, disabled, ...props }: Props) {\n  const fallbackId = useMemo(\n    () =>\n      `visualization-editor-control-${Math.random()\n        .toString(36)\n        .substr(2, 10)}`,\n    []\n  );\n  id = id || fallbackId;\n\n  if (children) {\n    return (\n      <label htmlFor={id} className=\"switch-with-label\">\n        <AntSwitch id={id} disabled={disabled} {...props} />\n        <Typography.Text className=\"switch-text\" disabled={disabled}>\n          {children}\n        </Typography.Text>\n      </label>\n    );\n  }\n\n  return <AntSwitch {...props} />;\n}\n\nSwitch.defaultProps = switchDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/TextArea.less",
    "content": ".visualization-editor-text-area {\n  resize: vertical;\n}\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/TextArea.tsx",
    "content": "import React from \"react\";\nimport cx from \"classnames\";\nimport AntInput from \"antd/lib/input\";\nimport withControlLabel from \"./withControlLabel\";\n\nimport \"./TextArea.less\";\n\nfunction TextArea({ className, ...otherProps }: any) {\n  return <AntInput.TextArea className={cx(\"visualization-editor-text-area\", className)} {...otherProps} />;\n}\n\nexport default withControlLabel(TextArea);\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/context-help.less",
    "content": "a.visualization-editor-context-help {\n  &,\n  .ant-typography & {\n    font: inherit;\n    color: inherit;\n\n    &:hover,\n    &:active {\n      color: #0a6ebd;\n    }\n  }\n}\n\n.context-help-default-icon {\n  margin-left: 5px;\n  margin-right: 5px;\n}\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/control-label.less",
    "content": ".visualization-editor-control-label {\n  &.visualization-editor-control-label-horizontal {\n    label {\n      margin-bottom: 0;\n    }\n  }\n}\n\n.visualization-editor-input {\n  width: 100% !important;\n}\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/createTabbedEditor.tsx",
    "content": "import { isFunction, map, filter, extend, merge } from \"lodash\";\nimport React from \"react\";\nimport Tabs from \"antd/lib/tabs\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport const UpdateOptionsStrategy = {\n  replace: (existingOptions: any, newOptions: any) => merge({}, newOptions),\n  shallowMerge: (existingOptions: any, newOptions: any) => extend({}, existingOptions, newOptions),\n  deepMerge: (existingOptions: any, newOptions: any) => merge({}, existingOptions, newOptions),\n};\n\n/*\n(ts-migrate) TODO: Migrate the remaining prop types\n...EditorPropTypes\n*/\ntype OwnProps = {\n  tabs?: {\n    key: string;\n    title: string | ((...args: any[]) => any);\n    isAvailable?: (...args: any[]) => any;\n    component: (...args: any[]) => any;\n  }[];\n};\n\nconst tabbedEditorDefaultProps = {\n  tabs: [],\n};\n\ntype Props = OwnProps & typeof tabbedEditorDefaultProps;\n\n// @ts-expect-error ts-migrate(2339) FIXME: Property 'options' does not exist on type 'Props'.\nexport function TabbedEditor({ tabs, options, data, onOptionsChange, ...restProps }: Props) {\n  const optionsChanged = (newOptions: any, updateStrategy = UpdateOptionsStrategy.deepMerge) => {\n    onOptionsChange(updateStrategy(options, newOptions));\n  };\n\n  // @ts-expect-error ts-migrate(2322) FIXME: Type '(number | ((() => string) & (() => string)) ... Remove this comment to see the full error message\n  tabs = filter(tabs, tab => (isFunction(tab.isAvailable) ? tab.isAvailable(options, data) : true));\n\n  return (\n    <Tabs animated={false} tabBarGutter={20}>\n      {map(tabs, ({ key, title, component: Component }) => (\n        <Tabs.TabPane\n          key={key}\n          tab={<span data-test={`VisualizationEditor.Tabs.${key}`}>{isFunction(title) ? title(options) : title}</span>}>\n          <Component options={options} data={data} onOptionsChange={optionsChanged} {...restProps} />\n        </Tabs.TabPane>\n      ))}\n    </Tabs>\n  );\n}\n\nTabbedEditor.defaultProps = tabbedEditorDefaultProps;\n\nexport default function createTabbedEditor(tabs: any) {\n  return function TabbedEditorWrapper(props: any) {\n    return <TabbedEditor {...props} tabs={tabs} />;\n  };\n}\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/index.ts",
    "content": "import AntSelect from \"antd/lib/select\";\nimport AntInput from \"antd/lib/input\";\nimport AntInputNumber from \"antd/lib/input-number\";\nimport Checkbox from \"antd/lib/checkbox\";\n\nimport RedashColorPicker from \"@/components/ColorPicker\";\nimport RedashTextAlignmentSelect from \"@/components/TextAlignmentSelect\";\n\nimport withControlLabel, { ControlLabel } from \"./withControlLabel\";\nimport createTabbedEditor from \"./createTabbedEditor\";\nimport Section from \"./Section\";\nimport Switch from \"./Switch\";\nimport TextArea from \"./TextArea\";\nimport ContextHelp from \"./ContextHelp\";\n\nexport { Section, ControlLabel, Checkbox, Switch, TextArea, ContextHelp, withControlLabel, createTabbedEditor };\nexport const Select = withControlLabel(AntSelect);\nexport const Input = withControlLabel(AntInput);\nexport const InputNumber = withControlLabel(AntInputNumber);\nexport const ColorPicker = withControlLabel(RedashColorPicker);\nexport const TextAlignmentSelect = withControlLabel(RedashTextAlignmentSelect);\n"
  },
  {
    "path": "viz-lib/src/components/visualizations/editor/withControlLabel.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport cx from \"classnames\";\nimport hoistNonReactStatics from \"hoist-non-react-statics\";\nimport * as Grid from \"antd/lib/grid\";\nimport Typography from \"antd/lib/typography\";\n\nimport \"./control-label.less\";\n\ntype OwnProps = {\n  layout?: \"vertical\" | \"horizontal\";\n  label?: React.ReactNode;\n  labelProps?: any;\n  disabled?: boolean;\n  children?: React.ReactNode;\n};\n\nconst controlLabelDefaultProps = {\n  layout: \"vertical\",\n  label: null,\n  disabled: false,\n  children: null,\n};\n\ntype Props = OwnProps & typeof controlLabelDefaultProps;\n\nexport function ControlLabel({ layout, label, labelProps, disabled, children }: Props) {\n  if (layout === \"vertical\" && label) {\n    return (\n      <div className=\"visualization-editor-control-label visualization-editor-control-label-vertical\">\n        <label {...labelProps}>\n          <Typography.Text disabled={disabled}>{label}</Typography.Text>\n        </label>\n        {children}\n      </div>\n    );\n  }\n\n  if (layout === \"horizontal\" && label) {\n    return (\n      <Grid.Row\n        className=\"visualization-editor-control-label visualization-editor-control-label-horizontal\"\n        // @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: Element[]; className: string; ty... Remove this comment to see the full error message\n        type=\"flex\"\n        align=\"middle\"\n        gutter={15}>\n        <Grid.Col span={12}>\n          <label {...labelProps}>\n            <Typography.Text disabled={disabled}>{label}</Typography.Text>\n          </label>\n        </Grid.Col>\n        <Grid.Col span={12}>{children}</Grid.Col>\n      </Grid.Row>\n    );\n  }\n\n  return children;\n}\n\nControlLabel.defaultProps = controlLabelDefaultProps;\n\nexport default function withControlLabel(WrappedControl: any) {\n  // eslint-disable-next-line react/prop-types\n  function ControlWrapper({ className, id, layout, label, labelProps, disabled, ...props }: any) {\n    const fallbackId = useMemo(\n      () =>\n        `visualization-editor-control-${Math.random()\n          .toString(36)\n          .substr(2, 10)}`,\n      []\n    );\n    labelProps = {\n      ...labelProps,\n      htmlFor: id || fallbackId,\n    };\n\n    return (\n      <ControlLabel layout={layout} label={label} labelProps={labelProps} disabled={disabled}>\n        {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message */}\n        <WrappedControl\n          className={cx(\"visualization-editor-input\", className)}\n          id={labelProps.htmlFor}\n          disabled={disabled}\n          {...props}\n        />\n      </ControlLabel>\n    );\n  }\n\n  // Copy static methods from `WrappedComponent`\n  hoistNonReactStatics(ControlWrapper, WrappedControl);\n\n  return ControlWrapper;\n}\n"
  },
  {
    "path": "viz-lib/src/index.ts",
    "content": "export * from \"./visualizations\";\nexport * from \"./visualizations/visualizationsSettings\";\nexport { VisualizationType } from \"./visualizations/prop-types\";\nexport {\n  default as registeredVisualizations,\n  getDefaultVisualization,\n  newVisualization,\n} from \"./visualizations/registeredVisualizations\";\n"
  },
  {
    "path": "viz-lib/src/lib/chooseTextColorForBackground.ts",
    "content": "import { maxBy } from \"lodash\";\nimport chroma from \"chroma-js\";\n\nexport default function chooseTextColorForBackground(backgroundColor: any, textColors = [\"#ffffff\", \"#333333\"]) {\n  try {\n    backgroundColor = chroma(backgroundColor);\n    return maxBy(textColors, color => chroma.contrast(backgroundColor, color));\n  } catch (e) {\n    return null;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/lib/hooks/useMemoWithDeepCompare.ts",
    "content": "import { isEqual } from \"lodash\";\nimport { useMemo, useRef } from \"react\";\n\nexport default function useMemoWithDeepCompare(create: any, inputs: any) {\n  const valueRef = useRef();\n  const value = useMemo(create, inputs);\n  if (!isEqual(value, valueRef.current)) {\n    // @ts-expect-error ts-migrate(2322) FIXME: Type 'unknown' is not assignable to type 'undefine... Remove this comment to see the full error message\n    valueRef.current = value;\n  }\n  return valueRef.current;\n}\n"
  },
  {
    "path": "viz-lib/src/lib/referenceCountingCache.ts",
    "content": "import { each, debounce } from \"lodash\";\n\nexport default function createReferenceCountingCache({ cleanupDelay = 2000 } = {}) {\n  const items = {};\n\n  const cleanup = debounce(() => {\n    each(items, (item, key) => {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'refCount' does not exist on type 'never'... Remove this comment to see the full error message\n      if (item.refCount <= 0) {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        delete items[key];\n      }\n    });\n  }, cleanupDelay);\n\n  function get(key: any, getter: any) {\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    if (!items[key]) {\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      items[key] = {\n        value: getter(),\n        refCount: 0,\n      };\n    }\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    const item = items[key];\n    item.refCount += 1;\n    return item.value;\n  }\n\n  function release(key: any) {\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    if (items[key]) {\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      const item = items[key];\n      if (item.refCount > 0) {\n        item.refCount -= 1;\n        if (item.refCount <= 0) {\n          cleanup();\n        }\n      }\n    }\n  }\n\n  return { get, release };\n}\n"
  },
  {
    "path": "viz-lib/src/lib/utils.ts",
    "content": "import moment from \"moment\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\n\nfunction formatDateTime(value: any) {\n  if (!value) {\n    return \"\";\n  }\n\n  const parsed = moment(value);\n  if (!parsed.isValid()) {\n    return \"-\";\n  }\n\n  return parsed.format(visualizationsSettings.dateTimeFormat);\n}\n\nfunction formatDate(value: any) {\n  if (!value) {\n    return \"\";\n  }\n\n  const parsed = moment(value);\n  if (!parsed.isValid()) {\n    return \"-\";\n  }\n\n  return parsed.format(visualizationsSettings.dateFormat);\n}\n\nexport function formatColumnValue(value: any, columnType = null) {\n  if (moment.isMoment(value)) {\n    if (columnType === \"date\") {\n      return formatDate(value);\n    }\n    return formatDateTime(value);\n  }\n\n  if (typeof value === \"boolean\") {\n    return value.toString();\n  }\n\n  return value;\n}\n"
  },
  {
    "path": "viz-lib/src/lib/value-format.tsx",
    "content": "import React from \"react\";\nimport ReactDOMServer from \"react-dom/server\";\nimport moment from \"moment/moment\";\nimport numeral from \"numeral\";\nimport { isString, isArray, isUndefined, isFinite, isNil, toString } from \"lodash\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\n\n\nnumeral.options.scalePercentBy100 = false;\n\n// eslint-disable-next-line\nconst urlPattern = /(^|[\\s\\n]|<br\\/?>)((?:https?|ftp):\\/\\/[\\-A-Z0-9+\\u0026\\u2019@#\\/%?=()~_|!:,.;]*[\\-A-Z0-9+\\u0026@#\\/%=~()_|])/gi;\n\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\n\nfunction NullValueComponent() {\n  return <span className=\"display-as-null\">{visualizationsSettings.nullValue}</span>;\n}\n\nexport function createTextFormatter(highlightLinks: any) {\n  if (highlightLinks) {\n    return (value: any) => {\n      if (value === null) {\n        return <NullValueComponent/>\n      }\n      if (isString(value)) {\n        const Link = visualizationsSettings.LinkComponent;\n        value = value.replace(urlPattern, (unused, prefix, href) => {\n          const link = ReactDOMServer.renderToStaticMarkup(\n            <Link href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n              {href}\n            </Link>\n          );\n          return prefix + link;\n        });\n      }\n      return toString(value);\n    };\n  }\n  return (value: any) => value === null ? <NullValueComponent/> : toString(value);\n}\n\nfunction toMoment(value: any) {\n  if (moment.isMoment(value)) {\n    return value;\n  }\n  if (isFinite(value)) {\n    return moment(value);\n  }\n  // same as default `moment(value)`, but avoid fallback to `new Date()`\n  return moment(toString(value), [moment.ISO_8601, moment.RFC_2822]);\n}\n\nexport function createDateTimeFormatter(format: any) {\n  if (isString(format) && format !== \"\") {\n    return (value: any) => {\n      if (value === null) {\n        return <NullValueComponent/>;\n      }\n      const wrapped = toMoment(value);\n      return wrapped.isValid() ? wrapped.format(format) : toString(value);\n    };\n  }\n  return (value: any) => value === null ? <NullValueComponent/> : toString(value);\n}\n\nexport function createBooleanFormatter(values: any) {\n  if (isArray(values)) {\n    if (values.length >= 2) {\n      // Both `true` and `false` specified\n      return (value: any) => {\n        if (value === null) {\n          return <NullValueComponent/>;\n        }\n        if (isNil(value)) {\n          return \"\";\n        }\n        return \"\" + values[value ? 1 : 0];\n      };\n    } else if (values.length === 1) {\n      // Only `true`\n      return (value: any) => (value ? values[0] : \"\");\n    }\n  }\n  return (value: any) => {\n    if (value === null) {\n      return <NullValueComponent/>;\n    }\n    if (isNil(value)) {\n      return \"\";\n    }\n    return value ? \"true\" : \"false\";\n  };\n}\n\nexport function createNumberFormatter(format: any, canReturnHTMLElement: boolean = false) {\n  if (isString(format) && format !== \"\") {\n    const n = numeral(0); // cache `numeral` instance\n    return (value: any) => {\n        if (canReturnHTMLElement && value === null) {\n            return <NullValueComponent/>;\n        }\n        if (value === \"\" || value === null) {\n            return \"\";\n        }\n        return n.set(value).format(format);\n    }\n  }\n  return (value: any) => (canReturnHTMLElement && value === null) ? <NullValueComponent/> : toString(value);\n}\n\nexport function formatSimpleTemplate(str: any, data: any) {\n  if (!isString(str)) {\n    return \"\";\n  }\n  return str.replace(/{{\\s*([^\\s]+?)\\s*}}/g, (match, prop) => {\n    if (hasOwnProperty.call(data, prop) && !isUndefined(data[prop])) {\n      return data[prop];\n    }\n    return match;\n  });\n}\n"
  },
  {
    "path": "viz-lib/src/services/resizeObserver.ts",
    "content": "const items = new Map();\n\nfunction checkItems() {\n  if (items.size > 0) {\n    items.forEach((item, node) => {\n      const bounds = node.getBoundingClientRect();\n      // convert to int (because these numbers needed for comparisons), but preserve 1 decimal point\n      const width = Math.round(bounds.width * 10);\n      const height = Math.round(bounds.height * 10);\n\n      if (item.width !== width || item.height !== height) {\n        item.width = width;\n        item.height = height;\n        item.callback(node);\n      }\n    });\n\n    setTimeout(checkItems, 100);\n  }\n}\n\nexport default function observe(node: any, callback: any) {\n  if (node && !items.has(node)) {\n    const shouldTrigger = items.size === 0;\n    items.set(node, { callback });\n    if (shouldTrigger) {\n      checkItems();\n    }\n    return () => items.delete(node);\n  }\n  return () => {};\n}\n"
  },
  {
    "path": "viz-lib/src/services/sanitize.ts",
    "content": "import { isString } from \"lodash\";\nimport DOMPurify from \"dompurify\";\n\nDOMPurify.setConfig({\n  ADD_ATTR: [\"target\"],\n});\n\nDOMPurify.addHook(\"afterSanitizeAttributes\", function(node) {\n  // Fix elements with `target` attribute:\n  // - allow only `target=\"_blank\"\n  // - add `rel=\"noopener noreferrer\"` to prevent https://www.owasp.org/index.php/Reverse_Tabnabbing\n\n  const target = node.getAttribute(\"target\");\n  if (isString(target) && target.toLowerCase() === \"_blank\") {\n    node.setAttribute(\"rel\", \"noopener noreferrer\");\n  } else {\n    node.removeAttribute(\"target\");\n  }\n});\n\nexport { DOMPurify };\n\nexport default DOMPurify.sanitize;\n"
  },
  {
    "path": "viz-lib/src/visualizations/ColorPalette.ts",
    "content": "import { values } from \"lodash\";\n\n// Define color palettes\nexport const BaseColors = {\n  Blue: \"#356AFF\",\n  Red: \"#E92828\",\n  Green: \"#3BD973\",\n  Purple: \"#604FE9\",\n  Cyan: \"#50F5ED\",\n  Orange: \"#FB8D3D\",\n  \"Light Blue\": \"#799CFF\",\n  Lilac: \"#B554FF\",\n  \"Light Green\": \"#8CFFB4\",\n  Brown: \"#A55F2A\",\n  Black: \"#000000\",\n  Gray: \"#494949\",\n  Pink: \"#FF7DE3\",\n  \"Dark Blue\": \"#002FB4\",\n};\n\n// Additional colors for the user to choose from\nexport const AdditionalColors = {\n  \"Indian Red\": \"#981717\",\n  \"Green 2\": \"#17BF51\",\n  \"Green 3\": \"#049235\",\n  \"Dark Turquoise\": \"#00B6EB\",\n  \"Dark Violet\": \"#A58AFF\",\n  \"Pink 2\": \"#C63FA9\",\n};\n\nconst Viridis = {\n  1: '#440154',\n  2: '#48186a',\n  3: '#472d7b',\n  4: '#424086',\n  5: '#3b528b',\n  6: '#33638d',\n  7: '#2c728e',\n  8: '#26828e',\n  9: '#21918c',\n  10: '#1fa088',\n  11: '#28ae80',\n  12: '#3fbc73',\n  13: '#5ec962',\n  14: '#84d44b',\n  15: '#addc30',\n  16: '#d8e219',\n  17: '#fde725',\n};\n\nconst Tableau = {\n  1 : \"#4e79a7\",\n  2 : \"#f28e2c\",\n  3 : \"#e15759\",\n  4 : \"#76b7b2\",\n  5 : \"#59a14f\",\n  6 : \"#edc949\",\n  7 : \"#af7aa1\",\n  8 : \"#ff9da7\",\n  9 : \"#9c755f\",\n  10 : \"#bab0ab\",\n}\n\nconst D3Category10 = {\n  1 : \"#1f77b4\",\n  2 : \"#ff7f0e\",\n  3 : \"#2ca02c\",\n  4 : \"#d62728\",\n  5 : \"#9467bd\",\n  6 : \"#8c564b\",\n  7 : \"#e377c2\",\n  8 : \"#7f7f7f\",\n  9 : \"#bcbd22\",\n  10 : \"#17becf\",\n}\n\nlet ColorPalette = {\n  ...BaseColors,\n  ...AdditionalColors,\n};\n\nexport const ColorPaletteArray = values(ColorPalette);\n\nexport default ColorPalette;\n\nexport const AllColorPalettes = {\n  \"Redash\" : ColorPalette,\n  \"Viridis\" : Viridis,\n  \"Tableau 10\" : Tableau,\n  \"D3 Category 10\" : D3Category10,\n}\n\nexport const AllColorPaletteArrays = {\n  \"Redash\" : ColorPaletteArray,\n  \"Viridis\" : values(Viridis),\n  \"Tableau 10\" : values(Tableau),\n  \"D3 Category 10\" : values(D3Category10),\n};\n\nexport const ColorPaletteTypes = {\n  \"Redash\" : 'discrete',\n  \"Viridis\" : 'continuous',\n  \"Tableau 10\" : 'discrete',\n  \"D3 Category 10\" : 'discrete',\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/Editor.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport registeredVisualizations from \"@/visualizations/registeredVisualizations\";\n\n/*\n(ts-migrate) TODO: Migrate the remaining prop types\n...EditorPropTypes\n*/\ntype Props = {\n  type: string;\n} & typeof EditorPropTypes;\n\nexport default function Editor({ type, options: optionsProp, data, ...otherProps }: Props) {\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  const { Editor, getOptions } = registeredVisualizations[type];\n  const options = useMemo(() => getOptions(optionsProp, data), [optionsProp, data]);\n\n  return <Editor options={options} data={data} {...otherProps} />;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/Renderer.tsx",
    "content": "import { isEqual } from \"lodash\";\nimport React, { useEffect, useRef } from \"react\";\nimport ErrorBoundary, { ErrorMessage } from \"@/components/ErrorBoundary\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\nimport registeredVisualizations from \"@/visualizations/registeredVisualizations\";\n\n/*\n(ts-migrate) TODO: Migrate the remaining prop types\n...RendererPropTypes\n*/\ntype Props = {\n  type: string;\n  addonBefore?: React.ReactNode;\n  addonAfter?: React.ReactNode;\n} & typeof RendererPropTypes;\n\nexport default function Renderer({\n  type,\n  data,\n  options: optionsProp,\n  visualizationName,\n  addonBefore,\n  addonAfter,\n  ...otherProps\n}: Props) {\n  const lastOptions = useRef();\n  const errorHandlerRef = useRef();\n\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  const { Renderer, getOptions } = registeredVisualizations[type];\n\n  // Avoid unnecessary updates (which may be expensive or cause issues with\n  // internal state of some visualizations like Table) - compare options deeply\n  // and use saved reference if nothing changed\n  // More details: https://github.com/getredash/redash/pull/3963#discussion_r306935810\n  let options = getOptions(optionsProp, data);\n  if (isEqual(lastOptions.current, options)) {\n    options = lastOptions.current;\n  }\n  lastOptions.current = options;\n\n  useEffect(() => {\n    if (errorHandlerRef.current) {\n      // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.\n      errorHandlerRef.current.reset();\n    }\n  }, [optionsProp, data]);\n\n  return (\n    <div className=\"visualization-renderer\">\n      {addonBefore}\n      {/* @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call. */}\n      <ErrorBoundary\n        ref={errorHandlerRef}\n        renderError={() => <ErrorMessage>Error while rendering visualization.</ErrorMessage>}>\n        <div className=\"visualization-renderer-wrapper\">\n          <Renderer options={options} data={data} visualizationName={visualizationName} {...otherProps} />\n        </div>\n      </ErrorBoundary>\n      {addonAfter}\n    </div>\n  );\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/box-plot/Editor.tsx",
    "content": "import React from \"react\";\nimport { Section, Input } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function Editor({ options, onOptionsChange }: any) {\n  const onXAxisLabelChanged = (xAxisLabel: any) => {\n    const newOptions = { ...options, xAxisLabel };\n    onOptionsChange(newOptions);\n  };\n\n  const onYAxisLabelChanged = (yAxisLabel: any) => {\n    const newOptions = { ...options, yAxisLabel };\n    onOptionsChange(newOptions);\n  };\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label=\"X Axis Label\"\n          data-test=\"BoxPlot.XAxisLabel\"\n          value={options.xAxisLabel}\n          onChange={(event: any) => onXAxisLabelChanged(event.target.value)}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label=\"Y Axis Label\"\n          data-test=\"BoxPlot.YAxisLabel\"\n          value={options.yAxisLabel}\n          onChange={(event: any) => onYAxisLabelChanged(event.target.value)}\n        />\n      </Section>\n    </React.Fragment>\n  );\n}\n\nEditor.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/box-plot/Renderer.tsx",
    "content": "import { map, each } from \"lodash\";\nimport d3 from \"d3\";\nimport React, { useState, useEffect } from \"react\";\nimport resizeObserver from \"@/services/resizeObserver\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\nimport box from \"./d3box\";\nimport \"./renderer.less\";\n\nfunction calcIqr(k: any) {\n  return (d: any) => {\n    const q1 = d.quartiles[0];\n    const q3 = d.quartiles[2];\n    const iqr = (q3 - q1) * k;\n\n    let i = -1;\n    let j = d.length;\n\n    i += 1;\n    while (d[i] < q1 - iqr) {\n      i += 1;\n    }\n\n    j -= 1;\n    while (d[j] > q3 + iqr) {\n      j -= 1;\n    }\n\n    return [i, j];\n  };\n}\n\nfunction render(container: any, data: any, { xAxisLabel, yAxisLabel }: any) {\n  container = d3.select(container);\n\n  const containerBounds = container.node().getBoundingClientRect();\n  const containerWidth = Math.floor(containerBounds.width);\n  const containerHeight = Math.floor(containerBounds.height);\n\n  const margin = {\n    top: 10,\n    right: 50,\n    bottom: 40,\n    left: 50,\n    inner: 25,\n  };\n  const width = containerWidth - margin.right - margin.left;\n  const height = containerHeight - margin.top - margin.bottom;\n\n  let min = Infinity;\n  let max = -Infinity;\n  const mydata: any = [];\n  let value = 0;\n  let d = [];\n\n  const columns = map(data.columns, col => col.name);\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'scale' does not exist on type 'typeof im... Remove this comment to see the full error message\n  const xscale = d3.scale\n    .ordinal()\n    .domain(columns)\n    .rangeBands([0, containerWidth - margin.left - margin.right]);\n\n  let boxWidth;\n  if (columns.length > 1) {\n    boxWidth = Math.min(xscale(columns[1]), 120.0);\n  } else {\n    boxWidth = 120.0;\n  }\n  margin.inner = boxWidth / 3.0;\n\n  each(columns, (column, i) => {\n    d = mydata[i] = [];\n    each(data.rows, row => {\n      value = row[column];\n      d.push(value);\n      if (value > max) max = Math.ceil(value);\n      if (value < min) min = Math.floor(value);\n    });\n  });\n\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'scale' does not exist on type 'typeof im... Remove this comment to see the full error message\n  const yscale = d3.scale\n    .linear()\n    .domain([min * 0.99, max * 1.01])\n    .range([height, 0]);\n\n  const chart = box()\n    .whiskers(calcIqr(1.5))\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'width' does not exist on type '{ (g: any... Remove this comment to see the full error message\n    .width(boxWidth - 2 * margin.inner)\n    .height(height)\n    .domain([min * 0.99, max * 1.01]);\n  const xAxis = d3.svg\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'axis' does not exist on type '(url: stri... Remove this comment to see the full error message\n    .axis()\n    .scale(xscale)\n    .orient(\"bottom\");\n\n  const yAxis = d3.svg\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'axis' does not exist on type '(url: stri... Remove this comment to see the full error message\n    .axis()\n    .scale(yscale)\n    .orient(\"left\");\n\n  const xLines = d3.svg\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'axis' does not exist on type '(url: stri... Remove this comment to see the full error message\n    .axis()\n    .scale(xscale)\n    .tickSize(height)\n    .orient(\"bottom\");\n\n  const yLines = d3.svg\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'axis' does not exist on type '(url: stri... Remove this comment to see the full error message\n    .axis()\n    .scale(yscale)\n    .tickSize(width)\n    .orient(\"right\");\n\n  function barOffset(i: any) {\n    return xscale(columns[i]) + (xscale(columns[1]) - margin.inner) / 2.0;\n  }\n\n  container.selectAll(\"*\").remove();\n\n  const svg = container\n    .append(\"svg\")\n    .attr(\"width\", containerWidth)\n    .attr(\"height\", height + margin.bottom + margin.top);\n\n  const plot = svg\n    .append(\"g\")\n    .attr(\"width\", containerWidth - margin.left - margin.right)\n    .attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n  svg\n    .append(\"text\")\n    .attr(\"class\", \"box\")\n    .attr(\"x\", containerWidth / 2.0)\n    .attr(\"text-anchor\", \"middle\")\n    .attr(\"y\", height + margin.bottom)\n    .text(xAxisLabel);\n\n  svg\n    .append(\"text\")\n    .attr(\"class\", \"box\")\n    .attr(\"transform\", `translate(10,${(height + margin.top + margin.bottom) / 2.0})rotate(-90)`)\n    .attr(\"text-anchor\", \"middle\")\n    .text(yAxisLabel);\n\n  plot\n    .append(\"rect\")\n    .attr(\"class\", \"grid-background\")\n    .attr(\"width\", width)\n    .attr(\"height\", height);\n\n  plot\n    .append(\"g\")\n    .attr(\"class\", \"grid\")\n    .call(yLines);\n\n  plot\n    .append(\"g\")\n    .attr(\"class\", \"grid\")\n    .call(xLines);\n\n  plot\n    .append(\"g\")\n    .attr(\"class\", \"x axis\")\n    .attr(\"transform\", `translate(0,${height})`)\n    .call(xAxis);\n\n  plot\n    .append(\"g\")\n    .attr(\"class\", \"y axis\")\n    .call(yAxis);\n\n  plot\n    .selectAll(\".box\")\n    .data(mydata)\n    .enter()\n    .append(\"g\")\n    .attr(\"class\", \"box\")\n    .attr(\"width\", boxWidth)\n    .attr(\"height\", height)\n    .attr(\"transform\", (_: any, i: any) => `translate(${barOffset(i)},${0})`)\n    .call(chart);\n}\n\nexport default function Renderer({ data, options }: any) {\n  const [container, setContainer] = useState(null);\n\n  useEffect(() => {\n    if (container) {\n      render(container, data, options);\n      const unwatch = resizeObserver(container, () => {\n        render(container, data, options);\n      });\n      return unwatch;\n    }\n  }, [container, data, options]);\n\n  // @ts-expect-error ts-migrate(2322) FIXME: Type 'Dispatch<SetStateAction<null>>' is not assig... Remove this comment to see the full error message\n  return <div className=\"box-plot-deprecated-visualization-container\" ref={setContainer} />;\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/box-plot/d3box.ts",
    "content": "/* eslint-disable */\n// Inspired by http://informationandvisualization.de/blog/box-plot\n// d3 v3 is provided as a global by the caller; using `any` since @types/d3 targets v6+\ndeclare const d3: any;\n\nfunction box() {\n  let width = 1,\n    height = 1,\n    duration = 0,\n    domain: any = null,\n    value = Number,\n    whiskers = boxWhiskers,\n    quartiles = boxQuartiles,\n    tickFormat: any = null;\n\n  // For each small multiple…\n  function box(g: any) {\n    g.each(function(d: any, i: any) {\n      d = d.map(value).sort(d3.ascending);\n      // @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message\n      let g = d3.select(this),\n        n = d.length,\n        min = d[0],\n        max = d[n - 1];\n\n      // Compute quartiles. Must return exactly 3 elements.\n      const quartileData = (d.quartiles = quartiles(d));\n\n      // Compute whiskers. Must return exactly 2 elements, or null.\n      // @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message\n      let whiskerIndices = whiskers && whiskers.call(this, d, i),\n        whiskerData = whiskerIndices && whiskerIndices.map(i => d[i]);\n\n      // Compute outliers. If no whiskers are specified, all data are \"outliers\".\n      // We compute the outliers as indices, so that we can join across transitions!\n      const outlierIndices = whiskerIndices\n        ? d3.range(0, whiskerIndices[0]).concat(d3.range(whiskerIndices[1] + 1, n))\n        : d3.range(n);\n\n      // Compute the new x-scale.\n      const x1 = d3.scale\n        .linear()\n        // @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message\n        .domain((domain && domain.call(this, d, i)) || [min, max])\n        .range([height, 0]);\n\n      // Retrieve the old x-scale, if this is an update.\n      const x0 =\n        // @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message\n        this.__chart__ ||\n        d3.scale\n          .linear()\n          .domain([0, Infinity])\n          .range(x1.range());\n\n      // Stash the new scale.\n      // @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message\n      this.__chart__ = x1;\n\n      // Note: the box, median, and box tick elements are fixed in number,\n      // so we only have to handle enter and update. In contrast, the outliers\n      // and other elements are variable, so we need to exit them! Variable\n      // elements also fade in and out.\n\n      // Update center line: the vertical line spanning the whiskers.\n      const center = g.selectAll(\"line.center\").data(whiskerData ? [whiskerData] : []);\n\n      center\n        .enter()\n        .insert(\"line\", \"rect\")\n        .attr(\"class\", \"center\")\n        .attr(\"x1\", width / 2)\n        .attr(\"y1\", (d: any) => x0(d[0]))\n        .attr(\"x2\", width / 2)\n        .attr(\"y2\", (d: any) => x0(d[1]))\n        .style(\"opacity\", 1e-6)\n        .transition()\n        .duration(duration)\n        .style(\"opacity\", 1)\n        .attr(\"y1\", (d: any) => x1(d[0]))\n        .attr(\"y2\", (d: any) => x1(d[1]));\n\n      center\n        .transition()\n        .duration(duration)\n        .style(\"opacity\", 1)\n        .attr(\"y1\", (d: any) => x1(d[0]))\n        .attr(\"y2\", (d: any) => x1(d[1]));\n\n      center\n        .exit()\n        .transition()\n        .duration(duration)\n        .style(\"opacity\", 1e-6)\n        .attr(\"y1\", (d: any) => x1(d[0]))\n        .attr(\"y2\", (d: any) => x1(d[1]))\n        .remove();\n\n      // Update innerquartile box.\n      const box = g.selectAll(\"rect.box\").data([quartileData]);\n\n      box\n        .enter()\n        .append(\"rect\")\n        .attr(\"class\", \"box\")\n        .attr(\"x\", 0)\n        .attr(\"y\", (d: any) => x0(d[2]))\n        .attr(\"width\", width)\n        .attr(\"height\", (d: any) => x0(d[0]) - x0(d[2]))\n        .transition()\n        .duration(duration)\n        .attr(\"y\", (d: any) => x1(d[2]))\n        .attr(\"height\", (d: any) => x1(d[0]) - x1(d[2]));\n\n      box\n        .transition()\n        .duration(duration)\n        .attr(\"y\", (d: any) => x1(d[2]))\n        .attr(\"height\", (d: any) => x1(d[0]) - x1(d[2]));\n\n      box.exit().remove();\n\n      // Update median line.\n      const medianLine = g.selectAll(\"line.median\").data([quartileData[1]]);\n\n      medianLine\n        .enter()\n        .append(\"line\")\n        .attr(\"class\", \"median\")\n        .attr(\"x1\", 0)\n        .attr(\"y1\", x0)\n        .attr(\"x2\", width)\n        .attr(\"y2\", x0)\n        .transition()\n        .duration(duration)\n        .attr(\"y1\", x1)\n        .attr(\"y2\", x1);\n\n      medianLine\n        .transition()\n        .duration(duration)\n        .attr(\"y1\", x1)\n        .attr(\"y2\", x1);\n\n      medianLine.exit().remove();\n\n      // Update whiskers.\n      const whisker = g.selectAll(\"line.whisker\").data(whiskerData || []);\n\n      whisker\n        .enter()\n        .insert(\"line\", \"circle, text\")\n        .attr(\"class\", \"whisker\")\n        .attr(\"x1\", 0)\n        .attr(\"y1\", x0)\n        .attr(\"x2\", width)\n        .attr(\"y2\", x0)\n        .style(\"opacity\", 1e-6)\n        .transition()\n        .duration(duration)\n        .attr(\"y1\", x1)\n        .attr(\"y2\", x1)\n        .style(\"opacity\", 1);\n\n      whisker\n        .transition()\n        .duration(duration)\n        .attr(\"y1\", x1)\n        .attr(\"y2\", x1)\n        .style(\"opacity\", 1);\n\n      whisker\n        .exit()\n        .transition()\n        .duration(duration)\n        .attr(\"y1\", x1)\n        .attr(\"y2\", x1)\n        .style(\"opacity\", 1e-6)\n        .remove();\n\n      // Update outliers.\n      const outlier = g.selectAll(\"circle.outlier\").data(outlierIndices, Number);\n\n      outlier\n        .enter()\n        .insert(\"circle\", \"text\")\n        .attr(\"class\", \"outlier\")\n        .attr(\"r\", 5)\n        .attr(\"cx\", width / 2)\n        .attr(\"cy\", (i: any) => x0(d[i]))\n        .style(\"opacity\", 1e-6)\n        .transition()\n        .duration(duration)\n        .attr(\"cy\", (i: any) => x1(d[i]))\n        .style(\"opacity\", 1);\n\n      outlier\n        .transition()\n        .duration(duration)\n        .attr(\"cy\", (i: any) => x1(d[i]))\n        .style(\"opacity\", 1);\n\n      outlier\n        .exit()\n        .transition()\n        .duration(duration)\n        .attr(\"cy\", (i: any) => x1(d[i]))\n        .style(\"opacity\", 1e-6)\n        .remove();\n\n      // Compute the tick format.\n      const format = tickFormat || x1.tickFormat(8);\n\n      // Update box ticks.\n      const boxTick = g.selectAll(\"text.box\").data(quartileData);\n\n      boxTick\n        .enter()\n        .append(\"text\")\n        .attr(\"class\", \"box\")\n        .attr(\"dy\", \".3em\")\n        .attr(\"dx\", (d: any, i: any) => (i & 1 ? 6 : -6))\n        .attr(\"x\", (d: any, i: any) => (i & 1 ? width : 0))\n        .attr(\"y\", x0)\n        .attr(\"text-anchor\", (d: any, i: any) => (i & 1 ? \"start\" : \"end\"))\n        .text(format)\n        .transition()\n        .duration(duration)\n        .attr(\"y\", x1);\n\n      boxTick\n        .transition()\n        .duration(duration)\n        .text(format)\n        .attr(\"y\", x1);\n\n      boxTick.exit().remove();\n\n      // Update whisker ticks. These are handled separately from the box\n      // ticks because they may or may not exist, and we want don't want\n      // to join box ticks pre-transition with whisker ticks post-.\n      const whiskerTick = g.selectAll(\"text.whisker\").data(whiskerData || []);\n\n      whiskerTick\n        .enter()\n        .append(\"text\")\n        .attr(\"class\", \"whisker\")\n        .attr(\"dy\", \".3em\")\n        .attr(\"dx\", 6)\n        .attr(\"x\", width)\n        .attr(\"y\", x0)\n        .text(format)\n        .style(\"opacity\", 1e-6)\n        .transition()\n        .duration(duration)\n        .attr(\"y\", x1)\n        .style(\"opacity\", 1);\n\n      whiskerTick\n        .transition()\n        .duration(duration)\n        .text(format)\n        .attr(\"y\", x1)\n        .style(\"opacity\", 1);\n\n      whiskerTick\n        .exit()\n        .transition()\n        .duration(duration)\n        .attr(\"y\", x1)\n        .style(\"opacity\", 1e-6)\n        .remove();\n    });\n    d3.timer.flush();\n  }\n\n  box.width = function(x: any) {\n    if (!arguments.length) return width;\n    width = x;\n    return box;\n  };\n\n  box.height = function(x: any) {\n    if (!arguments.length) return height;\n    height = x;\n    return box;\n  };\n\n  box.tickFormat = function(x: any) {\n    if (!arguments.length) return tickFormat;\n    tickFormat = x;\n    return box;\n  };\n\n  box.duration = function(x: any) {\n    if (!arguments.length) return duration;\n    duration = x;\n    return box;\n  };\n\n  box.domain = function(x: any) {\n    if (!arguments.length) return domain;\n    domain = x == null ? x : d3.functor(x);\n    return box;\n  };\n\n  box.value = function(x: any) {\n    if (!arguments.length) return value;\n    value = x;\n    return box;\n  };\n\n  box.whiskers = function(x: any) {\n    if (!arguments.length) return whiskers;\n    whiskers = x;\n    return box;\n  };\n\n  box.quartiles = function(x: any) {\n    if (!arguments.length) return quartiles;\n    quartiles = x;\n    return box;\n  };\n\n  return box;\n}\n\nfunction boxWhiskers(d: any) {\n  return [0, d.length - 1];\n}\n\nfunction boxQuartiles(d: any) {\n  return [d3.quantile(d, 0.25), d3.quantile(d, 0.5), d3.quantile(d, 0.75)];\n}\n\nexport default box;\n"
  },
  {
    "path": "viz-lib/src/visualizations/box-plot/index.ts",
    "content": "import Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nexport default {\n  type: \"BOXPLOT\",\n  name: \"Boxplot (Deprecated)\",\n  isDeprecated: true,\n  getOptions: (options: any) => ({\n    ...options,\n  }),\n  Renderer,\n  Editor,\n\n  defaultRows: 8,\n  minRows: 5,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/box-plot/renderer.less",
    "content": ".box-plot-deprecated-visualization-container {\n  overflow: hidden;\n  height: 500px;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/AxisSettings.tsx",
    "content": "import { isString, isObject, isFinite, isNumber, merge } from \"lodash\";\nimport React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport * as Grid from \"antd/lib/grid\";\nimport { Section, Select, Input, InputNumber, ContextHelp } from \"@/components/visualizations/editor\";\n\nfunction toNumber(value: any) {\n  value = isNumber(value) ? value : parseFloat(value);\n  return isFinite(value) ? value : null;\n}\n\ntype OwnProps = {\n  id: string;\n  options: {\n    type: string;\n    title?: {\n      text?: string;\n    };\n    rangeMin?: number;\n    rangeMax?: number;\n    tickFormat?: string;\n  };\n  features?: {\n    autoDetectType?: boolean;\n    range?: boolean;\n  };\n  onChange?: (...args: any[]) => any;\n};\n\nconst axisSettingsDefaultProps = {\n  features: {},\n  onChange: () => {},\n};\n\ntype Props = OwnProps & typeof axisSettingsDefaultProps;\n\nexport default function AxisSettings({ id, options, features, onChange }: Props) {\n  function optionsChanged(newOptions: any) {\n    onChange(merge({}, options, newOptions));\n  }\n\n  const [handleNameChange] = useDebouncedCallback(text => {\n    const title = isString(text) && text !== \"\" ? { text } : null;\n    optionsChanged({ title });\n  }, 200);\n\n  const [handleMinMaxChange] = useDebouncedCallback(opts => optionsChanged(opts), 200);\n\n  const [handleTickFormatChange] = useDebouncedCallback(opts => optionsChanged(opts), 200);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Scale\"\n          data-test={`Chart.${id}.Type`}\n          defaultValue={options.type}\n          onChange={(type: any) => optionsChanged({ type })}>\n          {features.autoDetectType && (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option value=\"-\" data-test={`Chart.${id}.Type.Auto`}>\n              Auto Detect\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          )}\n          {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          <Select.Option value=\"datetime\" data-test={`Chart.${id}.Type.DateTime`}>\n            Datetime\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          </Select.Option>\n          {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          <Select.Option value=\"linear\" data-test={`Chart.${id}.Type.Linear`}>\n            Linear\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          </Select.Option>\n          {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          <Select.Option value=\"logarithmic\" data-test={`Chart.${id}.Type.Logarithmic`}>\n            Logarithmic\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          </Select.Option>\n          {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          <Select.Option value=\"category\" data-test={`Chart.${id}.Type.Category`}>\n            Category\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          </Select.Option>\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label=\"Name\"\n          data-test={`Chart.${id}.Name`}\n          defaultValue={isObject(options.title) ? options.title.text : null}\n          onChange={(event: any) => handleNameChange(event.target.value)}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label={\n            <React.Fragment>\n              Tick Format\n              <ContextHelp.TickFormatSpecs />\n            </React.Fragment>\n          }\n          data-test={`Chart.${id}.TickFormat`}\n          defaultValue={options.tickFormat}\n          onChange={(event: any) => handleTickFormatChange({ tickFormat: event.target.value })}\n        />\n      </Section>\n\n      {features.range && (\n        // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n        <Section>\n          {/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: Element[]; gutter: number; type:... Remove this comment to see the full error message */}\n          <Grid.Row gutter={15} type=\"flex\" align=\"middle\">\n            <Grid.Col span={12}>\n              <InputNumber\n                label=\"Min Value\"\n                placeholder=\"Auto\"\n                data-test={`Chart.${id}.RangeMin`}\n                defaultValue={toNumber(options.rangeMin)}\n                onChange={(value: any) => handleMinMaxChange({ rangeMin: toNumber(value) })}\n              />\n            </Grid.Col>\n            <Grid.Col span={12}>\n              <InputNumber\n                label=\"Max Value\"\n                placeholder=\"Auto\"\n                data-test={`Chart.${id}.RangeMax`}\n                defaultValue={toNumber(options.rangeMax)}\n                onChange={(value: any) => handleMinMaxChange({ rangeMax: toNumber(value) })}\n              />\n            </Grid.Col>\n          </Grid.Row>\n        </Section>\n      )}\n    </React.Fragment>\n  );\n}\n\nAxisSettings.defaultProps = axisSettingsDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/ChartTypeSelect.tsx",
    "content": "import { filter, includes, map } from \"lodash\";\nimport React, { useMemo } from \"react\";\nimport { Select } from \"@/components/visualizations/editor\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\n\nconst allChartTypes = [\n  { type: \"line\", name: \"Line\", icon: \"line-chart\" },\n  { type: \"column\", name: \"Bar\", icon: \"bar-chart\" },\n  { type: \"area\", name: \"Area\", icon: \"area-chart\" },\n  { type: \"pie\", name: \"Pie\", icon: \"pie-chart\" },\n  { type: \"scatter\", name: \"Scatter\", icon: \"circle-o\" },\n  { type: \"bubble\", name: \"Bubble\", icon: \"circle-o\" },\n  { type: \"heatmap\", name: \"Heatmap\", icon: \"th\" },\n  { type: \"box\", name: \"Box\", icon: \"square-o\" },\n];\n\ntype OwnProps = {\n  hiddenChartTypes?: any[]; // TODO: PropTypes.oneOf(map(allChartTypes, \"type\"))\n};\n\nconst chartTypeSelectDefaultProps = {\n  hiddenChartTypes: [],\n};\n\ntype Props = OwnProps & typeof chartTypeSelectDefaultProps;\n\nexport default function ChartTypeSelect({ hiddenChartTypes, ...props }: Props) {\n  const chartTypes = useMemo(() => {\n    const result = [...allChartTypes];\n\n    if (visualizationsSettings.allowCustomJSVisualizations) {\n      result.push({ type: \"custom\", name: \"Custom\", icon: \"code\" });\n    }\n\n    if (hiddenChartTypes.length > 0) {\n      return filter(result, ({ type }) => !includes(hiddenChartTypes, type));\n    }\n\n    return result;\n  }, []);\n\n  return (\n    <Select {...props}>\n      {map(chartTypes, ({ type, name, icon }) => (\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n        <Select.Option key={type} value={type} data-test={`Chart.ChartType.${type}`}>\n          <i className={`fa fa-${icon}`} style={{ marginRight: 5 }} />\n          {name}\n          {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n        </Select.Option>\n      ))}\n    </Select>\n  );\n}\n\nChartTypeSelect.defaultProps = chartTypeSelectDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/ColorsSettings.test.tsx",
    "content": "import { after } from \"lodash\";\nimport React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport getOptions from \"../getOptions\";\nimport ColorsSettings from \"./ColorsSettings\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(options: any, done: any) {\n  options = getOptions(options);\n  return enzyme.mount(\n    <ColorsSettings\n      visualizationName=\"Test\"\n      data={{\n        columns: [\n          { name: \"a\", type: \"string\" },\n          { name: \"b\", type: \"number\" },\n        ],\n        rows: [{ a: \"v\", b: 3.14 }],\n      }}\n      options={options}\n      onOptionsChange={(changedOptions: any) => {\n        expect(changedOptions).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Chart -> Editor -> Colors Settings\", () => {\n  describe(\"for pie\", () => {\n    test(\"Changes series color\", done => {\n      const el = mount(\n        {\n          globalSeriesType: \"pie\",\n          columnMapping: { a: \"x\", b: \"y\" },\n        },\n        done\n      );\n\n      findByTestID(el, \"Chart.Series.v.Color\")\n        .find(\".color-picker-trigger\")\n        .last()\n        .simulate(\"click\");\n      findByTestID(el, \"ColorPicker\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"red\" } });\n    });\n  });\n\n  describe(\"for heatmap\", () => {\n    test(\"Changes color scheme\", done => {\n      const el = mount(\n        {\n          globalSeriesType: \"heatmap\",\n          columnMapping: { a: \"x\", b: \"y\" },\n        },\n        done\n      );\n\n      findByTestID(el, \"Chart.Colors.Heatmap.ColorScheme\")\n        .last()\n        .simulate(\"mouseDown\");\n      findByTestID(el, \"Chart.Colors.Heatmap.ColorScheme.Blues\")\n        .last()\n        .simulate(\"click\");\n    });\n\n    test(\"Sets custom color scheme\", done => {\n      const el = mount(\n        {\n          globalSeriesType: \"heatmap\",\n          columnMapping: { a: \"x\", b: \"y\" },\n          colorScheme: \"Custom...\",\n        },\n        after(2, done)\n      ); // we will perform 2 actions, so call `done` after all of them completed\n\n      findByTestID(el, \"Chart.Colors.Heatmap.MinColor\")\n        .find(\".color-picker-trigger\")\n        .last()\n        .simulate(\"click\");\n      findByTestID(el, \"ColorPicker\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"yellow\" } });\n\n      findByTestID(el, \"Chart.Colors.Heatmap.MaxColor\")\n        .find(\".color-picker-trigger\")\n        .last()\n        .simulate(\"click\");\n      findByTestID(el, \"ColorPicker\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"red\" } });\n    });\n  });\n\n  describe(\"for all except of pie and heatmap\", () => {\n    test(\"Changes series color\", done => {\n      const el = mount(\n        {\n          globalSeriesType: \"column\",\n          columnMapping: { a: \"x\", b: \"y\" },\n        },\n        done\n      );\n\n      findByTestID(el, \"Chart.Series.b.Color\")\n        .find(\".color-picker-trigger\")\n        .last()\n        .simulate(\"click\");\n\n      findByTestID(el, \"ColorPicker\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"red\" } });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/ColorsSettings.tsx",
    "content": "import React from \"react\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nimport PieColorsSettings from \"./PieColorsSettings\";\nimport HeatmapColorsSettings from \"./HeatmapColorsSettings\";\nimport DefaultColorsSettings from \"./DefaultColorsSettings\";\n\nconst components = {\n  pie: PieColorsSettings,\n  heatmap: HeatmapColorsSettings,\n};\n\nexport default function ColorsSettings({ options, ...props }: any) {\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  const Component = components[options.globalSeriesType] || DefaultColorsSettings;\n  return <Component options={options} {...props} />;\n}\n\nColorsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/ColumnMappingSelect.tsx",
    "content": "import { isString, map, uniq, flatten, filter, sortBy, keys } from \"lodash\";\nimport React from \"react\";\nimport { Section, Select } from \"@/components/visualizations/editor\";\n\nconst MappingTypes = {\n  x: { label: \"X Column\" },\n  y: { label: \"Y Columns\", multiple: true },\n  series: { label: \"Group by\" },\n  yError: { label: \"Errors column\" },\n  size: { label: \"Bubble Size Column\" },\n  zVal: { label: \"Color Column\" },\n};\n\nconst SwappedMappingTypes = {\n  ...MappingTypes,\n  x: { label: \"Y Column\" },\n  y: { label: \"X Columns\", multiple: true },\n};\n\ntype OwnProps = {\n  value?: string | string[];\n  availableColumns?: string[];\n  type?: any; // TODO: PropTypes.oneOf(keys(MappingTypes))\n  onChange?: (...args: any[]) => any;\n};\n\nconst columnMappingSelectDefaultProps = {\n  value: null,\n  availableColumns: [],\n  type: null,\n  onChange: () => {},\n};\n\ntype Props = OwnProps & typeof columnMappingSelectDefaultProps;\n\nexport default function ColumnMappingSelect({ value, availableColumns, type, onChange, areAxesSwapped }: Props) {\n  const options = sortBy(filter(uniq(flatten([availableColumns, value])), v => isString(v) && v !== \"\"));\n\n  // this swaps the ui, as the data will be swapped on render\n  const { label, multiple } = !areAxesSwapped ? MappingTypes[type] : SwappedMappingTypes[type];\n\n  return (\n    // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n    <Section>\n      <Select\n        label={label}\n        data-test={`Chart.ColumnMapping.${type}`}\n        mode={multiple ? \"multiple\" : \"default\"}\n        allowClear\n        showSearch\n        placeholder={multiple ? \"Choose columns...\" : \"Choose column...\"}\n        value={value || undefined}\n        // @ts-expect-error ts-migrate(2349) FIXME: This expression is not callable.\n        onChange={(column: any) => onChange(column || null, type)}>\n        {map(options, c => (\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n          <Select.Option key={c} value={c} data-test={`Chart.ColumnMapping.${type}.${c}`}>\n            {c}\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          </Select.Option>\n        ))}\n      </Select>\n    </Section>\n  );\n}\n\nColumnMappingSelect.defaultProps = columnMappingSelectDefaultProps;\n\nColumnMappingSelect.MappingTypes = MappingTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/CustomChartSettings.tsx",
    "content": "import { isNil, trimStart } from \"lodash\";\nimport React from \"react\";\nimport { Section, Switch, TextArea } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nconst defaultCustomCode = trimStart(`\n// Available variables are x, ys, element, and Plotly\n// Type console.log(x, ys); for more info about x and ys\n// To plot your graph call Plotly.newPlot(element, ...)\n// Plotly examples and docs: https://plot.ly/javascript/\n`);\n\nexport default function CustomChartSettings({ options, onOptionsChange }: any) {\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <TextArea\n          label=\"Custom code\"\n          data-test=\"Chart.Custom.Code\"\n          rows=\"10\"\n          defaultValue={isNil(options.customCode) ? defaultCustomCode : options.customCode}\n          onChange={(event: any) => onOptionsChange({ customCode: event.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n        <Switch\n          data-test=\"Chart.Custom.EnableConsoleLogs\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          defaultChecked={options.enableConsoleLogs}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(enableConsoleLogs: any) => any' is not assi... Remove this comment to see the full error message\n          onChange={(enableConsoleLogs: any) => onOptionsChange({ enableConsoleLogs })}>\n          Show errors in the console\n        </Switch>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n        <Switch\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          id=\"chart-editor-auto-update-custom-chart\"\n          data-test=\"Chart.Custom.AutoUpdate\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          defaultChecked={options.autoRedraw}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(autoRedraw: any) => any' is not assignable ... Remove this comment to see the full error message\n          onChange={(autoRedraw: any) => onOptionsChange({ autoRedraw })}>\n          Auto update graph\n        </Switch>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nCustomChartSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/DataLabelsSettings.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport getOptions from \"../getOptions\";\nimport DataLabelsSettings from \"./DataLabelsSettings\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(options: any, done: any) {\n  options = getOptions(options);\n  return enzyme.mount(\n    <DataLabelsSettings\n      visualizationName=\"Test\"\n      data={{ columns: [], rows: [] }}\n      options={options}\n      onOptionsChange={(changedOptions: any) => {\n        expect(changedOptions).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Chart -> Editor -> Data Labels Settings\", () => {\n  test(\"Sets Show Data Labels option\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        showDataLabels: false,\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.DataLabels.ShowDataLabels\")\n      .last()\n      .find(\"input\")\n      .simulate(\"change\", { target: { checked: true } });\n  });\n\n  test(\"Changes number format\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        numberFormat: \"0[.]0000\",\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.DataLabels.NumberFormat\")\n      .last()\n      .simulate(\"change\", { target: { value: \"0.00\" } });\n  });\n\n  test(\"Changes percent values format\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        percentFormat: \"0[.]00%\",\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.DataLabels.PercentFormat\")\n      .last()\n      .simulate(\"change\", { target: { value: \"0.0%\" } });\n  });\n\n  test(\"Changes date/time format\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        dateTimeFormat: \"YYYY-MM-DD HH:mm:ss\",\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.DataLabels.DateTimeFormat\")\n      .last()\n      .simulate(\"change\", { target: { value: \"YYYY MMM DD\" } });\n  });\n\n  test(\"Changes data labels format\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        textFormat: null,\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.DataLabels.TextFormat\")\n      .last()\n      .simulate(\"change\", { target: { value: \"{{ @@x }} :: {{ @@y }} / {{ @@yPercent }}\" } });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/DataLabelsSettings.tsx",
    "content": "import { includes } from \"lodash\";\nimport React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Input, Checkbox, ContextHelp } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function DataLabelsSettings({ options, onOptionsChange }: any) {\n  const isShowDataLabelsAvailable = includes(\n    [\"line\", \"area\", \"column\", \"scatter\", \"pie\", \"heatmap\"],\n    options.globalSeriesType\n  );\n\n  const [debouncedOnOptionsChange] = useDebouncedCallback(onOptionsChange, 200);\n\n  return (\n    <React.Fragment>\n      {isShowDataLabelsAvailable && (\n        // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n        <Section>\n          <Checkbox\n            data-test=\"Chart.DataLabels.ShowDataLabels\"\n            defaultChecked={options.showDataLabels}\n            onChange={event => onOptionsChange({ showDataLabels: event.target.checked })}>\n            Show Data Labels\n          </Checkbox>\n        </Section>\n      )}\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label={\n            <React.Fragment>\n              Number Values Format\n              <ContextHelp.NumberFormatSpecs />\n            </React.Fragment>\n          }\n          data-test=\"Chart.DataLabels.NumberFormat\"\n          defaultValue={options.numberFormat}\n          onChange={(e: any) => debouncedOnOptionsChange({ numberFormat: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label={\n            <React.Fragment>\n              Percent Values Format\n              <ContextHelp.NumberFormatSpecs />\n            </React.Fragment>\n          }\n          data-test=\"Chart.DataLabels.PercentFormat\"\n          defaultValue={options.percentFormat}\n          onChange={(e: any) => debouncedOnOptionsChange({ percentFormat: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label={\n            <React.Fragment>\n              Date/Time Values Format\n              <ContextHelp.DateTimeFormatSpecs />\n            </React.Fragment>\n          }\n          data-test=\"Chart.DataLabels.DateTimeFormat\"\n          defaultValue={options.dateTimeFormat}\n          onChange={(e: any) => debouncedOnOptionsChange({ dateTimeFormat: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label={\n            <React.Fragment>\n              Data Labels\n              {/* @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message */}\n              <ContextHelp placement=\"topRight\" arrowPointAtCenter>\n                <div style={{ paddingBottom: 5 }}>Use special names to access additional properties:</div>\n                <div>\n                  <code>{\"{{ @@name }}\"}</code> series name;\n                </div>\n                <div>\n                  <code>{\"{{ @@x }}\"}</code> x-value;\n                </div>\n                <div>\n                  <code>{\"{{ @@y }}\"}</code> y-value;\n                </div>\n                <div>\n                  <code>{\"{{ @@yPercent }}\"}</code> relative y-value;\n                </div>\n                <div>\n                  <code>{\"{{ @@yError }}\"}</code> y deviation;\n                </div>\n                <div>\n                  <code>{\"{{ @@size }}\"}</code> bubble size;\n                </div>\n                <div style={{ paddingTop: 5 }}>\n                  Also, all query result columns can be referenced\n                  <br />\n                  using\n                  <code style={{ whiteSpace: \"nowrap\" }}>{\"{{ column_name }}\"}</code> syntax.\n                </div>\n              </ContextHelp>\n            </React.Fragment>\n          }\n          data-test=\"Chart.DataLabels.TextFormat\"\n          placeholder=\"(auto)\"\n          defaultValue={options.textFormat}\n          onChange={(e: any) => debouncedOnOptionsChange({ textFormat: e.target.value })}\n        />\n      </Section>\n    </React.Fragment>\n  );\n}\n\nDataLabelsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/DefaultColorsSettings.tsx",
    "content": "import { map } from \"lodash\";\nimport React, { useMemo, useCallback } from \"react\";\nimport Table from \"antd/lib/table\";\nimport ColorPicker from \"@/components/ColorPicker\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport { AllColorPalettes } from \"@/visualizations/ColorPalette\";\nimport getChartData from \"../getChartData\";\nimport { Section, Select } from \"@/components/visualizations/editor\";\n\nexport default function DefaultColorsSettings({ options, data, onOptionsChange }: any) {\n  const colors = useMemo(\n    () => ({\n      Automatic: null,\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      ...AllColorPalettes[options.color_scheme],\n    }),\n    [options.color_scheme]\n  );\n\n  const series = useMemo(\n    () =>\n      map(getChartData(data.rows, options), ({ name }) => ({\n        key: name,\n        color: (options.seriesOptions[name] || {}).color || null,\n      })),\n    [options, data]\n  );\n\n  const updateSeriesOption = useCallback(\n    (key, prop, value) => {\n      onOptionsChange({\n        seriesOptions: {\n          [key]: {\n            [prop]: value,\n          },\n        },\n      });\n    },\n    [onOptionsChange]\n  );\n\n  const columns = [\n    {\n      title: \"Series\",\n      dataIndex: \"key\",\n    },\n    {\n      title: \"Color\",\n      dataIndex: \"color\",\n      width: \"1%\",\n      render: (unused: any, item: any) => (\n        <ColorPicker\n          data-test={`Chart.Series.${item.key}.Color`}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'never'.\n          interactive\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '{ \"Indian Red\": string; \"Green 2\": string; \"... Remove this comment to see the full error message\n          presetColors={colors}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          placement=\"topRight\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          color={item.color}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(value: any) => void' is not assignable to t... Remove this comment to see the full error message\n          onChange={(value: any) => updateSeriesOption(item.key, \"color\", value)}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'never'.\n          addonAfter={<ColorPicker.Label color={item.color} presetColors={colors} />}\n        />\n      ),\n    },\n  ];\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n          <Select\n            label=\"Color Scheme\"\n            defaultValue={options.color_scheme}\n            data-test=\"ColorScheme\"\n            onChange={(val : any) => onOptionsChange({ color_scheme: val })}>\n            {Object.keys(AllColorPalettes).map(option => (\n             // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n              <Select.Option data-test={`ColorOption${option}`} key={option} value={option}>{option}</Select.Option>\n            ))}\n          </Select>\n        </Section>\n      <Table showHeader={false} dataSource={series} columns={columns} pagination={false} />\n    </React.Fragment>\n  )\n}\n\nDefaultColorsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/GeneralSettings.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport getOptions from \"../getOptions\";\nimport GeneralSettings from \"./GeneralSettings\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction elementExists(wrapper: any, testId: any) {\n  return findByTestID(wrapper, testId).length > 0;\n}\n\nfunction mount(options: any, done: any) {\n  options = getOptions(options);\n  return enzyme.mount(\n    <GeneralSettings\n      visualizationName=\"Test\"\n      data={{ columns: [], rows: [] }}\n      options={options}\n      onOptionsChange={(changedOptions: any) => {\n        expect(changedOptions).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Chart -> Editor -> General Settings\", () => {\n  test(\"Changes global series type\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        showDataLabels: false,\n        seriesOptions: {\n          a: { type: \"column\" },\n          b: { type: \"line\" },\n        },\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.GlobalSeriesType\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Chart.ChartType.pie\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Pie: changes direction\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"pie\",\n        direction: { type: \"counterclockwise\" },\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.PieDirection\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Chart.PieDirection.Clockwise\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Toggles legend\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        legend: { enabled: true },\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.LegendPlacement\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Chart.LegendPlacement.HideLegend\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Box: toggles show points\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"box\",\n        showpoints: false,\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.ShowPoints\")\n      .last()\n      .find(\"input\")\n      .simulate(\"change\", { target: { checked: true } });\n  });\n\n  test(\"Enables stacking\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        series: {},\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.Stacking\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Chart.Stacking.Stack\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Toggles normalize values to percentage\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        series: {},\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.NormalizeValues\")\n      .last()\n      .find(\"input\")\n      .simulate(\"change\", { target: { checked: true } });\n  });\n\n  test(\"Keep missing/null values\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        missingValuesAsZero: true,\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.MissingValues\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Chart.MissingValues.Keep\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  describe(\"Column mappings should be available\", () => {\n    test(\"for bubble\", () => {\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.\n      const el = mount({\n        globalSeriesType: \"column\",\n        seriesOptions: {\n          a: { type: \"column\" },\n          b: { type: \"bubble\" },\n          c: { type: \"heatmap\" },\n        },\n      });\n\n      expect(elementExists(el, \"Chart.ColumnMapping.x\")).toBeTruthy();\n      expect(elementExists(el, \"Chart.ColumnMapping.y\")).toBeTruthy();\n      expect(elementExists(el, \"Chart.ColumnMapping.size\")).toBeTruthy();\n    });\n\n    test(\"for heatmap\", () => {\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.\n      const el = mount({\n        globalSeriesType: \"heatmap\",\n        seriesOptions: {\n          a: { type: \"column\" },\n          b: { type: \"bubble\" },\n          c: { type: \"heatmap\" },\n        },\n      });\n\n      expect(elementExists(el, \"Chart.ColumnMapping.x\")).toBeTruthy();\n      expect(elementExists(el, \"Chart.ColumnMapping.y\")).toBeTruthy();\n      expect(elementExists(el, \"Chart.ColumnMapping.zVal\")).toBeTruthy();\n    });\n\n    test(\"for all types except of bubble, heatmap and custom\", () => {\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.\n      const el = mount({\n        globalSeriesType: \"column\",\n        seriesOptions: {\n          a: { type: \"column\" },\n          b: { type: \"bubble\" },\n          c: { type: \"heatmap\" },\n        },\n      });\n\n      expect(elementExists(el, \"Chart.ColumnMapping.x\")).toBeTruthy();\n      expect(elementExists(el, \"Chart.ColumnMapping.y\")).toBeTruthy();\n      expect(elementExists(el, \"Chart.ColumnMapping.series\")).toBeTruthy();\n      expect(elementExists(el, \"Chart.ColumnMapping.yError\")).toBeTruthy();\n    });\n  });\n\n  test(\"Toggles horizontal bar chart\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        series: {},\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.SwappedAxes\")\n      .last()\n      .find(\"input\")\n      .simulate(\"change\", { target: { checked: true } });\n  });\n\n  test(\"Toggles Enable click events\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        series: {},\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.EnableClickEvents\")\n      .last()\n      .find(\"input\")\n      .simulate(\"change\", { target: { checked: true } });\n  });\n\n\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx",
    "content": "import { isArray, map, mapValues, includes, some, each, difference, toNumber } from \"lodash\";\nimport React, { useMemo } from \"react\";\nimport { Section, Select, Checkbox, InputNumber, ContextHelp, Input } from \"@/components/visualizations/editor\";\nimport { UpdateOptionsStrategy } from \"@/components/visualizations/editor/createTabbedEditor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport { AllColorPalettes } from \"@/visualizations/ColorPalette\";\nimport ChartTypeSelect from \"./ChartTypeSelect\";\nimport ColumnMappingSelect from \"./ColumnMappingSelect\";\nimport { useDebouncedCallback } from \"use-debounce/lib\";\n\nfunction getAvailableColumnMappingTypes(options: any) {\n  const result = [\"x\", \"y\"];\n\n  if (!includes([\"custom\", \"heatmap\"], options.globalSeriesType)) {\n    result.push(\"series\");\n  }\n\n  if (options.globalSeriesType === \"bubble\" || some(options.seriesOptions, { type: \"bubble\" })) {\n    result.push(\"size\");\n  }\n\n  if (options.globalSeriesType === \"heatmap\") {\n    result.push(\"zVal\");\n  }\n\n  if (!includes([\"custom\", \"bubble\", \"heatmap\"], options.globalSeriesType)) {\n    result.push(\"yError\");\n  }\n\n  return result;\n}\n\nfunction getMappedColumns(options: any, availableColumns: any) {\n  const mappedColumns = {};\n  const availableTypes = getAvailableColumnMappingTypes(options);\n  each(availableTypes, type => {\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    mappedColumns[type] = ColumnMappingSelect.MappingTypes[type].multiple ? [] : null;\n  });\n\n  availableColumns = map(availableColumns, c => c.name);\n  const usedColumns: any = [];\n\n  each(options.columnMapping, (type, column) => {\n    if (includes(availableColumns, column) && includes(availableTypes, type)) {\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      const { multiple } = ColumnMappingSelect.MappingTypes[type];\n      if (multiple) {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        mappedColumns[type].push(column);\n      } else {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        mappedColumns[type] = column;\n      }\n      usedColumns.push(column);\n    }\n  });\n\n  return {\n    mappedColumns,\n    unusedColumns: difference(availableColumns, usedColumns),\n  };\n}\n\nfunction mappedColumnsToColumnMappings(mappedColumns: any) {\n  const result = {};\n  each(mappedColumns, (value, type) => {\n    if (isArray(value)) {\n      each(value, v => {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        result[v] = type;\n      });\n    } else {\n      if (value) {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        result[value] = type;\n      }\n    }\n  });\n  return result;\n}\n\nexport default function GeneralSettings({ options, data, onOptionsChange }: any) {\n  const { mappedColumns, unusedColumns } = useMemo(() => getMappedColumns(options, data.columns), [\n    options,\n    data.columns,\n  ]);\n\n  function handleGlobalSeriesTypeChange(globalSeriesType: any) {\n    onOptionsChange({\n      globalSeriesType,\n      showDataLabels: globalSeriesType === \"pie\",\n      swappedAxes: false,\n      seriesOptions: mapValues(options.seriesOptions, series => ({\n        ...series,\n        type: globalSeriesType,\n      })),\n    });\n  }\n\n  function handleColumnMappingChange(column: any, type: any) {\n    const columnMapping = mappedColumnsToColumnMappings({\n      ...mappedColumns,\n      [type]: column,\n    });\n    onOptionsChange({ columnMapping }, UpdateOptionsStrategy.shallowMerge);\n  }\n\n  function handleLegendPlacementChange(value: any) {\n    if (value === \"hidden\") {\n      onOptionsChange({ legend: { enabled: false } });\n    } else {\n      onOptionsChange({ legend: { enabled: true, placement: value } });\n    }\n  }\n\n  function handleAxesSwapping() {\n    // moves any item in the right Y axis to the left one\n    const seriesOptions = mapValues(options.seriesOptions, series => ({\n      ...series,\n      yAxis: 0,\n    }));\n    onOptionsChange({ swappedAxes: !options.swappedAxes, seriesOptions });\n  }\n\n  const [debouncedOnOptionsChange] = useDebouncedCallback(onOptionsChange, 200);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <ChartTypeSelect\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '{ label: string; \"data-test\": string; defaul... Remove this comment to see the full error message\n          label=\"Chart Type\"\n          data-test=\"Chart.GlobalSeriesType\"\n          defaultValue={options.globalSeriesType}\n          onChange={handleGlobalSeriesTypeChange}\n        />\n      </Section>\n\n      {includes([\"column\", \"line\", \"box\"], options.globalSeriesType) && (\n        // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n        <Section>\n          <Checkbox\n            data-test=\"Chart.SwappedAxes\"\n            defaultChecked={options.swappedAxes}\n            checked={options.swappedAxes}\n            onChange={handleAxesSwapping}>\n            Horizontal Chart\n          </Checkbox>\n        </Section>\n      )}\n\n      {map(mappedColumns, (value, type) => (\n        <ColumnMappingSelect\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          key={type}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          type={type}\n          value={value}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          areAxesSwapped={options.swappedAxes}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'unknown[]' is not assignable to type 'never'... Remove this comment to see the full error message\n          availableColumns={unusedColumns}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(column: any, type: any) => void' is not ass... Remove this comment to see the full error message\n          onChange={handleColumnMappingChange}\n        />\n      ))}\n\n      {includes([\"bubble\"], options.globalSeriesType) && (\n        <React.Fragment>\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            <InputNumber\n              label=\"Bubble Size Coefficient\"\n              data-test=\"Chart.BubbleCoefficient\"\n              defaultValue={options.coefficient}\n              onChange={(value: any) => onOptionsChange({ coefficient: toNumber(value) })}\n            />\n          </Section>\n\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            <Select\n              label=\"Bubble Size Proportional To\"\n              data-test=\"Chart.SizeMode\"\n              defaultValue={options.sizemode}\n              onChange={(mode: any) => onOptionsChange({ sizemode: mode })}>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"area\" data-test=\"Chart.SizeMode.Area\">\n                Area\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"diameter\" data-test=\"Chart.SizeMode.Diameter\">\n                Diameter\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n            </Select>\n          </Section>\n        </React.Fragment>\n      )}\n\n      {includes([\"pie\"], options.globalSeriesType) && (\n        // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n        <Section>\n          <Select\n            label=\"Direction\"\n            data-test=\"Chart.PieDirection\"\n            defaultValue={options.direction.type}\n            onChange={(type: any) => onOptionsChange({ direction: { type } })}>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value=\"counterclockwise\" data-test=\"Chart.PieDirection.Counterclockwise\">\n              Counterclockwise\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value=\"clockwise\" data-test=\"Chart.PieDirection.Clockwise\">\n              Clockwise\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          </Select>\n          <Select\n            label=\"Sort\"\n            defaultValue={options.piesort}\n            onChange={(val: any) => onOptionsChange({ piesort: val })}>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value={true}>\n              True\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value={false}>\n              False\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          </Select>\n        </Section>\n      )}\n\n      {!includes([\"custom\", \"heatmap\"], options.globalSeriesType) && (\n        <React.Fragment>\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            <Select\n              label=\"Legend Placement\"\n              data-test=\"Chart.LegendPlacement\"\n              value={options.legend.enabled ? options.legend.placement : \"hidden\"}\n              onChange={handleLegendPlacementChange}>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"hidden\" data-test=\"Chart.LegendPlacement.HideLegend\">\n                Hide legend\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"auto\" data-test=\"Chart.LegendPlacement.Auto\">\n                Right\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"below\" data-test=\"Chart.LegendPlacement.Below\">\n                Bottom\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n            </Select>\n          </Section>\n\n          {options.legend.enabled && (\n            // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n            <Section>\n              <Select\n                label=\"Legend Items Order\"\n                data-test=\"Chart.LegendItemsOrder\"\n                value={options.legend.traceorder}\n                onChange={(traceorder: any) => onOptionsChange({ legend: { traceorder } })}>\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n                <Select.Option value=\"normal\" data-test=\"Chart.LegendItemsOrder.Normal\">\n                  Normal\n                  {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n                </Select.Option>\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n                <Select.Option value=\"reversed\" data-test=\"Chart.LegendItemsOrder.Reversed\">\n                  Reversed\n                  {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n                </Select.Option>\n              </Select>\n            </Section>\n          )}\n        </React.Fragment>\n      )}\n\n      {includes([\"box\"], options.globalSeriesType) && (\n        // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n        <Section>\n          <Checkbox\n            data-test=\"Chart.ShowPoints\"\n            defaultChecked={options.showpoints}\n            onChange={event => onOptionsChange({ showpoints: event.target.checked })}>\n            Show All Points\n          </Checkbox>\n        </Section>\n      )}\n\n      {!includes([\"custom\", \"heatmap\"], options.globalSeriesType) && (\n        // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n        <Section>\n          <Select\n            label=\"Stacking\"\n            data-test=\"Chart.Stacking\"\n            defaultValue={options.series.stacking}\n            disabled={!includes([\"line\", \"area\", \"column\"], options.globalSeriesType)}\n            onChange={(stacking: any) => onOptionsChange({ series: { stacking } })}>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value={null} data-test=\"Chart.Stacking.Disabled\">\n              Disabled\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value=\"stack\" data-test=\"Chart.Stacking.Stack\">\n              Stack\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          </Select>\n        </Section>\n      )}\n\n      {includes([\"line\", \"area\", \"column\"], options.globalSeriesType) && (\n        // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n        <Section>\n          <Checkbox\n            data-test=\"Chart.NormalizeValues\"\n            defaultChecked={options.series.percentValues}\n            onChange={event => onOptionsChange({ series: { percentValues: event.target.checked } })}>\n            Normalize values to percentage\n          </Checkbox>\n        </Section>\n      )}\n\n      {includes([\"line\", \"area\"], options.globalSeriesType) && (\n        // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n        <Section>\n          <Select\n            label=\"Line Shape\"\n            data-test=\"Chart.LineShape\"\n            defaultValue={options.lineShape}\n            onChange={(val: any) => onOptionsChange({ lineShape: val })}>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value=\"linear\" data-test=\"Chart.LineShape.Linear\">\n              Linear\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value=\"spline\" data-test=\"Chart.LineShape.Spline\">\n              Spline\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value=\"hv\" data-test=\"Chart.LineShape.HorizontalVertical\">\n              Horizontal-Vertical\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value=\"vh\" data-test=\"Chart.LineShape.VerticalHorizontal\">\n              Vertical-Horizontal\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          </Select>\n        </Section>\n      )}\n\n      {!includes([\"custom\", \"heatmap\", \"bubble\"], options.globalSeriesType) && (\n        // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n        <Section>\n          <Select\n            label=\"Missing and NULL values\"\n            data-test=\"Chart.MissingValues\"\n            defaultValue={options.missingValuesAsZero ? 1 : 0}\n            onChange={(value: any) => onOptionsChange({ missingValuesAsZero: !!value })}>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value={0} data-test=\"Chart.MissingValues.Keep\">\n              Do not display in chart\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            <Select.Option value={1} data-test=\"Chart.MissingValues.Zero\">\n              Convert to 0 and display in chart\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          </Select>\n        </Section>\n      )}\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Chart.EnableClickEvents\"\n          defaultChecked={options.enableLink}\n          onChange={event => onOptionsChange({ enableLink: event.target.checked })}>\n          Enable click events\n        </Checkbox>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Chart.EnableClickEvents.NewTab\"\n          defaultChecked={options.linkOpenNewTab}\n          onChange={event => onOptionsChange({ linkOpenNewTab: event.target.checked })}\n          disabled={!(options.enableLink === true)}\n        >\n          Open in new tab\n        </Checkbox>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label={\n            <React.Fragment>\n              URL template\n              {/* @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message */}\n              <ContextHelp\n                placement=\"topLeft\"\n                arrowPointAtCenter\n                // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message\n                icon={ContextHelp.defaultIcon}>\n                <div>\n                  Every curve can be referenced using <code>{\"{{ @@x1 }} {{ @@y1 }} {{ @@x2 }} {{ @@y2 }} ...\"}</code> syntax:<br/>\n                  axis with any curve number according to the Series config.\n                </div>\n                <div>\n                  The first met curve X and Y values can be referenced by just<code>{\"{{ @@x }} {{ @@y }}\"}</code> syntax.\n                </div>\n                <div>\n                  Any unresolved reference would be replaced with an empty string.\n                </div>\n              </ContextHelp>\n            </React.Fragment>\n          }\n          data-test=\"Chart.DataLabels.TextFormat\"\n          placeholder=\"(nothing)\"\n          defaultValue={options.linkFormat}\n          onChange={(e: any) => debouncedOnOptionsChange({ linkFormat: e.target.value })}\n          disabled={!(options.enableLink === true)}\n        />\n      </Section>\n    </React.Fragment>\n  );\n}\n\nGeneralSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/HeatmapColorsSettings.tsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport { Section, Select, ColorPicker } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport ColorPalette from \"@/visualizations/ColorPalette\";\n\nconst ColorSchemes = [\n  \"Blackbody\",\n  \"Bluered\",\n  \"Blues\",\n  \"Earth\",\n  \"Electric\",\n  \"Greens\",\n  \"Greys\",\n  \"Hot\",\n  \"Jet\",\n  \"Picnic\",\n  \"Portland\",\n  \"Rainbow\",\n  \"RdBu\",\n  \"Reds\",\n  \"Viridis\",\n  \"YlGnBu\",\n  \"YlOrRd\",\n  \"Custom...\",\n];\n\nexport default function HeatmapColorsSettings({ options, onOptionsChange }: any) {\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Color Scheme\"\n          data-test=\"Chart.Colors.Heatmap.ColorScheme\"\n          placeholder=\"Choose Color Scheme...\"\n          allowClear\n          value={options.colorScheme || undefined}\n          onChange={(value: any) => onOptionsChange({ colorScheme: value || null })}>\n          {map(ColorSchemes, scheme => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={scheme} value={scheme} data-test={`Chart.Colors.Heatmap.ColorScheme.${scheme}`}>\n              {scheme}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {options.colorScheme === \"Custom...\" && (\n        <React.Fragment>\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            <ColorPicker\n              layout=\"horizontal\"\n              label=\"Min Color:\"\n              data-test=\"Chart.Colors.Heatmap.MinColor\"\n              interactive\n              placement=\"topLeft\"\n              presetColors={ColorPalette}\n              color={options.heatMinColor}\n              onChange={(heatMinColor: any) => onOptionsChange({ heatMinColor })}\n              // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n              addonAfter={<ColorPicker.Label color={options.heatMinColor} presetColors={ColorPalette} />}\n            />\n          </Section>\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            <ColorPicker\n              layout=\"horizontal\"\n              label=\"Max Color:\"\n              data-test=\"Chart.Colors.Heatmap.MaxColor\"\n              interactive\n              placement=\"topRight\"\n              presetColors={ColorPalette}\n              color={options.heatMaxColor}\n              onChange={(heatMaxColor: any) => onOptionsChange({ heatMaxColor })}\n              // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n              addonAfter={<ColorPicker.Label color={options.heatMaxColor} presetColors={ColorPalette} />}\n            />\n          </Section>\n        </React.Fragment>\n      )}\n    </React.Fragment>\n  );\n}\n\nHeatmapColorsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/PieColorsSettings.tsx",
    "content": "import { each, map } from \"lodash\";\nimport React, { useMemo, useCallback } from \"react\";\nimport Table from \"antd/lib/table\";\nimport ColorPicker from \"@/components/ColorPicker\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport { AllColorPalettes } from \"@/visualizations/ColorPalette\";\nimport getChartData from \"../getChartData\";\nimport { Section, Select } from \"@/components/visualizations/editor\";\n\nfunction getUniqueValues(chartData: any) {\n  const uniqueValuesNames = new Set();\n  each(chartData, series => {\n    each(series.data, row => {\n      uniqueValuesNames.add(row.x);\n    });\n  });\n  return [...uniqueValuesNames];\n}\n\nexport default function PieColorsSettings({ options, data, onOptionsChange }: any) {\n  const colors = useMemo(\n    () => ({\n      Automatic: null,\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      ...AllColorPalettes[options.color_scheme],\n    }),\n    [options.color_scheme]\n  );\n\n  const series = useMemo(\n    () =>\n      map(getUniqueValues(getChartData(data.rows, options)), value => ({\n        key: value,\n        // @ts-expect-error ts-migrate(2538) FIXME: Type 'unknown' cannot be used as an index type.\n        color: (options.valuesOptions[value] || {}).color || null,\n      })),\n    [options, data]\n  );\n\n  const updateValuesOption = useCallback(\n    (key, prop, value) => {\n      onOptionsChange({\n        valuesOptions: {\n          [key]: {\n            [prop]: value,\n          },\n        },\n      });\n    },\n    [onOptionsChange]\n  );\n\n  const columns = [\n    {\n      title: \"Values\",\n      dataIndex: \"key\",\n    },\n    {\n      title: \"Color\",\n      dataIndex: \"color\",\n      width: \"1%\",\n      render: (unused: any, item: any) => (\n        <ColorPicker\n          data-test={`Chart.Series.${item.key}.Color`}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'never'.\n          interactive\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '{ \"Indian Red\": string; \"Green 2\": string; \"... Remove this comment to see the full error message\n          presetColors={colors}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          placement=\"topRight\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          color={item.color}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(value: any) => void' is not assignable to t... Remove this comment to see the full error message\n          onChange={(value: any) => updateValuesOption(item.key, \"color\", value)}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'never'.\n          addonAfter={<ColorPicker.Label color={item.color} presetColors={colors} />}\n        />\n      ),\n    },\n  ];\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n          <Select\n            label=\"Color Scheme\"\n            defaultValue={options.color_scheme}\n            data-test=\"ColorScheme\"\n            onChange={(val : any) => onOptionsChange({ color_scheme: val })}>\n            {Object.keys(AllColorPalettes).map(option => (\n             // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n              <Select.Option data-test={`ColorOption${option}`} key={option} value={option}>{option}</Select.Option>\n            ))}\n          </Select>\n        </Section>\n      <Table showHeader={false} dataSource={series} columns={columns} pagination={false} />\n    </React.Fragment>\n  )\n}\n\nPieColorsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/SeriesSettings.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport getOptions from \"../getOptions\";\nimport SeriesSettings from \"./SeriesSettings\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(options: any, done: any) {\n  options = getOptions(options);\n  return enzyme.mount(\n    <SeriesSettings\n      visualizationName=\"Test\"\n      data={{ columns: [{ name: \"a\", type: \"string\" }], rows: [{ a: \"test\" }] }}\n      options={options}\n      onOptionsChange={(changedOptions: any) => {\n        expect(changedOptions).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Chart -> Editor -> Series Settings\", () => {\n  test(\"Changes series type\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        columnMapping: { a: \"y\" },\n        seriesOptions: {\n          a: { type: \"column\", label: \"a\", yAxis: 0 },\n        },\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.Series.a.Type\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Chart.ChartType.area\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Changes series label\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        columnMapping: { a: \"y\" },\n        seriesOptions: {\n          a: { type: \"column\", label: \"a\", yAxis: 0 },\n        },\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.Series.a.Label\")\n      .last()\n      .simulate(\"change\", { target: { value: \"test\" } });\n  });\n\n  test(\"Changes series axis\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        columnMapping: { a: \"y\" },\n        seriesOptions: {\n          a: { type: \"column\", name: \"a\", yAxis: 0 },\n        },\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.Series.a.UseRightAxis\")\n      .last()\n      .find(\"input\")\n      .simulate(\"change\", { target: { checked: true } });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/SeriesSettings.tsx",
    "content": "import { includes, map, extend, fromPairs } from \"lodash\";\nimport React, { useMemo, useCallback } from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport Table from \"antd/lib/table\";\nimport Input from \"antd/lib/input\";\nimport Radio from \"antd/lib/radio\";\n// @ts-expect-error ts-migrate(2724) FIXME: Module '\"../../../../node_modules/react-sortable-h... Remove this comment to see the full error message\nimport { sortableElement } from \"react-sortable-hoc\";\nimport { SortableContainer, DragHandle } from \"@/components/sortable\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport ChartTypeSelect from \"./ChartTypeSelect\";\nimport getChartData from \"../getChartData\";\n\nconst SortableBodyRow = sortableElement((props: any) => <tr {...props} />);\n\nfunction getTableColumns(options: any, updateSeriesOption: any, debouncedUpdateSeriesOption: any) {\n  const result = [\n    {\n      title: \"Order\",\n      dataIndex: \"zIndex\",\n      render: (unused: any, item: any) => (\n        <span className=\"series-settings-order\">\n          <DragHandle />\n          {item.zIndex + 1}\n        </span>\n      ),\n    },\n    {\n      title: \"Label\",\n      dataIndex: \"name\",\n      render: (unused: any, item: any) => (\n        <Input\n          data-test={`Chart.Series.${item.key}.Label`}\n          placeholder={item.key}\n          defaultValue={item.name}\n          onChange={event => debouncedUpdateSeriesOption(item.key, \"name\", event.target.value)}\n        />\n      ),\n    },\n  ];\n\n  if (!includes([\"pie\", \"heatmap\"], options.globalSeriesType)) {\n    if (!options.swappedAxes) {\n      result.push({\n        title: \"Y Axis\",\n        dataIndex: \"yAxis\",\n        render: (unused, item) => (\n          <Radio.Group\n            className=\"series-settings-y-axis\"\n            value={item.yAxis === 1 ? 1 : 0}\n            onChange={event => updateSeriesOption(item.key, \"yAxis\", event.target.value)}>\n            <Radio value={0} data-test={`Chart.Series.${item.key}.UseLeftAxis`}>\n              left\n            </Radio>\n            <Radio value={1} data-test={`Chart.Series.${item.key}.UseRightAxis`}>\n              right\n            </Radio>\n          </Radio.Group>\n        ),\n      });\n    }\n\n    result.push({\n      title: \"Type\",\n      dataIndex: \"type\",\n      render: (unused, item) => (\n        <ChartTypeSelect\n          data-test={`Chart.Series.${item.key}.Type`}\n          dropdownMatchSelectWidth={false}\n          value={item.type}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          hiddenChartTypes={[\"pie\", \"heatmap\", \"bubble\", \"box\"]}\n          onChange={(value: any) => updateSeriesOption(item.key, \"type\", value)}\n        />\n      ),\n    });\n  }\n\n  return result;\n}\n\nexport default function SeriesSettings({ options, data, onOptionsChange }: any) {\n  const series = useMemo(\n    () =>\n      map(\n        getChartData(data.rows, options), // returns sorted series\n        ({ name }, zIndex) =>\n          extend({ key: name, type: options.globalSeriesType }, options.seriesOptions[name], { zIndex })\n      ),\n    [options, data]\n  );\n\n  const handleSortEnd = useCallback(\n    ({ oldIndex, newIndex }) => {\n      const seriesOptions = [...series];\n      seriesOptions.splice(newIndex, 0, ...seriesOptions.splice(oldIndex, 1));\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'key' does not exist on type 'Boolean'.\n      onOptionsChange({ seriesOptions: fromPairs(map(seriesOptions, ({ key }, zIndex) => [key, { zIndex }])) });\n    },\n    [onOptionsChange, series]\n  );\n\n  const updateSeriesOption = useCallback(\n    (key, prop, value) => {\n      onOptionsChange({\n        seriesOptions: {\n          [key]: {\n            [prop]: value,\n          },\n        },\n      });\n    },\n    [onOptionsChange]\n  );\n  const [debouncedUpdateSeriesOption] = useDebouncedCallback(updateSeriesOption, 200);\n\n  const columns = useMemo(() => getTableColumns(options, updateSeriesOption, debouncedUpdateSeriesOption), [\n    options,\n    updateSeriesOption,\n    debouncedUpdateSeriesOption,\n  ]);\n\n  return (\n    <SortableContainer\n      axis=\"y\"\n      lockAxis=\"y\"\n      lockToContainerEdges\n      useDragHandle\n      helperClass=\"chart-editor-series-dragged-item\"\n      helperContainer={(container: any) => container.querySelector(\"tbody\")}\n      onSortEnd={handleSortEnd}\n      containerProps={{\n        className: \"chart-editor-series\",\n      }}>\n      {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message */}\n      <Table\n        dataSource={series}\n        columns={columns}\n        components={{\n          body: {\n            row: SortableBodyRow,\n          },\n        }}\n        // @ts-expect-error ts-migrate(2322) FIXME: Type '(item: object) => { index: any; }' is not as... Remove this comment to see the full error message\n        onRow={item => ({ index: item.zIndex })}\n        pagination={false}\n      />\n    </SortableContainer>\n  );\n}\n\nSeriesSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/XAxisSettings.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport getOptions from \"../getOptions\";\nimport XAxisSettings from \"./XAxisSettings\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(options: any, done: any) {\n  options = getOptions(options);\n  return enzyme.mount(\n    <XAxisSettings\n      visualizationName=\"Test\"\n      data={{ columns: [], rows: [] }}\n      options={options}\n      onOptionsChange={(changedOptions: any) => {\n        expect(changedOptions).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Chart -> Editor -> X-Axis Settings\", () => {\n  test(\"Changes axis type\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        xAxis: { type: \"-\", labels: { enabled: true } },\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.XAxis.Type\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Chart.XAxis.Type.Linear\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Changes axis name\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        xAxis: { type: \"-\", labels: { enabled: true } },\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.XAxis.Name\")\n      .last()\n      .simulate(\"change\", { target: { value: \"test\" } });\n  });\n\n  test(\"Changes axis tick format\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        xAxis: { },\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.XAxis.TickFormat\")\n      .last()\n      .simulate(\"change\", { target: { value: \"%B\" } });\n  });\n\n  test(\"Sets Show Labels option\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        xAxis: { type: \"-\", labels: { enabled: false } },\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.XAxis.ShowLabels\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Sets Sort X Values option\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        sortX: false,\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.XAxis.Sort\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Sets Reverse X Values option\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        reverseX: false,\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.XAxis.Reverse\")\n      .last()\n      .simulate(\"click\");\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/XAxisSettings.tsx",
    "content": "import React from \"react\";\nimport { Section, Switch } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nimport AxisSettings from \"./AxisSettings\";\n\nexport default function XAxisSettings({ options, onOptionsChange }: any) {\n  return (\n    <React.Fragment>\n      <AxisSettings\n        id=\"XAxis\"\n        features={{ autoDetectType: true }}\n        options={options.xAxis}\n        // @ts-expect-error ts-migrate(2322) FIXME: Type '(xAxis: any) => any' is not assignable to ty... Remove this comment to see the full error message\n        onChange={(xAxis: any) => onOptionsChange({ xAxis })}\n      />\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n        <Switch\n          data-test=\"Chart.XAxis.Sort\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          defaultChecked={options.sortX}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(sortX: any) => any' is not assignable to ty... Remove this comment to see the full error message\n          onChange={(sortX: any) => onOptionsChange({ sortX })}>\n          Sort Values\n        </Switch>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n        <Switch\n          data-test=\"Chart.XAxis.Reverse\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          defaultChecked={options.reverseX}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(reverseX: any) => any' is not assignable to... Remove this comment to see the full error message\n          onChange={(reverseX: any) => onOptionsChange({ reverseX })}>\n          Reverse Order\n        </Switch>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n        <Switch\n          data-test=\"Chart.XAxis.ShowLabels\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          defaultChecked={options.xAxis.labels.enabled}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(enabled: any) => any' is not assignable to ... Remove this comment to see the full error message\n          onChange={(enabled: any) => onOptionsChange({ xAxis: { labels: { enabled } } })}>\n          Show Labels\n        </Switch>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nXAxisSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/YAxisSettings.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport getOptions from \"../getOptions\";\nimport YAxisSettings from \"./YAxisSettings\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction elementExists(wrapper: any, testId: any) {\n  return findByTestID(wrapper, testId).length > 0;\n}\n\nfunction mount(options: any, done: any) {\n  options = getOptions(options);\n  return enzyme.mount(\n    <YAxisSettings\n      visualizationName=\"Test\"\n      data={{ columns: [], rows: [] }}\n      options={options}\n      onOptionsChange={(changedOptions: any) => {\n        expect(changedOptions).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Chart -> Editor -> Y-Axis Settings\", () => {\n  test(\"Changes axis type\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        yAxis: [{ type: \"linear\" }, { type: \"linear\", opposite: true }],\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.LeftYAxis.Type\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Chart.LeftYAxis.Type.Category\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Changes axis name\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        yAxis: [{ type: \"linear\" }, { type: \"linear\", opposite: true }],\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.LeftYAxis.Name\")\n      .last()\n      .simulate(\"change\", { target: { value: \"test\" } });\n  });\n\n  test(\"Changes axis tick format\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        yAxis: [],\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.LeftYAxis.TickFormat\")\n      .last()\n      .simulate(\"change\", { target: { value: \"s\" } });\n  });\n\n  test(\"Changes axis min value\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        yAxis: [{ type: \"linear\" }, { type: \"linear\", opposite: true }],\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.LeftYAxis.RangeMin\")\n      .find(\"input\")\n      .last()\n      .simulate(\"change\", { target: { value: \"50\" } });\n  });\n\n  test(\"Changes axis max value\", done => {\n    const el = mount(\n      {\n        globalSeriesType: \"column\",\n        yAxis: [{ type: \"linear\" }, { type: \"linear\", opposite: true }],\n      },\n      done\n    );\n\n    findByTestID(el, \"Chart.LeftYAxis.RangeMax\")\n      .find(\"input\")\n      .last()\n      .simulate(\"change\", { target: { value: \"200\" } });\n  });\n\n  describe(\"for non-heatmap\", () => {\n    test(\"Right Y Axis should be available\", () => {\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.\n      const el = mount({\n        globalSeriesType: \"column\",\n        yAxis: [{ type: \"linear\" }, { type: \"linear\", opposite: true }],\n      });\n\n      expect(elementExists(el, \"Chart.RightYAxis.Type\")).toBeTruthy();\n    });\n  });\n\n  describe(\"for heatmap\", () => {\n    test(\"Right Y Axis should not be available\", () => {\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.\n      const el = mount({\n        globalSeriesType: \"heatmap\",\n        yAxis: [{ type: \"linear\" }, { type: \"linear\", opposite: true }],\n      });\n\n      expect(elementExists(el, \"Chart.RightYAxis.Type\")).toBeFalsy();\n    });\n\n    test(\"Sets Sort X Values option\", done => {\n      const el = mount(\n        {\n          globalSeriesType: \"heatmap\",\n          sortY: false,\n        },\n        done\n      );\n\n      findByTestID(el, \"Chart.LeftYAxis.Sort\")\n        .last()\n        .simulate(\"click\");\n    });\n\n    test(\"Sets Reverse Y Values option\", done => {\n      const el = mount(\n        {\n          globalSeriesType: \"heatmap\",\n          reverseY: false,\n        },\n        done\n      );\n\n      findByTestID(el, \"Chart.LeftYAxis.Reverse\")\n        .last()\n        .simulate(\"click\");\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/YAxisSettings.tsx",
    "content": "import React from \"react\";\nimport { Section, Switch } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nimport AxisSettings from \"./AxisSettings\";\n\nexport default function YAxisSettings({ options, onOptionsChange }: any) {\n  const [leftYAxis, rightYAxis] = options.yAxis;\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section.Title>{!options.swappedAxes ? \"Left Y Axis\" : \"X Axis\"}</Section.Title>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <AxisSettings\n          id=\"LeftYAxis\"\n          features={{ range: true }}\n          options={leftYAxis}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(axis: any) => any' is not assignable to typ... Remove this comment to see the full error message\n          onChange={(axis: any) => onOptionsChange({ yAxis: [axis, rightYAxis] })}\n        />\n      </Section>\n\n      {options.globalSeriesType !== \"heatmap\" && !options.swappedAxes && (\n        <React.Fragment>\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section.Title>Right Y Axis</Section.Title>\n\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            <AxisSettings\n              id=\"RightYAxis\"\n              features={{ range: true }}\n              options={rightYAxis}\n              // @ts-expect-error ts-migrate(2322) FIXME: Type '(axis: any) => any' is not assignable to typ... Remove this comment to see the full error message\n              onChange={(axis: any) => onOptionsChange({ yAxis: [leftYAxis, axis] })}\n            />\n          </Section>\n\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n            <Switch\n              // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n              id=\"chart-editor-y-axis-align-at-zero\"\n              data-test=\"Chart.YAxis.AlignAtZero\"\n              // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n              defaultChecked={options.alignYAxesAtZero}\n              // @ts-expect-error ts-migrate(2322) FIXME: Type '(alignYAxesAtZero: any) => any' is not assig... Remove this comment to see the full error message\n              onChange={(alignYAxesAtZero: any) => onOptionsChange({ alignYAxesAtZero })}>\n              Align Y Axes at Zero\n            </Switch>\n          </Section>\n        </React.Fragment>\n      )}\n\n      {options.globalSeriesType === \"heatmap\" && (\n        <React.Fragment>\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n            <Switch\n              // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n              id=\"chart-editor-y-axis-sort\"\n              data-test=\"Chart.LeftYAxis.Sort\"\n              // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n              defaultChecked={options.sortY}\n              // @ts-expect-error ts-migrate(2322) FIXME: Type '(sortY: any) => any' is not assignable to ty... Remove this comment to see the full error message\n              onChange={(sortY: any) => onOptionsChange({ sortY })}>\n              Sort Values\n            </Switch>\n          </Section>\n\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n            <Switch\n              // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n              id=\"chart-editor-y-axis-reverse\"\n              data-test=\"Chart.LeftYAxis.Reverse\"\n              // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n              defaultChecked={options.reverseY}\n              // @ts-expect-error ts-migrate(2322) FIXME: Type '(reverseY: any) => any' is not assignable to... Remove this comment to see the full error message\n              onChange={(reverseY: any) => onOptionsChange({ reverseY })}>\n              Reverse Order\n            </Switch>\n          </Section>\n        </React.Fragment>\n      )}\n    </React.Fragment>\n  );\n}\n\nYAxisSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/__snapshots__/ColorsSettings.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Chart -> Editor -> Colors Settings for all except of pie and heatmap Changes series color 1`] = `\n{\n  \"seriesOptions\": {\n    \"b\": {\n      \"color\": \"#FF0000\",\n    },\n  },\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Colors Settings for heatmap Changes color scheme 1`] = `\n{\n  \"colorScheme\": \"Blues\",\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Colors Settings for heatmap Sets custom color scheme 1`] = `\n{\n  \"heatMinColor\": \"#FFFF00\",\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Colors Settings for heatmap Sets custom color scheme 2`] = `\n{\n  \"heatMaxColor\": \"#FF0000\",\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Colors Settings for pie Changes series color 1`] = `\n{\n  \"valuesOptions\": {\n    \"v\": {\n      \"color\": \"#FF0000\",\n    },\n  },\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/__snapshots__/DataLabelsSettings.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Chart -> Editor -> Data Labels Settings Changes data labels format 1`] = `\n{\n  \"textFormat\": \"{{ @@x }} :: {{ @@y }} / {{ @@yPercent }}\",\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Data Labels Settings Changes date/time format 1`] = `\n{\n  \"dateTimeFormat\": \"YYYY MMM DD\",\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Data Labels Settings Changes number format 1`] = `\n{\n  \"numberFormat\": \"0.00\",\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Data Labels Settings Changes percent values format 1`] = `\n{\n  \"percentFormat\": \"0.0%\",\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Data Labels Settings Sets Show Data Labels option 1`] = `\n{\n  \"showDataLabels\": true,\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/__snapshots__/GeneralSettings.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Chart -> Editor -> General Settings Box: toggles show points 1`] = `\n{\n  \"showpoints\": true,\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> General Settings Changes global series type 1`] = `\n{\n  \"globalSeriesType\": \"pie\",\n  \"seriesOptions\": {\n    \"a\": {\n      \"type\": \"pie\",\n    },\n    \"b\": {\n      \"type\": \"pie\",\n    },\n  },\n  \"showDataLabels\": true,\n  \"swappedAxes\": false,\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> General Settings Enables stacking 1`] = `\n{\n  \"series\": {\n    \"stacking\": \"stack\",\n  },\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> General Settings Keep missing/null values 1`] = `\n{\n  \"missingValuesAsZero\": false,\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> General Settings Pie: changes direction 1`] = `\n{\n  \"direction\": {\n    \"type\": \"clockwise\",\n  },\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> General Settings Toggles Enable click events 1`] = `\n{\n  \"enableLink\": true,\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> General Settings Toggles horizontal bar chart 1`] = `\n{\n  \"seriesOptions\": {},\n  \"swappedAxes\": true,\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> General Settings Toggles legend 1`] = `\n{\n  \"legend\": {\n    \"enabled\": false,\n  },\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> General Settings Toggles normalize values to percentage 1`] = `\n{\n  \"series\": {\n    \"percentValues\": true,\n  },\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/__snapshots__/SeriesSettings.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Chart -> Editor -> Series Settings Changes series axis 1`] = `\n{\n  \"seriesOptions\": {\n    \"a\": {\n      \"yAxis\": 1,\n    },\n  },\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Series Settings Changes series label 1`] = `\n{\n  \"seriesOptions\": {\n    \"a\": {\n      \"name\": \"test\",\n    },\n  },\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Series Settings Changes series type 1`] = `\n{\n  \"seriesOptions\": {\n    \"a\": {\n      \"type\": \"area\",\n    },\n  },\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/__snapshots__/XAxisSettings.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Chart -> Editor -> X-Axis Settings Changes axis name 1`] = `\n{\n  \"xAxis\": {\n    \"labels\": {\n      \"enabled\": true,\n    },\n    \"title\": {\n      \"text\": \"test\",\n    },\n    \"type\": \"-\",\n  },\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> X-Axis Settings Changes axis tick format 1`] = `\n{\n  \"xAxis\": {\n    \"labels\": {\n      \"enabled\": true,\n    },\n    \"tickFormat\": \"%B\",\n    \"type\": \"-\",\n  },\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> X-Axis Settings Changes axis type 1`] = `\n{\n  \"xAxis\": {\n    \"labels\": {\n      \"enabled\": true,\n    },\n    \"type\": \"linear\",\n  },\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> X-Axis Settings Sets Reverse X Values option 1`] = `\n{\n  \"reverseX\": true,\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> X-Axis Settings Sets Show Labels option 1`] = `\n{\n  \"xAxis\": {\n    \"labels\": {\n      \"enabled\": true,\n    },\n  },\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> X-Axis Settings Sets Sort X Values option 1`] = `\n{\n  \"sortX\": true,\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/__snapshots__/YAxisSettings.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Chart -> Editor -> Y-Axis Settings Changes axis max value 1`] = `\n{\n  \"yAxis\": [\n    {\n      \"rangeMax\": 200,\n      \"type\": \"linear\",\n    },\n    {\n      \"opposite\": true,\n      \"type\": \"linear\",\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Y-Axis Settings Changes axis min value 1`] = `\n{\n  \"yAxis\": [\n    {\n      \"rangeMin\": 50,\n      \"type\": \"linear\",\n    },\n    {\n      \"opposite\": true,\n      \"type\": \"linear\",\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Y-Axis Settings Changes axis name 1`] = `\n{\n  \"yAxis\": [\n    {\n      \"title\": {\n        \"text\": \"test\",\n      },\n      \"type\": \"linear\",\n    },\n    {\n      \"opposite\": true,\n      \"type\": \"linear\",\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Y-Axis Settings Changes axis tick format 1`] = `\n{\n  \"yAxis\": [\n    {\n      \"tickFormat\": \"s\",\n      \"type\": \"linear\",\n    },\n    {\n      \"opposite\": true,\n      \"type\": \"linear\",\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Y-Axis Settings Changes axis type 1`] = `\n{\n  \"yAxis\": [\n    {\n      \"type\": \"category\",\n    },\n    {\n      \"opposite\": true,\n      \"type\": \"linear\",\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Y-Axis Settings for heatmap Sets Reverse Y Values option 1`] = `\n{\n  \"reverseY\": true,\n}\n`;\n\nexports[`Visualizations -> Chart -> Editor -> Y-Axis Settings for heatmap Sets Sort X Values option 1`] = `\n{\n  \"sortY\": true,\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/editor.less",
    "content": ".chart-editor-series {\n  .series-settings-order {\n    display: flex;\n    align-items: center;\n    white-space: nowrap;\n  }\n\n  .series-settings-y-axis {\n    white-space: nowrap;\n  }\n\n  .drag-handle {\n    height: 28px;\n    padding: 0 5px;\n    margin-left: -5px;\n  }\n\n  &.sortable-container {\n    table {\n      background: transparent;\n    }\n\n    thead th {\n      // TODO: replace with @table-header-bg\n      // Cannot do it not because of conflict between Antd and Bootstrap variables\n      background: mix(#ffffff, rgb(102, 136, 153), 97%) !important;\n    }\n\n    &.sortable-container-dragging tbody {\n      td {\n        background: transparent !important;\n      }\n\n      .chart-editor-series-dragged-item {\n        td {\n          // TODO: replace with @table-row-hover-bg\n          // Cannot do it not because of conflict between Antd and Bootstrap variables\n          background: mix(#ffffff, rgb(102, 136, 153), 95%) !important;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/index.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport getOptions from \"../getOptions\";\nimport Editor from \"./index\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction elementExists(wrapper: any, testId: any) {\n  return findByTestID(wrapper, testId).length > 0;\n}\n\nfunction mount(options: any, data: any) {\n  options = getOptions(options);\n  return enzyme.mount(<Editor visualizationName=\"Test\" data={data} options={options} onOptionsChange={() => {}} />);\n}\n\ndescribe(\"Visualizations -> Chart -> Editor (wrapper)\", () => {\n  test(\"Renders generic wrapper\", () => {\n    const el = mount({ globalSeriesType: \"column\" }, { columns: [], rows: [] });\n\n    expect(elementExists(el, \"VisualizationEditor.Tabs.General\")).toBeTruthy();\n    expect(elementExists(el, \"VisualizationEditor.Tabs.XAxis\")).toBeTruthy();\n    expect(elementExists(el, \"VisualizationEditor.Tabs.YAxis\")).toBeTruthy();\n    expect(elementExists(el, \"VisualizationEditor.Tabs.Series\")).toBeTruthy();\n    expect(elementExists(el, \"VisualizationEditor.Tabs.Colors\")).toBeTruthy();\n    expect(elementExists(el, \"VisualizationEditor.Tabs.DataLabels\")).toBeTruthy();\n\n    expect(elementExists(el, \"Chart.GlobalSeriesType\")).toBeTruthy(); // general settings block exists\n    expect(elementExists(el, \"Chart.Custom.Code\")).toBeFalsy(); // custom settings block does not exist\n  });\n\n  test(\"Renders wrapper for custom charts\", () => {\n    const el = mount({ globalSeriesType: \"custom\" }, { columns: [], rows: [] });\n\n    expect(elementExists(el, \"VisualizationEditor.Tabs.General\")).toBeTruthy();\n    expect(elementExists(el, \"VisualizationEditor.Tabs.XAxis\")).toBeFalsy();\n    expect(elementExists(el, \"VisualizationEditor.Tabs.YAxis\")).toBeFalsy();\n    expect(elementExists(el, \"VisualizationEditor.Tabs.Series\")).toBeFalsy();\n    expect(elementExists(el, \"VisualizationEditor.Tabs.Colors\")).toBeFalsy();\n    expect(elementExists(el, \"VisualizationEditor.Tabs.DataLabels\")).toBeFalsy();\n\n    expect(elementExists(el, \"Chart.GlobalSeriesType\")).toBeTruthy(); // general settings block exists\n    expect(elementExists(el, \"Chart.Custom.Code\")).toBeTruthy(); // custom settings block exists\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Editor/index.tsx",
    "content": "/* eslint-disable react/prop-types */\nimport React from \"react\";\nimport createTabbedEditor from \"@/components/visualizations/editor/createTabbedEditor\";\n\nimport GeneralSettings from \"./GeneralSettings\";\nimport XAxisSettings from \"./XAxisSettings\";\nimport YAxisSettings from \"./YAxisSettings\";\nimport SeriesSettings from \"./SeriesSettings\";\nimport ColorsSettings from \"./ColorsSettings\";\nimport DataLabelsSettings from \"./DataLabelsSettings\";\nimport CustomChartSettings from \"./CustomChartSettings\";\n\nimport \"./editor.less\";\n\nconst isCustomChart = (options: any) => options.globalSeriesType === \"custom\";\nconst isPieChart = (options: any) => options.globalSeriesType === \"pie\";\n\nexport default createTabbedEditor([\n  {\n    key: \"General\",\n    title: \"General\",\n    component: (props: any) => (\n      <React.Fragment>\n        <GeneralSettings {...props} />\n        {isCustomChart(props.options) && <CustomChartSettings {...props} />}\n      </React.Fragment>\n    ),\n  },\n  {\n    key: \"XAxis\",\n    title: ({ swappedAxes }: any) => (!swappedAxes ? \"X Axis\" : \"Y Axis\"),\n    component: XAxisSettings,\n    isAvailable: (options: any) => !isCustomChart(options) && !isPieChart(options),\n  },\n  {\n    key: \"YAxis\",\n    title: ({ swappedAxes }: any) => (!swappedAxes ? \"Y Axis\" : \"X Axis\"),\n    component: YAxisSettings,\n    isAvailable: (options: any) => !isCustomChart(options) && !isPieChart(options),\n  },\n  {\n    key: \"Series\",\n    title: \"Series\",\n    component: SeriesSettings,\n    isAvailable: (options: any) => !isCustomChart(options),\n  },\n  {\n    key: \"Colors\",\n    title: \"Colors\",\n    component: ColorsSettings,\n    isAvailable: (options: any) => !isCustomChart(options),\n  },\n  {\n    key: \"DataLabels\",\n    title: \"Data Labels\",\n    component: DataLabelsSettings,\n    isAvailable: (options: any) => !isCustomChart(options),\n  },\n]);\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Renderer/CustomPlotlyChart.tsx",
    "content": "import React, { useState, useEffect, useMemo } from \"react\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\n\nimport resizeObserver from \"@/services/resizeObserver\";\n\nimport getChartData from \"../getChartData\";\nimport { Plotly, prepareCustomChartData, createCustomChartRenderer } from \"../plotly\";\n\nexport default function CustomPlotlyChart({ options, data }: any) {\n  const [container, setContainer] = useState(null);\n\n  const renderCustomChart = useMemo(() => createCustomChartRenderer(options.customCode, options.enableConsoleLogs), [\n    options.customCode,\n    options.enableConsoleLogs,\n  ]);\n\n  const plotlyData = useMemo(() => prepareCustomChartData(getChartData(data.rows, options)), [options, data]);\n\n  useEffect(() => {\n    if (container) {\n      const unwatch = resizeObserver(container, () => {\n        // Clear existing data with blank data for succeeding codeCall adds data to existing plot.\n        Plotly.purge(container);\n        renderCustomChart(plotlyData.x, plotlyData.ys, container, Plotly);\n      });\n      return unwatch;\n    }\n  }, [container, plotlyData, renderCustomChart]);\n\n  // Cleanup when component destroyed\n  useEffect(() => {\n    if (container) {\n      return () => Plotly.purge(container);\n    }\n  }, [container]);\n\n  // @ts-expect-error ts-migrate(2322) FIXME: Type 'Dispatch<SetStateAction<null>>' is not assig... Remove this comment to see the full error message\n  return <div className=\"chart-visualization-container\" ref={setContainer} />;\n}\n\nCustomPlotlyChart.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Renderer/PlotlyChart.tsx",
    "content": "import React, { useState, useEffect, useContext, useRef } from \"react\";\nimport useMedia from \"use-media\";\nimport { ErrorBoundaryContext } from \"@/components/ErrorBoundary\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\nimport getChartData from \"../getChartData\";\nimport initChart from \"./initChart\";\n\nexport interface PlotlyChartProps {\n  data: {\n    rows: any[];\n    columns: any[];\n  };\n  options: object;\n}\n\nexport default function PlotlyChart({ options, data }: PlotlyChartProps) {\n  const [container, setContainer] = useState(null);\n  const [chart, setChart] = useState(null);\n\n  const errorHandler = useContext(ErrorBoundaryContext);\n  const errorHandlerRef = useRef();\n  // @ts-expect-error ts-migrate(2322) FIXME: Type '{ handleError: (error: any) => void; reset: ... Remove this comment to see the full error message\n  errorHandlerRef.current = errorHandler;\n\n  const isMobile = useMedia({ maxWidth: 768 });\n  const isMobileRef = useRef();\n  // @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'undefine... Remove this comment to see the full error message\n  isMobileRef.current = isMobile;\n\n  useEffect(() => {\n    if (container) {\n      let isDestroyed = false;\n\n      const chartData = getChartData(data.rows, options);\n      const _chart = initChart(container, options, chartData, visualizationsSettings, (error: any) => {\n        // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.\n        errorHandlerRef.current.handleError(error);\n      });\n      _chart.initialized.then(() => {\n        if (!isDestroyed) {\n          setChart(_chart);\n        }\n      });\n      return () => {\n        isDestroyed = true;\n        _chart.destroy();\n      };\n    }\n  }, [options, data, container]);\n\n  useEffect(() => {\n    if (chart) {\n      // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.\n      chart.setZoomEnabled(!isMobile);\n    }\n  }, [chart, isMobile]);\n\n  // @ts-expect-error ts-migrate(2322) FIXME: Type 'Dispatch<SetStateAction<null>>' is not assig... Remove this comment to see the full error message\n  return <div className=\"chart-visualization-container\" ref={setContainer} />;\n}\n\nPlotlyChart.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Renderer/index.tsx",
    "content": "import React from \"react\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\n\nimport PlotlyChart from \"./PlotlyChart\";\nimport CustomPlotlyChart from \"./CustomPlotlyChart\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\n\nimport \"./renderer.less\";\n\nexport default function Renderer({ options, ...props }: any) {\n  if (options.globalSeriesType === \"custom\" && visualizationsSettings.allowCustomJSVisualizations) {\n    return <CustomPlotlyChart options={options} {...props} />;\n  }\n  return <PlotlyChart options={options} {...props} />;\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Renderer/initChart.ts",
    "content": "import { isArray, isObject, isString, isFunction, startsWith, reduce, merge, map, each, isNil } from \"lodash\";\nimport resizeObserver from \"@/services/resizeObserver\";\nimport { Plotly, prepareData, prepareLayout, updateData, updateAxes, updateChartSize } from \"../plotly\";\nimport { formatSimpleTemplate } from \"@/lib/value-format\";\n\nconst navigateToUrl = (url: string, shouldOpenNewTab: boolean = true) =>\n  shouldOpenNewTab\n    ? window.open(url, \"_blank\")\n    : window.location.href = url;\n\nfunction createErrorHandler(errorHandler: any) {\n  return (error: any) => {\n    // This error happens only when chart width is 20px and looks that\n    // it's safe to just ignore it: 1px less or more and chart will get fixed.\n    if (isString(error) && startsWith(error, \"ax.dtick error\")) {\n      return;\n    }\n    errorHandler(error);\n  };\n}\n\n// This utility is intended to reduce amount of plot updates when multiple Plotly.relayout\n// calls needed in order to compute/update the plot.\n// `.append()` method takes an array of two element: first one is a object with updates for layout,\n// and second is an optional function that will be called when plot is updated. That function may\n// return an array with same structure if further updates needed.\n// `.process()` merges all updates into a single object and calls `Plotly.relayout()`. After that\n// it calls all callbacks, collects their return values and does another loop if needed.\nfunction initPlotUpdater() {\n  let actions: any = [];\n\n  const updater = {\n    append(action: any) {\n      if (isArray(action) && isObject(action[0])) {\n        actions.push(action);\n      }\n      return updater;\n    },\n    process(plotlyElement: any): Promise<void> {\n      if (actions.length > 0) {\n        const updates = reduce(actions, (updates, action) => merge(updates, action[0]), {});\n        const handlers = map(actions, action => (isFunction(action[1]) ? action[1] : () => null));\n        actions = [];\n        return Plotly.relayout(plotlyElement, updates).then(() => {\n          each(handlers, handler => updater.append(handler()));\n          return updater.process(plotlyElement);\n        });\n      } else {\n        return Promise.resolve();\n      }\n    },\n  };\n\n  return updater;\n}\n\nexport default function initChart(container: any, options: any, data: any, additionalOptions: any, onError: any) {\n  const handleError = createErrorHandler(onError);\n\n  const plotlyOptions = {\n    showLink: false,\n    displaylogo: false,\n  };\n\n  if (additionalOptions.hidePlotlyModeBar) {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'displayModeBar' does not exist on type '... Remove this comment to see the full error message\n    plotlyOptions.displayModeBar = false;\n  }\n\n  const plotlyData = prepareData(data, options);\n  const plotlyLayout = prepareLayout(container, options, plotlyData);\n\n  let isDestroyed = false;\n\n  let updater = initPlotUpdater();\n\n  function createSafeFunction(fn: any) {\n    // @ts-expect-error ts-migrate(7019) FIXME: Rest parameter 'args' implicitly has an 'any[]' ty... Remove this comment to see the full error message\n    return (...args) => {\n      if (!isDestroyed) {\n        try {\n          return fn(...args);\n        } catch (error) {\n          handleError(error);\n        }\n      }\n    };\n  }\n\n  let unwatchResize = () => {};\n\n  const promise = Promise.resolve()\n    .then(() => Plotly.newPlot(container, plotlyData, plotlyLayout, plotlyOptions))\n    .then(\n      createSafeFunction(() =>\n        updater\n          .append(updateAxes(container, plotlyData, plotlyLayout, options))\n          .append(updateChartSize(container, plotlyLayout, options))\n          .process(container)\n      )\n    )\n    .then(\n      createSafeFunction(() => {\n        container.on(\n          \"plotly_restyle\",\n          createSafeFunction((updates: any) => {\n            // This event is triggered if some plotly data/layout has changed.\n            // We need to catch only changes of traces visibility to update stacking\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'visible' does not exist on type 'object'... Remove this comment to see the full error message\n            if (isArray(updates) && isObject(updates[0]) && updates[0].visible) {\n              updateData(plotlyData, options);\n              updater.append(updateAxes(container, plotlyData, plotlyLayout, options)).process(container);\n            }\n          })\n        );\n        options.onHover && container.on(\"plotly_hover\", options.onHover);\n        options.onUnHover && container.on(\"plotly_unhover\", options.onUnHover);\n        container.on('plotly_click',\n          createSafeFunction((data: any) => {\n            if (options.enableLink === true) {\n              try {\n                var templateValues: { [k: string]: any } = {}\n                data.points.forEach((point: any, i: number) => {\n                  var sourceDataElement = [...point.data?.sourceData?.entries()][point.pointNumber ?? 0]?.[1]?.row ?? {};\n\n                  if (isNil(templateValues['@@x'])) templateValues['@@x'] = sourceDataElement.x;\n                  if (isNil(templateValues['@@y'])) templateValues['@@y'] = sourceDataElement.y;\n\n                  templateValues[`@@y${i + 1}`] = sourceDataElement.y;\n                  templateValues[`@@x${i + 1}`] = sourceDataElement.x;\n                })\n                navigateToUrl(\n                  formatSimpleTemplate(options.linkFormat, templateValues).replace(/{{\\s*([^\\s]+?)\\s*}}/g, () => ''),\n                  options.linkOpenNewTab);\n              } catch (error) {\n                console.error('Click error: [%s]', error.message, { error });\n              }\n            }\n          }));\n\n        unwatchResize = resizeObserver(\n          container,\n          createSafeFunction(() => {\n            updater.append(updateChartSize(container, plotlyLayout, options)).process(container);\n          })\n        );\n      })\n    )\n    .catch(handleError);\n\n  const result: any = {\n    initialized: promise.then(() => result),\n    setZoomEnabled: createSafeFunction((allowZoom: any) => {\n      const layoutUpdates = { dragmode: allowZoom ? \"zoom\" : false };\n      // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ dragmode: string | boolean; }'... Remove this comment to see the full error message\n      return Plotly.relayout(container, layoutUpdates);\n    }),\n    destroy: createSafeFunction(() => {\n      isDestroyed = true;\n      container.removeAllListeners(\"plotly_restyle\");\n      unwatchResize();\n      delete container.__previousSize; // added by `updateChartSize`\n      Plotly.purge(container);\n    }),\n  };\n\n  return result;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/Renderer/renderer.less",
    "content": ".chart-visualization-container {\n  height: 400px;\n  overflow: hidden;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/fixtures/getChartData/multiple-series-grouped.json",
    "content": "{\n  \"input\": {\n    \"data\": [\n      { \"a\": 42, \"b\": 10, \"g\": \"first\" },\n      { \"a\": 62, \"b\": 73, \"g\": \"first\" },\n      { \"a\": 21, \"b\": 82, \"g\": \"second\" },\n      { \"a\": 85, \"b\": 50, \"g\": \"first\" },\n      { \"a\": 95, \"b\": 32, \"g\": \"second\" }\n    ],\n    \"options\": {\n      \"columnMapping\": {\n        \"a\": \"x\",\n        \"b\": \"y\",\n        \"g\": \"series\"\n      },\n      \"seriesOptions\": {}\n    }\n  },\n  \"output\": {\n    \"data\": [\n      {\n        \"name\": \"first\",\n        \"type\": \"column\",\n        \"data\": [\n          { \"x\": 42, \"y\": 10, \"$raw\": { \"a\": 42, \"b\": 10, \"g\": \"first\" } },\n          { \"x\": 62, \"y\": 73, \"$raw\": { \"a\": 62, \"b\": 73, \"g\": \"first\" } },\n          { \"x\": 85, \"y\": 50, \"$raw\": { \"a\": 85, \"b\": 50, \"g\": \"first\" } }\n        ]\n      },\n      {\n        \"name\": \"second\",\n        \"type\": \"column\",\n        \"data\": [\n          { \"x\": 21, \"y\": 82, \"$raw\": { \"a\": 21, \"b\": 82, \"g\": \"second\" } },\n          { \"x\": 95, \"y\": 32, \"$raw\": { \"a\": 95, \"b\": 32, \"g\": \"second\" } }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/fixtures/getChartData/multiple-series-multiple-y.json",
    "content": "{\n  \"input\": {\n    \"data\": [\n      { \"a\": 42, \"b\": 10, \"c\": 41, \"d\": 92 },\n      { \"a\": 62, \"b\": 73 },\n      { \"a\": 21, \"b\": null, \"c\": 33 },\n      { \"a\": 85, \"b\": 50 },\n      { \"a\": 95 }\n    ],\n    \"options\": {\n      \"columnMapping\": {\n        \"a\": \"x\",\n        \"b\": \"y\",\n        \"c\": \"y\"\n      },\n      \"seriesOptions\": {}\n    }\n  },\n  \"output\": {\n    \"data\": [\n      {\n        \"name\": \"b\",\n        \"type\": \"column\",\n        \"data\": [\n          { \"x\": 42, \"y\": 10, \"$raw\": { \"a\": 42, \"b\": 10, \"c\": 41, \"d\": 92 } },\n          { \"x\": 62, \"y\": 73, \"$raw\": { \"a\": 62, \"b\": 73 } },\n          { \"x\": 21, \"y\": null, \"$raw\": { \"a\": 21, \"b\": null, \"c\": 33 } },\n          { \"x\": 85, \"y\": 50, \"$raw\": { \"a\": 85, \"b\": 50 } }\n        ]\n      },\n      {\n        \"name\": \"c\",\n        \"type\": \"column\",\n        \"data\": [\n          { \"x\": 42, \"y\": 41, \"$raw\": { \"a\": 42, \"b\": 10, \"c\": 41, \"d\": 92 } },\n          { \"x\": 21, \"y\": 33, \"$raw\": { \"a\": 21, \"b\": null, \"c\": 33 } }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/fixtures/getChartData/multiple-series-sorted.json",
    "content": "{\n  \"input\": {\n    \"data\": [\n      { \"a\": 42, \"b\": 10, \"g\": \"first\" },\n      { \"a\": 62, \"b\": 73, \"g\": \"first\" },\n      { \"a\": 21, \"b\": 82, \"g\": \"second\" },\n      { \"a\": 85, \"b\": 50, \"g\": \"first\" },\n      { \"a\": 95, \"b\": 32, \"g\": \"second\" }\n    ],\n    \"options\": {\n      \"columnMapping\": {\n        \"a\": \"x\",\n        \"b\": \"y\",\n        \"g\": \"series\"\n      },\n      \"seriesOptions\": {\n        \"first\": { \"zIndex\": 2 },\n        \"second\": { \"zIndex\": 1 }\n      }\n    }\n  },\n  \"output\": {\n    \"data\": [\n      {\n        \"name\": \"second\",\n        \"type\": \"column\",\n        \"data\": [\n          { \"x\": 21, \"y\": 82, \"$raw\": { \"a\": 21, \"b\": 82, \"g\": \"second\" } },\n          { \"x\": 95, \"y\": 32, \"$raw\": { \"a\": 95, \"b\": 32, \"g\": \"second\" } }\n        ]\n      },\n      {\n        \"name\": \"first\",\n        \"type\": \"column\",\n        \"data\": [\n          { \"x\": 42, \"y\": 10, \"$raw\": { \"a\": 42, \"b\": 10, \"g\": \"first\" } },\n          { \"x\": 62, \"y\": 73, \"$raw\": { \"a\": 62, \"b\": 73, \"g\": \"first\" } },\n          { \"x\": 85, \"y\": 50, \"$raw\": { \"a\": 85, \"b\": 50, \"g\": \"first\" } }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/fixtures/getChartData/single-series.json",
    "content": "{\n  \"input\": {\n    \"data\": [\n      { \"a\": 42, \"b\": 10, \"c\": 41, \"d\": 92 },\n      { \"a\": 62, \"b\": 73 },\n      { \"a\": 21, \"b\": null },\n      { \"a\": 85, \"b\": 50 },\n      { \"a\": 95 }\n    ],\n    \"options\": {\n      \"columnMapping\": {\n        \"a\": \"x\",\n        \"b\": \"y\"\n      },\n      \"seriesOptions\": {}\n    }\n  },\n  \"output\": {\n    \"data\": [\n      {\n        \"name\": \"b\",\n        \"type\": \"column\",\n        \"data\": [\n          { \"x\": 42, \"y\": 10, \"$raw\": { \"a\": 42, \"b\": 10, \"c\": 41, \"d\": 92 } },\n          { \"x\": 62, \"y\": 73, \"$raw\": { \"a\": 62, \"b\": 73 } },\n          { \"x\": 21, \"y\": null, \"$raw\": { \"a\": 21, \"b\": null } },\n          { \"x\": 85, \"y\": 50, \"$raw\": { \"a\": 85, \"b\": 50 } }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/getChartData.test.ts",
    "content": "/* eslint-disable global-require, import/no-unresolved */\nimport getChartData from \"./getChartData\";\n\ndescribe(\"Visualizations\", () => {\n  describe(\"Chart\", () => {\n    describe(\"getChartData\", () => {\n      test(\"Single series\", () => {\n        const { input, output } = require(\"./fixtures/getChartData/single-series\");\n        const data = getChartData(input.data, input.options);\n        expect(data).toEqual(output.data);\n      });\n\n      test(\"Multiple series: multiple Y mappings\", () => {\n        const { input, output } = require(\"./fixtures/getChartData/multiple-series-multiple-y\");\n        const data = getChartData(input.data, input.options);\n        expect(data).toEqual(output.data);\n      });\n\n      test(\"Multiple series: grouped\", () => {\n        const { input, output } = require(\"./fixtures/getChartData/multiple-series-grouped\");\n        const data = getChartData(input.data, input.options);\n        expect(data).toEqual(output.data);\n      });\n\n      test(\"Multiple series: sorted\", () => {\n        const { input, output } = require(\"./fixtures/getChartData/multiple-series-sorted\");\n        const data = getChartData(input.data, input.options);\n        expect(data).toEqual(output.data);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/getChartData.ts",
    "content": "import { isNil, isObject, each, forOwn, sortBy, values } from \"lodash\";\n\nfunction addPointToSeries(point: any, seriesCollection: any, seriesName: any) {\n  if (seriesCollection[seriesName] === undefined) {\n    seriesCollection[seriesName] = {\n      name: seriesName,\n      type: \"column\",\n      data: [],\n    };\n  }\n\n  seriesCollection[seriesName].data.push(point);\n}\n\nexport default function getChartData(data: any, options: any) {\n  const series = {};\n\n  const mappings = options.columnMapping;\n\n  each(data, row => {\n    let point = { $raw: row };\n    let seriesName = null;\n    let xValue = 0;\n    const yValues = {};\n    let eValue: any = null;\n    let sizeValue: any = null;\n    let zValue: any = null;\n\n    forOwn(row, (value, definition) => {\n      definition = \"\" + definition;\n      const definitionParts = definition.split(\"::\") || definition.split(\"__\");\n      const name = definitionParts[0];\n      const type = mappings ? mappings[definition] : definitionParts[1];\n\n      if (type === \"unused\") {\n        return;\n      }\n\n      if (type === \"x\") {\n        xValue = value;\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        point[type] = value;\n      }\n      if (type === \"y\") {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        yValues[name] = value;\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        point[type] = value;\n      }\n      if (type === \"yError\") {\n        eValue = value;\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        point[type] = value;\n      }\n\n      if (type === \"series\") {\n        seriesName = String(value);\n      }\n\n      if (type === \"size\") {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        point[type] = value;\n        sizeValue = value;\n      }\n\n      if (type === \"zVal\") {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        point[type] = value;\n        zValue = value;\n      }\n\n      if (type === \"multiFilter\" || type === \"multi-filter\") {\n        seriesName = String(value);\n      }\n    });\n\n    if (isNil(seriesName)) {\n      each(yValues, (yValue, ySeriesName) => {\n        // @ts-expect-error ts-migrate(2322) FIXME: Type '{ x: number; y: never; $raw: any; }' is not ... Remove this comment to see the full error message\n        point = { x: xValue, y: yValue, $raw: point.$raw };\n        if (eValue !== null) {\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'yError' does not exist on type '{ $raw: ... Remove this comment to see the full error message\n          point.yError = eValue;\n        }\n\n        if (sizeValue !== null) {\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'size' does not exist on type '{ $raw: an... Remove this comment to see the full error message\n          point.size = sizeValue;\n        }\n\n        if (zValue !== null) {\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'zVal' does not exist on type '{ $raw: an... Remove this comment to see the full error message\n          point.zVal = zValue;\n        }\n        addPointToSeries(point, series, ySeriesName);\n      });\n    } else {\n      addPointToSeries(point, series, seriesName);\n    }\n  });\n  return sortBy(values(series), ({ name }) => {\n    if (isObject(options.seriesOptions[name])) {\n      return (options.seriesOptions[name] as any).zIndex || 0;\n    }\n    return 0;\n  });\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/getOptions.ts",
    "content": "import { merge } from \"lodash\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\n\nconst DEFAULT_OPTIONS = {\n  globalSeriesType: \"column\",\n  sortX: true,\n  legend: { enabled: true, placement: \"auto\", traceorder: \"normal\" },\n  xAxis: { type: \"-\", labels: { enabled: true } },\n  yAxis: [{ type: \"linear\" }, { type: \"linear\", opposite: true }],\n  alignYAxesAtZero: false,\n  error_y: { type: \"data\", visible: true },\n  series: { stacking: null, error_y: { type: \"data\", visible: true } },\n  seriesOptions: {},\n  valuesOptions: {},\n  columnMapping: {},\n  direction: { type: \"counterclockwise\" },\n  sizemode: \"diameter\",\n  coefficient: 1,\n  piesort: true,\n  color_scheme: \"Redash\",\n  lineShape: \"linear\",\n\n  // showDataLabels: false, // depends on chart type\n  numberFormat: \"0,0[.]00000\",\n  percentFormat: \"0[.]00%\",\n  // dateTimeFormat: 'DD/MM/YYYY HH:mm', // will be set from visualizationsSettings\n  textFormat: \"\", // default: combination of {{ @@yPercent }} ({{ @@y }} ± {{ @@yError }})\n\n  enableLink: false,\n  linkOpenNewTab: true,\n  linkFormat: \"\", // template like a textFormat\n\n  missingValuesAsZero: true,\n};\n\nexport default function getOptions(options: any) {\n  const result = merge(\n    {},\n    DEFAULT_OPTIONS,\n    {\n      showDataLabels: options.globalSeriesType === \"pie\",\n      dateTimeFormat: visualizationsSettings.dateTimeFormat,\n    },\n    options\n  );\n\n  // Backward compatibility\n  if ([\"normal\", \"percent\"].indexOf(result.series.stacking) >= 0) {\n    result.series.percentValues = result.series.stacking === \"percent\";\n    result.series.stacking = \"stack\";\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/index.ts",
    "content": "import getOptions from \"./getOptions\";\nimport Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nexport default {\n  type: \"CHART\",\n  name: \"Chart\",\n  isDefault: true,\n  getOptions,\n  Renderer,\n  Editor,\n\n  defaultColumns: 6,\n  defaultRows: 8,\n  minColumns: 1,\n  minRows: 5,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/customChartUtils.ts",
    "content": "import { each } from \"lodash\";\nimport { normalizeValue } from \"./utils\";\n\nexport function prepareCustomChartData(series: any) {\n  const x: any = [];\n  const ys = {};\n\n  each(series, ({ name, data }) => {\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    ys[name] = [];\n    each(data, point => {\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 2-3 arguments, but got 1.\n      x.push(normalizeValue(point.x));\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      ys[name].push(normalizeValue(point.y));\n    });\n  });\n\n  return { x, ys };\n}\n\nexport function createCustomChartRenderer(code: any, logErrorsToConsole = false) {\n  // Create a function from custom code; catch syntax errors\n  let render = () => {};\n  try {\n    // @ts-expect-error ts-migrate(2322) FIXME: Type 'Function' is not assignable to type '() => v... Remove this comment to see the full error message\n    render = new Function(\"x, ys, element, Plotly\", code); // eslint-disable-line no-new-func\n  } catch (err) {\n    if (logErrorsToConsole) {\n      console.log(`Error while executing custom graph: ${err}`); // eslint-disable-line no-console\n    }\n  }\n\n  // Return function that will invoke custom code; catch runtime errors\n  return (x: any, ys: any, element: any, Plotly: any) => {\n    try {\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 4.\n      render(x, ys, element, Plotly);\n    } catch (err) {\n      if (logErrorsToConsole) {\n        console.log(`Error while executing custom graph: ${err}`); // eslint-disable-line no-console\n      }\n    }\n  };\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/bar/default.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"column\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"column\", \"color\": \"red\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"offsetgroup\": \"0\",\n        \"type\": \"bar\",\n        \"name\": \"a\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"10 ± 0\", \"20 ± 0\", \"30 ± 0\", \"40 ± 0\"],\n        \"textposition\": \"inside\",\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/bar/normalized.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"column\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": \"stack\", \"error_y\": { \"type\": \"data\", \"visible\": true }, \"percentValues\": true },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"column\", \"color\": \"red\" },\n        \"b\": { \"type\": \"column\", \"color\": \"blue\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      },\n      {\n        \"name\": \"b\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 40, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 10, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"type\": \"bar\",\n        \"name\": \"a\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [20, 40, 60, 80],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"20% (10 ± 0)\", \"40% (20 ± 0)\", \"60% (30 ± 0)\", \"80% (40 ± 0)\"],\n        \"textposition\": \"inside\",\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      },\n      {\n        \"visible\": true,\n        \"type\": \"bar\",\n        \"name\": \"b\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [80, 60, 40, 20],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"blue\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"80% (40 ± 0)\", \"60% (30 ± 0)\", \"40% (20 ± 0)\", \"20% (10 ± 0)\"],\n        \"textposition\": \"inside\",\n        \"marker\": { \"color\": \"blue\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/bar/stacked.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"column\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": \"stack\", \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"column\", \"color\": \"red\" },\n        \"b\": { \"type\": \"column\", \"color\": \"blue\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      },\n      {\n        \"name\": \"b\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 1, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 2, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 3, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 4, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"type\": \"bar\",\n        \"name\": \"a\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"10 ± 0\", \"20 ± 0\", \"30 ± 0\", \"40 ± 0\"],\n        \"textposition\": \"inside\",\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      },\n      {\n        \"visible\": true,\n        \"type\": \"bar\",\n        \"name\": \"b\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [1, 2, 3, 4],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"blue\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"1 ± 0\", \"2 ± 0\", \"3 ± 0\", \"4 ± 0\"],\n        \"textposition\": \"inside\",\n        \"marker\": { \"color\": \"blue\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/box/default.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"box\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"box\", \"color\": \"red\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"type\": \"box\",\n        \"mode\": \"markers\",\n        \"boxpoints\": \"outliers\",\n        \"hoverinfo\": false,\n        \"marker\": { \"color\": \"red\",  \"size\": 3 },\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hover\": [],\n        \"text\": [\"10 ± 0\", \"20 ± 0\", \"30 ± 0\", \"40 ± 0\"],\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/box/with-points.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"box\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"box\", \"color\": \"red\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true,\n      \"showpoints\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"type\": \"box\",\n        \"mode\": \"markers\",\n        \"boxpoints\": \"all\",\n        \"jitter\": 0.3,\n        \"pointpos\": -1.8,\n        \"hoverinfo\": false,\n        \"marker\": { \"color\": \"red\",  \"size\": 3 },\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hover\": [],\n        \"text\": [\"10 ± 0\", \"20 ± 0\", \"30 ± 0\", \"40 ± 0\"],\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/bubble/default.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"bubble\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"bubble\", \"color\": \"red\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0, \"size\": 51 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0, \"size\": 52 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0, \"size\": 53 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0, \"size\": 54 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"mode\": \"markers\",\n        \"marker\": { \"color\": \"red\", \"size\": [51, 52, 53, 54], \"sizemode\": \"diameter\" },\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"10 ± 0: 51\", \"20 ± 0: 52\", \"30 ± 0: 53\", \"40 ± 0: 54\"],\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/heatmap/default.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"heatmap\",\n      \"colorScheme\": \"Bluered\",\n      \"seriesOptions\": {},\n      \"showDataLabels\": false\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": 12, \"y\": 21, \"zVal\": 3 },\n          { \"x\": 11, \"y\": 22, \"zVal\": 2 },\n          { \"x\": 11, \"y\": 21, \"zVal\": 1 },\n          { \"x\": 12, \"y\": 22, \"zVal\": 4 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"x\": [12, 11],\n        \"y\": [21, 22],\n        \"z\": [[3, 1], [4, 2]],\n        \"type\": \"heatmap\",\n        \"name\": \"\",\n        \"colorscale\": [\n          [ 0, \"rgb(0,0,255)\" ],\n          [ 1, \"rgb(255,0,0)\" ]\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/heatmap/reversed.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"heatmap\",\n      \"colorScheme\": \"Bluered\",\n      \"seriesOptions\": {},\n      \"showDataLabels\": false,\n      \"reverseX\": true,\n      \"reverseY\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": 12, \"y\": 21, \"zVal\": 3 },\n          { \"x\": 11, \"y\": 22, \"zVal\": 2 },\n          { \"x\": 11, \"y\": 21, \"zVal\": 1 },\n          { \"x\": 12, \"y\": 22, \"zVal\": 4 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"x\": [11, 12],\n        \"y\": [22, 21],\n        \"z\": [[2, 4], [1, 3]],\n        \"type\": \"heatmap\",\n        \"name\": \"\",\n        \"colorscale\": [\n          [ 0, \"rgb(0,0,255)\" ],\n          [ 1, \"rgb(255,0,0)\" ]\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/heatmap/sorted-reversed.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"heatmap\",\n      \"colorScheme\": \"Bluered\",\n      \"seriesOptions\": {},\n      \"showDataLabels\": false,\n      \"sortX\": true,\n      \"sortY\": true,\n      \"reverseX\": true,\n      \"reverseY\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": 12, \"y\": 21, \"zVal\": 3 },\n          { \"x\": 11, \"y\": 22, \"zVal\": 2 },\n          { \"x\": 11, \"y\": 21, \"zVal\": 1 },\n          { \"x\": 12, \"y\": 22, \"zVal\": 4 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"x\": [12, 11],\n        \"y\": [22, 21],\n        \"z\": [[4, 2], [3, 1]],\n        \"type\": \"heatmap\",\n        \"name\": \"\",\n        \"colorscale\": [\n          [ 0, \"rgb(0,0,255)\" ],\n          [ 1, \"rgb(255,0,0)\" ]\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/heatmap/sorted.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"heatmap\",\n      \"colorScheme\": \"Bluered\",\n      \"seriesOptions\": {},\n      \"showDataLabels\": false,\n      \"sortX\": true,\n      \"sortY\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": 12, \"y\": 21, \"zVal\": 3 },\n          { \"x\": 11, \"y\": 22, \"zVal\": 2 },\n          { \"x\": 11, \"y\": 21, \"zVal\": 1 },\n          { \"x\": 12, \"y\": 22, \"zVal\": 4 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"x\": [11, 12],\n        \"y\": [21, 22],\n        \"z\": [[1, 3], [2, 4]],\n        \"type\": \"heatmap\",\n        \"name\": \"\",\n        \"colorscale\": [\n          [ 0, \"rgb(0,0,255)\" ],\n          [ 1, \"rgb(255,0,0)\" ]\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/heatmap/with-labels.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"heatmap\",\n      \"colorScheme\": \"Greys\",\n      \"seriesOptions\": {},\n      \"showDataLabels\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": 12, \"y\": 21, \"zVal\": 3 },\n          { \"x\": 11, \"y\": 22, \"zVal\": 2 },\n          { \"x\": 11, \"y\": 21, \"zVal\": 1 },\n          { \"x\": 12, \"y\": 22, \"zVal\": 4 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"x\": [12, 11],\n        \"y\": [21, 22],\n        \"z\": [[3, 1], [4, 2]],\n        \"type\": \"heatmap\",\n        \"name\": \"\",\n        \"colorscale\": [\n          [ 0, \"rgb(0,0,0)\" ],\n          [ 1, \"rgb(255,255,255)\" ]\n        ]\n      },\n      {\n        \"x\": [12, 11, 12, 11],\n        \"y\": [21, 21, 22, 22],\n        \"mode\": \"text\",\n        \"hoverinfo\": \"skip\",\n        \"showlegend\": false,\n        \"text\": [\"3\", \"1\", \"4\", \"2\"],\n        \"textfont\": {\n          \"color\": [\"#333333\", \"#ffffff\", \"#333333\", \"#ffffff\"]\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/line-area/default.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"line\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"line\", \"color\": \"red\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true,\n      \"lineShape\": \"linear\"\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"10 ± 0\", \"20 ± 0\", \"30 ± 0\", \"40 ± 0\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/line-area/keep-missing-values.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"line\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": \"stack\", \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"line\", \"color\": \"red\" },\n        \"b\": { \"type\": \"line\", \"color\": \"blue\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": false,\n      \"lineShape\": \"linear\"\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      },\n      {\n        \"name\": \"b\",\n        \"data\": [\n          { \"x\": \"x2\", \"y\": 2, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 4, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"10 ± 0\", \"20 ± 0\", \"30 ± 0\", \"40 ± 0\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      },\n      {\n        \"visible\": true,\n        \"name\": \"b\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [null, 22, null, 44],\n        \"error_y\": { \"array\": [null, 0, null, 0], \"color\": \"blue\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"\", \"2 ± 0\", \"\", \"4 ± 0\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"blue\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/line-area/missing-values-0.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"line\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": \"stack\", \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"line\", \"color\": \"red\" },\n        \"b\": { \"type\": \"line\", \"color\": \"blue\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true,\n      \"lineShape\": \"linear\"\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      },\n      {\n        \"name\": \"b\",\n        \"data\": [\n          { \"x\": \"x2\", \"y\": 2, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 4, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"10 ± 0\", \"20 ± 0\", \"30 ± 0\", \"40 ± 0\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      },\n      {\n        \"visible\": true,\n        \"name\": \"b\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 22, 30, 44],\n        \"error_y\": { \"array\": [null, 0, null, 0], \"color\": \"blue\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"0\", \"2 ± 0\", \"0\", \"4 ± 0\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"blue\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/line-area/normalized-stacked.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"line\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": \"stack\", \"error_y\": { \"type\": \"data\", \"visible\": true }, \"percentValues\": true },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"line\", \"color\": \"red\" },\n        \"b\": { \"type\": \"line\", \"color\": \"blue\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true,\n      \"lineShape\": \"linear\"\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      },\n      {\n        \"name\": \"b\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 40, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 10, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [20, 40, 60, 80],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"20% (10 ± 0)\", \"40% (20 ± 0)\", \"60% (30 ± 0)\", \"80% (40 ± 0)\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      },\n      {\n        \"visible\": true,\n        \"name\": \"b\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [100, 100, 100, 100],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"blue\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"80% (40 ± 0)\", \"60% (30 ± 0)\", \"40% (20 ± 0)\", \"20% (10 ± 0)\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"blue\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/line-area/normalized.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"line\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true }, \"percentValues\": true },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"line\", \"color\": \"red\" },\n        \"b\": { \"type\": \"line\", \"color\": \"blue\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true,\n      \"lineShape\": \"linear\"\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      },\n      {\n        \"name\": \"b\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 40, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 10, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [20, 40, 60, 80],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"20% (10 ± 0)\", \"40% (20 ± 0)\", \"60% (30 ± 0)\", \"80% (40 ± 0)\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      },\n      {\n        \"visible\": true,\n        \"name\": \"b\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [80, 60, 40, 20],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"blue\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"80% (40 ± 0)\", \"60% (30 ± 0)\", \"40% (20 ± 0)\", \"20% (10 ± 0)\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"blue\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/line-area/stacked.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"line\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": \"stack\", \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"line\", \"color\": \"red\" },\n        \"b\": { \"type\": \"line\", \"color\": \"blue\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true,\n      \"lineShape\": \"linear\"\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      },\n      {\n        \"name\": \"b\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 1, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 2, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 3, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 4, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"10 ± 0\", \"20 ± 0\", \"30 ± 0\", \"40 ± 0\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      },\n      {\n        \"visible\": true,\n        \"name\": \"b\",\n        \"mode\": \"lines+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [11, 22, 33, 44],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"blue\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"1 ± 0\", \"2 ± 0\", \"3 ± 0\", \"4 ± 0\"],\n        \"line\": { \"shape\": \"linear\" },\n        \"marker\": { \"color\": \"blue\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/pie/custom-tooltip.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"pie\",\n      \"seriesOptions\": {},\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"{{ @@name }}: {{ @@yPercent }} ({{ @@y }})\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [{ \"type\": \"linear\" }, { \"type\": \"linear\", \"opposite\": true }],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y\": \"y\"\n      },\n      \"color_scheme\": \"Redash\"\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"a1\", \"y\": 10 },\n          { \"x\": \"a2\", \"y\": 60 },\n          { \"x\": \"a3\", \"y\": 100 },\n          { \"x\": \"a4\", \"y\": 30 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"values\": [10, 60, 100, 30],\n        \"labels\": [\"a1\", \"a2\", \"a3\", \"a4\"],\n        \"type\": \"pie\",\n        \"hole\": 0.4,\n        \"marker\": {\n          \"colors\": [\"#356AFF\", \"#E92828\", \"#3BD973\", \"#604FE9\"]\n        },\n        \"hoverinfo\": \"text+label\",\n        \"hover\": [],\n        \"text\": [\"a: 5% (10)\", \"a: 30% (60)\", \"a: 50% (100)\", \"a: 15% (30)\"],\n        \"textinfo\": \"percent\",\n        \"textposition\": \"inside\",\n        \"textfont\": { \"color\": [\"#ffffff\", \"#ffffff\", \"#333333\", \"#ffffff\"] },\n        \"name\": \"a\",\n        \"direction\": \"counterclockwise\",\n        \"domain\": { \"x\": [0, 0.98], \"y\": [0, 0.9] },\n        \"color_scheme\": \"Redash\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/pie/default.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"pie\",\n      \"seriesOptions\": {},\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [{ \"type\": \"linear\" }, { \"type\": \"linear\", \"opposite\": true }],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y\": \"y\"\n      },\n      \"color_scheme\": \"Redash\"\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"a1\", \"y\": 10 },\n          { \"x\": \"a2\", \"y\": 60 },\n          { \"x\": \"a3\", \"y\": 100 },\n          { \"x\": \"a4\", \"y\": 30 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"values\": [10, 60, 100, 30],\n        \"labels\": [\"a1\", \"a2\", \"a3\", \"a4\"],\n        \"type\": \"pie\",\n        \"hole\": 0.4,\n        \"marker\": {\n          \"colors\": [\"#356AFF\", \"#E92828\", \"#3BD973\", \"#604FE9\"]\n        },\n        \"hoverinfo\": \"text+label\",\n        \"hover\": [],\n        \"text\": [\"5% (10)\", \"30% (60)\", \"50% (100)\", \"15% (30)\"],\n        \"textinfo\": \"percent\",\n        \"textposition\": \"inside\",\n        \"textfont\": { \"color\": [\"#ffffff\", \"#ffffff\", \"#333333\", \"#ffffff\"] },\n        \"name\": \"a\",\n        \"direction\": \"counterclockwise\",\n        \"domain\": { \"x\": [0, 0.98], \"y\": [0, 0.9] },\n        \"color_scheme\": \"Redash\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/pie/without-labels.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"pie\",\n      \"seriesOptions\": {},\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": false,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [{ \"type\": \"linear\" }, { \"type\": \"linear\", \"opposite\": true }],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y\": \"y\"\n      },\n      \"color_scheme\": \"Redash\"\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"a1\", \"y\": 10 },\n          { \"x\": \"a2\", \"y\": 60 },\n          { \"x\": \"a3\", \"y\": 100 },\n          { \"x\": \"a4\", \"y\": 30 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"values\": [10, 60, 100, 30],\n        \"labels\": [\"a1\", \"a2\", \"a3\", \"a4\"],\n        \"type\": \"pie\",\n        \"hole\": 0.4,\n        \"marker\": {\n          \"colors\": [\"#356AFF\", \"#E92828\", \"#3BD973\", \"#604FE9\"]\n        },\n        \"hoverinfo\": \"text+label\",\n        \"hover\": [],\n        \"text\": [\"5% (10)\", \"30% (60)\", \"50% (100)\", \"15% (30)\"],\n        \"textinfo\": \"none\",\n        \"textposition\": \"inside\",\n        \"textfont\": { \"color\": [\"#ffffff\", \"#ffffff\", \"#333333\", \"#ffffff\"] },\n        \"name\": \"a\",\n        \"direction\": \"counterclockwise\",\n        \"domain\": { \"x\": [0, 0.98], \"y\": [0, 0.9] },\n        \"color_scheme\": \"Redash\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/pie/without-x.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"pie\",\n      \"seriesOptions\": {},\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [{ \"type\": \"linear\" }, { \"type\": \"linear\", \"opposite\": true }],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"color_scheme\": \"Redash\"\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"a1\", \"y\": 10 },\n          { \"x\": \"a2\", \"y\": 60 },\n          { \"x\": \"a3\", \"y\": 100 },\n          { \"x\": \"a4\", \"y\": 30 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"values\": [200],\n        \"labels\": [\"Slice 0\"],\n        \"type\": \"pie\",\n        \"hole\": 0.4,\n        \"marker\": {\n          \"colors\": [\"#356AFF\"]\n        },\n        \"hoverinfo\": \"text+label\",\n        \"hover\": [],\n        \"text\": [\"100% (200)\"],\n        \"textinfo\": \"percent\",\n        \"textposition\": \"inside\",\n        \"textfont\": { \"color\": [\"#ffffff\"] },\n        \"name\": \"a\",\n        \"direction\": \"counterclockwise\",\n        \"domain\": { \"x\": [0, 0.98], \"y\": [0, 0.9] },\n        \"color_scheme\": \"Redash\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/scatter/default.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"scatter\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": true,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"scatter\", \"color\": \"red\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"type\": \"scatter\",\n        \"mode\": \"markers+text\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"10 ± 0\", \"20 ± 0\", \"30 ± 0\", \"40 ± 0\"],\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/scatter/without-labels.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"scatter\",\n      \"numberFormat\": \"0,0[.]00000\",\n      \"percentFormat\": \"0[.]00%\",\n      \"textFormat\": \"\",\n      \"showDataLabels\": false,\n      \"direction\": { \"type\": \"counterclockwise\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [\n        { \"type\": \"linear\" },\n        { \"type\": \"linear\", \"opposite\": true }\n      ],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"seriesOptions\": {\n        \"a\": { \"type\": \"scatter\", \"color\": \"red\" }\n      },\n      \"columnMapping\": {\n        \"x\": \"x\",\n        \"y1\": \"y\"\n      },\n      \"missingValuesAsZero\": true\n    },\n    \"data\": [\n      {\n        \"name\": \"a\",\n        \"data\": [\n          { \"x\": \"x1\", \"y\": 10, \"yError\": 0 },\n          { \"x\": \"x2\", \"y\": 20, \"yError\": 0 },\n          { \"x\": \"x3\", \"y\": 30, \"yError\": 0 },\n          { \"x\": \"x4\", \"y\": 40, \"yError\": 0 }\n        ]\n      }\n    ]\n  },\n  \"output\": {\n    \"series\": [\n      {\n        \"visible\": true,\n        \"name\": \"a\",\n        \"type\": \"scatter\",\n        \"mode\": \"markers\",\n        \"x\": [\"x1\", \"x2\", \"x3\", \"x4\"],\n        \"y\": [10, 20, 30, 40],\n        \"error_y\": { \"array\": [0, 0, 0, 0], \"color\": \"red\" },\n        \"hoverinfo\": \"text+x+name\",\n        \"hover\": [],\n        \"text\": [\"10 ± 0\", \"20 ± 0\", \"30 ± 0\", \"40 ± 0\"],\n        \"marker\": { \"color\": \"red\" },\n        \"insidetextfont\": { \"color\": \"#ffffff\" },\n        \"yaxis\": \"y\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/box-single-axis.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"box\",\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [{ \"type\": \"linear\" }, { \"type\": \"linear\", \"opposite\": true }],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      }\n    },\n    \"series\": [{ \"name\": \"a\" }]\n  },\n  \"output\": {\n    \"layout\": {\n      \"margin\": { \"l\": 10, \"r\": 10, \"b\": 5, \"t\": 20, \"pad\": 4 },\n      \"width\": 400,\n      \"height\": 300,\n      \"autosize\": false,\n      \"showlegend\": true,\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      },\n      \"boxmode\": \"group\",\n      \"boxgroupgap\": 0.5,\n      \"xaxis\": {\n        \"automargin\": true,\n        \"showticklabels\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"-\"\n      },\n      \"yaxis\": {\n        \"automargin\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"linear\",\n        \"autorange\": true,\n        \"range\": null\n      },\n      \"hoverlabel\": {\n        \"namelength\": -1\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/box-with-second-axis.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"box\",\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [{ \"type\": \"linear\" }, { \"type\": \"linear\", \"opposite\": true }],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      }\n    },\n    \"series\": [\n      { \"name\": \"a\" },\n      { \"name\": \"b\", \"yaxis\": \"y2\" }\n    ]\n  },\n  \"output\": {\n    \"layout\": {\n      \"margin\": { \"l\": 10, \"r\": 10, \"b\": 5, \"t\": 20, \"pad\": 4 },\n      \"width\": 400,\n      \"height\": 300,\n      \"autosize\": false,\n      \"showlegend\": true,\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      },\n      \"boxmode\": \"group\",\n      \"boxgroupgap\": 0.5,\n      \"xaxis\": {\n        \"automargin\": true,\n        \"showticklabels\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"-\"\n      },\n      \"yaxis\": {\n        \"automargin\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"linear\",\n        \"autorange\": true,\n        \"range\": null\n      },\n      \"yaxis2\": {\n        \"automargin\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"linear\",\n        \"autorange\": true,\n        \"range\": null,\n        \"overlaying\": \"y\",\n        \"side\": \"right\"\n      },\n      \"hoverlabel\": {\n        \"namelength\": -1\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/default-single-axis.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"column\",\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [{ \"type\": \"linear\" }, { \"type\": \"linear\", \"opposite\": true }],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      }\n    },\n    \"series\": [{ \"name\": \"a\" }]\n  },\n  \"output\": {\n    \"layout\": {\n      \"margin\": { \"l\": 10, \"r\": 10, \"b\": 5, \"t\": 20, \"pad\": 4 },\n      \"width\": 400,\n      \"height\": 300,\n      \"autosize\": false,\n      \"showlegend\": true,\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      },\n      \"xaxis\": {\n        \"automargin\": true,\n        \"showticklabels\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"-\"\n      },\n      \"yaxis\": {\n        \"automargin\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"linear\",\n        \"autorange\": true,\n        \"range\": null\n      },\n      \"hoverlabel\": {\n        \"namelength\": -1\n      },\n      \"hovermode\": \"x\"\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/default-with-second-axis.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"column\",\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [{ \"type\": \"linear\" }, { \"type\": \"linear\", \"opposite\": true }],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } },\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      }\n    },\n    \"series\": [\n      { \"name\": \"a\" },\n      { \"name\": \"b\", \"yaxis\": \"y2\" }\n    ]\n  },\n  \"output\": {\n    \"layout\": {\n      \"margin\": { \"l\": 10, \"r\": 10, \"b\": 5, \"t\": 20, \"pad\": 4 },\n      \"width\": 400,\n      \"height\": 300,\n      \"autosize\": false,\n      \"showlegend\": true,\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      },\n      \"xaxis\": {\n        \"automargin\": true,\n        \"showticklabels\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"-\"\n      },\n      \"yaxis\": {\n        \"automargin\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"linear\",\n        \"autorange\": true,\n        \"range\": null\n      },\n      \"yaxis2\": {\n        \"automargin\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"linear\",\n        \"autorange\": true,\n        \"range\": null,\n        \"overlaying\": \"y\",\n        \"side\": \"right\"\n      },\n      \"hoverlabel\": {\n        \"namelength\": -1\n      },\n      \"hovermode\": \"x\"\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/default-with-stacking.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"column\",\n      \"legend\": { \"enabled\": false, \"traceorder\": \"normal\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [{ \"type\": \"linear\" }, { \"type\": \"linear\", \"opposite\": true }],\n      \"series\": { \"stacking\": \"stack\", \"error_y\": { \"type\": \"data\", \"visible\": true } }\n    },\n    \"series\": [{ \"name\": \"a\" }]\n  },\n  \"output\": {\n    \"layout\": {\n      \"margin\": { \"l\": 10, \"r\": 10, \"b\": 5, \"t\": 20, \"pad\": 4 },\n      \"width\": 400,\n      \"height\": 300,\n      \"autosize\": false,\n      \"showlegend\": false,\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      },\n      \"barmode\": \"relative\",\n      \"xaxis\": {\n        \"automargin\": true,\n        \"showticklabels\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"-\"\n      },\n      \"yaxis\": {\n        \"automargin\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"linear\",\n        \"autorange\": true,\n        \"range\": null\n      },\n      \"hoverlabel\": {\n        \"namelength\": -1\n      },\n      \"hovermode\": \"x\"\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/default-without-legend.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"column\",\n      \"legend\": { \"enabled\": false, \"traceorder\": \"normal\" },\n      \"xAxis\": { \"type\": \"-\", \"labels\": { \"enabled\": true } },\n      \"yAxis\": [{ \"type\": \"linear\" }, { \"type\": \"linear\", \"opposite\": true }],\n      \"series\": { \"stacking\": null, \"error_y\": { \"type\": \"data\", \"visible\": true } }\n    },\n    \"series\": [{ \"name\": \"a\" }]\n  },\n  \"output\": {\n    \"layout\": {\n      \"margin\": { \"l\": 10, \"r\": 10, \"b\": 5, \"t\": 20, \"pad\": 4 },\n      \"width\": 400,\n      \"height\": 300,\n      \"autosize\": false,\n      \"showlegend\": false,\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      },\n      \"xaxis\": {\n        \"automargin\": true,\n        \"showticklabels\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"-\"\n      },\n      \"yaxis\": {\n        \"automargin\": true,\n        \"title\": null,\n        \"tickformat\": null,\n        \"type\": \"linear\",\n        \"autorange\": true,\n        \"range\": null\n      },\n      \"hoverlabel\": {\n        \"namelength\": -1\n      },\n      \"hovermode\": \"x\"\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/pie-multiple-series.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"pie\",\n      \"textFormat\": \"\",\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      }\n    },\n    \"series\": [{ \"name\": \"a\" }, { \"name\": \"b\" }, { \"name\": \"c\" }]\n  },\n  \"output\": {\n    \"layout\": {\n      \"margin\": { \"l\": 10, \"r\": 10, \"b\": 5, \"t\": 20, \"pad\": 4 },\n      \"width\": 400,\n      \"height\": 300,\n      \"autosize\": false,\n      \"showlegend\": true,\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      },\n      \"hoverlabel\": {\n        \"namelength\": -1\n      },\n      \"annotations\": [\n        {\n          \"x\": 0.24,\n          \"y\": 0.485,\n          \"xanchor\": \"center\",\n          \"yanchor\": \"top\",\n          \"text\": \"a\",\n          \"showarrow\": false\n        },\n        {\n          \"x\": 0.74,\n          \"y\": 0.485,\n          \"xanchor\": \"center\",\n          \"yanchor\": \"top\",\n          \"text\": \"b\",\n          \"showarrow\": false\n        },\n        {\n          \"x\": 0.24,\n          \"y\": 0.985,\n          \"xanchor\": \"center\",\n          \"yanchor\": \"top\",\n          \"text\": \"c\",\n          \"showarrow\": false\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/pie-without-annotations.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"pie\",\n      \"textFormat\": \"{{ @@name }}\",\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      }\n    },\n    \"series\": [{ \"name\": \"a\" }]\n  },\n  \"output\": {\n    \"layout\": {\n      \"margin\": { \"l\": 10, \"r\": 10, \"b\": 5, \"t\": 20, \"pad\": 4 },\n      \"width\": 400,\n      \"height\": 300,\n      \"autosize\": false,\n      \"showlegend\": true,\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      },\n      \"hoverlabel\": {\n        \"namelength\": -1\n      },\n      \"annotations\": []\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/pie.json",
    "content": "{\n  \"input\": {\n    \"options\": {\n      \"globalSeriesType\": \"pie\",\n      \"textFormat\": \"\",\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      }\n    },\n    \"series\": [{ \"name\": \"a\" }]\n  },\n  \"output\": {\n    \"layout\": {\n      \"margin\": { \"l\": 10, \"r\": 10, \"b\": 5, \"t\": 20, \"pad\": 4 },\n      \"width\": 400,\n      \"height\": 300,\n      \"autosize\": false,\n      \"showlegend\": true,\n      \"legend\": {\n        \"traceorder\": \"normal\"\n      },\n      \"hoverlabel\": {\n        \"namelength\": -1\n      },\n      \"annotations\": [\n        {\n          \"x\": 0.49,\n          \"y\": 0.985,\n          \"xanchor\": \"center\",\n          \"yanchor\": \"top\",\n          \"text\": \"a\",\n          \"showarrow\": false\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/index.ts",
    "content": "import * as Plotly from \"plotly.js\";\n\nimport \"./locales\"\nimport prepareData from \"./prepareData\";\nimport prepareLayout from \"./prepareLayout\";\nimport updateData from \"./updateData\";\nimport updateAxes from \"./updateAxes\";\nimport updateChartSize from \"./updateChartSize\";\nimport { prepareCustomChartData, createCustomChartRenderer } from \"./customChartUtils\";\n\nconst rangeSliderIcon = {\n  'width': 400,\n  'height': 400,\n  'path': 'M50 180h300a20 20 0 0 1 0 40H50a20 20 0 0 1 0-40z M160 200a40 40 0 1 0 -80 0a40 40 0 1 0 80 0 M320 200a40 40 0 1 0 -80 0a40 40 0 1 0 80 0',\n};\n\nPlotly.setPlotConfig({\n  modeBarButtonsToRemove: [\"sendDataToCloud\"],\n  modeBarButtonsToAdd: [\"togglespikelines\", \"v1hovermode\",\n    {\n      name: 'toggleRangeslider',\n      title: 'Toggle rangeslider',\n      icon: rangeSliderIcon,\n      click: function(gd: any) {\n        if(gd?.layout?.xaxis) {\n          let newRangeslider: any = {};\n          if (gd.layout.xaxis?.rangeslider) {\n            newRangeslider = null;\n          }\n          (Plotly.relayout as any)(gd, 'xaxis.rangeslider', newRangeslider);\n        }\n      }\n    },\n  ],\n  locale: window.navigator.language,\n});\n\nexport {\n  Plotly,\n  prepareData,\n  prepareLayout,\n  updateData,\n  updateAxes,\n  updateChartSize,\n  prepareCustomChartData,\n  createCustomChartRenderer,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/locales.ts",
    "content": "import * as Plotly from \"plotly.js\";\n\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeAf from \"plotly.js/lib/locales/af\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeAm from \"plotly.js/lib/locales/am\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeAr_dz from \"plotly.js/lib/locales/ar-dz\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeAr_eg from \"plotly.js/lib/locales/ar-eg\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeAr from \"plotly.js/lib/locales/ar\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeAz from \"plotly.js/lib/locales/az\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeBg from \"plotly.js/lib/locales/bg\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeBs from \"plotly.js/lib/locales/bs\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeCa from \"plotly.js/lib/locales/ca\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeCs from \"plotly.js/lib/locales/cs\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeCy from \"plotly.js/lib/locales/cy\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeDa from \"plotly.js/lib/locales/da\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeDe_ch from \"plotly.js/lib/locales/de-ch\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeDe from \"plotly.js/lib/locales/de\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeEl from \"plotly.js/lib/locales/el\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeEo from \"plotly.js/lib/locales/eo\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeEs_ar from \"plotly.js/lib/locales/es-ar\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeEs_pe from \"plotly.js/lib/locales/es-pe\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeEs from \"plotly.js/lib/locales/es\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeEt from \"plotly.js/lib/locales/et\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeEu from \"plotly.js/lib/locales/eu\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeFa from \"plotly.js/lib/locales/fa\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeFi from \"plotly.js/lib/locales/fi\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeFo from \"plotly.js/lib/locales/fo\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeFr_ch from \"plotly.js/lib/locales/fr-ch\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeFr from \"plotly.js/lib/locales/fr\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeGl from \"plotly.js/lib/locales/gl\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeGu from \"plotly.js/lib/locales/gu\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeHe from \"plotly.js/lib/locales/he\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeHi_in from \"plotly.js/lib/locales/hi-in\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeHr from \"plotly.js/lib/locales/hr\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeHu from \"plotly.js/lib/locales/hu\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeHy from \"plotly.js/lib/locales/hy\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeId from \"plotly.js/lib/locales/id\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeIs from \"plotly.js/lib/locales/is\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeIt from \"plotly.js/lib/locales/it\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeJa from \"plotly.js/lib/locales/ja\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeKa from \"plotly.js/lib/locales/ka\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeKm from \"plotly.js/lib/locales/km\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeKo from \"plotly.js/lib/locales/ko\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeLt from \"plotly.js/lib/locales/lt\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeLv from \"plotly.js/lib/locales/lv\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeMe_me from \"plotly.js/lib/locales/me-me\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeMe from \"plotly.js/lib/locales/me\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeMk from \"plotly.js/lib/locales/mk\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeMl from \"plotly.js/lib/locales/ml\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeMs from \"plotly.js/lib/locales/ms\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeMt from \"plotly.js/lib/locales/mt\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeNl_be from \"plotly.js/lib/locales/nl-be\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeNl from \"plotly.js/lib/locales/nl\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeNo from \"plotly.js/lib/locales/no\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localePa from \"plotly.js/lib/locales/pa\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localePl from \"plotly.js/lib/locales/pl\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localePt_br from \"plotly.js/lib/locales/pt-br\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localePt_pt from \"plotly.js/lib/locales/pt-pt\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeRm from \"plotly.js/lib/locales/rm\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeRo from \"plotly.js/lib/locales/ro\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeRu from \"plotly.js/lib/locales/ru\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeSk from \"plotly.js/lib/locales/sk\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeSl from \"plotly.js/lib/locales/sl\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeSq from \"plotly.js/lib/locales/sq\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeSr_sr from \"plotly.js/lib/locales/sr-sr\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeSr from \"plotly.js/lib/locales/sr\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeSv from \"plotly.js/lib/locales/sv\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeSw from \"plotly.js/lib/locales/sw\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeTa from \"plotly.js/lib/locales/ta\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeTh from \"plotly.js/lib/locales/th\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeTr from \"plotly.js/lib/locales/tr\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeTt from \"plotly.js/lib/locales/tt\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeUk from \"plotly.js/lib/locales/uk\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeUr from \"plotly.js/lib/locales/ur\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeVi from \"plotly.js/lib/locales/vi\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeZh_cn from \"plotly.js/lib/locales/zh-cn\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeZh_hk from \"plotly.js/lib/locales/zh-hk\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module\nimport localeZh_tw from \"plotly.js/lib/locales/zh-tw\";\n\n(Plotly as any).register([\n    localeAf,\n    localeAm,\n    localeAr_dz,\n    localeAr_eg,\n    localeAr,\n    localeAz,\n    localeBg,\n    localeBs,\n    localeCa,\n    localeCs,\n    localeCy,\n    localeDa,\n    localeDe_ch,\n    localeDe,\n    localeEl,\n    localeEo,\n    localeEs_ar,\n    localeEs_pe,\n    localeEs,\n    localeEt,\n    localeEu,\n    localeFa,\n    localeFi,\n    localeFo,\n    localeFr_ch,\n    localeFr,\n    localeGl,\n    localeGu,\n    localeHe,\n    localeHi_in,\n    localeHr,\n    localeHu,\n    localeHy,\n    localeId,\n    localeIs,\n    localeIt,\n    localeJa,\n    localeKa,\n    localeKm,\n    localeKo,\n    localeLt,\n    localeLv,\n    localeMe_me,\n    localeMe,\n    localeMk,\n    localeMl,\n    localeMs,\n    localeMt,\n    localeNl_be,\n    localeNl,\n    localeNo,\n    localePa,\n    localePl,\n    localePt_br,\n    localePt_pt,\n    localeRm,\n    localeRo,\n    localeRu,\n    localeSk,\n    localeSl,\n    localeSq,\n    localeSr_sr,\n    localeSr,\n    localeSv,\n    localeSw,\n    localeTa,\n    localeTh,\n    localeTr,\n    localeTt,\n    localeUk,\n    localeUr,\n    localeVi,\n    localeZh_cn,\n    localeZh_hk,\n    localeZh_tw,\n]);\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/prepareData.test.ts",
    "content": "/* eslint-disable global-require, import/no-unresolved */\nimport prepareData from \"./prepareData\";\n\nfunction cleanSeries(series: any) {\n  return series.map(({ sourceData, ...rest }: any) => rest);\n}\n\ndescribe(\"Visualizations\", () => {\n  describe(\"Chart\", () => {\n    describe(\"prepareData\", () => {\n      describe(\"heatmap\", () => {\n        test(\"default\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/heatmap/default\");\n          const series = prepareData(input.data, input.options);\n          expect(series).toEqual(output.series);\n        });\n        test(\"sorted\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/heatmap/sorted\");\n          const series = prepareData(input.data, input.options);\n          expect(series).toEqual(output.series);\n        });\n        test(\"reversed\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/heatmap/reversed\");\n          const series = prepareData(input.data, input.options);\n          expect(series).toEqual(output.series);\n        });\n        test(\"sorted & reversed\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/heatmap/sorted\");\n          const series = prepareData(input.data, input.options);\n          expect(series).toEqual(output.series);\n        });\n        test(\"with labels\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/heatmap/with-labels\");\n          const series = prepareData(input.data, input.options);\n          expect(series).toEqual(output.series);\n        });\n      });\n\n      describe(\"pie\", () => {\n        test(\"default\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/pie/default\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"without X mapped\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/pie/without-x\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"without labels\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/pie/without-labels\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"custom tooltip\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/pie/custom-tooltip\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n      });\n\n      describe(\"bar (column)\", () => {\n        test(\"default\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/bar/default\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"stacked\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/bar/stacked\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"normalized values\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/bar/normalized\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n      });\n\n      describe(\"lines & area\", () => {\n        test(\"default\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/line-area/default\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"stacked\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/line-area/stacked\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"normalized values\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/line-area/normalized\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"stacked & normalized values\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/line-area/normalized-stacked\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"keep missing values\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/line-area/keep-missing-values\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"convert missing values to 0\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/line-area/missing-values-0\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n      });\n\n      describe(\"scatter\", () => {\n        test(\"default\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/scatter/default\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"without labels\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/scatter/without-labels\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n      });\n\n      describe(\"bubble\", () => {\n        test(\"default\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/bubble/default\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n      });\n\n      describe(\"box\", () => {\n        test(\"default\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/box/default\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n\n        test(\"with points\", () => {\n          const { input, output } = require(\"./fixtures/prepareData/box/with-points\");\n          const series = cleanSeries(prepareData(input.data, input.options));\n          expect(series).toEqual(output.series);\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/prepareData.ts",
    "content": "import preparePieData from \"./preparePieData\";\nimport prepareHeatmapData from \"./prepareHeatmapData\";\nimport prepareDefaultData from \"./prepareDefaultData\";\nimport updateData from \"./updateData\";\n\nexport default function prepareData(seriesList: any, options: any) {\n  switch (options.globalSeriesType) {\n    case \"pie\":\n      return updateData(preparePieData(seriesList, options), options);\n    case \"heatmap\":\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1.\n      return updateData(prepareHeatmapData(seriesList, options, options));\n    default:\n      return updateData(prepareDefaultData(seriesList, options), options);\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/prepareDefaultData.ts",
    "content": "import { isNil, extend, each, includes, map, sortBy, toString } from \"lodash\";\nimport chooseTextColorForBackground from \"@/lib/chooseTextColorForBackground\";\nimport { AllColorPaletteArrays, ColorPaletteTypes } from \"@/visualizations/ColorPalette\";\nimport { cleanNumber, normalizeValue, getSeriesAxis } from \"./utils\";\n\nfunction getSeriesColor(options: any, seriesOptions: any, seriesIndex: any, numSeries: any) {\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  let palette = AllColorPaletteArrays[options.color_scheme];\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  if (ColorPaletteTypes[options.color_scheme] === \"continuous\" && palette.length > numSeries) {\n    const step = (palette.length - 1) / (numSeries - 1 || 1);\n    const index = Math.round(step * seriesIndex);\n    return seriesOptions.color || palette[index % palette.length];\n  }\n  return seriesOptions.color || palette[seriesIndex % palette.length];\n}\n\nfunction getHoverInfoPattern(options: any) {\n  const hasX = /{{\\s*@@x\\s*}}/.test(options.textFormat);\n  const hasName = /{{\\s*@@name\\s*}}/.test(options.textFormat);\n  let result = \"text\";\n  if (!hasX) result += \"+x\";\n  if (!hasName) result += \"+name\";\n  return result;\n}\n\nfunction prepareBarSeries(series: any, options: any, additionalOptions: any) {\n  series.type = \"bar\";\n  if (!options.series.stacking) {\n    series.offsetgroup = toString(additionalOptions.index);\n  }\n  if (options.showDataLabels) {\n    series.textposition = \"inside\";\n  } else {\n    series.textposition = \"none\";\n  }\n  return series;\n}\n\nfunction prepareLineSeries(series: any, options: any) {\n  series.mode = \"lines\" + (options.showDataLabels ? \"+text\" : \"\");\n  series.line = {\n    shape: options.lineShape,\n  };\n  return series;\n}\n\nfunction prepareAreaSeries(series: any, options: any) {\n  series.mode = \"lines\" + (options.showDataLabels ? \"+text\" : \"\");\n  series.line = {\n    shape: options.lineShape,\n  };\n  series.fill = options.series.stacking ? \"tonexty\" : \"tozeroy\";\n  return series;\n}\n\nfunction prepareScatterSeries(series: any, options: any) {\n  series.type = \"scatter\";\n  series.mode = \"markers\" + (options.showDataLabels ? \"+text\" : \"\");\n  return series;\n}\n\nfunction prepareBubbleSeries(series: any, options: any, { seriesColor, data }: any) {\n  const coefficient = options.coefficient || 1;\n  series.mode = \"markers\";\n  series.marker = {\n    color: seriesColor,\n    size: map(data, (i) => i.size * coefficient),\n    sizemode: options.sizemode || \"diameter\",\n  };\n  return series;\n}\n\nfunction prepareBoxSeries(series: any, options: any, { seriesColor }: any) {\n  series.type = \"box\";\n  series.mode = \"markers\";\n\n  series.boxpoints = \"outliers\";\n  series.hoverinfo = false;\n  series.marker = {\n    color: seriesColor,\n    size: 3,\n  };\n  if (options.showpoints) {\n    series.boxpoints = \"all\";\n    series.jitter = 0.3;\n    series.pointpos = -1.8;\n  }\n  return series;\n}\n\nfunction prepareSeries(series: any, options: any, numSeries: any, additionalOptions: any) {\n  const { hoverInfoPattern, index } = additionalOptions;\n\n  const seriesOptions = extend({ type: options.globalSeriesType, yAxis: 0 }, options.seriesOptions[series.name]);\n  const seriesColor = getSeriesColor(options, seriesOptions, index, numSeries);\n  const seriesYAxis = getSeriesAxis(series, options);\n\n  // Sort by x - `Map` preserves order of items\n  const data = options.sortX ? sortBy(series.data, (d) => normalizeValue(d.x, options.xAxis.type)) : series.data;\n\n  // For bubble/scatter charts `y` may be any (similar to `x`) - numeric is only bubble size;\n  // for other types `y` is always number\n  const cleanYValue = includes([\"bubble\", \"scatter\"], seriesOptions.type)\n    ? (v: any, axixType: any) => {\n        v = normalizeValue(v, axixType);\n        return includes([\"scatter\"], seriesOptions.type) && options.missingValuesAsZero && isNil(v) ? 0.0 : v;\n      }\n    : (v: any) => {\n        v = cleanNumber(v);\n        return options.missingValuesAsZero && isNil(v) ? 0.0 : v;\n      };\n\n  const sourceData = new Map();\n  const xValues: any[] = [];\n  const yValues: any[] = [];\n\n  const yErrorValues: any = [];\n  each(data, (row) => {\n    const x = normalizeValue(row.x, options.xAxis.type); // number/datetime/category\n    const y = cleanYValue(row.y, seriesYAxis === \"y2\" ? options.yAxis[1].type : options.yAxis[0].type); // depends on series type!\n    const yError = cleanNumber(row.yError); // always number\n    const size = cleanNumber(row.size); // always number\n\n    // aggregate y value for the same x\n    if (!isNil(x) && [\"column\", \"line\", \"area\"].includes(seriesOptions.type)) {\n      const item = sourceData.get(x);\n\n      if (item) {\n        if (!isNil(y)) {\n          item.y = (isNil(item.y) ? 0.0 : item.y) + y;\n        }\n        if (!isNil(yError)) {\n          item.yError = (isNil(item.yError) ? 0.0 : item.yError) + yError;\n        }\n        yValues[item.index] = item.y;\n        yErrorValues[item.index] = item.yError;\n        return;\n      }\n    }\n\n    sourceData.set(x, {\n      x,\n      y,\n      yError,\n      size,\n      yPercent: null, // will be updated later\n      row,\n      index: xValues.length,\n    });\n    xValues.push(x);\n    yValues.push(y);\n    yErrorValues.push(yError);\n  });\n\n  const plotlySeries = {\n    visible: true,\n    hoverinfo: hoverInfoPattern,\n    x: xValues,\n    y: yValues,\n    error_y: {\n      array: yErrorValues,\n      color: seriesColor,\n    },\n    name: seriesOptions.name || series.name,\n    marker: { color: seriesColor },\n    insidetextfont: {\n      color: chooseTextColorForBackground(seriesColor),\n    },\n    yaxis: seriesYAxis,\n    sourceData,\n  };\n\n  additionalOptions = { ...additionalOptions, seriesColor, data };\n\n  switch (seriesOptions.type) {\n    case \"column\":\n      return prepareBarSeries(plotlySeries, options, additionalOptions);\n    case \"line\":\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 3.\n      return prepareLineSeries(plotlySeries, options, additionalOptions);\n    case \"area\":\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 3.\n      return prepareAreaSeries(plotlySeries, options, additionalOptions);\n    case \"scatter\":\n      // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 3.\n      return prepareScatterSeries(plotlySeries, options, additionalOptions);\n    case \"bubble\":\n      return prepareBubbleSeries(plotlySeries, options, additionalOptions);\n    case \"box\":\n      return prepareBoxSeries(plotlySeries, options, additionalOptions);\n    default:\n      return plotlySeries;\n  }\n}\n\nexport default function prepareDefaultData(seriesList: any, options: any) {\n  const additionalOptions = {\n    hoverInfoPattern: getHoverInfoPattern(options),\n  };\n  const numSeries = seriesList.length;\n\n  return map(seriesList, (series, index) => prepareSeries(series, options, numSeries, { ...additionalOptions, index }));\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/prepareHeatmapData.ts",
    "content": "import { map, max, uniq, sortBy, flatten, find, findIndex } from \"lodash\";\nimport { createNumberFormatter } from \"@/lib/value-format\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module 'plot... Remove this comment to see the full error message\nimport Colorscale from \"plotly.js/src/components/colorscale\";\nimport d3 from \"d3\";\nimport chooseTextColorForBackground from \"@/lib/chooseTextColorForBackground\";\n\nconst defaultColorScheme = [\n  [0, \"#356aff\"],\n  [0.14, \"#4a7aff\"],\n  [0.28, \"#5d87ff\"],\n  [0.42, \"#7398ff\"],\n  [0.56, \"#fb8c8c\"],\n  [0.71, \"#ec6463\"],\n  [0.86, \"#ec4949\"],\n  [1, \"#e92827\"],\n];\n\nfunction getColor(value: any, scheme: any) {\n  if (value == 1) { return scheme[scheme.length - 1][1]; }\n  const upperboundIndex = findIndex(scheme, (range: any) => value < range[0]);\n  const scale = d3.interpolate(scheme[upperboundIndex - 1][1], scheme[upperboundIndex][1]);\n  return scale(value);\n}\n\nfunction prepareSeries(series: any, options: any, additionalOptions: any) {\n  const { colorScheme, formatNumber } = additionalOptions;\n\n  const plotlySeries = {\n    x: [],\n    y: [],\n    z: [],\n    type: \"heatmap\",\n    name: \"\",\n    colorscale: colorScheme,\n  };\n\n  // @ts-expect-error ts-migrate(2322) FIXME: Type 'any[]' is not assignable to type 'never[]'.\n  plotlySeries.x = uniq(map(series.data, v => v.x));\n  // @ts-expect-error ts-migrate(2322) FIXME: Type 'any[]' is not assignable to type 'never[]'.\n  plotlySeries.y = uniq(map(series.data, v => v.y));\n\n  if (options.sortX) {\n    plotlySeries.x = sortBy(plotlySeries.x);\n  }\n\n  if (options.sortY) {\n    plotlySeries.y = sortBy(plotlySeries.y);\n  }\n\n  if (options.reverseX) {\n    plotlySeries.x.reverse();\n  }\n\n  if (options.reverseY) {\n    plotlySeries.y.reverse();\n  }\n\n  const zMax = max(map(series.data, d => d.zVal));\n\n  // Use text trace instead of default annotation for better performance\n  const dataLabels = {\n    x: [],\n    y: [],\n    mode: \"text\",\n    hoverinfo: \"skip\",\n    showlegend: false,\n    text: [],\n    textfont: {\n      color: [],\n    },\n  };\n\n  for (let i = 0; i < plotlySeries.y.length; i += 1) {\n    const item = [];\n    for (let j = 0; j < plotlySeries.x.length; j += 1) {\n      const datum = find(series.data, { x: plotlySeries.x[j], y: plotlySeries.y[i] });\n\n      const zValue = (datum && datum.zVal) || 0;\n      item.push(zValue);\n\n      if (isFinite(zMax) && options.showDataLabels) {\n        dataLabels.x.push(plotlySeries.x[j]);\n        dataLabels.y.push(plotlySeries.y[i]);\n        // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'any' is not assignable to parame... Remove this comment to see the full error message\n        dataLabels.text.push(formatNumber(zValue));\n        if (options.colorScheme) {\n          const bgcolor = getColor(zValue / zMax, colorScheme);\n          const fgcolor = chooseTextColorForBackground(bgcolor);\n          // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message\n          dataLabels.textfont.color.push(fgcolor);\n        }\n      }\n    }\n    // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'any[]' is not assignable to para... Remove this comment to see the full error message\n    plotlySeries.z.push(item);\n  }\n\n  if (isFinite(zMax) && options.showDataLabels) {\n    return [plotlySeries, dataLabels];\n  }\n  return [plotlySeries];\n}\n\nexport default function prepareHeatmapData(seriesList: any, options: any) {\n  let colorScheme = [];\n\n  if (!options.colorScheme) {\n    colorScheme = defaultColorScheme;\n  } else if (options.colorScheme === \"Custom...\") {\n    colorScheme = [\n      [0, options.heatMinColor],\n      [1, options.heatMaxColor],\n    ];\n  } else {\n    colorScheme = Colorscale.getScale(options.colorScheme);\n  }\n\n  const additionalOptions = {\n    colorScheme,\n    formatNumber: createNumberFormatter(options.numberFormat),\n  };\n\n  return flatten(map(seriesList, series => prepareSeries(series, options, additionalOptions)));\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/prepareLayout.test.ts",
    "content": "/* eslint-disable global-require, import/no-unresolved */\nimport getOptions from \"../getOptions\";\nimport prepareLayout from \"./prepareLayout\";\n\nconst fakeElement = { offsetWidth: 400, offsetHeight: 300 };\n\ndescribe(\"Visualizations\", () => {\n  describe(\"Chart\", () => {\n    describe(\"prepareLayout\", () => {\n      test(\"Pie\", () => {\n        const { input, output } = require(\"./fixtures/prepareLayout/pie\");\n        const layout = prepareLayout(fakeElement, getOptions(input.options), input.series);\n        expect(layout).toEqual(output.layout);\n      });\n\n      test(\"Pie without annotations\", () => {\n        const { input, output } = require(\"./fixtures/prepareLayout/pie-without-annotations\");\n        const layout = prepareLayout(fakeElement, getOptions(input.options), input.series);\n        expect(layout).toEqual(output.layout);\n      });\n\n      test(\"Pie with multiple series\", () => {\n        const { input, output } = require(\"./fixtures/prepareLayout/pie-multiple-series\");\n        const layout = prepareLayout(fakeElement, getOptions(input.options), input.series);\n        expect(layout).toEqual(output.layout);\n      });\n\n      test(\"Box with single Y axis\", () => {\n        const { input, output } = require(\"./fixtures/prepareLayout/box-single-axis\");\n        const layout = prepareLayout(fakeElement, getOptions(input.options), input.series);\n        expect(layout).toEqual(output.layout);\n      });\n\n      test(\"Box with second Y axis\", () => {\n        const { input, output } = require(\"./fixtures/prepareLayout/box-with-second-axis\");\n        const layout = prepareLayout(fakeElement, getOptions(input.options), input.series);\n        expect(layout).toEqual(output.layout);\n      });\n\n      test(\"Default with single Y axis\", () => {\n        const { input, output } = require(\"./fixtures/prepareLayout/default-single-axis\");\n        const layout = prepareLayout(fakeElement, getOptions(input.options), input.series);\n        expect(layout).toEqual(output.layout);\n      });\n\n      test(\"Default with second Y axis\", () => {\n        const { input, output } = require(\"./fixtures/prepareLayout/default-with-second-axis\");\n        const layout = prepareLayout(fakeElement, getOptions(input.options), input.series);\n        expect(layout).toEqual(output.layout);\n      });\n\n      test(\"Default without legend\", () => {\n        const { input, output } = require(\"./fixtures/prepareLayout/default-without-legend\");\n        const layout = prepareLayout(fakeElement, getOptions(input.options), input.series);\n        expect(layout).toEqual(output.layout);\n      });\n\n      test(\"Default with stacking\", () => {\n        const { input, output } = require(\"./fixtures/prepareLayout/default-with-stacking\");\n        const layout = prepareLayout(fakeElement, getOptions(input.options), input.series);\n        expect(layout).toEqual(output.layout);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/prepareLayout.ts",
    "content": "import { isObject, isUndefined, filter, map } from \"lodash\";\nimport { getPieDimensions } from \"./preparePieData\";\n\nfunction getAxisTitle(axis: any) {\n  return isObject(axis.title) ? axis.title.text : null;\n}\n\nfunction getAxisScaleType(axis: any) {\n  switch (axis.type) {\n    case \"datetime\":\n      return \"date\";\n    case \"logarithmic\":\n      return \"log\";\n    default:\n      return axis.type;\n  }\n}\n\nfunction prepareXAxis(axisOptions: any, additionalOptions: any) {\n  const axis = {\n    title: getAxisTitle(axisOptions),\n    type: getAxisScaleType(axisOptions),\n    automargin: true,\n    tickformat: axisOptions.tickFormat ?? null,\n  };\n\n  if (additionalOptions.sortX && axis.type === \"category\") {\n    if (additionalOptions.reverseX) {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryorder' does not exist on type '{... Remove this comment to see the full error message\n      axis.categoryorder = \"category descending\";\n    } else {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'categoryorder' does not exist on type '{... Remove this comment to see the full error message\n      axis.categoryorder = \"category ascending\";\n    }\n  }\n\n  if (!isUndefined(axisOptions.labels)) {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'showticklabels' does not exist on type '... Remove this comment to see the full error message\n    axis.showticklabels = axisOptions.labels.enabled;\n  }\n\n  return axis;\n}\n\nfunction prepareYAxis(axisOptions: any) {\n  return {\n    title: getAxisTitle(axisOptions),\n    type: getAxisScaleType(axisOptions),\n    automargin: true,\n    autorange: true,\n    range: null,\n    tickformat: axisOptions.tickFormat ?? null,\n  };\n}\n\nfunction preparePieLayout(layout: any, options: any, data: any) {\n  const hasName = /{{\\s*@@name\\s*}}/.test(options.textFormat);\n\n  const { cellsInRow, cellWidth, cellHeight, xPadding } = getPieDimensions(data);\n\n  if (hasName) {\n    layout.annotations = [];\n  } else {\n    layout.annotations = filter(\n      map(data, (series, index) => {\n        const xPosition = ((index as number) % cellsInRow) * cellWidth;\n        const yPosition = Math.floor((index as number) / cellsInRow) * cellHeight;\n        return {\n          x: xPosition + (cellWidth - xPadding) / 2,\n          y: yPosition + cellHeight - 0.015,\n          xanchor: \"center\",\n          yanchor: \"top\",\n          text: series.name,\n          showarrow: false,\n        };\n      })\n    );\n  }\n\n  return layout;\n}\n\nfunction prepareDefaultLayout(layout: any, options: any, data: any) {\n  const y2Series = data.filter((s: any) => s.yaxis === \"y2\");\n\n  layout.xaxis = prepareXAxis(options.xAxis, options);\n\n  layout.yaxis = prepareYAxis(options.yAxis[0]);\n  if (y2Series.length > 0) {\n    layout.yaxis2 = prepareYAxis(options.yAxis[1]);\n    layout.yaxis2.overlaying = \"y\";\n    layout.yaxis2.side = \"right\";\n  }\n\n  if (options.series.stacking) {\n    layout.barmode = \"relative\";\n  }\n\n  return layout;\n}\n\nfunction prepareBoxLayout(layout: any, options: any, data: any) {\n  layout = prepareDefaultLayout(layout, options, data);\n  layout.boxmode = \"group\";\n  layout.boxgroupgap = 0.5;\n  return layout;\n}\n\nexport default function prepareLayout(element: any, options: any, data: any) {\n  const layout: any = {\n    margin: { l: 10, r: 10, b: 5, t: 20, pad: 4 },\n    // plot size should be at least 5x5px\n    width: Math.max(5, Math.floor(element.offsetWidth)),\n    height: Math.max(5, Math.floor(element.offsetHeight)),\n    autosize: false,\n    showlegend: options.legend.enabled,\n    legend: {\n      traceorder: options.legend.traceorder,\n    },\n    hoverlabel: {\n      namelength: -1,\n    },\n  };\n\n  if ([\"line\", \"area\", \"column\"].includes(options.globalSeriesType)) {\n    layout.hovermode = options.swappedAxes ? 'y' : 'x';\n  }\n\n  switch (options.globalSeriesType) {\n    case \"pie\":\n      return preparePieLayout(layout, options, data);\n    case \"box\":\n      return prepareBoxLayout(layout, options, data);\n    default:\n      return prepareDefaultLayout(layout, options, data);\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/preparePieData.ts",
    "content": "import { isString, each, extend, includes, map, reduce } from \"lodash\";\nimport d3 from \"d3\";\nimport chooseTextColorForBackground from \"@/lib/chooseTextColorForBackground\";\nimport { AllColorPaletteArrays, ColorPaletteTypes } from \"@/visualizations/ColorPalette\";\n\nimport { cleanNumber, normalizeValue } from \"./utils\";\n\nexport function getPieDimensions(series: any) {\n  const rows = series.length > 2 ? 2 : 1;\n  const cellsInRow = Math.ceil(series.length / rows);\n  const cellWidth = 1 / cellsInRow;\n  const cellHeight = 1 / rows;\n  const xPadding = 0.02;\n  const yPadding = 0.1;\n\n  return { rows, cellsInRow, cellWidth, cellHeight, xPadding, yPadding };\n}\n\nfunction getPieHoverInfoPattern(options: any) {\n  const hasX = /{{\\s*@@x\\s*}}/.test(options.textFormat);\n  let result = \"text\";\n  if (!hasX) result += \"+label\";\n  return result;\n}\n\nfunction prepareSeries(series: any, options: any, additionalOptions: any) {\n  const {\n    cellWidth,\n    cellHeight,\n    xPadding,\n    yPadding,\n    cellsInRow,\n    hasX,\n    index,\n    hoverInfoPattern,\n    getValueColor,\n  } = additionalOptions;\n  const seriesOptions = extend({ type: options.globalSeriesType, yAxis: 0 }, options.seriesOptions[series.name]);\n\n  const xPosition = (index % cellsInRow) * cellWidth;\n  const yPosition = Math.floor(index / cellsInRow) * cellHeight;\n\n  const labelsValuesMap = new Map();\n\n  const sourceData = new Map();\n  const seriesTotal = reduce(\n    series.data,\n    (result, row) => {\n      const y = cleanNumber(row.y);\n      return result + Math.abs(y);\n    },\n    0\n  );\n  each(series.data, row => {\n    const x = hasX ? normalizeValue(row.x, options.xAxis.type) : `Slice ${index}`;\n    const y = cleanNumber(row.y);\n\n    if (labelsValuesMap.has(x)) {\n      labelsValuesMap.set(x, labelsValuesMap.get(x) + y);\n    } else {\n      labelsValuesMap.set(x, y);\n    }\n    const aggregatedY = labelsValuesMap.get(x);\n\n\n    sourceData.set(x, {\n      x,\n      y: aggregatedY,\n      yPercent: (aggregatedY / seriesTotal) * 100,\n      row,\n    });\n  });\n\n  const markerColors = map(Array.from(sourceData.values()), data => getValueColor(data.row.x));\n  const textColors = map(markerColors, c => chooseTextColorForBackground(c));\n\n  const labels = Array.from(labelsValuesMap.keys());\n  const values = Array.from(labelsValuesMap.values());\n\n  return {\n    visible: true,\n    values,\n    labels,\n    type: \"pie\",\n    hole: 0.4,\n    marker: {\n      colors: markerColors,\n    },\n    hoverinfo: hoverInfoPattern,\n    text: [],\n    textinfo: options.showDataLabels ? \"percent\" : \"none\",\n    textposition: \"inside\",\n    textfont: {\n      color: textColors,\n    },\n    name: seriesOptions.name || series.name,\n    direction: options.direction.type,\n    domain: {\n      x: [xPosition, xPosition + cellWidth - xPadding],\n      y: [yPosition, yPosition + cellHeight - yPadding],\n    },\n    sourceData,\n    sort: options.piesort,\n    color_scheme: options.color_scheme,\n  };\n}\n\nexport default function preparePieData(seriesList: any, options: any) {\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  const palette = AllColorPaletteArrays[options.color_scheme];\n  const valuesColors = {};\n  let getDefaultColor : Function;\n\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  if (typeof(seriesList[0]) !== 'undefined' && ColorPaletteTypes[options.color_scheme] === 'continuous') {\n    const uniqueXValues =[... new Set(seriesList[0].data.map((d: any) => d.x))];\n    const step = (palette.length - 1) / (uniqueXValues.length - 1 || 1);\n    const colorIndices = d3.range(uniqueXValues.length).map(function(i) {\n      return Math.round(step * i);\n    });\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'scale' does not exist on type 'typeof im... Remove this comment to see the full error message\n    getDefaultColor = d3.scale.ordinal()\n      .domain(uniqueXValues) // Set domain as the unique x-values\n      .range(colorIndices.map(index => palette[index]));\n  } else {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'scale' does not exist on type 'typeof im... Remove this comment to see the full error message\n    getDefaultColor = d3.scale\n      .ordinal()\n      .domain([])\n      .range(palette);\n  };\n\n  each(options.valuesOptions, (item, key) => {\n    if (isString(item.color) && item.color !== \"\") {\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      valuesColors[key] = item.color;\n    }\n  });\n\n  const additionalOptions = {\n    ...getPieDimensions(seriesList),\n    hasX: includes(options.columnMapping, \"x\"),\n    hoverInfoPattern: getPieHoverInfoPattern(options),\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    getValueColor: (v: any) => valuesColors[v] || getDefaultColor(v),\n  };\n\n  return map(seriesList, (series, index) => prepareSeries(series, options, { ...additionalOptions, index }));\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/updateAxes.ts",
    "content": "import { isObject, isNumber, each } from \"lodash\";\n\nfunction calculateAxisRange(range: any, min: any, max: any) {\n  return [isNumber(min) ? min : range[0], isNumber(max) ? max : range[1]];\n}\n\nfunction calculateAbsoluteDiff(value: any, totalRange: any, percentageDiff: any) {\n  return (percentageDiff * totalRange) / (1 - Math.abs(value) / totalRange - percentageDiff);\n}\n\nfunction alignYAxesAtZero(axisA: any, axisB: any) {\n  // Make sure the origin is included in both axes\n  axisA.range[1] = Math.max(0, axisA.range[1]);\n  axisB.range[1] = Math.max(0, axisB.range[1]);\n  axisA.range[0] = Math.min(0, axisA.range[0]);\n  axisB.range[0] = Math.min(0, axisB.range[0]);\n\n  const totalRangeA = axisA.range[1] - axisA.range[0];\n  const proportionA = axisA.range[1] / totalRangeA;\n  const totalRangeB = axisB.range[1] - axisB.range[0];\n  const proportionB = axisB.range[1] / totalRangeB;\n\n  // Calculate the difference between the proportions and distribute them within the two axes\n  const diff = Math.abs(proportionB - proportionA) / 2;\n\n  // Don't do anything if the difference is too low\n  if (diff < 0.01) {\n    return;\n  }\n\n  // Select the two that will correct the proportion by always augmenting, so the chart is not cut\n  if (proportionA < proportionB) {\n    // increase axisA max and axisB min\n    axisA.range[1] += calculateAbsoluteDiff(axisA.range[1], totalRangeA, diff);\n    axisB.range[0] -= calculateAbsoluteDiff(axisA.range[0], totalRangeB, diff);\n  } else {\n    // increase axisB max and axisA min\n    axisB.range[1] += calculateAbsoluteDiff(axisB.range[1], totalRangeB, diff);\n    axisA.range[0] -= calculateAbsoluteDiff(axisA.range[0], totalRangeA, diff);\n  }\n}\n\nexport default function updateAxes(plotlyElement: any, seriesList: any, layout: any, options: any) {\n  const updates = {};\n  if (isObject(layout.yaxis)) {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'yaxis' does not exist on type '{}'.\n    updates.yaxis = {\n      ...layout.yaxis,\n      autorange: true,\n      range: null,\n    };\n  }\n  if (isObject(layout.yaxis2)) {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'yaxis2' does not exist on type '{}'.\n    updates.yaxis2 = {\n      ...layout.yaxis2,\n      autorange: true,\n      range: null,\n    };\n  }\n\n  return [\n    updates,\n    () => {\n      // Update Y Ranges\n      if (isObject(layout.yaxis)) {\n        const axisOptions = options.yAxis[0];\n        const defaultRange = plotlyElement.layout.yaxis.range;\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'yaxis' does not exist on type '{}'.\n        updates.yaxis.autorange = false;\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'yaxis' does not exist on type '{}'.\n        updates.yaxis.range = calculateAxisRange(defaultRange, axisOptions.rangeMin, axisOptions.rangeMax);\n      }\n\n      if (isObject(layout.yaxis2)) {\n        const axisOptions = options.yAxis[1];\n        const defaultRange = plotlyElement.layout.yaxis2.range;\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'yaxis2' does not exist on type '{}'.\n        updates.yaxis2.autorange = false;\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'yaxis2' does not exist on type '{}'.\n        updates.yaxis2.range = calculateAxisRange(defaultRange, axisOptions.rangeMin, axisOptions.rangeMax);\n      }\n\n      // Swap Axes\n      if (options.swappedAxes) {\n        each(seriesList, series => {\n          series.orientation = \"h\";\n          const { x, y } = series;\n          series.x = y;\n          series.y = x;\n        });\n\n        const { xaxis } = layout;\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'yaxis' does not exist on type '{}'.\n        const { yaxis, yaxis2 } = updates;\n\n        if (isObject(xaxis) && isObject(yaxis)) {\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'xaxis' does not exist on type '{}'.\n          updates.xaxis = yaxis;\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'yaxis' does not exist on type '{}'.\n          updates.yaxis = xaxis;\n        }\n        if (isObject(yaxis2)) {\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'yaxis2' does not exist on type '{}'.\n          updates.yaxis2 = null;\n        }\n      }\n\n      // Align Y axes\n      if (options.alignYAxesAtZero && isObject(layout.yaxis) && isObject(layout.yaxis2)) {\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'yaxis' does not exist on type '{}'.\n        alignYAxesAtZero(updates.yaxis, updates.yaxis2);\n      }\n\n      return [updates, null]; // no further updates\n    },\n  ];\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/updateChartSize.ts",
    "content": "import { find, pick, extend } from \"lodash\";\n\nfunction fixLegendContainer(plotlyElement: any) {\n  const legend = plotlyElement.querySelector(\".legend\");\n  if (legend) {\n    let node = legend.parentNode;\n    while (node) {\n      if (node.tagName.toLowerCase() === \"svg\") {\n        node.style.overflow = \"visible\";\n        break;\n      }\n      node = node.parentNode;\n    }\n  }\n}\n\nfunction placeLegendNextToPlot(plotlyElement: any, layout: any) {\n  const transformName = find(\n    [\"transform\", \"WebkitTransform\", \"MozTransform\", \"MsTransform\", \"OTransform\"],\n    prop => prop in plotlyElement.style\n  );\n\n  layout.legend = extend({}, layout.legend, {\n    orientation: \"v\",\n    // vertical legend will be rendered properly, so just place it to the right\n    // side of plot\n    y: 1,\n    x: 1,\n    xanchor: \"left\",\n    yanchor: \"top\",\n  });\n\n  const legend = plotlyElement.querySelector(\".legend\");\n  if (legend) {\n    // @ts-expect-error ts-migrate(2538) FIXME: Type 'undefined' cannot be used as an index type.\n    legend.style[transformName] = null;\n  }\n\n  return [pick(layout, [\"width\", \"height\", \"legend\"]), null]; // no further updates\n}\n\nfunction placeLegendBelowPlot(plotlyElement: any, layout: any) {\n  const transformName = find(\n    [\"transform\", \"WebkitTransform\", \"MozTransform\", \"MsTransform\", \"OTransform\"],\n    prop => prop in plotlyElement.style\n  );\n\n  // Save current `layout.height` value because `Plotly.relayout().then(...)` handler may be called multiple\n  // times within single update, and since the handler mutates `layout` object - it may lead to bugs\n  const layoutHeight = layout.height;\n\n  // change legend orientation to horizontal; plotly has a bug with this\n  // legend alignment - it does not preserve enough space under the plot;\n  // so we'll hack this: update plot (it will re-render legend), compute\n  // legend height, reduce plot size by legend height (but not less than\n  // half of plot container's height - legend will have max height equal to\n  // plot height), re-render plot again and offset legend to the space under\n  // the plot.\n  // Related issue: https://github.com/plotly/plotly.js/issues/1199\n  layout.legend = extend({}, layout.legend, {\n    orientation: \"h\",\n    // locate legend inside of plot area - otherwise plotly will preserve\n    // some amount of space under the plot; also this will limit legend height\n    // to plot's height\n    y: 0,\n    x: 0,\n    xanchor: \"left\",\n    yanchor: \"bottom\",\n  });\n\n  // set `overflow: visible` to svg containing legend because later we will\n  // position legend outside of it\n  fixLegendContainer(plotlyElement);\n\n  return [\n    pick(layout, [\"width\", \"height\", \"legend\"]),\n    () => {\n      const legend = plotlyElement.querySelector(\".legend\"); // eslint-disable-line no-shadow\n      if (legend) {\n        // compute real height of legend - items may be split into few columnns,\n        // also scrollbar may be shown\n        const bounds = legend.getBoundingClientRect();\n\n        // here we have two values:\n        // 1. height of plot container excluding height of legend items;\n        //    it may be any value between 0 and plot container's height;\n        // 2. half of plot containers height. Legend cannot be larger than\n        //    plot; if legend is too large, plotly will reduce it's height and\n        //    show a scrollbar; in this case, height of plot === height of legend,\n        //    so we can split container's height half by half between them.\n        layout.height = Math.floor(Math.max(layoutHeight / 2, layoutHeight - (bounds.bottom - bounds.top)));\n        // offset the legend\n        // @ts-expect-error ts-migrate(2538) FIXME: Type 'undefined' cannot be used as an index type.\n        legend.style[transformName] = \"translate(0, \" + layout.height + \"px)\";\n        return [pick(layout, [\"height\"]), null]; // no further updates\n      }\n    },\n  ];\n}\n\nfunction placeLegendAuto(plotlyElement: any, layout: any) {\n  if (layout.width <= 600) {\n    return placeLegendBelowPlot(plotlyElement, layout);\n  } else {\n    return placeLegendNextToPlot(plotlyElement, layout);\n  }\n}\n\nexport default function updateChartSize(plotlyElement: any, layout: any, options: any) {\n  // update layout size to plot container\n  // plot size should be at least 5x5px\n  layout.width = Math.max(5, Math.floor(plotlyElement.offsetWidth));\n  layout.height = Math.max(5, Math.floor(plotlyElement.offsetHeight));\n\n  const [previousWidth, previousHeight] = plotlyElement.__previousSize || [];\n\n  if (layout.width === previousWidth && layout.height === previousHeight) {\n    return;\n  }\n\n  plotlyElement.__previousSize = [layout.width, layout.height];\n\n  if (options.legend.enabled) {\n    switch (options.legend.placement) {\n      case \"auto\":\n        return placeLegendAuto(plotlyElement, layout);\n        break;\n      case \"below\":\n        return placeLegendBelowPlot(plotlyElement, layout);\n        break;\n      // no default\n    }\n  } else {\n    return [pick(layout, [\"width\", \"height\"]), null]; // no further updates\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/updateData.ts",
    "content": "import { isNil, each, extend, filter, identity, includes, map, sortBy } from \"lodash\";\nimport { createNumberFormatter, formatSimpleTemplate } from \"@/lib/value-format\";\nimport { normalizeValue } from \"./utils\";\n\nfunction shouldUseUnifiedXAxis(options: any) {\n  return options.sortX && options.xAxis.type === \"category\" && options.globalSeriesType !== \"box\";\n}\n\nfunction defaultFormatSeriesText(item: any) {\n  let result = item[\"@@y\"];\n  if (item[\"@@yError\"] !== undefined) {\n    result = `${result} \\u00B1 ${item[\"@@yError\"]}`;\n  }\n  if (item[\"@@yPercent\"] !== undefined) {\n    result = `${item[\"@@yPercent\"]} (${result})`;\n  }\n  if (item[\"@@size\"] !== undefined) {\n    result = `${result}: ${item[\"@@size\"]}`;\n  }\n  return result;\n}\n\nfunction defaultFormatSeriesTextForPie(item: any) {\n  return item[\"@@yPercent\"] + \" (\" + item[\"@@y\"] + \")\";\n}\n\nfunction createTextFormatter(options: any) {\n  if (options.textFormat === \"\") {\n    return options.globalSeriesType === \"pie\" ? defaultFormatSeriesTextForPie : defaultFormatSeriesText;\n  }\n  return (item: any) => formatSimpleTemplate(options.textFormat, item);\n}\n\nfunction formatValue(value: any, axis: any, options: any) {\n  let axisType = null;\n  switch (axis) {\n    case \"x\":\n      axisType = options.xAxis.type;\n      break;\n    case \"y\":\n      axisType = options.yAxis[0].type;\n      break;\n    case \"y2\":\n      axisType = options.yAxis[1].type;\n      break;\n    // no default\n  }\n  return normalizeValue(value, axisType, options.dateTimeFormat);\n}\n\nfunction updateSeriesText(seriesList: any, options: any) {\n  const formatNumber = createNumberFormatter(options.numberFormat);\n  const formatPercent = createNumberFormatter(options.percentFormat);\n  const formatText = createTextFormatter(options);\n\n  const defaultY = options.missingValuesAsZero ? 0.0 : null;\n\n  each(seriesList, series => {\n    const seriesOptions = options.seriesOptions[series.name] || { type: options.globalSeriesType };\n\n    series.text = [];\n    series.hover = [];\n    const xValues = options.globalSeriesType === \"pie\" ? series.labels : series.x;\n    xValues.forEach((x: any) => {\n      const text = {\n        \"@@name\": series.name,\n      };\n      const item = series.sourceData.get(x) || { x, y: defaultY, row: { x, y: defaultY } };\n\n      const yValueIsAny = includes([\"bubble\", \"scatter\"], seriesOptions.type);\n\n      // for `formatValue` we have to use original value of `x` and `y`: `item.x`/`item.y` contains value\n      // already processed with `normalizeValue`, and if they were `moment` instances - they are formatted\n      // using default (ISO) date/time format. Here we need to use custom date/time format, so we pass original value\n      // to `formatValue` which will call `normalizeValue` again, but this time with different date/time format\n      // (if needed)\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      text[\"@@x\"] = formatValue(item.row.x, \"x\", options);\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      text[\"@@y\"] = yValueIsAny ? formatValue(item.row.y, series.yaxis, options) : formatNumber(item.y);\n      if (item.yError !== undefined) {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        text[\"@@yError\"] = formatNumber(item.yError);\n      }\n      if (item.size !== undefined) {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        text[\"@@size\"] = formatNumber(item.size);\n      }\n\n      if (options.series.percentValues || options.globalSeriesType === \"pie\") {\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        text[\"@@yPercent\"] = formatPercent(Math.abs(item.yPercent));\n      }\n\n      extend(text, item.row.$raw);\n\n      series.text.push(formatText(text));\n    });\n  });\n}\n\nfunction updatePercentValues(seriesList: any, options: any) {\n  if (options.series.percentValues) {\n    // Some series may not have corresponding x-values;\n    // do calculations for each x only for series that do have that x\n    const sumOfCorrespondingPoints = new Map();\n    each(seriesList, series => {\n      series.sourceData.forEach((item: any) => {\n        const sum = sumOfCorrespondingPoints.get(item.x) || 0;\n        sumOfCorrespondingPoints.set(item.x, sum + Math.abs(item.y || 0.0));\n      });\n    });\n\n    each(seriesList, series => {\n      const yValues: any = [];\n\n      series.sourceData.forEach((item: any) => {\n        if (isNil(item.y) && !options.missingValuesAsZero) {\n          item.yPercent = null;\n        } else {\n          const sum = sumOfCorrespondingPoints.get(item.x);\n          item.yPercent = (item.y / sum) * 100;\n        }\n        yValues.push(item.yPercent);\n      });\n\n      series.y = yValues;\n    });\n  }\n}\n\nfunction getUnifiedXAxisValues(seriesList: any, sorted: any) {\n  const set = new Set();\n  each(seriesList, series => {\n    // `Map.forEach` will walk items in insertion order\n    series.sourceData.forEach((item: any) => {\n      set.add(item.x);\n    });\n  });\n\n  const result = [...set];\n  return sorted ? sortBy(result, identity) : result;\n}\n\nfunction updateUnifiedXAxisValues(seriesList: any, options: any) {\n  const unifiedX = getUnifiedXAxisValues(seriesList, options.sortX);\n  const defaultY = options.missingValuesAsZero ? 0.0 : null;\n  each(seriesList, series => {\n    series.x = [];\n    series.y = [];\n    series.error_y.array = [];\n    each(unifiedX, x => {\n      series.x.push(x);\n      const item = series.sourceData.get(x);\n      if (item) {\n        series.y.push(options.series.percentValues ? item.yPercent : item.y);\n        series.error_y.array.push(item.yError);\n      } else {\n        series.y.push(defaultY);\n        series.error_y.array.push(null);\n      }\n    });\n  });\n}\n\nfunction updatePieData(seriesList: any, options: any) {\n  updateSeriesText(seriesList, options);\n}\n\nfunction updateLineAreaData(seriesList: any, options: any) {\n  // Apply \"percent values\" modification\n  updatePercentValues(seriesList, options);\n  if (options.series.stacking) {\n    updateUnifiedXAxisValues(seriesList, options);\n\n    // Calculate cumulative value for each x tick\n    const cumulativeValues = {};\n    each(seriesList, series => {\n      series.y = map(series.y, (y, i) => {\n        if (isNil(y) && !options.missingValuesAsZero) {\n          return null;\n        }\n        const x = series.x[i];\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        const stackedY = y + (cumulativeValues[x] || 0.0);\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        cumulativeValues[x] = stackedY;\n        return stackedY;\n      });\n    });\n  } else {\n    if (shouldUseUnifiedXAxis(options)) {\n      updateUnifiedXAxisValues(seriesList, options);\n    }\n  }\n\n  // Finally - update text labels\n  updateSeriesText(seriesList, options);\n}\n\nfunction updateDefaultData(seriesList: any, options: any) {\n  // Apply \"percent values\" modification\n  updatePercentValues(seriesList, options);\n\n  if (!options.series.stacking) {\n    if (shouldUseUnifiedXAxis(options)) {\n      updateUnifiedXAxisValues(seriesList, options);\n    }\n  }\n\n  // Finally - update text labels\n  updateSeriesText(seriesList, options);\n}\n\nexport default function updateData(seriesList: any, options: any) {\n  // Use only visible series\n  const visibleSeriesList = filter(seriesList, s => s.visible === true);\n\n  if (visibleSeriesList.length > 0) {\n    switch (options.globalSeriesType) {\n      case \"pie\":\n        updatePieData(visibleSeriesList, options);\n        break;\n      case \"line\":\n      case \"area\":\n        updateLineAreaData(visibleSeriesList, options);\n        break;\n      case \"heatmap\":\n        break;\n      default:\n        updateDefaultData(visibleSeriesList, options);\n        break;\n    }\n  }\n  return seriesList;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/chart/plotly/utils.ts",
    "content": "import { isUndefined } from \"lodash\";\nimport moment from \"moment\";\n// @ts-expect-error ts-migrate(7016) FIXME: Could not find a declaration file for module 'plot... Remove this comment to see the full error message\nimport plotlyCleanNumber from \"plotly.js/src/lib/clean_number\";\n\nexport function cleanNumber(value: any) {\n  return isUndefined(value) ? value : plotlyCleanNumber(value);\n}\n\nexport function getSeriesAxis(series: any, options: any) {\n  const seriesOptions = options.seriesOptions[series.name] || { type: options.globalSeriesType };\n  if (seriesOptions.yAxis === 1 && (!options.series.stacking || seriesOptions.type === \"line\")) {\n    return \"y2\";\n  }\n  return \"y\";\n}\n\nexport function normalizeValue(value: any, axisType: any, dateTimeFormat = \"YYYY-MM-DD HH:mm:ss\") {\n  if (axisType === \"datetime\" && moment.utc(value).isValid()) {\n    value = moment.utc(value);\n  }\n  if (moment.isMoment(value)) {\n    return value.format(dateTimeFormat);\n  }\n  return value;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/ColorPalette.ts",
    "content": "import { extend } from \"lodash\";\nimport ColorPalette from \"@/visualizations/ColorPalette\";\n\nexport default extend(\n  {\n    White: \"#ffffff\",\n    Black: \"#000000\",\n    \"Light Gray\": \"#dddddd\",\n  },\n  ColorPalette\n);\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Editor/BoundsSettings.tsx",
    "content": "import { isArray, isFinite, cloneDeep } from \"lodash\";\nimport React, { useState, useEffect, useCallback } from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport * as Grid from \"antd/lib/grid\";\nimport { Section, InputNumber, ControlLabel } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nimport useLoadGeoJson from \"../hooks/useLoadGeoJson\";\nimport { getGeoJsonBounds } from \"./utils\";\n\nexport default function BoundsSettings({ options, onOptionsChange }: any) {\n  // Bounds may be changed in editor or on preview (by drag/zoom map).\n  // Changes from preview does not come frequently (only when user release mouse button),\n  // but changes from editor should be debounced.\n  // Therefore this component has intermediate state to hold immediate user input,\n  // which is updated from `options.bounds` and by inputs immediately on user input,\n  // but `onOptionsChange` event is debounced and uses last value from internal state.\n\n  const [bounds, setBounds] = useState(options.bounds);\n  const [onOptionsChangeDebounced] = useDebouncedCallback(onOptionsChange, 200);\n\n  const [geoJson] = useLoadGeoJson(options.mapType);\n\n  // `options.bounds` could be empty only if user didn't edit bounds yet - through preview or in this editor.\n  // In this case we should keep empty bounds value because it tells renderer to fit map every time.\n  useEffect(() => {\n    if (options.bounds) {\n      setBounds(options.bounds);\n    } else {\n      const defaultBounds = getGeoJsonBounds(geoJson);\n      if (defaultBounds) {\n        setBounds(defaultBounds);\n      }\n    }\n  }, [options.bounds, geoJson]);\n\n  const updateBounds = useCallback(\n    (i, j, v) => {\n      v = parseFloat(v); // InputNumber may emit `null` and empty strings instead of numbers\n      if (isFinite(v)) {\n        const newBounds = cloneDeep(bounds);\n        newBounds[i][j] = v;\n        setBounds(newBounds);\n        onOptionsChangeDebounced({ bounds: newBounds });\n      }\n    },\n    [bounds, onOptionsChangeDebounced]\n  );\n\n  const boundsAvailable = isArray(bounds);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'null | un... Remove this comment to see the full error message */}\n        <ControlLabel label=\"North-East Latitude and Longitude\">\n          {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message */}\n          <Grid.Row gutter={15}>\n            <Grid.Col span={12}>\n              <InputNumber\n                disabled={!boundsAvailable}\n                value={boundsAvailable ? bounds[1][0] : undefined}\n                onChange={(value: any) => updateBounds(1, 0, value)}\n              />\n            </Grid.Col>\n            <Grid.Col span={12}>\n              <InputNumber\n                disabled={!boundsAvailable}\n                value={boundsAvailable ? bounds[1][1] : undefined}\n                onChange={(value: any) => updateBounds(1, 1, value)}\n              />\n            </Grid.Col>\n          </Grid.Row>\n        </ControlLabel>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'null | un... Remove this comment to see the full error message */}\n        <ControlLabel label=\"South-West Latitude and Longitude\">\n          {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message */}\n          <Grid.Row gutter={15}>\n            <Grid.Col span={12}>\n              <InputNumber\n                disabled={!boundsAvailable}\n                value={boundsAvailable ? bounds[0][0] : undefined}\n                onChange={(value: any) => updateBounds(0, 0, value)}\n              />\n            </Grid.Col>\n            <Grid.Col span={12}>\n              <InputNumber\n                disabled={!boundsAvailable}\n                value={boundsAvailable ? bounds[0][1] : undefined}\n                onChange={(value: any) => updateBounds(0, 1, value)}\n              />\n            </Grid.Col>\n          </Grid.Row>\n        </ControlLabel>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nBoundsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Editor/ColorsSettings.tsx",
    "content": "import React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Select, InputNumber, ColorPicker } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport ColorPalette from \"../ColorPalette\";\n\nexport default function ColorsSettings({ options, onOptionsChange }: any) {\n  const [onOptionsChangeDebounced] = useDebouncedCallback(onOptionsChange, 200);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Clustering Mode\"\n          data-test=\"Choropleth.Editor.ClusteringMode\"\n          defaultValue={options.clusteringMode}\n          onChange={(clusteringMode: any) => onOptionsChange({ clusteringMode })}>\n          {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          <Select.Option value=\"q\" data-test=\"Choropleth.Editor.ClusteringMode.q\">\n            quantile\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          </Select.Option>\n          {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          <Select.Option value=\"e\" data-test=\"Choropleth.Editor.ClusteringMode.e\">\n            equidistant\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          </Select.Option>\n          {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          <Select.Option value=\"k\" data-test=\"Choropleth.Editor.ClusteringMode.k\">\n            k-means\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          </Select.Option>\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <InputNumber\n          layout=\"horizontal\"\n          label=\"Steps\"\n          data-test=\"Choropleth.Editor.ColorSteps\"\n          min={3}\n          max={11}\n          defaultValue={options.steps}\n          onChange={(steps: any) => onOptionsChangeDebounced({ steps })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <ColorPicker\n          layout=\"horizontal\"\n          label=\"Min Color\"\n          interactive\n          presetColors={ColorPalette}\n          placement=\"topRight\"\n          color={options.colors.min}\n          triggerProps={{ \"data-test\": \"Choropleth.Editor.Colors.Min\" }}\n          onChange={(min: any) => onOptionsChange({ colors: { min } })}\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n          addonAfter={<ColorPicker.Label color={options.colors.min} presetColors={ColorPalette} />}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <ColorPicker\n          layout=\"horizontal\"\n          label=\"Max Color\"\n          interactive\n          presetColors={ColorPalette}\n          placement=\"topRight\"\n          color={options.colors.max}\n          triggerProps={{ \"data-test\": \"Choropleth.Editor.Colors.Max\" }}\n          onChange={(max: any) => onOptionsChange({ colors: { max } })}\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n          addonAfter={<ColorPicker.Label color={options.colors.max} presetColors={ColorPalette} />}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <ColorPicker\n          layout=\"horizontal\"\n          label=\"No Value Color\"\n          interactive\n          presetColors={ColorPalette}\n          placement=\"topRight\"\n          color={options.colors.noValue}\n          triggerProps={{ \"data-test\": \"Choropleth.Editor.Colors.NoValue\" }}\n          onChange={(noValue: any) => onOptionsChange({ colors: { noValue } })}\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n          addonAfter={<ColorPicker.Label color={options.colors.noValue} presetColors={ColorPalette} />}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <ColorPicker\n          layout=\"horizontal\"\n          label=\"Background Color\"\n          interactive\n          presetColors={ColorPalette}\n          placement=\"topRight\"\n          color={options.colors.background}\n          triggerProps={{ \"data-test\": \"Choropleth.Editor.Colors.Background\" }}\n          onChange={(background: any) => onOptionsChange({ colors: { background } })}\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n          addonAfter={<ColorPicker.Label color={options.colors.background} presetColors={ColorPalette} />}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <ColorPicker\n          layout=\"horizontal\"\n          label=\"Borders Color\"\n          interactive\n          presetColors={ColorPalette}\n          placement=\"topRight\"\n          color={options.colors.borders}\n          triggerProps={{ \"data-test\": \"Choropleth.Editor.Colors.Borders\" }}\n          onChange={(borders: any) => onOptionsChange({ colors: { borders } })}\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n          addonAfter={<ColorPicker.Label color={options.colors.borders} presetColors={ColorPalette} />}\n        />\n      </Section>\n    </React.Fragment>\n  );\n}\n\nColorsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Editor/FormatSettings.tsx",
    "content": "import { map } from \"lodash\";\nimport React, { useMemo } from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport * as Grid from \"antd/lib/grid\";\nimport {\n  Section,\n  Select,\n  Input,\n  Checkbox,\n  TextArea,\n  TextAlignmentSelect,\n  ContextHelp,\n} from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nimport useLoadGeoJson from \"../hooks/useLoadGeoJson\";\nimport { getGeoJsonFields } from \"./utils\";\n\ntype OwnTemplateFormatHintProps = {\n  geoJsonProperties?: string[];\n};\n\nconst templateFormatHintDefaultProps = {\n  geoJsonProperties: [],\n};\n\ntype TemplateFormatHintProps = OwnTemplateFormatHintProps & typeof templateFormatHintDefaultProps;\n\nfunction TemplateFormatHint({ geoJsonProperties }: TemplateFormatHintProps) {\n  return (\n    // @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message\n    <ContextHelp placement=\"topLeft\" arrowPointAtCenter>\n      <div style={{ paddingBottom: 5 }}>\n        <div>\n          All query result columns can be referenced using <code>{\"{{ column_name }}\"}</code> syntax.\n        </div>\n        <div>\n          Use <code>{\"{{ @@value }}\"}</code> to access formatted value.\n        </div>\n      </div>\n      {geoJsonProperties.length > 0 && (\n        <React.Fragment>\n          <div className=\"p-b-5\">GeoJSON properties could be accessed by these names:</div>\n          <div style={{ maxHeight: 300, overflow: \"auto\" }}>\n            {map(geoJsonProperties, property => (\n              <div key={property}>\n                <code>{`{{ @@${property}}}`}</code>\n              </div>\n            ))}\n          </div>\n        </React.Fragment>\n      )}\n    </ContextHelp>\n  );\n}\n\nTemplateFormatHint.defaultProps = templateFormatHintDefaultProps;\n\nexport default function GeneralSettings({ options, onOptionsChange }: any) {\n  const [onOptionsChangeDebounced] = useDebouncedCallback(onOptionsChange, 200);\n  const [geoJson] = useLoadGeoJson(options.mapType);\n  const geoJsonFields = useMemo(() => getGeoJsonFields(geoJson), [geoJson]);\n\n  const templateFormatHint = <TemplateFormatHint geoJsonProperties={geoJsonFields} />;\n\n  return (\n    <div className=\"choropleth-visualization-editor-format-settings\">\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Grid.Row gutter={15}>\n          <Grid.Col span={12}>\n            <Input\n              label={\n                <React.Fragment>\n                  Value Format\n                  <ContextHelp.NumberFormatSpecs />\n                </React.Fragment>\n              }\n              data-test=\"Choropleth.Editor.ValueFormat\"\n              defaultValue={options.valueFormat}\n              onChange={(event: any) => onOptionsChangeDebounced({ valueFormat: event.target.value })}\n            />\n          </Grid.Col>\n          <Grid.Col span={12}>\n            <Input\n              label=\"Value Placeholder\"\n              data-test=\"Choropleth.Editor.ValuePlaceholder\"\n              defaultValue={options.noValuePlaceholder}\n              onChange={(event: any) => onOptionsChangeDebounced({ noValuePlaceholder: event.target.value })}\n            />\n          </Grid.Col>\n        </Grid.Row>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Choropleth.Editor.LegendVisibility\"\n          checked={options.legend.visible}\n          onChange={event => onOptionsChange({ legend: { visible: event.target.checked } })}>\n          Show Legend\n        </Checkbox>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Grid.Row gutter={15}>\n          <Grid.Col span={12}>\n            <Select\n              label=\"Legend Position\"\n              data-test=\"Choropleth.Editor.LegendPosition\"\n              disabled={!options.legend.visible}\n              defaultValue={options.legend.position}\n              onChange={(position: any) => onOptionsChange({ legend: { position } })}>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"top-left\" data-test=\"Choropleth.Editor.LegendPosition.TopLeft\">\n                top / left\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"top-right\" data-test=\"Choropleth.Editor.LegendPosition.TopRight\">\n                top / right\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"bottom-left\" data-test=\"Choropleth.Editor.LegendPosition.BottomLeft\">\n                bottom / left\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"bottom-right\" data-test=\"Choropleth.Editor.LegendPosition.BottomRight\">\n                bottom / right\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n            </Select>\n          </Grid.Col>\n          <Grid.Col span={12}>\n            <TextAlignmentSelect\n              data-test=\"Choropleth.Editor.LegendTextAlignment\"\n              label=\"Legend Text Alignment\"\n              disabled={!options.legend.visible}\n              defaultValue={options.legend.alignText}\n              onChange={(event: any) => onOptionsChange({ legend: { alignText: event.target.value } })}\n            />\n          </Grid.Col>\n        </Grid.Row>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Choropleth.Editor.TooltipEnabled\"\n          checked={options.tooltip.enabled}\n          onChange={event => onOptionsChange({ tooltip: { enabled: event.target.checked } })}>\n          Show Tooltip\n        </Checkbox>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label={<React.Fragment>Tooltip Template {templateFormatHint}</React.Fragment>}\n          data-test=\"Choropleth.Editor.TooltipTemplate\"\n          disabled={!options.tooltip.enabled}\n          defaultValue={options.tooltip.template}\n          onChange={(event: any) => onOptionsChangeDebounced({ tooltip: { template: event.target.value } })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Choropleth.Editor.PopupEnabled\"\n          checked={options.popup.enabled}\n          onChange={event => onOptionsChange({ popup: { enabled: event.target.checked } })}>\n          Show Popup\n        </Checkbox>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <TextArea\n          label={<React.Fragment>Popup Template {templateFormatHint}</React.Fragment>}\n          data-test=\"Choropleth.Editor.PopupTemplate\"\n          disabled={!options.popup.enabled}\n          rows={4}\n          defaultValue={options.popup.template}\n          onChange={(event: any) => onOptionsChangeDebounced({ popup: { template: event.target.value } })}\n        />\n      </Section>\n    </div>\n  );\n}\n\nGeneralSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Editor/GeneralSettings.tsx",
    "content": "import { isString, map, filter, get } from \"lodash\";\nimport React, { useMemo, useCallback } from \"react\";\nimport * as Grid from \"antd/lib/grid\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport { Section, Select } from \"@/components/visualizations/editor\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\n\nimport useLoadGeoJson from \"../hooks/useLoadGeoJson\";\nimport { getGeoJsonFields } from \"./utils\";\n\nexport default function GeneralSettings({ options, data, onOptionsChange }: any) {\n  const [geoJson, isLoadingGeoJson] = useLoadGeoJson(options.mapType);\n  const geoJsonFields = useMemo(() => getGeoJsonFields(geoJson), [geoJson]);\n\n  // While geoJson is loading - show last selected field in select\n  const targetFields = isLoadingGeoJson ? filter([options.targetField], isString) : geoJsonFields;\n\n  const fieldNames = get(visualizationsSettings, `choroplethAvailableMaps.${options.mapType}.fieldNames`, {});\n\n  const handleMapChange = useCallback(\n    mapType => {\n      onOptionsChange({ mapType: mapType || null });\n    },\n    [onOptionsChange]\n  );\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Map\"\n          data-test=\"Choropleth.Editor.MapType\"\n          defaultValue={options.mapType}\n          onChange={handleMapChange}>\n          {map(visualizationsSettings.choroplethAvailableMaps, (_, mapType) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={mapType} data-test={`Choropleth.Editor.MapType.${mapType}`}>\n              {get(visualizationsSettings, `choroplethAvailableMaps.${mapType}.name`, mapType)}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Grid.Row gutter={15}>\n          <Grid.Col span={12}>\n            <Select\n              label=\"Key Column\"\n              className=\"w-100\"\n              data-test=\"Choropleth.Editor.KeyColumn\"\n              disabled={data.columns.length === 0}\n              defaultValue={options.keyColumn}\n              onChange={(keyColumn: any) => onOptionsChange({ keyColumn })}>\n              {map(data.columns, ({ name }) => (\n                // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n                <Select.Option key={name} data-test={`Choropleth.Editor.KeyColumn.${name}`}>\n                  {name}\n                  {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n                </Select.Option>\n              ))}\n            </Select>\n          </Grid.Col>\n          <Grid.Col span={12}>\n            <Select\n              label=\"Target Field\"\n              className=\"w-100\"\n              data-test=\"Choropleth.Editor.TargetField\"\n              disabled={isLoadingGeoJson || targetFields.length === 0}\n              loading={isLoadingGeoJson}\n              value={options.targetField}\n              onChange={(targetField: any) => onOptionsChange({ targetField })}>\n              {map(targetFields, field => (\n                // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n                <Select.Option key={field} data-test={`Choropleth.Editor.TargetField.${field}`}>\n                  {(fieldNames as any)[field] || field}\n                  {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n                </Select.Option>\n              ))}\n            </Select>\n          </Grid.Col>\n        </Grid.Row>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Value Column\"\n          data-test=\"Choropleth.Editor.ValueColumn\"\n          disabled={data.columns.length === 0}\n          defaultValue={options.valueColumn}\n          onChange={(valueColumn: any) => onOptionsChange({ valueColumn })}>\n          {map(data.columns, ({ name }) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={name} data-test={`Choropleth.Editor.ValueColumn.${name}`}>\n              {name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nGeneralSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Editor/index.ts",
    "content": "import createTabbedEditor from \"@/components/visualizations/editor/createTabbedEditor\";\n\nimport GeneralSettings from \"./GeneralSettings\";\nimport ColorsSettings from \"./ColorsSettings\";\nimport FormatSettings from \"./FormatSettings\";\nimport BoundsSettings from \"./BoundsSettings\";\n\nexport default createTabbedEditor([\n  { key: \"General\", title: \"General\", component: GeneralSettings },\n  { key: \"Colors\", title: \"Colors\", component: ColorsSettings },\n  { key: \"Format\", title: \"Format\", component: FormatSettings },\n  { key: \"Bounds\", title: \"Bounds\", component: BoundsSettings },\n]);\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Editor/utils.ts",
    "content": "import { isObject, isArray, reduce, keys, uniq } from \"lodash\";\nimport L from \"leaflet\";\n\nexport function getGeoJsonFields(geoJson: any) {\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'features' does not exist on type 'object... Remove this comment to see the full error message\n  const features = isObject(geoJson) && isArray(geoJson.features) ? geoJson.features : [];\n  return reduce(\n    features,\n    // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.\n    (result, feature) => {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'properties' does not exist on type 'obje... Remove this comment to see the full error message\n      const properties = isObject(feature) && isObject(feature.properties) ? feature.properties : {};\n      return uniq([...result, ...keys(properties)]);\n    },\n    []\n  );\n}\n\nexport function getGeoJsonBounds(geoJson: any) {\n  if (isObject(geoJson)) {\n    // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'object' is not assignable to par... Remove this comment to see the full error message\n    const layer = L.geoJSON(geoJson);\n    const bounds = layer.getBounds();\n    if (bounds.isValid()) {\n      return [\n        // @ts-expect-error ts-migrate(2551) FIXME: Property '_southWest' does not exist on type 'LatL... Remove this comment to see the full error message\n        [bounds._southWest.lat, bounds._southWest.lng],\n        // @ts-expect-error ts-migrate(2551) FIXME: Property '_northEast' does not exist on type 'LatL... Remove this comment to see the full error message\n        [bounds._northEast.lat, bounds._northEast.lng],\n      ];\n    }\n  }\n  return null;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Renderer/Legend.tsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport ColorPicker from \"@/components/ColorPicker\";\n\ntype OwnProps = {\n  items?: {\n    color: string;\n    text: string;\n  }[];\n  alignText?: \"left\" | \"center\" | \"right\";\n};\n\nconst legendDefaultProps = {\n  items: [],\n  alignText: \"left\",\n};\n\ntype Props = OwnProps & typeof legendDefaultProps;\n\nexport default function Legend({ items, alignText }: Props) {\n  return (\n    <div className=\"choropleth-visualization-legend\">\n      {map(items, (item, index) => (\n        <div key={`legend${index}`} className=\"legend-item\">\n          {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'. */}\n          <ColorPicker.Swatch color={item.color} />\n          <div className={`legend-item-text text-${alignText}`}>{item.text}</div>\n        </div>\n      ))}\n    </div>\n  );\n}\n\nLegend.defaultProps = legendDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Renderer/index.tsx",
    "content": "import { omit, noop } from \"lodash\";\nimport React, { useState, useEffect, useRef } from \"react\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\nimport useMemoWithDeepCompare from \"@/lib/hooks/useMemoWithDeepCompare\";\n\nimport useLoadGeoJson from \"../hooks/useLoadGeoJson\";\nimport initChoropleth from \"./initChoropleth\";\nimport { prepareData } from \"./utils\";\nimport \"./renderer.less\";\n\nexport default function Renderer({ data, options, onOptionsChange }: any) {\n  const [container, setContainer] = useState(null);\n  const [geoJson] = useLoadGeoJson(options.mapType);\n  const onBoundsChangeRef = useRef();\n  // @ts-expect-error ts-migrate(2322) FIXME: Type '(...args: any[]) => void' is not assignable ... Remove this comment to see the full error message\n  onBoundsChangeRef.current = onOptionsChange ? (bounds: any) => onOptionsChange({ ...options, bounds }) : noop;\n\n  const optionsWithoutBounds = useMemoWithDeepCompare(() => omit(options, [\"bounds\"]), [options]);\n\n  const [map, setMap] = useState(null);\n\n  useEffect(() => {\n    if (container) {\n      // @ts-expect-error ts-migrate(7019) FIXME: Rest parameter 'args' implicitly has an 'any[]' ty... Remove this comment to see the full error message\n      const _map = initChoropleth(container, (...args) => onBoundsChangeRef.current(...args));\n      // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ updateLayers: (geoJson: any, d... Remove this comment to see the full error message\n      setMap(_map);\n      return () => {\n        _map.destroy();\n      };\n    }\n  }, [container]);\n\n  useEffect(() => {\n    if (map) {\n      // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.\n      map.updateLayers(\n        geoJson,\n        // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.\n        prepareData(data.rows, optionsWithoutBounds.keyColumn, optionsWithoutBounds.valueColumn),\n        options // detect changes for all options except bounds, but pass them all!\n      );\n    }\n  }, [map, geoJson, data.rows, optionsWithoutBounds]); // eslint-disable-line react-hooks/exhaustive-deps\n\n  // This may come only from editor\n  useEffect(() => {\n    if (map) {\n      // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.\n      map.updateBounds(options.bounds);\n    }\n  }, [map, options, onOptionsChange]);\n\n  return (\n    // @ts-expect-error ts-migrate(2322) FIXME: Type 'Dispatch<SetStateAction<null>>' is not assig... Remove this comment to see the full error message\n    <div className=\"map-visualization-container\" style={{ background: options.colors.background }} ref={setContainer} />\n  );\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Renderer/initChoropleth.tsx",
    "content": "import { isFunction, isObject, isArray, map } from \"lodash\";\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport L from \"leaflet\";\nimport \"leaflet/dist/leaflet.css\";\nimport \"leaflet-fullscreen\";\nimport \"leaflet-fullscreen/dist/leaflet.fullscreen.css\";\nimport { formatSimpleTemplate } from \"@/lib/value-format\";\nimport sanitize from \"@/services/sanitize\";\nimport resizeObserver from \"@/services/resizeObserver\";\nimport {\n  createNumberFormatter,\n  createScale,\n  darkenColor,\n  getColorByValue,\n  getValueForFeature,\n  prepareFeatureProperties,\n} from \"./utils\";\nimport Legend from \"./Legend\";\n\nconst CustomControl = L.Control.extend({\n  options: {\n    position: \"topright\",\n  },\n  onAdd() {\n    const div = document.createElement(\"div\");\n    div.className = \"leaflet-bar leaflet-custom-toolbar\";\n    div.style.background = \"#fff\";\n    div.style.backgroundClip = \"padding-box\";\n    return div;\n  },\n  onRemove() {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'getContainer' does not exist on type '{ ... Remove this comment to see the full error message\n    ReactDOM.unmountComponentAtNode(this.getContainer());\n  },\n});\n\nfunction prepareLayer({ feature, layer, data, options, limits, colors, formatValue }: any) {\n  const value = getValueForFeature(feature, data, options.targetField);\n  const valueFormatted = formatValue(value);\n  const featureData = prepareFeatureProperties(feature, valueFormatted, data, options.targetField);\n  const color = getColorByValue(value, limits, colors, options.colors.noValue);\n\n  layer.setStyle({\n    color: options.colors.borders,\n    weight: 1,\n    fillColor: color,\n    fillOpacity: 1,\n  });\n\n  if (options.tooltip.enabled) {\n    layer.bindTooltip(sanitize(formatSimpleTemplate(options.tooltip.template, featureData)), { sticky: true });\n  }\n\n  if (options.popup.enabled) {\n    layer.bindPopup(sanitize(formatSimpleTemplate(options.popup.template, featureData)));\n  }\n\n  layer.on(\"mouseover\", () => {\n    layer.setStyle({\n      weight: 2,\n      fillColor: darkenColor(color),\n    });\n  });\n  layer.on(\"mouseout\", () => {\n    layer.setStyle({\n      weight: 1,\n      fillColor: color,\n    });\n  });\n}\n\nfunction validateBounds(bounds: any, fallbackBounds: any) {\n  if (bounds) {\n    bounds = L.latLngBounds(bounds[0], bounds[1]);\n    if (bounds.isValid()) {\n      return bounds;\n    }\n  }\n  if (fallbackBounds && fallbackBounds.isValid()) {\n    return fallbackBounds;\n  }\n  return null;\n}\n\nexport default function initChoropleth(container: any, onBoundsChange: any) {\n  const _map = L.map(container, {\n    center: [0.0, 0.0],\n    zoom: 1,\n    zoomSnap: 0,\n    scrollWheelZoom: false,\n    maxBoundsViscosity: 1,\n    attributionControl: false,\n    // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ center: [number, number]; zoom... Remove this comment to see the full error message\n    fullscreenControl: true,\n  });\n  let _choropleth: any = null;\n  const _legend = new CustomControl();\n\n  function handleMapBoundsChange() {\n    if (isFunction(onBoundsChange)) {\n      const bounds = _map.getBounds();\n      onBoundsChange([\n        // @ts-expect-error ts-migrate(2551) FIXME: Property '_southWest' does not exist on type 'LatL... Remove this comment to see the full error message\n        [bounds._southWest.lat, bounds._southWest.lng],\n        // @ts-expect-error ts-migrate(2551) FIXME: Property '_northEast' does not exist on type 'LatL... Remove this comment to see the full error message\n        [bounds._northEast.lat, bounds._northEast.lng],\n      ]);\n    }\n  }\n\n  let boundsChangedFromMap = false;\n  const onMapMoveEnd = () => {\n    handleMapBoundsChange();\n  };\n  _map.on(\"focus\", () => {\n    boundsChangedFromMap = true;\n    _map.on(\"moveend\", onMapMoveEnd);\n  });\n  _map.on(\"blur\", () => {\n    _map.off(\"moveend\", onMapMoveEnd);\n    boundsChangedFromMap = false;\n  });\n\n  function updateLayers(geoJson: any, data: any, options: any) {\n    _map.eachLayer(layer => _map.removeLayer(layer));\n    _map.removeControl(_legend);\n\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'features' does not exist on type 'object... Remove this comment to see the full error message\n    if (!isObject(geoJson) || !isArray(geoJson.features)) {\n      _choropleth = null;\n      // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'null' is not assignable to param... Remove this comment to see the full error message\n      _map.setMaxBounds(null);\n      return;\n    }\n\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'features' does not exist on type 'object... Remove this comment to see the full error message\n    const { limits, colors, legend } = createScale(geoJson.features, data, options);\n    const formatValue = createNumberFormatter(options.valueFormat, options.noValuePlaceholder);\n\n    // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'object' is not assignable to par... Remove this comment to see the full error message\n    _choropleth = L.geoJSON(geoJson, {\n      onEachFeature(feature, layer) {\n        prepareLayer({ feature, layer, data, options, limits, colors, formatValue });\n      },\n    }).addTo(_map);\n\n    const mapBounds = _choropleth.getBounds();\n    const bounds = validateBounds(options.bounds, mapBounds);\n    _map.fitBounds(bounds, { animate: false, duration: 0 });\n\n    // equivalent to `_map.setMaxBounds(mapBounds)` but without animation\n    _map.options.maxBounds = mapBounds;\n    _map.panInsideBounds(mapBounds, { animate: false, duration: 0 });\n\n    // update legend\n    if (options.legend.visible && legend.length > 0) {\n      _legend.setPosition(options.legend.position.replace(\"-\", \"\"));\n      _map.addControl(_legend);\n      ReactDOM.render(\n        // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.\n        <Legend\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '{ text: any; color: any; limit: any; }[]' is... Remove this comment to see the full error message\n          items={map(legend, item => ({ ...item, text: formatValue(item.limit) }))}\n          alignText={options.legend.alignText}\n        />,\n        _legend.getContainer()\n      );\n    }\n  }\n\n  function updateBounds(bounds: any) {\n    if (!boundsChangedFromMap) {\n      const layerBounds = _choropleth ? _choropleth.getBounds() : _map.getBounds();\n      bounds = validateBounds(bounds, layerBounds);\n      if (bounds) {\n        _map.fitBounds(bounds, { animate: false, duration: 0 });\n      }\n    }\n  }\n\n  const unwatchResize = resizeObserver(container, () => {\n    _map.invalidateSize(false);\n  });\n\n  return {\n    updateLayers,\n    updateBounds,\n    destroy() {\n      unwatchResize();\n      _map.removeControl(_legend); // _map.remove() does not cleanup controls - bug in Leaflet?\n      _map.remove();\n    },\n  };\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Renderer/renderer.less",
    "content": ".choropleth-visualization-legend {\n  padding: 3px;\n  cursor: default;\n\n  > div {\n    line-height: 1;\n    margin: 5px;\n  }\n\n  .legend-item {\n    display: flex;\n    align-items: center;\n\n    .color-swatch {\n      margin-right: 5px;\n    }\n\n    .legend-item-text {\n      flex: 1 1 auto;\n\n      &.text-left {\n        text-align: left;\n      }\n      &.text-center {\n        text-align: center;\n      }\n      &.text-right {\n        text-align: right;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/Renderer/utils.ts",
    "content": "import { isString, isObject, isFinite, each, map, extend, uniq, filter, first } from \"lodash\";\nimport chroma from \"chroma-js\";\nimport { createNumberFormatter as createFormatter } from \"@/lib/value-format\";\n\nexport function darkenColor(color: any) {\n  return chroma(color)\n    .darken()\n    .hex();\n}\n\nexport function createNumberFormatter(format: any, placeholder: any) {\n  const formatter = createFormatter(format);\n  return (value: any) => {\n    if (isFinite(value)) {\n      return formatter(value);\n    }\n    return placeholder;\n  };\n}\n\nexport function prepareData(data: any, keyColumn: any, valueColumn: any) {\n  if (!keyColumn || !valueColumn) {\n    return {};\n  }\n\n  const result = {};\n  each(data, item => {\n    if (item[keyColumn]) {\n      const value = parseFloat(item[valueColumn]);\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      result[item[keyColumn]] = {\n        code: item[keyColumn],\n        value: isFinite(value) ? value : undefined,\n        item,\n      };\n    }\n  });\n  return result;\n}\n\nexport function prepareFeatureProperties(feature: any, valueFormatted: any, data: any, targetField: any) {\n  const result = {};\n  each(feature.properties, (value, key) => {\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    result[\"@@\" + key] = value;\n  });\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  result[\"@@value\"] = valueFormatted;\n  const datum = data[feature.properties[targetField]] || {};\n  return extend(result, datum.item);\n}\n\nexport function getValueForFeature(feature: any, data: any, targetField: any) {\n  const code = feature.properties[targetField];\n  if (isString(code) && isObject(data[code])) {\n    return (data[code] as any).value;\n  }\n  return undefined;\n}\n\nexport function getColorByValue(value: any, limits: any, colors: any, defaultColor: any) {\n  if (isFinite(value)) {\n    for (let i = 0; i < limits.length; i += 1) {\n      if (value <= limits[i]) {\n        return colors[i];\n      }\n    }\n  }\n  return defaultColor;\n}\n\nexport function createScale(features: any, data: any, options: any) {\n  // Calculate limits\n  const values = uniq(\n    filter(\n      map(features, feature => getValueForFeature(feature, data, options.targetField)),\n      isFinite\n    )\n  );\n  if (values.length === 0) {\n    return {\n      limits: [],\n      colors: [],\n      legend: [],\n    };\n  }\n  const steps = Math.min(values.length, options.steps);\n  if (steps === 1) {\n    return {\n      limits: values,\n      colors: [options.colors.max],\n      legend: [\n        {\n          color: options.colors.max,\n          limit: first(values),\n        },\n      ],\n    };\n  }\n  const limits = chroma.limits(values, options.clusteringMode, steps - 1);\n\n  // Create color buckets\n  const colors = chroma.scale([options.colors.min, options.colors.max]).colors(limits.length);\n\n  // Group values for legend\n  const legend = map(colors, (color, index) => ({\n    color,\n    limit: limits[index],\n  })).reverse();\n\n  return { limits, colors, legend };\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/getOptions.ts",
    "content": "import { isNil, merge, first, keys, get } from \"lodash\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\nimport ColorPalette from \"./ColorPalette\";\n\nfunction getDefaultMap() {\n  return first(keys(visualizationsSettings.choroplethAvailableMaps)) || null;\n}\n\nconst DEFAULT_OPTIONS = {\n  mapType: \"countries\",\n  keyColumn: null,\n  targetField: null,\n  valueColumn: null,\n  clusteringMode: \"e\",\n  steps: 5,\n  valueFormat: \"0,0.00\",\n  noValuePlaceholder: \"N/A\",\n  colors: {\n    min: ColorPalette[\"Light Blue\"],\n    max: ColorPalette[\"Dark Blue\"],\n    background: ColorPalette.White,\n    borders: ColorPalette.White,\n    noValue: ColorPalette[\"Light Gray\"],\n  },\n  legend: {\n    visible: true,\n    position: \"bottom-left\",\n    alignText: \"right\",\n  },\n  tooltip: {\n    enabled: true,\n    template: \"<b>{{ @@name }}</b>: {{ @@value }}\",\n  },\n  popup: {\n    enabled: true,\n    template: \"Country: <b>{{ @@name_long }} ({{ @@iso_a2 }})</b>\\n<br>\\nValue: <b>{{ @@value }}</b>\",\n  },\n};\n\nexport default function getOptions(options: any) {\n  const result = merge({}, DEFAULT_OPTIONS, options);\n\n  // Both renderer and editor always provide new `bounds` array, so no need to clone it here.\n  // Keeping original object also reduces amount of updates in components\n  result.bounds = get(options, \"bounds\");\n\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  if (isNil(visualizationsSettings.choroplethAvailableMaps[result.mapType])) {\n    result.mapType = getDefaultMap();\n  }\n\n  // backward compatibility\n  if (!isNil(result.countryCodeColumn)) {\n    result.keyColumn = result.countryCodeColumn;\n  }\n  delete result.countryCodeColumn;\n\n  if (!isNil(result.countryCodeType)) {\n    result.targetField = result.countryCodeType;\n  }\n  delete result.countryCodeType;\n\n  return result;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/hooks/useLoadGeoJson.ts",
    "content": "import { isString, isObject, get } from \"lodash\";\nimport { useState, useEffect } from \"react\";\nimport axios from \"axios\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\nimport createReferenceCountingCache from \"@/lib/referenceCountingCache\";\n\nconst cache = createReferenceCountingCache();\n\nexport default function useLoadGeoJson(mapType: any) {\n  const [geoJson, setGeoJson] = useState(null);\n  const [isLoading, setIsLoading] = useState(false);\n\n  useEffect(() => {\n    const mapUrl = get(visualizationsSettings, `choroplethAvailableMaps.${mapType}.url`, undefined);\n\n    if (isString(mapUrl)) {\n      setIsLoading(true);\n      let cancelled = false;\n\n      const promise = cache.get(mapUrl, () => axios.get(mapUrl).catch(() => null));\n      promise.then(({ data }: any) => {\n        if (!cancelled) {\n          // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'object | null' is not assignable... Remove this comment to see the full error message\n          setGeoJson(isObject(data) ? data : null);\n          setIsLoading(false);\n        }\n      });\n\n      return () => {\n        cancelled = true;\n        cache.release(mapUrl);\n      };\n    } else {\n      setGeoJson(null);\n      setIsLoading(false);\n    }\n  }, [mapType]);\n\n  return [geoJson, isLoading];\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/index.ts",
    "content": "import getOptions from \"./getOptions\";\nimport Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nexport default {\n  type: \"CHOROPLETH\",\n  name: \"Map (Choropleth)\",\n  getOptions,\n  Renderer,\n  Editor,\n\n  defaultColumns: 6,\n  defaultRows: 8,\n  minColumns: 2,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/maps/convert-projection.ts",
    "content": "// This helper converts USA map from Mercator projection to Albers (USA)\n// Usage: `node convert-projection.js > usa-albers.geo.json`\n\nconst { each, map, filter } = require(\"lodash\");\n// @ts-expect-error ts-migrate(2403) FIXME: Subsequent variable declarations must have the sam... Remove this comment to see the full error message\nconst d3 = require(\"d3\");\n\nconst albersUSA = d3.geo.albersUsa();\nconst mercator = d3.geo.mercator();\n\nconst geojson = require(\"./usa.geo.json\");\n\nfunction convertPoint(coord: any) {\n  const pt = albersUSA(coord);\n  return pt ? mercator.invert(pt) : null;\n}\n\nfunction convertLineString(points: any) {\n  return filter(map(points, convertPoint));\n}\n\nfunction convertPolygon(polygon: any) {\n  return map(polygon, convertLineString);\n}\n\nfunction convertMultiPolygon(multiPolygon: any) {\n  return map(multiPolygon, convertPolygon);\n}\n\neach(geojson.features, (feature: any) => {\n  switch (feature.geometry.type) {\n    case \"Polygon\":\n      feature.geometry.coordinates = convertPolygon(feature.geometry.coordinates);\n      break;\n    case \"MultiPolygon\":\n      feature.geometry.coordinates = convertMultiPolygon(feature.geometry.coordinates);\n      break;\n  }\n});\n\nconsole.log(JSON.stringify(geojson));\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/maps/countries.geo.json",
    "content": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Belize\",\"name\":\"Belize\",\"name_long\":\"Belize\",\"iso_a2\":\"BZ\",\"iso_a3\":\"BLZ\",\"iso_n3\":\"084\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-89.14308041050332,17.80831899664932],[-89.15090938999553,17.95546763760042],[-89.02985734735182,18.001511338772488],[-88.84834387892661,17.883198147040233],[-88.49012285027935,18.486830552641603],[-88.3000310940937,18.4999822046599],[-88.29633622918482,18.35327281338327],[-88.10681291375437,18.348673610909287],[-88.1234785631685,18.07667470954101],[-88.2853549873228,17.644142971258034],[-88.19786678745265,17.489475409408456],[-88.30264075392444,17.131693630435663],[-88.23951799187991,17.036066392479555],[-88.35542822951057,16.530774237529627],[-88.55182451043585,16.265467434143147],[-88.73243364129594,16.233634751851355],[-88.93061275913527,15.887273464415074],[-89.22912167026928,15.88693756760517],[-89.15080603713095,17.015576687075836],[-89.14308041050332,17.80831899664932]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Can.\",\"name\":\"Canada\",\"name_long\":\"Canada\",\"iso_a2\":\"CA\",\"iso_a3\":\"CAN\",\"iso_n3\":\"124\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-63.6645,46.55001],[-62.9393,46.41587],[-62.01208,46.44314],[-62.50391,46.03339],[-62.87433,45.96818],[-64.1428,46.39265],[-64.39261,46.72747],[-64.01486,47.03601],[-63.6645,46.55001]]],[[[-61.806305,49.10506],[-62.29318,49.08717],[-63.58926,49.40069],[-64.51912,49.87304],[-64.17322,49.95718],[-62.85829,49.70641],[-61.835585,49.28855],[-61.806305,49.10506]]],[[[-123.51000158755114,48.51001089130344],[-124.0128907883995,48.370846259141416],[-125.65501277733837,48.8250045843385],[-125.95499446679275,49.179995835967645],[-126.85000443587187,49.53000031188043],[-127.0299934495444,49.81499583597008],[-128.05933630436624,49.9949590114266],[-128.44458410710217,50.539137681676124],[-128.35841365625544,50.770648098343685],[-127.3085810960299,50.55257355407195],[-126.69500097721232,50.400903225295394],[-125.75500667382319,50.29501821552938],[-125.4150015875588,49.95000051533261],[-124.92076818911934,49.475274970083404],[-123.92250870832102,49.06248362893581],[-123.51000158755114,48.51001089130344]]],[[[-56.13403581401712,50.6870097926793],[-56.795881720595276,49.81230866149096],[-56.1431050278843,50.150117499382844],[-55.471492275602934,49.93581533466846],[-55.82240108908093,49.58712860777911],[-54.935142584845664,49.31301097268684],[-54.47377539734378,49.55669118915918],[-53.476549445191324,49.24913890237405],[-53.78601375997124,48.51678050393363],[-53.086133999226256,48.687803656603535],[-52.958648240762244,48.157164211614486],[-52.64809872090419,47.5355484075755],[-53.069158291218336,46.65549876564495],[-53.52145626485304,46.61829173439483],[-54.17893551290254,46.80706574155701],[-53.961868659060485,47.62520701760192],[-54.24048214376214,47.75227936460763],[-55.4007730780115,46.884993801453135],[-55.99748084168584,46.9197203639533],[-55.29121904155278,47.389562486351],[-56.25079871278052,47.63254507098739],[-57.3252292547771,47.572807115258],[-59.26601518414677,47.603347886742505],[-59.419494188053704,47.899453843774864],[-58.796586473207405,48.25152537697949],[-59.231624518456535,48.52318838153781],[-58.39180497906523,49.12558055276418],[-57.35868974468604,50.718274034215845],[-56.73865007183201,51.28743825947853],[-55.870976935435294,51.632094224649194],[-55.406974249886616,51.58827261006573],[-55.60021826844209,51.31707469339793],[-56.13403581401712,50.6870097926793]]],[[[-133.1800040417117,54.16997549093531],[-132.71000788443132,54.040009315423525],[-131.74998958400326,54.12000438090922],[-132.049480347351,52.984621487024526],[-131.1790425218266,52.180432847698285],[-131.57782954982292,52.18237071390925],[-132.18042842677855,52.639707139692405],[-132.54999243231387,53.100014960332146],[-133.05461117875552,53.41146881775537],[-133.2396644827927,53.8510802272624],[-133.1800040417117,54.16997549093531]]],[[[-79.26582,62.158675],[-79.65752,61.63308],[-80.09956,61.7181],[-80.36215,62.01649],[-80.315395,62.085565],[-79.92939,62.3856],[-79.52002,62.36371],[-79.26582,62.158675]]],[[[-81.89825,62.7108],[-83.06857,62.15922],[-83.77462,62.18231],[-83.99367,62.4528],[-83.25048,62.91409],[-81.87699,62.90458],[-81.89825,62.7108]]],[[[-85.16130794954985,65.65728465439281],[-84.97576371940596,65.217518215589],[-84.4640120104195,65.37177236598018],[-83.88262630891975,65.10961782496355],[-82.78757687043877,64.76669302027469],[-81.64201371939254,64.45513580998696],[-81.55344031444425,63.979609280037145],[-80.81736121287886,64.05748566350101],[-80.10345130076661,63.725981350348604],[-80.99101986359568,63.41124603947497],[-82.54717810741701,63.65172231714524],[-83.10879757356506,64.10187571883972],[-84.10041663281388,63.56971181909802],[-85.52340471061902,63.05237905542409],[-85.86676876498237,63.637252916103556],[-87.22198320183674,63.541238104905226],[-86.35275977247127,64.03583323837071],[-86.22488644076513,64.82291697860826],[-85.88384782585487,65.73877838811705],[-85.16130794954985,65.65728465439281]]],[[[-75.86588,67.14886],[-76.98687,67.09873],[-77.2364,67.58809],[-76.81166,68.14856],[-75.89521,68.28721],[-75.1145,68.01036],[-75.10333,67.58202],[-75.21597,67.44425],[-75.86588,67.14886]]],[[[-95.64768120380052,69.10769035832178],[-96.2695212038006,68.75704035832175],[-97.61740120380057,69.0600303583218],[-98.43180120380052,68.9507003583218],[-99.79740120380053,69.4000303583218],[-98.91740120380055,69.7100303583218],[-98.2182612038005,70.14354035832176],[-97.15740120380056,69.86003035832181],[-96.55740120380054,69.68003035832176],[-96.25740120380053,69.49003035832177],[-95.64768120380052,69.10769035832178]]],[[[-90.5471,69.49766],[-90.55151,68.47499],[-89.21515,69.25873],[-88.01966,68.61508],[-88.31749,67.87338],[-87.35017,67.19872],[-86.30607,67.92146],[-85.57664,68.78456],[-85.52197,69.88211],[-84.10081,69.80539],[-82.62258,69.65826],[-81.28043,69.16202],[-81.2202,68.66567],[-81.96436,68.13253],[-81.25928,67.59716],[-81.38653,67.11078],[-83.34456,66.41154],[-84.73542,66.2573],[-85.76943,66.55833],[-86.0676,66.05625],[-87.03143,65.21297],[-87.32324,64.77563],[-88.48296,64.09897],[-89.91444,64.03273],[-90.70398,63.61017],[-90.77004,62.96021],[-91.93342,62.83508],[-93.15698,62.02469],[-94.24153,60.89865],[-94.62931,60.11021],[-94.6846,58.94882],[-93.21502,58.78212],[-92.76462,57.84571],[-92.29703,57.08709],[-90.89769,57.28468],[-89.03953,56.85172],[-88.03978,56.47162],[-87.32421,55.99914],[-86.07121,55.72383],[-85.01181,55.3026],[-83.36055,55.24489],[-82.27285,55.14832],[-82.4362,54.28227],[-82.12502,53.27703],[-81.40075,52.15788],[-79.91289,51.20842],[-79.14301,51.53393],[-78.60191,52.56208],[-79.12421,54.14145],[-79.82958,54.66772],[-78.22874,55.13645],[-77.0956,55.83741],[-76.54137,56.53423],[-76.62319,57.20263],[-77.30226,58.05209],[-78.51688,58.80458],[-77.33676,59.85261],[-77.77272,60.75788],[-78.10687,62.31964],[-77.41067,62.55053],[-75.69621,62.2784],[-74.6682,62.18111],[-73.83988,62.4438],[-72.90853,62.10507],[-71.67708,61.52535],[-71.37369,61.13717],[-69.59042,61.06141],[-69.62033,60.22125],[-69.2879,58.95736],[-68.37455,58.80106],[-67.64976,58.21206],[-66.20178,58.76731],[-65.24517,59.87071],[-64.58352,60.33558],[-63.80475,59.4426],[-62.50236,58.16708],[-61.39655,56.96745],[-61.79866,56.33945],[-60.46853,55.77548],[-59.56962,55.20407],[-57.97508,54.94549],[-57.3332,54.6265],[-56.93689,53.78032],[-56.15811,53.64749],[-55.75632,53.27036],[-55.68338,52.14664],[-56.40916,51.7707],[-57.12691,51.41972],[-58.77482,51.0643],[-60.03309,50.24277],[-61.72366,50.08046],[-63.86251,50.29099],[-65.36331,50.2982],[-66.39905,50.22897],[-67.23631,49.51156],[-68.51114,49.06836],[-69.95362,47.74488],[-71.10458,46.82171],[-70.25522,46.98606],[-68.65,48.3],[-66.55243,49.1331],[-65.05626,49.23278],[-64.17099,48.74248],[-65.11545,48.07085],[-64.79854,46.99297],[-64.47219,46.23849],[-63.17329,45.73902],[-61.52072,45.88377],[-60.51815,47.00793],[-60.4486,46.28264],[-59.80287,45.9204],[-61.03988,45.26525],[-63.25471,44.67014],[-64.24656,44.26553],[-65.36406,43.54523],[-66.1234,43.61867],[-66.16173,44.46512],[-64.42549,45.29204],[-66.02605,45.25931],[-67.13741,45.13753],[-67.79134,45.70281],[-67.79046,47.06636],[-68.23444,47.35486],[-68.905,47.185],[-69.237216,47.447781],[-69.99997,46.69307],[-70.305,45.915],[-70.66,45.46],[-71.08482,45.30524],[-71.405,45.255],[-71.50506,45.0082],[-73.34783,45.00738],[-74.867,45.00048],[-75.31821,44.81645],[-76.375,44.09631],[-76.5,44.01845889375872],[-76.82003414580558,43.628784288093755],[-77.7378850979577,43.629055589363304],[-78.72027991404238,43.625089423184875],[-79.17167355011188,43.46633942318422],[-79.01,43.27],[-78.92,42.965],[-78.9393621487437,42.86361135514804],[-80.24744767934794,42.36619985612259],[-81.27774654816716,42.20902598730686],[-82.43927771679162,41.675105088867156],[-82.69008928092018,41.675105088867156],[-83.02981014680694,41.83279572200584],[-83.14199968131256,41.975681057292825],[-83.12,42.08],[-82.9,42.43],[-82.43,42.98],[-82.1376423815039,43.571087551439916],[-82.33776312543108,44.44],[-82.55092464875818,45.347516587905375],[-83.59285071484308,45.81689362241237],[-83.46955074739463,45.99468638771259],[-83.61613094759059,46.11692698829907],[-83.89076534700575,46.11692698829907],[-84.09185126416148,46.275418606138174],[-84.14211951367338,46.51222585711574],[-84.3367,46.40877],[-84.6049,46.4396],[-84.54374874544587,46.538684190449146],[-84.77923824739992,46.637101955749046],[-84.87607988151485,46.90008331968238],[-85.65236324740343,47.22021881773051],[-86.46199083122826,47.55333801939204],[-87.43979262330024,47.94],[-88.37811418328673,48.302917588893735],[-89.27291744663668,48.019808254582664],[-89.6,48.01],[-90.83,48.27],[-91.64,48.14],[-92.61,48.45],[-93.63087,48.60926],[-94.32914,48.67074],[-94.64,48.84],[-94.81758,49.38905],[-95.15609,49.38425],[-95.15906950917204,49],[-97.22872000000481,49.0007],[-100.65,49],[-104.04826,48.99986],[-107.05,49],[-110.05,49],[-113,49],[-116.04818,49],[-117.03121,49],[-120,49],[-122.84,49],[-122.97421,49.0025377777778],[-124.91024,49.98456],[-125.62461,50.41656],[-127.43561,50.83061],[-127.99276,51.71583],[-127.85032,52.32961],[-129.12979,52.75538],[-129.30523,53.56159],[-130.51497,54.28757],[-130.53611,54.80278],[-129.98,55.285],[-130.00778,55.91583],[-131.70781,56.55212],[-132.73042,57.69289],[-133.35556,58.41028],[-134.27111,58.86111],[-134.945,59.27056],[-135.47583,59.78778],[-136.47972,59.46389],[-137.4525,58.905],[-138.34089,59.56211],[-139.039,60],[-140.013,60.27682],[-140.99778,60.30639],[-140.9925,66.00003],[-140.986,69.712],[-139.12052,69.47102],[-137.54636,68.99002],[-136.50358,68.89804],[-135.62576,69.31512],[-134.41464,69.62743],[-132.92925,69.50534],[-131.43136,69.94451],[-129.79471,70.19369],[-129.10773,69.77927],[-128.36156,70.01286],[-128.13817,70.48384],[-127.44712,70.37721],[-125.75632,69.48058],[-124.42483,70.1584],[-124.28968,69.39969],[-123.06108,69.56372],[-122.6835,69.85553],[-121.47226,69.79778],[-119.94288,69.37786],[-117.60268,69.01128],[-116.22643,68.84151],[-115.2469,68.90591],[-113.89794,68.3989],[-115.30489,67.90261],[-113.49727,67.68815],[-110.798,67.80612],[-109.94619,67.98104],[-108.8802,67.38144],[-107.79239,67.88736],[-108.81299,68.31164],[-108.16721,68.65392],[-106.95,68.7],[-106.15,68.8],[-105.34282,68.56122],[-104.33791,68.018],[-103.22115,68.09775],[-101.45433,67.64689],[-99.90195,67.80566],[-98.4432,67.78165],[-98.5586,68.40394],[-97.66948,68.57864],[-96.11991,68.23939],[-96.12588,67.29338],[-95.48943,68.0907],[-94.685,68.06383],[-94.23282,69.06903],[-95.30408,69.68571],[-96.47131,70.08976],[-96.39115,71.19482],[-95.2088,71.92053],[-93.88997,71.76015],[-92.87818,71.31869],[-91.51964,70.19129],[-92.40692,69.69997],[-90.5471,69.49766]]],[[[-114.1671699999999,73.12145],[-114.66634,72.65277],[-112.44101999999988,72.95540000000011],[-111.05039,72.4504],[-109.92034999999989,72.96113],[-109.00654,72.63335],[-108.18835,71.65089],[-107.68599,72.06548],[-108.39639,73.08953000000011],[-107.51645,73.23598],[-106.52259,73.07601],[-105.40246,72.67259],[-104.77484,71.6984],[-104.46475999999984,70.99297],[-102.78537,70.49776],[-100.9807799999999,70.02432],[-101.08929,69.58447000000012],[-102.73116,69.50402],[-102.09329,69.11962000000011],[-102.43024,68.75282],[-104.24,68.91],[-105.96,69.18000000000015],[-107.12254,69.11922],[-109,68.78],[-111.53414887520013,68.63005915681794],[-113.3132,68.53554],[-113.85495999999983,69.00744000000012],[-115.22,69.28],[-116.10794,69.16821],[-117.34,69.96000000000012],[-116.67472999999988,70.06655],[-115.13112,70.2373],[-113.72141,70.19237],[-112.4161,70.36638],[-114.35,70.6],[-116.48684,70.52045],[-117.9048,70.54056000000014],[-118.43238,70.9092],[-116.11311,71.30918],[-117.65568,71.2952],[-119.40199,71.55859],[-118.56267,72.30785],[-117.86642,72.70594],[-115.18909,73.31459000000012],[-114.1671699999999,73.12145]]],[[[-104.5,73.42],[-105.38,72.76],[-106.94,73.46],[-106.6,73.6],[-105.26,73.64],[-104.5,73.42]]],[[[-76.34,73.10268498995302],[-76.25140380859375,72.82638549804688],[-77.31443786621091,72.85554504394527],[-78.39167022705081,72.87665557861328],[-79.48625183105466,72.74220275878909],[-79.77583312988284,72.80290222167974],[-80.87609863281253,73.3331832885742],[-80.83388519287105,73.69318389892578],[-80.35305786132812,73.75971984863278],[-78.06443786621094,73.65193176269534],[-76.34,73.10268498995302]]],[[[-86.56217851433414,73.15744700793846],[-85.77437130404454,72.53412588163383],[-84.85011247428824,73.34027822538712],[-82.31559017610098,73.75095083281059],[-80.60008765330764,72.71654368762421],[-80.7489416165244,72.06190664335077],[-78.77063859731078,72.35217316353416],[-77.82462398955958,72.74961660429105],[-75.60584469267573,72.24367849393741],[-74.22861609566499,71.7671442735579],[-74.09914079455771,71.33084015571765],[-72.24222571479766,71.5569245469945],[-71.20001542833519,70.92001251899723],[-68.7860542466849,70.52502370877426],[-67.91497046575694,70.12194753689761],[-66.96903337265417,69.18608734809189],[-68.80512285020055,68.72019847276442],[-66.44986609563387,68.06716339789202],[-64.86231441919522,67.84753856065163],[-63.42493445499676,66.92847321234066],[-61.85198137068058,66.86212067327784],[-62.1631768459423,66.16025136988961],[-63.918444383384184,64.99866852483284],[-65.14886023625363,65.42603261988668],[-66.72121904159854,66.3880410834322],[-68.01501603867396,66.26272573512439],[-68.14128740097917,65.68978913030438],[-67.08964616562339,65.108455105237],[-65.73208045109976,64.64840566675863],[-65.32016760930128,64.38273712834606],[-64.66940629744968,63.39292674422748],[-65.01380388045891,62.67418508569599],[-66.27504472519047,62.945098781986076],[-68.78318620469273,63.74567007105181],[-67.36968075221304,62.883965562584876],[-66.3282972886672,62.280074774822054],[-66.16556820338016,61.93089712182589],[-68.87736650254465,62.33014923771282],[-71.02343705919384,62.91070811629584],[-72.235378587519,63.39783600529517],[-71.8862784491713,63.67998932560885],[-73.37830624051838,64.19396312118383],[-74.8344189114226,64.67907562932379],[-74.81850257027673,64.38909332951798],[-77.70997982452005,64.22954234481679],[-78.55594885935417,64.57290639918014],[-77.89728105336192,65.30919220647479],[-76.0182742987972,65.32696889918316],[-73.95979529488272,65.45476471624089],[-74.29388342964964,65.8117713487294],[-73.94491248238265,66.31057811142672],[-72.65116716173941,67.28457550726387],[-72.92605994331609,67.72692576768239],[-73.31161780464575,68.06943716091291],[-74.84330725777681,68.55462718370129],[-76.86910091826674,68.89473562283027],[-76.22864905465735,69.14776927354742],[-77.28736996123712,69.76954010688328],[-78.1686339993266,69.82648753526891],[-78.95724219431673,70.16688019477542],[-79.49245500356366,69.87180776638891],[-81.30547095409176,69.74318512641435],[-84.94470618359847,69.9666340196444],[-87.06000342481789,70.26000112576537],[-88.6817132230015,70.41074127876081],[-89.51341956252304,70.76203766548099],[-88.46772111688075,71.21818553332133],[-89.8881512112875,71.22255219184996],[-90.20516028518202,72.2350743679608],[-89.43657670770494,73.12946421985237],[-88.40824154331281,73.53788890247121],[-85.82615108920092,73.80381582304521],[-86.56217851433414,73.15744700793846]]],[[[-100.35642,73.84389],[-99.16387,73.63339],[-97.38,73.76],[-97.12,73.47],[-98.05359,72.99052],[-96.54,72.56],[-96.72,71.66],[-98.35966,71.27285],[-99.32286,71.35639],[-100.01482,71.73827],[-102.5,72.51],[-102.48,72.83],[-100.43836,72.70588],[-101.54,73.36],[-100.35642,73.84389]]],[[[-93.19629553910022,72.77199249947336],[-94.26904659704726,72.02459625923598],[-95.40985551632266,72.06188080513459],[-96.03374508338246,72.94027680123182],[-96.01826799191099,73.4374299180958],[-95.49579342322403,73.86241689726418],[-94.50365759965234,74.1349067247392],[-92.42001217321177,74.10002513294219],[-90.50979285354259,73.85673248971203],[-92.0039652168299,72.9662442084585],[-93.19629553910022,72.77199249947336]]],[[[-120.46,71.38360179308759],[-123.09219,70.90164],[-123.62,71.34],[-125.92894873747335,71.86868846301141],[-125.5,72.29226081179502],[-124.80729,73.02256],[-123.9399999999999,73.68000000000015],[-124.91775,74.29275000000013],[-121.53788,74.44893],[-120.10978,74.24135],[-117.55563999999987,74.18577],[-116.58442,73.89607],[-115.51081,73.47519],[-116.76793999999988,73.22292],[-119.22,72.52],[-120.46,71.82],[-120.46,71.38360179308759]]],[[[-93.61275590694049,74.97999726022445],[-94.15690873897384,74.59234650338686],[-95.60868058956561,74.66686391875177],[-96.82093217648458,74.92762319609658],[-96.2885874092298,75.37782827422335],[-94.85081987178913,75.6472175157609],[-93.97774654821794,75.29648956979597],[-93.61275590694049,74.97999726022445]]],[[[-98.5,76.72],[-97.735585,76.25656],[-97.704415,75.74344],[-98.16,75],[-99.80874,74.89744],[-100.88366,75.05736],[-100.86292,75.64075],[-102.50209,75.5638],[-102.56552,76.3366],[-101.48973,76.30537],[-99.98349,76.64634],[-98.57699,76.58859],[-98.5,76.72]]],[[[-108.21141,76.20168],[-107.81943,75.84552],[-106.92893,76.01282],[-105.881,75.9694],[-105.70498,75.47951],[-106.31347,75.00527],[-109.7,74.85],[-112.22307,74.41696],[-113.74381,74.39427],[-113.87135,74.72029],[-111.79421,75.1625],[-116.31221,75.04343],[-117.7104,75.2222],[-116.34602,76.19903],[-115.40487,76.47887],[-112.59056,76.14134],[-110.81422,75.54919],[-109.0671,75.47321],[-110.49726,76.42982],[-109.5811,76.79417],[-108.54859,76.67832],[-108.21141,76.20168]]],[[[-94.68408586299947,77.09787832305838],[-93.57392106807313,76.77629588490609],[-91.60502315953661,76.77851797149461],[-90.74184587274922,76.44959747995681],[-90.96966142450799,76.07401317005946],[-89.82223792189927,75.84777374948563],[-89.18708289259979,75.61016551380763],[-87.83827633334963,75.56618886992723],[-86.37919226758868,75.48242137318218],[-84.78962521029061,75.69920400664651],[-82.75344458691006,75.78431509063125],[-81.12853084992437,75.71398346628203],[-80.05751095245915,75.33684886341588],[-79.83393286814832,74.92312734648719],[-80.45777075877584,74.65730377877779],[-81.94884253612554,74.44245901152433],[-83.22889360221143,74.56402781849096],[-86.0974523587333,74.41003205026115],[-88.15035030796022,74.39230703398499],[-89.76472205275837,74.51555532500115],[-92.42244096552943,74.837757880341],[-92.7682854886428,75.38681997344216],[-92.88990597204173,75.88265534128266],[-93.893824022176,76.31924367950054],[-95.96245744503582,76.44138092722247],[-97.12137895382949,76.75107778594761],[-96.74512285031236,77.16138865834515],[-94.68408586299947,77.09787832305838]]],[[[-116.19858659550734,77.64528677032621],[-116.33581336145838,76.87696157501055],[-117.10605058476878,76.53003184681913],[-118.04041215703813,76.4811717800871],[-119.89931758688569,76.05321340606199],[-121.4999950771265,75.9000186225328],[-122.85492448615896,76.11654287383568],[-122.8549252936032,76.11654287383568],[-121.15753536032825,76.86450755482835],[-119.10393897182104,77.51221995717464],[-117.57013078496597,77.4983189968881],[-116.19858659550734,77.64528677032621]]],[[[-93.84000301794399,77.5199972602345],[-94.29560828324526,77.4913426785287],[-96.16965410031008,77.5551113959769],[-96.43630449093612,77.83462921824362],[-94.42257727738638,77.82000478790499],[-93.72065629756588,77.63433136668033],[-93.84000301794399,77.5199972602345]]],[[[-110.18693803591297,77.6970148790503],[-112.05119116905848,77.40922882761686],[-113.53427893761906,77.73220652944116],[-112.72458675825384,78.05105011668195],[-111.26444332563085,78.15295604116156],[-109.8544518705471,77.99632477488484],[-110.18693803591297,77.6970148790503]]],[[[-109.66314571820259,78.60197256134569],[-110.88131425661886,78.40691986766001],[-112.54209143761517,78.4079017198735],[-112.5258908760916,78.55055451121522],[-111.5000103422334,78.84999359813057],[-110.96366065147602,78.80444082306522],[-109.66314571820259,78.60197256134569]]],[[[-95.83029496944934,78.05694122996326],[-97.30984290239799,77.85059723582178],[-98.12428931353396,78.08285696075758],[-98.55286780474664,78.4581053738451],[-98.63198442258552,78.87193024363839],[-97.33723141151262,78.83198436147677],[-96.75439876990879,78.765812689927],[-95.55927792029458,78.41831452098029],[-95.83029496944934,78.05694122996326]]],[[[-100.06019182005214,78.3247543403159],[-99.67093909381362,77.9075446642074],[-101.30394019245301,78.01898489044481],[-102.94980872273305,78.34322866486022],[-105.17613277873154,78.38033234324574],[-104.21042945027716,78.6774201524918],[-105.41958045125854,78.91833567983645],[-105.49228919149316,79.30159393992919],[-103.52928239623793,79.16534902619165],[-100.82515804726881,78.80046173777869],[-100.06019182005214,78.3247543403159]]],[[[-87.02,79.66],[-85.81435,79.3369],[-87.18756,79.0393],[-89.03535,78.28723],[-90.80436,78.21533],[-92.87669,78.34333],[-93.95116,78.75099],[-93.93574,79.11373],[-93.14524,79.3801],[-94.974,79.37248],[-96.07614,79.70502],[-96.70972,80.15777],[-96.01644,80.60233],[-95.32345,80.90729],[-94.29843,80.97727],[-94.73542,81.20646],[-92.40984,81.25739],[-91.13289,80.72345],[-89.45,80.50932203389829],[-87.81,80.32],[-87.02,79.66]]],[[[-68.5,83.10632151676575],[-65.82735,83.02801],[-63.68,82.9],[-61.85,82.6286],[-61.89388,82.36165],[-64.334,81.92775],[-66.75342,81.72527],[-67.65755,81.50141],[-65.48031,81.50657],[-67.84,80.9],[-69.4697,80.61683],[-71.18,79.8],[-73.2428,79.63415],[-73.88,79.43016220480207],[-76.90773,79.32309],[-75.52924,79.19766],[-76.22046,79.01907],[-75.39345,78.52581],[-76.34354,78.18296],[-77.88851,77.89991],[-78.36269,77.50859],[-79.75951,77.20968],[-79.61965,76.98336],[-77.91089,77.022045],[-77.88911,76.777955],[-80.56125,76.17812],[-83.17439,76.45403],[-86.11184,76.29901],[-87.6,76.42],[-89.49068,76.47239],[-89.6161,76.95213],[-87.76739,77.17833],[-88.26,77.9],[-87.65,77.97022222222222],[-84.97634,77.53873],[-86.34,78.18],[-87.96192,78.37181],[-87.15198,78.75867],[-85.37868,78.9969],[-85.09495,79.34543],[-86.50734,79.73624],[-86.93179,80.25145],[-84.19844,80.20836],[-83.40869565217383,80.1],[-81.84823,80.46442],[-84.1,80.58],[-87.59895,80.51627],[-89.36663,80.85569],[-90.2,81.26],[-91.36786,81.5531],[-91.58702,81.89429],[-90.1,82.085],[-88.93227,82.11751],[-86.97024,82.27961],[-85.5,82.65227345805704],[-84.260005,82.6],[-83.18,82.32],[-82.42,82.86],[-81.1,83.02],[-79.30664,83.13056],[-76.25,83.1720588235294],[-75.71878,83.06404],[-72.83153,83.23324],[-70.665765,83.16978075838284],[-68.5,83.10632151676575]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Bhs.\",\"name\":\"Bahamas\",\"name_long\":\"Bahamas\",\"iso_a2\":\"BS\",\"iso_a3\":\"BHS\",\"iso_n3\":\"044\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-77.53466,23.75975],[-77.78,23.71],[-78.03405,24.28615],[-78.40848,24.57564],[-78.19087,25.2103],[-77.89,25.17],[-77.54,24.34],[-77.53466,23.75975]]],[[[-77.82,26.58],[-78.91,26.42],[-78.98,26.79],[-78.51,26.87],[-77.85,26.84],[-77.82,26.58]]],[[[-77,26.59],[-77.17255,25.87918],[-77.35641,26.00735],[-77.34,26.53],[-77.78802,26.92516],[-77.79,27.04],[-77,26.59]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"C.R.\",\"name\":\"Costa Rica\",\"name_long\":\"Costa Rica\",\"iso_a2\":\"CR\",\"iso_a3\":\"CRI\",\"iso_n3\":\"188\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-82.96578304719736,8.225027980985985],[-83.50843726269431,8.446926581247283],[-83.71147396516908,8.656836249216866],[-83.59631303580665,8.830443223501419],[-83.63264156770784,9.051385809765321],[-83.90988562695372,9.29080272057358],[-84.30340165885636,9.487354030795714],[-84.64764421256866,9.615537421095707],[-84.71335079622777,9.908051866083852],[-84.97566036654133,10.086723130733006],[-84.91137488477024,9.795991522658923],[-85.11092342806532,9.55703969974131],[-85.33948828809227,9.83454214114866],[-85.66078650586698,9.933347479690724],[-85.79744483106285,10.134885565629034],[-85.79170874707843,10.439337266476613],[-85.65931372754666,10.75433095951172],[-85.94172543002176,10.895278428587801],[-85.7125404528073,11.088444932494824],[-85.56185197624418,11.217119248901597],[-84.90300330273895,10.952303371621896],[-84.67306901725627,11.082657172078143],[-84.35593075228104,10.999225572142905],[-84.19017859570485,10.793450018756674],[-83.89505449088595,10.726839097532446],[-83.65561174186158,10.938764146361422],[-83.40231970898296,10.395438137244652],[-83.01567664257517,9.992982082555555],[-82.54619625520348,9.566134751824677],[-82.93289099804358,9.476812038608173],[-82.92715491405916,9.074330145702916],[-82.71918311230053,8.925708726431495],[-82.86865719270477,8.807266343618522],[-82.82977067740516,8.62629547773237],[-82.91317643912421,8.42351715741907],[-82.96578304719736,8.225027980985985]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Grlnd.\",\"name\":\"Greenland\",\"name_long\":\"Greenland\",\"iso_a2\":\"GL\",\"iso_a3\":\"GRL\",\"iso_n3\":\"304\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-46.76379,82.62796],[-43.40644,83.22516],[-39.89753,83.18018],[-38.62214,83.54905],[-35.08787,83.64513],[-27.10046,83.51966],[-20.84539,82.72669],[-22.69182,82.34165],[-26.51753,82.29765],[-31.9,82.2],[-31.39646,82.02154],[-27.85666,82.13178],[-24.84448,81.78697],[-22.90328,82.09317],[-22.07175,81.73449],[-23.16961,81.15271],[-20.62363,81.52462],[-15.76818,81.91245],[-12.77018,81.71885],[-12.20855,81.29154],[-16.28533,80.58004],[-16.85,80.35],[-20.04624,80.17708],[-17.73035,80.12912],[-18.9,79.4],[-19.70499,78.75128],[-19.67353,77.63859],[-18.47285,76.98565],[-20.03503,76.94434],[-21.67944,76.62795],[-19.83407,76.09808],[-19.59896,75.24838],[-20.66818,75.15585],[-19.37281,74.29561],[-21.59422,74.22382],[-20.43454,73.81713],[-20.76234,73.46436],[-22.17221,73.30955],[-23.56593,73.30663],[-22.31311,72.62928],[-22.29954,72.18409],[-24.27834,72.59788],[-24.79296,72.3302],[-23.44296,72.08016],[-22.13281,71.46898],[-21.75356,70.66369],[-23.53603,70.471],[-24.30702,70.85649],[-25.54341,71.43094],[-25.20135,70.75226],[-26.36276,70.22646],[-23.72742,70.18401],[-22.34902,70.12946],[-25.02927,69.2588],[-27.74737,68.47046],[-30.67371,68.12503],[-31.77665,68.12078],[-32.81105,67.73547],[-34.20196,66.67974],[-36.35284,65.9789],[-37.04378,65.93768],[-38.37505,65.69213],[-39.81222,65.45848],[-40.66899,64.83997],[-40.68281,64.13902],[-41.1887,63.48246],[-42.81938,62.68233],[-42.41666,61.90093],[-42.86619,61.07404],[-43.3784,60.09772],[-44.7875,60.03676],[-46.26364,60.85328],[-48.26294,60.85843],[-49.23308,61.40681],[-49.90039,62.38336],[-51.63325,63.62691],[-52.14014,64.27842],[-52.27659,65.1767],[-53.66166,66.09957],[-53.30161,66.8365],[-53.96911,67.18899],[-52.9804,68.35759],[-51.47536,68.72958],[-51.08041,69.14781],[-50.87122,69.9291],[-52.013585,69.574925],[-52.55792,69.42616],[-53.45629,69.283625],[-54.68336,69.61003],[-54.75001,70.28932],[-54.35884,70.821315],[-53.431315,70.835755],[-51.39014,70.56978],[-53.10937,71.20485],[-54.00422,71.54719],[-55,71.40653696727257],[-55.83468,71.65444],[-54.71819,72.58625],[-55.32634,72.95861],[-56.12003,73.64977],[-57.32363,74.71026],[-58.59679,75.09861],[-58.58516,75.51727],[-61.26861,76.10238],[-63.39165,76.1752],[-66.06427,76.13486],[-68.50438,76.06141],[-69.66485,76.37975],[-71.40257,77.00857],[-68.77671,77.32312],[-66.76397,77.37595],[-71.04293,77.63595],[-73.297,78.04419],[-73.15938,78.43271],[-69.37345,78.91388],[-65.7107,79.39436],[-65.3239,79.75814],[-68.02298,80.11721],[-67.15129,80.51582],[-63.68925,81.21396],[-62.23444,81.3211],[-62.65116,81.77042],[-60.28249,82.03363],[-57.20744,82.19074],[-54.13442,82.19962],[-53.04328,81.88833],[-50.39061,82.43883],[-48.00386,82.06481],[-46.59984,81.985945],[-44.523,81.6607],[-46.9007,82.19979],[-46.76379,82.62796]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Dom. Rep.\",\"name\":\"Dominican Rep.\",\"name_long\":\"Dominican Republic\",\"iso_a2\":\"DO\",\"iso_a3\":\"DOM\",\"iso_n3\":\"214\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-71.71236141629296,19.714455878167357],[-71.58730445014663,19.8849105900821],[-70.80670610216174,19.880285549391985],[-70.21436499701613,19.62288524014616],[-69.95081519232758,19.647999986240006],[-69.76925004747008,19.293267116772437],[-69.22212582057988,19.313214219637103],[-69.25434607611385,19.015196234609874],[-68.80941199408083,18.979074408437853],[-68.31794328476897,18.612197577381693],[-68.68931596543452,18.205142320218613],[-69.16494584824892,18.42264842373511],[-69.62398759629764,18.380712998930246],[-69.95293392605154,18.42830699307106],[-70.1332329983179,18.245915025296895],[-70.51713721381422,18.184290879788833],[-70.66929846869763,18.426885891183034],[-70.99995012071719,18.283328762276213],[-71.4002099270339,17.5985643579766],[-71.65766191271202,17.7575727401387],[-71.70830481635805,18.04499705654609],[-71.68773759630587,18.31666006110447],[-71.94511206733556,18.61690013272026],[-71.70130265978248,18.78541697842405],[-71.62487321642283,19.169837958243306],[-71.71236141629296,19.714455878167357]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Cuba\",\"name\":\"Cuba\",\"name_long\":\"Cuba\",\"iso_a2\":\"CU\",\"iso_a3\":\"CUB\",\"iso_n3\":\"192\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-82.26815121125706,23.188610744717703],[-81.40445716014683,23.11727142993878],[-80.6187686835812,23.105980129483],[-79.67952368846025,22.76530324959883],[-79.28148596873207,22.399201565027056],[-78.34743445505649,22.512166246017088],[-77.99329586456028,22.277193508385935],[-77.14642249216105,21.657851467367834],[-76.52382483590856,21.206819566324373],[-76.19462012399319,21.220565497314013],[-75.59822241891267,21.016624457274133],[-75.67106035022806,20.735091254148],[-74.9338960435845,20.693905137611385],[-74.17802486845126,20.28462779385974],[-74.29664811877724,20.05037852628068],[-74.96159461129294,19.92343537035569],[-75.63468014189459,19.873774318923196],[-76.323656175426,19.95289093676206],[-77.75548092315306,19.855480861891873],[-77.08510840524674,20.413353786698792],[-77.49265458851661,20.67310537361389],[-78.13729224314159,20.73994883878343],[-78.48282670766119,21.02861338956585],[-78.71986650258401,21.598113511638434],[-79.28499996612794,21.5591753199065],[-80.21747534861865,21.827324327069036],[-80.51753455272141,22.03707896574176],[-81.82094336620318,22.19205658618507],[-82.16999182811864,22.38710927987075],[-81.79500179719267,22.636964830001958],[-82.77589799674084,22.688150336187064],[-83.49445878775936,22.16851797127613],[-83.90880042187563,22.154565334557333],[-84.05215084505326,21.910575059491254],[-84.54703019889638,21.801227728761642],[-84.97491105827311,21.89602814380109],[-84.44706214062776,22.204949856041907],[-84.23035702181178,22.565754706303764],[-83.7782399156902,22.788118394455694],[-83.26754757356575,22.983041897060644],[-82.51043616405751,23.078746649665188],[-82.26815121125706,23.188610744717703]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Hond.\",\"name\":\"Honduras\",\"name_long\":\"Honduras\",\"iso_a2\":\"HN\",\"iso_a3\":\"HND\",\"iso_n3\":\"340\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-87.31665442579549,12.984685777229004],[-87.48940873894713,13.297534898323931],[-87.79311113152653,13.384480495655168],[-87.72350297722932,13.785050360565606],[-87.85951534702161,13.893312486217097],[-88.06534257684012,13.96462596277979],[-88.50399797234962,13.845485948130943],[-88.54123084181595,13.980154730683523],[-88.84307288283276,14.140506700085211],[-89.05851192905766,14.340029405164215],[-89.35332597528281,14.424132798719086],[-89.14553504103719,14.678019110569153],[-89.22522009963124,14.874286200413678],[-89.15481096063353,15.066419175674866],[-88.6806796943556,15.34624705653539],[-88.22502275262195,15.72772247971403],[-88.12115312371537,15.688655096901359],[-87.90181250685241,15.864458319558196],[-87.61568010125234,15.8787985295192],[-87.52292090528846,15.797278957578783],[-87.36776241733213,15.84694000901129],[-86.90319129102818,15.756712958229569],[-86.44094560417739,15.78283539475319],[-86.11923397494434,15.893448798073962],[-86.00195431185784,16.00540578863439],[-85.68331743034628,15.953651841693953],[-85.44400387240256,15.885749009662446],[-85.18244361035721,15.90915843349063],[-84.98372188997882,15.995923163308701],[-84.52697974316715,15.857223619037427],[-84.36825558138258,15.835157782448732],[-84.06305457226682,15.648244126849136],[-83.77397661002612,15.42407176356687],[-83.41038123242036,15.270902818253774],[-83.14721900097413,14.99582916916421],[-83.48998877636602,15.016267198135663],[-83.62858496777288,14.880073960830371],[-83.97572140169359,14.749435939996486],[-84.22834164095241,14.74876414637663],[-84.4493359036486,14.621614284722511],[-84.64958207877963,14.666805324761867],[-84.8200367906943,14.81958669683263],[-84.92450069857233,14.790492865452336],[-85.05278744173688,14.551541042534723],[-85.14875057650288,14.560196844943619],[-85.16536454948482,14.35436961512505],[-85.51441301140028,14.079011745657908],[-85.69866533073696,13.960078436738002],[-85.8012947252685,13.836054999237604],[-86.09626380079061,14.038187364147234],[-86.31214209668985,13.771356106008225],[-86.52070817741992,13.778487453664468],[-86.75508663607962,13.75484548589094],[-86.73382178419149,13.263092556201398],[-86.88055701368438,13.254204209847217],[-87.00576900912743,13.025794379117258],[-87.31665442579549,12.984685777229004]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Guat.\",\"name\":\"Guatemala\",\"name_long\":\"Guatemala\",\"iso_a2\":\"GT\",\"iso_a3\":\"GTM\",\"iso_n3\":\"320\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-90.09555457229098,13.735337632700734],[-90.60862403030085,13.909771429901951],[-91.23241024449605,13.927832342987957],[-91.68974667027912,14.126218166556455],[-92.22775000686983,14.538828640190928],[-92.20322953974731,14.830102850804069],[-92.08721594925207,15.06458466232844],[-92.22924862340628,15.25144664149586],[-91.74796017125591,16.066564846251723],[-90.46447262242265,16.069562079324655],[-90.43886695022204,16.410109768128095],[-90.60084672724092,16.47077789963876],[-90.71182186558772,16.687483018454728],[-91.08167009150065,16.918476670799404],[-91.45392127151516,17.252177232324172],[-91.0022692532842,17.25465770107418],[-91.00151994501596,17.81759491624571],[-90.06793351923098,17.819326076727474],[-89.14308041050332,17.80831899664932],[-89.15080603713095,17.015576687075836],[-89.22912167026928,15.88693756760517],[-88.93061275913527,15.887273464415074],[-88.60458614780583,15.70638011317736],[-88.51836402052686,15.855389105690975],[-88.22502275262202,15.727722479713902],[-88.68067969435563,15.346247056535304],[-89.15481096063357,15.06641917567481],[-89.22522009963127,14.874286200413621],[-89.14553504103718,14.678019110569084],[-89.35332597528279,14.424132798719116],[-89.58734269891654,14.362586167859488],[-89.53421932652051,14.244815578666305],[-89.72193396682073,14.134228013561694],[-90.0646779039966,13.881969509328924],[-90.09555457229098,13.735337632700734]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Haiti\",\"name\":\"Haiti\",\"name_long\":\"Haiti\",\"iso_a2\":\"HT\",\"iso_a3\":\"HTI\",\"iso_n3\":\"332\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-73.18979061551762,19.915683905511912],[-72.57967281766362,19.871500555902358],[-71.71236141629296,19.714455878167357],[-71.62487321642283,19.169837958243306],[-71.70130265978248,18.78541697842405],[-71.94511206733556,18.61690013272026],[-71.68773759630587,18.31666006110447],[-71.70830481635805,18.04499705654609],[-72.37247616238935,18.21496084235406],[-72.84441118029488,18.14561107021836],[-73.45455481636503,18.217906398994696],[-73.92243323433566,18.030992743395004],[-74.45803361682478,18.342549953682706],[-74.36992529976713,18.66490753831941],[-73.44954220243272,18.526052964751145],[-72.69493709989064,18.445799465401862],[-72.334881557897,18.668421535715254],[-72.79164954292489,19.10162506761803],[-72.78410478381028,19.48359141690341],[-73.41502234566175,19.639550889560283],[-73.18979061551762,19.915683905511912]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Mex.\",\"name\":\"Mexico\",\"name_long\":\"Mexico\",\"iso_a2\":\"MX\",\"iso_a3\":\"MEX\",\"iso_n3\":\"484\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-97.14000830767071,25.869997463478395],[-97.52807247596655,24.992144069920297],[-97.70294552284223,24.272343044526735],[-97.77604183631905,22.932579860927657],[-97.87236670611111,22.44421173755336],[-97.69904395220419,21.898689480064263],[-97.38895952023677,21.411018988525825],[-97.18933346229329,20.635433254473128],[-96.52557552772032,19.890930894444068],[-96.29212724484177,19.320371405509547],[-95.90088497595995,18.82802419684873],[-94.83906348344271,18.562717393462208],[-94.4257295397562,18.144370835843347],[-93.5486512926824,18.423836981677937],[-92.7861138577835,18.52483856859226],[-92.0373481920904,18.704569200103432],[-91.40790340855926,18.87608327888023],[-90.77186987991087,19.28412038825678],[-90.53358985061305,19.8674181177513],[-90.45147599970124,20.707521877520435],[-90.27861833368489,20.99985545499555],[-89.60132117385149,21.26172577563449],[-88.54386633986284,21.49367544197662],[-87.65841651075772,21.458845526611977],[-87.05189022494807,21.543543199138295],[-86.81198238803296,21.331514797444754],[-86.84590796583262,20.849864610268355],[-87.38329118523586,20.25540477139873],[-87.62105445021075,19.64655304613592],[-87.43675045444176,19.47240346931227],[-87.58656043165593,19.04013011319074],[-87.83719112827151,18.25981598558343],[-88.09066402866318,18.51664785407405],[-88.30003109409364,18.49998220466],[-88.4901228502793,18.48683055264172],[-88.84834387892658,17.883198147040332],[-89.02985734735176,18.00151133877256],[-89.15090938999549,17.955467637600407],[-89.14308041050333,17.808318996649405],[-90.0679335192309,17.81932607672752],[-91.00151994501596,17.817594916245696],[-91.00226925328417,17.25465770107428],[-91.45392127151511,17.252177232324186],[-91.0816700915006,16.91847667079952],[-90.71182186558764,16.687483018454767],[-90.60084672724093,16.47077789963879],[-90.438866950222,16.41010976812811],[-90.46447262242265,16.069562079324726],[-91.74796017125595,16.066564846251765],[-92.2292486234063,15.251446641495873],[-92.08721594925203,15.064584662328512],[-92.20322953974727,14.83010285080411],[-92.22775000686983,14.538828640190957],[-93.35946387406176,15.615429592343672],[-93.87516883011851,15.940164292865914],[-94.69165646033014,16.200975246642884],[-95.25022701697304,16.128318182840644],[-96.05338212765331,15.752087917539596],[-96.55743404822829,15.65351512294279],[-97.26359249549665,15.917064927631316],[-98.01302995480961,16.107311713113912],[-98.94767574745651,16.566043402568763],[-99.69739742714705,16.70616404872817],[-100.82949886758131,17.17107107184205],[-101.66608862995446,17.649026394109626],[-101.91852800170022,17.916090196193977],[-102.47813208698891,17.975750637275098],[-103.50098954955808,18.29229462327885],[-103.91752743204682,18.74857168220001],[-104.9920096504755,19.316133938061682],[-105.49303849976144,19.946767279535436],[-105.73139604370766,20.434101874264115],[-105.39777299683135,20.531718654863425],[-105.50066077352443,20.81689504646613],[-105.27075232625793,21.07628489835514],[-105.26581722697402,21.42210358325235],[-105.6031609769754,21.871145941652568],[-105.69341386597313,22.269080308516152],[-106.02871639689897,22.773752346278627],[-106.90998043498837,23.767774359628902],[-107.91544877809139,24.54891531015295],[-108.40190487347098,25.17231395110593],[-109.26019873740665,25.58060944264406],[-109.44408932171734,25.824883938087677],[-109.29164384645627,26.442934068298428],[-109.80145768923182,26.676175645447927],[-110.3917317370857,27.16211497650454],[-110.64101884646163,27.859876003525528],[-111.17891883018785,27.941240546169066],[-111.75960689985163,28.46795258230395],[-112.2282346260904,28.95440867768349],[-112.27182369672869,29.266844387320074],[-112.80959448937398,30.021113593052345],[-113.16381059451868,30.78688080496943],[-113.14866939985717,31.17096588797892],[-113.87188106978186,31.567608344035193],[-114.2057366606035,31.52404511161313],[-114.77645117883503,31.799532172161147],[-114.93669979537212,31.3934846054276],[-114.77123185917351,30.913617255165267],[-114.67389929895177,30.162681179315992],[-114.33097449426292,29.75043244070741],[-113.58887508833544,29.061611436473015],[-113.42405310754054,28.82617361095123],[-113.27196936730553,28.7547826197399],[-113.14003943566439,28.411289374295958],[-112.9622983467965,28.42519033458251],[-112.76158708377488,27.780216783147523],[-112.45791052941166,27.52581370697476],[-112.2449519519368,27.17172679291076],[-111.6164890206192,26.662817287700477],[-111.28467464887302,25.732589830014433],[-110.98781938357239,25.294606228124564],[-110.71000688357134,24.82600434010186],[-110.65504899782887,24.298594672131117],[-110.17285620811343,24.265547593680424],[-109.77184709352855,23.811182562754198],[-109.4091043770557,23.36467234953625],[-109.43339230023292,23.1855876734287],[-109.85421932660171,22.818271592698068],[-110.03139197471444,22.823077500901206],[-110.29507097048366,23.43097321216669],[-110.94950130902805,24.00096426034599],[-111.67056840701268,24.484423122652515],[-112.18203589562147,24.738412787367167],[-112.14898881717085,25.47012523040405],[-112.3007108223797,26.012004299416613],[-112.77729671919155,26.32195954030317],[-113.46467078332194,26.768185533143424],[-113.59672990604383,26.639459540304472],[-113.84893673384424,26.90006378835244],[-114.46574662968003,27.142090358991368],[-115.055142178185,27.72272675222291],[-114.98225257043741,27.798200181585116],[-114.57036556685495,27.74148529714489],[-114.19932878299925,28.115002549750553],[-114.16201839888463,28.566111965442303],[-114.93184221073663,29.279479275015486],[-115.518653937627,29.556361599235398],[-115.88736528202958,30.180793768834178],[-116.25835038945293,30.83646434175358],[-116.72152625208498,31.635743720012044],[-117.12775999999985,32.53534],[-115.99135,32.61239000000012],[-114.72139,32.72083],[-114.815,32.52528],[-113.30498,32.03914],[-111.02361,31.33472],[-109.035,31.341940000000136],[-108.24194,31.34222],[-108.24,31.75485371816637],[-106.50759,31.75452],[-106.1429,31.39995],[-105.63159,31.08383],[-105.03737,30.64402],[-104.70575,30.12173],[-104.4569699999999,29.57196],[-103.94,29.27],[-103.11,28.97],[-102.48,29.76],[-101.6624,29.7793],[-100.9576,29.380710000000132],[-100.45584,28.696120000000118],[-100.11,28.110000000000127],[-99.52,27.54],[-99.3,26.84],[-99.02,26.37],[-98.24,26.06],[-97.53,25.84],[-97.14000830767071,25.869997463478395]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Jam.\",\"name\":\"Jamaica\",\"name_long\":\"Jamaica\",\"iso_a2\":\"JM\",\"iso_a3\":\"JAM\",\"iso_n3\":\"388\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-77.56960079619921,18.490525417550487],[-76.89661861846211,18.400866807524082],[-76.36535905628554,18.160700588447597],[-76.19965857614164,17.886867173732966],[-76.9025614081757,17.868237819891746],[-77.20634131540348,17.70111623785982],[-77.76602291534061,17.86159739834224],[-78.33771928578561,18.225967922432233],[-78.21772661000388,18.454532782459193],[-77.79736467152563,18.524218451404778],[-77.56960079619921,18.490525417550487]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"P.R.\",\"name\":\"Puerto Rico\",\"name_long\":\"Puerto Rico\",\"iso_a2\":\"PR\",\"iso_a3\":\"PRI\",\"iso_n3\":\"630\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-66.28243445500821,18.514761664295364],[-65.7713028632093,18.426679185453878],[-65.59100379094295,18.228034979723915],[-65.84716386581377,17.97590566657186],[-66.59993445500949,17.981822618069273],[-67.18416236028527,17.946553453030077],[-67.24242753769435,18.374460150622937],[-67.10067908391774,18.52060110114435],[-66.28243445500821,18.514761664295364]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Pan.\",\"name\":\"Panama\",\"name_long\":\"Panama\",\"iso_a2\":\"PA\",\"iso_a3\":\"PAN\",\"iso_n3\":\"591\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-77.88157141794525,7.223771267114785],[-78.21493608266012,7.512254950384161],[-78.42916073272607,8.052041123888927],[-78.18209570993864,8.319182440621773],[-78.4354652574657,8.38770538984079],[-78.62212053090394,8.718124497915028],[-79.12030717641375,8.996092027213022],[-79.55787736684519,8.932374986197146],[-79.76057817251004,8.5845150822244],[-80.16448116730334,8.333315944853595],[-80.38265906443961,8.298408514840432],[-80.4806892564973,8.09030752200107],[-80.00368994822716,7.547524115423371],[-80.276670701809,7.419754136581715],[-80.42115800649708,7.271571966984764],[-80.8864009264208,7.220541490096537],[-81.05954281281473,7.817921047390596],[-81.18971574575795,7.647905585150339],[-81.51951473664468,7.706610012233909],[-81.72131120474445,8.108962714058435],[-82.13144120962892,8.175392767769635],[-82.39093441438257,8.29236237226229],[-82.82008134635042,8.290863755725823],[-82.85095801464482,8.073822740099956],[-82.96578304719736,8.225027980985985],[-82.91317643912421,8.42351715741907],[-82.82977067740516,8.62629547773237],[-82.86865719270477,8.807266343618522],[-82.71918311230053,8.925708726431495],[-82.92715491405916,9.074330145702916],[-82.93289099804358,9.476812038608173],[-82.54619625520348,9.566134751824677],[-82.18712256542341,9.20744863528678],[-82.20758643261095,8.9955752628901],[-81.80856686066929,8.950616766796173],[-81.71415401887204,9.031955471223583],[-81.43928707551154,8.786234035675719],[-80.94730160187676,8.858503526235905],[-80.52190121125008,9.111072089062432],[-79.91459977895599,9.31276520429762],[-79.57330278188431,9.611610012241526],[-79.02119177927793,9.552931423374105],[-79.05845048696037,9.454565334506526],[-78.50088762074719,9.420458889193881],[-78.05592770049802,9.2477304142583],[-77.72951351592641,8.946844387238869],[-77.35336076527385,8.67050466555807],[-77.47472286651133,8.524286200388218],[-77.24256649444008,7.935278225125444],[-77.43110795765699,7.638061224798735],[-77.75341386586139,7.709839789252142],[-77.88157141794525,7.223771267114785]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Nic.\",\"name\":\"Nicaragua\",\"name_long\":\"Nicaragua\",\"iso_a2\":\"NI\",\"iso_a3\":\"NIC\",\"iso_n3\":\"558\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-85.7125404528073,11.088444932494824],[-86.05848832878526,11.403438625529944],[-86.52584998243296,11.806876532432597],[-86.74599158399633,12.143961900272487],[-87.16751624220116,12.458257961471656],[-87.66849341505471,12.909909979702633],[-87.5574666002756,13.064551703336065],[-87.39238623731923,12.914018256069838],[-87.31665442579549,12.984685777228975],[-87.00576900912756,13.025794379117157],[-86.88055701368437,13.254204209847245],[-86.7338217841916,13.263092556201443],[-86.7550866360797,13.754845485890913],[-86.5207081774199,13.77848745366444],[-86.31214209668993,13.77135610600817],[-86.0962638007906,14.038187364147248],[-85.80129472526859,13.83605499923759],[-85.69866533073693,13.960078436738087],[-85.51441301140025,14.079011745657837],[-85.1653645494848,14.354369615125078],[-85.14875057650296,14.560196844943619],[-85.05278744173692,14.551541042534723],[-84.9245006985724,14.790492865452352],[-84.82003679069435,14.819586696832669],[-84.64958207877962,14.666805324761754],[-84.4493359036486,14.621614284722495],[-84.22834164095241,14.748764146376658],[-83.97572140169359,14.749435939996461],[-83.62858496777292,14.880073960830302],[-83.48998877636612,15.016267198135536],[-83.14721900097413,14.99582916916411],[-83.23323442252394,14.899866034398102],[-83.2841615465476,14.6766238468972],[-83.18212643098728,14.31070302983845],[-83.41249996614445,13.970077826386557],[-83.51983191601468,13.567699286345883],[-83.55220720084554,13.127054348193086],[-83.49851538769427,12.869292303921227],[-83.47332312695198,12.419087225794428],[-83.62610449902292,12.320850328007566],[-83.71961300325506,11.893124497927726],[-83.65085751009072,11.629032090700118],[-83.8554703437504,11.373311265503787],[-83.80893571647155,11.103043524617274],[-83.65561174186158,10.938764146361422],[-83.89505449088595,10.726839097532446],[-84.19017859570485,10.793450018756674],[-84.35593075228104,10.999225572142905],[-84.67306901725627,11.082657172078143],[-84.90300330273895,10.952303371621896],[-85.56185197624418,11.217119248901597],[-85.7125404528073,11.088444932494824]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"El. S.\",\"name\":\"El Salvador\",\"name_long\":\"El Salvador\",\"iso_a2\":\"SV\",\"iso_a3\":\"SLV\",\"iso_n3\":\"222\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-87.79311113152657,13.384480495655055],[-87.90411210808952,13.149016831917137],[-88.48330156121682,13.163951320849492],[-88.8432279121297,13.259733588102478],[-89.2567427233293,13.458532823129303],[-89.81239356154767,13.520622056527998],[-90.09555457229098,13.735337632700734],[-90.0646779039966,13.881969509328924],[-89.72193396682073,14.134228013561694],[-89.53421932652051,14.244815578666305],[-89.58734269891654,14.362586167859488],[-89.35332597528279,14.424132798719116],[-89.05851192905766,14.340029405164085],[-88.84307288283284,14.140506700085169],[-88.541230841816,13.980154730683479],[-88.50399797234971,13.845485948130857],[-88.06534257684012,13.964625962779778],[-87.8595153470216,13.893312486216983],[-87.72350297722939,13.785050360565506],[-87.79311113152657,13.384480495655055]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Tr.T.\",\"name\":\"Trinidad and Tobago\",\"name_long\":\"Trinidad and Tobago\",\"iso_a2\":\"TT\",\"iso_a3\":\"TTO\",\"iso_n3\":\"780\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-61.68,10.76],[-61.105,10.89],[-60.895,10.855],[-60.935,10.11],[-61.77,10],[-61.95,10.09],[-61.66,10.365],[-61.68,10.76]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"U.S.A.\",\"name\":\"United States\",\"name_long\":\"United States\",\"iso_a2\":\"US\",\"iso_a3\":\"USA\",\"iso_n3\":\"840\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-155.54211,19.08348],[-155.68817,18.91619],[-155.93665,19.05939],[-155.90806,19.33888],[-156.07347,19.70294],[-156.02368,19.81422],[-155.85008,19.97729],[-155.91907,20.17395],[-155.86108,20.26721],[-155.78505,20.2487],[-155.40214,20.07975],[-155.22452,19.99302],[-155.06226,19.8591],[-154.80741,19.50871],[-154.83147,19.45328],[-155.22217,19.23972],[-155.54211,19.08348]]],[[[-156.07926,20.64397],[-156.41445,20.57241],[-156.58673,20.783],[-156.70167,20.8643],[-156.71055,20.92676],[-156.61258,21.01249],[-156.25711,20.91745],[-155.99566,20.76404],[-156.07926,20.64397]]],[[[-156.75824,21.17684],[-156.78933,21.06873],[-157.32521,21.09777],[-157.25027,21.21958],[-156.75824,21.17684]]],[[[-157.65283,21.32217],[-157.70703,21.26442],[-157.7786,21.27729],[-158.12667,21.31244],[-158.2538,21.53919],[-158.29265,21.57912],[-158.0252,21.71696],[-157.94161,21.65272],[-157.65283,21.32217]]],[[[-159.34512,21.982],[-159.46372,21.88299],[-159.80051,22.06533],[-159.74877,22.1382],[-159.5962,22.23618],[-159.36569,22.21494],[-159.34512,21.982]]],[[[-94.81758,49.38905],[-94.63999999999987,48.84000000000012],[-94.32914,48.67074000000011],[-93.63087,48.60926],[-92.61,48.45],[-91.64,48.14],[-90.82999999999986,48.27],[-89.6,48.010000000000105],[-89.27291744663668,48.01980825458284],[-88.37811418328653,48.30291758889382],[-87.43979262330024,47.94],[-86.46199083122815,47.55333801939204],[-85.65236324740323,47.22021881773051],[-84.87607988151485,46.90008331968238],[-84.77923824739983,46.63710195574913],[-84.54374874544567,46.53868419044923],[-84.6049,46.4396],[-84.3367,46.40877000000011],[-84.1421195136733,46.51222585711574],[-84.09185126416148,46.27541860613826],[-83.89076534700567,46.116926988299156],[-83.6161309475905,46.116926988299156],[-83.46955074739463,45.99468638771259],[-83.59285071484308,45.81689362241255],[-82.55092464875818,45.34751658790545],[-82.33776312543108,44.44],[-82.13764238150397,43.57108755144],[-82.43,42.9800000000001],[-82.89999999999989,42.43000000000015],[-83.11999999999989,42.08],[-83.14199968131256,41.975681057293],[-83.02981014680694,41.83279572200601],[-82.69008928092018,41.675105088867326],[-82.43927771679162,41.675105088867326],[-81.27774654816707,42.20902598730686],[-80.24744767934784,42.36619985612267],[-78.9393621487437,42.86361135514812],[-78.92,42.965],[-79.00999999999988,43.27],[-79.17167355011188,43.46633942318431],[-78.72027991404238,43.62508942318496],[-77.73788509795762,43.62905558936339],[-76.82003414580558,43.628784288093755],[-76.5,44.018458893758606],[-76.375,44.09631],[-75.31821,44.816450000000174],[-74.867,45.000480000000124],[-73.34783,45.00738],[-71.50505999999987,45.0082000000001],[-71.405,45.25500000000014],[-71.08482,45.30524000000017],[-70.6599999999998,45.46],[-70.305,45.915],[-69.99997,46.69307],[-69.237216,47.447781],[-68.905,47.185],[-68.23444,47.35486],[-67.79046,47.06636],[-67.79134,45.70281000000014],[-67.13741,45.13753],[-66.96466,44.80970000000016],[-68.03252,44.3252],[-69.05999999999989,43.98],[-70.11617,43.684050000000155],[-70.645475633411,43.09023834896405],[-70.81489,42.8653],[-70.825,42.335],[-70.495,41.805],[-70.08,41.78],[-70.185,42.145],[-69.88497,41.92283000000012],[-69.96503,41.63717000000017],[-70.64,41.475],[-71.12039,41.49445000000017],[-71.85999999999984,41.32],[-72.295,41.27],[-72.87643,41.22065],[-73.71,40.93110235165449],[-72.24126,41.11948000000015],[-71.94499999999982,40.93],[-73.345,40.63],[-73.982,40.628],[-73.952325,40.75075],[-74.25671,40.47351],[-73.96244,40.42763],[-74.17838,39.70926],[-74.90604,38.93954],[-74.98041,39.1964],[-75.20002,39.248450000000105],[-75.52805,39.4985],[-75.32,38.96],[-75.0718347647898,38.78203223017928],[-75.05673,38.40412000000012],[-75.37747,38.01551],[-75.94023,37.21689],[-76.03127,37.2566],[-75.72204999999978,37.93705000000011],[-76.23287,38.319215],[-76.35,39.15],[-76.542725,38.71761500000011],[-76.32933,38.08326],[-76.98999793161354,38.23999176691339],[-76.30162,37.917945],[-76.25874,36.96640000000011],[-75.9718,36.89726],[-75.86803999999984,36.55125],[-75.72749,35.55074000000013],[-76.36318,34.80854000000013],[-77.39763499999988,34.51201],[-78.05496,33.92547],[-78.55434999999983,33.86133000000012],[-79.06067,33.49395],[-79.20357,33.15839],[-80.301325,32.509355],[-80.86498,32.0333],[-81.33629,31.44049],[-81.49042,30.72999000000013],[-81.31371,30.035520000000105],[-80.98,29.180000000000117],[-80.53558499999988,28.47213],[-80.5299999999998,28.040000000000106],[-80.05653928497756,26.880000000000138],[-80.088015,26.205765],[-80.13155999999987,25.816775],[-80.38103,25.20616],[-80.67999999999988,25.08],[-81.17213,25.201260000000133],[-81.33,25.64],[-81.70999999999981,25.87],[-82.24,26.730000000000132],[-82.70515,27.49504],[-82.85526,27.88624],[-82.65,28.550000000000153],[-82.92999999999988,29.100000000000136],[-83.70959,29.93656],[-84.1,30.090000000000117],[-85.10882,29.63615],[-85.28784,29.68612000000013],[-85.7731,30.152610000000124],[-86.39999999999988,30.40000000000012],[-87.53036,30.27433],[-88.41782,30.3849],[-89.18048999999984,30.31598],[-89.59383117841978,30.15999400483685],[-89.413735,29.89419],[-89.43,29.48864],[-89.21767,29.29108],[-89.40823,29.15961],[-89.77928,29.307140000000143],[-90.15463,29.11743],[-90.880225,29.148535000000123],[-91.62678499999987,29.67700000000013],[-92.49906,29.5523],[-93.22637,29.78375],[-93.84842,29.71363],[-94.69,29.480000000000132],[-95.60026,28.73863],[-96.59404,28.30748],[-97.13999999999982,27.83],[-97.37,27.38],[-97.37999999999987,26.69],[-97.33,26.21000000000012],[-97.13999999999982,25.87],[-97.52999999999989,25.84],[-98.24,26.060000000000116],[-99.01999999999988,26.37],[-99.3,26.84],[-99.51999999999987,27.54],[-100.11,28.110000000000127],[-100.45584,28.696120000000118],[-100.9576,29.380710000000132],[-101.6624,29.779300000000116],[-102.48,29.76],[-103.11,28.97],[-103.94,29.27],[-104.45696999999984,29.57196],[-104.70575,30.12173],[-105.03737,30.64402],[-105.63159,31.08383000000012],[-106.1429,31.39995],[-106.50758999999982,31.75452],[-108.24,31.7548537181664],[-108.24194,31.34222],[-109.035,31.34194000000016],[-111.02361,31.33472],[-113.30498,32.03914],[-114.815,32.52528],[-114.72138999999986,32.72083],[-115.9913499999999,32.61239000000014],[-117.12775999999978,32.53534],[-117.29593769127388,33.04622461520389],[-117.944,33.621236431201396],[-118.41060227589749,33.740909223124504],[-118.51989482279971,34.02778157757575],[-119.081,34.078],[-119.43884064201669,34.3484771782843],[-120.36778,34.44711],[-120.62286,34.60855],[-120.74433,35.15686000000011],[-121.71456999999988,36.16153],[-122.54747,37.551760000000115],[-122.51201,37.78339000000013],[-122.95319,38.11371000000011],[-123.7272,38.95166000000012],[-123.86517,39.76699000000013],[-124.39807,40.3132],[-124.17886,41.142020000000116],[-124.2137,41.99964000000014],[-124.53284,42.7659900000001],[-124.14214,43.70838],[-124.020535,44.615895],[-123.89893,45.52341],[-124.079635,46.86475],[-124.39567,47.72017000000011],[-124.68721008300783,48.18443298339855],[-124.56610107421876,48.3797149658204],[-123.12,48.04],[-122.58736,47.096],[-122.34,47.36],[-122.5,48.18],[-122.84,49.000000000000114],[-120,49.000000000000114],[-117.03121,49.000000000000114],[-116.04818,49.000000000000114],[-113,49.000000000000114],[-110.04999999999983,49.000000000000114],[-107.05,49.000000000000114],[-104.04826,48.99986],[-100.65,49.000000000000114],[-97.22872000000471,49.00070000000011],[-95.15906950917196,49.000000000000114],[-95.15609,49.38425],[-94.81758,49.38905]]],[[[-153.0063140533369,57.11584219016589],[-154.0050902984581,56.73467682558106],[-154.5164027577701,56.9927489284467],[-154.67099280497115,57.46119578717249],[-153.76277950744148,57.81657461204377],[-153.2287294179211,57.968968410872435],[-152.56479061583514,57.901427313866975],[-152.1411472239063,57.59105866152199],[-153.0063140533369,57.11584219016589]]],[[[-165.57916419173358,59.90998688418755],[-166.19277014876727,59.754440822988975],[-166.848337368822,59.94140615502096],[-167.45527706609008,60.21306915957938],[-166.46779212142462,60.38416982689778],[-165.67442969466367,60.293606879306246],[-165.57916419173358,59.90998688418755]]],[[[-171.7316568675394,63.78251536727592],[-171.1144335602452,63.592191067144995],[-170.4911124339407,63.69497549097352],[-169.68250545965358,63.431115627691156],[-168.6894394603007,63.2975062120006],[-168.7719408844546,63.18859813094545],[-169.52943986720504,62.9769314642779],[-170.29055620021597,63.194437567794466],[-170.67138566799088,63.37582184513897],[-171.55306311753867,63.317789211675084],[-171.7911106028912,63.405845852300494],[-171.7316568675394,63.78251536727592]]],[[[-155.06779029032424,71.1477763943237],[-154.34416520894123,70.6964085964702],[-153.90000627339262,70.8899885118357],[-152.2100060699353,70.82999217394485],[-152.27000240782615,70.60000621202985],[-150.73999243874454,70.43001658800571],[-149.72000301816752,70.53001048449045],[-147.61336157935708,70.2140349392418],[-145.6899898002253,70.12000967068676],[-144.92001095907642,69.9899917670405],[-143.5894461804252,70.15251414659832],[-142.07251034871342,69.85193817817265],[-140.98598752156073,69.71199839952638],[-140.9859883290049,69.71199839952638],[-140.9924987520294,66.00002859156868],[-140.99776974812312,60.30639679629861],[-140.0129978161531,60.27683787702759],[-139.03900042031586,60.000007229240026],[-138.34089,59.56211000000016],[-137.4525,58.905000000000115],[-136.4797200000001,59.46389],[-135.47583,59.78778],[-134.945,59.27056000000013],[-134.27111,58.86111],[-133.35554888220722,58.410285142645165],[-132.73042,57.69289000000011],[-131.70780999999988,56.55212],[-130.00778,55.91583],[-129.9799942633583,55.28499787049722],[-130.53611018946725,54.8027534043494],[-131.08581823797215,55.17890615500204],[-131.9672114671423,55.49777558045906],[-132.25001074285947,56.36999624289746],[-133.53918108435641,57.17888743756214],[-134.07806292029605,58.1230675319669],[-135.03821103227907,58.18771474876393],[-136.62806230995466,58.21220937767046],[-137.80000627968604,58.49999542910379],[-139.867787041413,59.53776154238915],[-140.82527381713305,59.727517401765084],[-142.57444353556446,60.08444651960499],[-143.9588809948799,59.9991804063234],[-145.92555681682785,60.45860972761429],[-147.11437394914668,60.88465607364463],[-148.22430620012767,60.672989406977166],[-148.01806555885076,59.97832896589363],[-148.5708225168609,59.914172675203304],[-149.72785783587585,59.70565827090556],[-150.60824337461645,59.36821116803949],[-151.71639278868332,59.15582103131999],[-151.85943315326716,59.744984035879604],[-151.4097190012472,60.72580272077939],[-150.34694149473253,61.03358755150986],[-150.62111080625698,61.284424953854455],[-151.89583919981686,60.72719798445129],[-152.5783298410956,60.06165721296429],[-154.01917212625762,59.35027944603428],[-153.28751135965317,58.8647276882198],[-154.2324924387585,58.14637360293054],[-155.30749142151024,57.72779450136633],[-156.3083347239231,57.42277435976365],[-156.55609737854633,56.979984849670636],[-158.11721655986776,56.46360809999419],[-158.43332129619716,55.99415355083855],[-159.60332739971744,55.56668610292012],[-160.2897196116342,55.643580634170576],[-161.2230476552578,55.364734605523495],[-162.23776607974108,55.02418691672011],[-163.06944658104638,54.68973704692717],[-164.7855692210272,54.40417308208217],[-164.94222632552004,54.57222483989534],[-163.84833960676568,55.03943146424612],[-162.87000139061593,55.348043117893205],[-161.80417497459604,55.89498647727043],[-160.56360470278116,56.00805451112504],[-160.0705598622845,56.41805532492876],[-158.68444291891944,57.01667511659787],[-158.46109737855394,57.21692129172888],[-157.7227703521839,57.57000051536306],[-157.55027442119356,58.32832632103023],[-157.041674974577,58.91888458926172],[-158.19473120830548,58.61580231386984],[-158.5172179840231,58.78778148053732],[-159.05860612692874,58.424186102931685],[-159.71166704001735,58.93139028587634],[-159.9812888255002,58.57254914004164],[-160.35527116599653,59.07112335879364],[-161.35500342511506,58.670837714260756],[-161.96889360252635,58.67166453717738],[-162.05498653872468,59.26692536074745],[-161.87417070213536,59.6336213242906],[-162.5180590484921,59.98972361921391],[-163.81834143782015,59.79805573184339],[-164.66221757714646,60.26748444278265],[-165.34638770247483,60.50749563256241],[-165.35083187565186,61.07389516869751],[-166.12137915755596,61.500019029376226],[-165.73445187077053,62.074996853271806],[-164.91917863671785,62.63307648380793],[-164.56250790103934,63.14637848576305],[-163.75333248599702,63.21944896102377],[-163.0672244944579,63.05945872664802],[-162.26055538638172,63.54193573674117],[-161.5344498362486,63.455816962326764],[-160.77250668032113,63.766108100023274],[-160.95833513084256,64.22279857040277],[-161.5180684072122,64.40278758407531],[-160.77777767641476,64.78860382756642],[-161.39192623598763,64.77723501246234],[-162.45305009666885,64.55944468856822],[-162.7577860178941,64.33860545516882],[-163.5463942128843,64.5591604681905],[-164.96082984114517,64.44694509546885],[-166.42528825586447,64.68667206487072],[-166.84500423893905,65.08889557561453],[-168.11056006576717,65.66999705673675],[-166.70527116602196,66.0883177761394],[-164.4747096425755,66.5766600612975],[-163.65251176659564,66.5766600612975],[-163.78860165103617,66.07720734319668],[-161.67777442121016,66.11611969671242],[-162.48971452538,66.73556509059512],[-163.71971696679108,67.1163945583701],[-164.4309913808565,67.6163382025778],[-165.39028683170676,68.04277212185025],[-166.76444068099602,68.35887685817968],[-166.20470740462662,68.88303091091618],[-164.4308105133435,68.91553538682774],[-163.16861365461452,69.3711148139129],[-162.93056616926202,69.85806183539927],[-161.90889726463553,70.33332998318764],[-160.9347965159337,70.44768992784958],[-159.03917578838715,70.89164215766894],[-158.11972286683397,70.82472117785105],[-156.58082455139805,71.35776357694175],[-155.06779029032424,71.1477763943237]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Arg.\",\"name\":\"Argentina\",\"name_long\":\"Argentina\",\"iso_a2\":\"AR\",\"iso_a3\":\"ARG\",\"iso_n3\":\"032\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-65.5,-55.2],[-66.45,-55.25],[-66.95992,-54.89681],[-67.56244,-54.87001],[-68.63335,-54.8695],[-68.63401022758316,-52.63637045887445],[-68.25,-53.1],[-67.75,-53.85],[-66.45,-54.45],[-65.05,-54.7],[-65.5,-55.2]]],[[[-64.96489213729458,-22.075861504812348],[-64.37702104354227,-22.798091322523547],[-63.98683814152247,-21.993644301035957],[-62.846468471921554,-22.034985446869456],[-62.6850571356579,-22.249029229422405],[-60.84656470400994,-23.8807125790383],[-60.02896603050399,-24.032796319273245],[-58.80712846539495,-24.771459242453275],[-57.77721716981796,-25.16233977630904],[-57.63366004091114,-25.60365650808167],[-58.61817359071972,-27.12371876394712],[-57.60975969097615,-27.395898532828426],[-56.48670162619299,-27.54849903738625],[-55.6958455063982,-27.38783700939082],[-54.78879492859505,-26.621785577096087],[-54.625290696823555,-25.739255466415486],[-54.13004960795441,-25.54763925547725],[-53.62834896504873,-26.124865004177437],[-53.648735317587885,-26.92347258881611],[-54.49072526713553,-27.474756768505767],[-55.1622863429846,-27.88191537853342],[-56.2908996242391,-28.852760512000852],[-57.62513342958291,-30.216294854454244],[-57.87493730328191,-31.016556084926165],[-58.14244035504075,-32.04450367607619],[-58.13264767112142,-33.040566908502015],[-58.34961117209883,-33.263188978815435],[-58.42707414410438,-33.90945444105755],[-58.49544206402654,-34.43148976007011],[-57.225829637263644,-35.28802662530789],[-57.362358771378744,-35.977390232081504],[-56.73748735210546,-36.41312590916658],[-56.78828528504834,-36.901571547189334],[-57.74915686708343,-38.183870538079915],[-59.231857062401865,-38.720220228837206],[-61.23744523786561,-38.928424574541154],[-62.33595699731015,-38.82770720800437],[-62.12576310896293,-39.424104913084875],[-62.330530971919444,-40.17258635840032],[-62.14599443220524,-40.67689666113674],[-62.745802781816984,-41.02876148861209],[-63.77049475773253,-41.166789239263665],[-64.73208980981971,-40.802677097335135],[-65.11803524439159,-41.06431487402888],[-64.97856055363584,-42.05800099056932],[-64.30340796574248,-42.359016208669495],[-63.75594784204235,-42.04368661882451],[-63.45805904809589,-42.563138116222355],[-64.3788038804563,-42.87355844499964],[-65.1818039618397,-43.495380954767796],[-65.32882341171013,-44.501366062193696],[-65.5652689276616,-45.03678557716979],[-66.50996578638936,-45.03962778094584],[-67.29379391139244,-45.5518962542552],[-67.58054643418009,-46.30177296324254],[-66.59706641301726,-47.03392465595381],[-65.64102657740145,-47.236134535511894],[-65.98508826360074,-48.13328907653114],[-67.16617896184766,-48.697337334996945],[-67.81608761256646,-49.86966887797042],[-68.72874508327317,-50.26421843851887],[-69.1385391913478,-50.7325102679478],[-68.81556148952353,-51.771104011594105],[-68.14999487982041,-52.3499834061277],[-68.57154537624135,-52.29944385534626],[-69.49836218939609,-52.14276091263725],[-71.91480383979635,-52.009022305865926],[-72.32940385607404,-51.42595631287241],[-72.30997351753237,-50.677009779666356],[-72.97574683296463,-50.74145029073431],[-73.32805091011448,-50.37878508890987],[-73.41543575712004,-49.31843637471296],[-72.64824744331494,-48.87861825947679],[-72.33116085477195,-48.244238376661826],[-72.44735531278027,-47.73853281025353],[-71.91725847033021,-46.8848381487918],[-71.55200944689125,-45.56073292417713],[-71.65931555854533,-44.97368865334144],[-71.22277889675973,-44.784242852559416],[-71.32980078803621,-44.40752166115169],[-71.79362260607195,-44.20717213315611],[-71.46405615913051,-43.78761117937833],[-71.91542395698391,-43.40856454851742],[-72.14889807807853,-42.25488819760139],[-71.74680375841547,-42.051386407235995],[-71.91573401557756,-40.83233936947073],[-71.68076127794646,-39.80816415787807],[-71.41351660834904,-38.916022230791114],[-70.81466427273472,-38.55299529394074],[-71.11862504747543,-37.5768274879472],[-71.1218806627098,-36.65812387466234],[-70.36476925320167,-36.005088799789945],[-70.38804948594908,-35.16968759535944],[-69.81730912950147,-34.193571465798286],[-69.81477698431921,-33.27388600029985],[-70.07439938015364,-33.09120981214803],[-70.53506893581945,-31.365010267870286],[-69.91900834825192,-30.336339206668313],[-70.01355038112987,-29.36792286551855],[-69.65613033718314,-28.459141127233693],[-69.00123491074828,-27.52121388113613],[-68.2955415513704,-26.89933969493579],[-68.59479977077268,-26.506908868111267],[-68.38600114609736,-26.185016371365233],[-68.41765296087613,-24.518554782816878],[-67.32844295924414,-24.02530323659091],[-66.98523393417764,-22.98634856536283],[-67.10667355006362,-22.7359245744764],[-66.27333940292485,-21.832310479420684],[-64.96489213729458,-22.075861504812348]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Bolivia\",\"name\":\"Bolivia\",\"name_long\":\"Bolivia\",\"iso_a2\":\"BO\",\"iso_a3\":\"BOL\",\"iso_n3\":\"068\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-62.846468471921554,-22.03498544686945],[-63.98683814152247,-21.99364430103595],[-64.37702104354226,-22.79809132252354],[-64.9648921372946,-22.075861504812327],[-66.27333940292485,-21.83231047942072],[-67.1066735500636,-22.735924574476414],[-67.82817989772273,-22.872918796482175],[-68.21991309271128,-21.494346612231865],[-68.75716712103375,-20.372657972904463],[-68.44222510443092,-19.40506845467143],[-68.96681840684187,-18.981683444904107],[-69.10024695501949,-18.260125420812674],[-69.59042375352405,-17.580011895419332],[-68.9596353827533,-16.50069793057127],[-69.38976416693471,-15.660129082911654],[-69.16034664577495,-15.323973890853019],[-69.33953467474701,-14.953195489158832],[-68.9488866848366,-14.453639418193283],[-68.92922380234954,-13.602683607643007],[-68.88007951523997,-12.899729099176653],[-68.66507971868961,-12.561300144097173],[-69.52967810736496,-10.951734307502194],[-68.78615759954948,-11.03638030359628],[-68.27125362819326,-11.01452117273682],[-68.04819230820539,-10.712059014532485],[-67.17380123561074,-10.30681243249961],[-66.64690833196279,-9.931331475466862],[-65.33843522811642,-9.76198780684639],[-65.44483700220539,-10.511451104375432],[-65.32189876978302,-10.895872084194679],[-65.40228146021303,-11.566270440317153],[-64.3163529120316,-12.461978041232191],[-63.19649878605057,-12.627032565972433],[-62.80306026879638,-13.000653171442686],[-62.127080857986385,-13.198780612849724],[-61.71320431176078,-13.489202162330052],[-61.08412126325565,-13.479383640194598],[-60.503304002511136,-13.775954685117659],[-60.45919816755003,-14.354007256734555],[-60.26432634137736,-14.64597909918364],[-60.251148851142936,-15.077218926659322],[-60.542965664295146,-15.093910414289596],[-60.158389655179036,-16.258283786690082],[-58.24121985536669,-16.299573256091293],[-58.38805843772404,-16.877109063385276],[-58.28080400250226,-17.271710300366017],[-57.734558274961,-17.55246835700777],[-57.498371141170985,-18.174187513911292],[-57.67600887717431,-18.961839694904025],[-57.949997321185826,-19.40000416430682],[-57.85380164247451,-19.969995212486186],[-58.166392381408045,-20.176700941653678],[-58.183471442280506,-19.868399346600363],[-59.115042487206104,-19.356906019775398],[-60.04356462262649,-19.342746677327426],[-61.786326463453776,-19.633736667562964],[-62.26596126977079,-20.513734633061276],[-62.29117936872922,-21.05163461678739],[-62.685057135657885,-22.249029229422387],[-62.846468471921554,-22.03498544686945]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Brazil\",\"name\":\"Brazil\",\"name_long\":\"Brazil\",\"iso_a2\":\"BR\",\"iso_a3\":\"BRA\",\"iso_n3\":\"076\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-57.62513342958296,-30.216294854454258],[-56.29089962423908,-28.85276051200089],[-55.16228634298457,-27.881915378533463],[-54.490725267135524,-27.47475676850579],[-53.648735317587885,-26.923472588816086],[-53.62834896504874,-26.124865004177472],[-54.13004960795439,-25.547639255477254],[-54.625290696823576,-25.73925546641551],[-54.42894609233059,-25.162184747012166],[-54.29347632507745,-24.570799655863965],[-54.29295956075451,-24.02101409271073],[-54.652834235235126,-23.83957813893396],[-55.02790178080954,-24.001273695575225],[-55.40074723979542,-23.956935316668805],[-55.517639329639636,-23.571997572526634],[-55.610682745981144,-22.655619398694842],[-55.79795813660691,-22.356929620047822],[-56.47331743022939,-22.086300144135283],[-56.8815095689029,-22.28215382252148],[-57.937155727761294,-22.090175876557172],[-57.87067399761779,-20.73268767668195],[-58.166392381408045,-20.176700941653678],[-57.85380164247451,-19.969995212486186],[-57.949997321185826,-19.40000416430682],[-57.67600887717431,-18.961839694904025],[-57.498371141170985,-18.174187513911292],[-57.734558274961,-17.55246835700777],[-58.28080400250226,-17.271710300366017],[-58.38805843772404,-16.877109063385276],[-58.24121985536669,-16.299573256091293],[-60.158389655179036,-16.258283786690082],[-60.542965664295146,-15.093910414289596],[-60.251148851142936,-15.077218926659322],[-60.26432634137736,-14.64597909918364],[-60.45919816755003,-14.354007256734555],[-60.503304002511136,-13.775954685117659],[-61.08412126325565,-13.479383640194598],[-61.71320431176078,-13.489202162330052],[-62.127080857986385,-13.198780612849724],[-62.80306026879638,-13.000653171442686],[-63.19649878605057,-12.627032565972433],[-64.3163529120316,-12.461978041232191],[-65.40228146021303,-11.566270440317153],[-65.32189876978302,-10.895872084194679],[-65.44483700220539,-10.511451104375432],[-65.33843522811642,-9.76198780684639],[-66.64690833196279,-9.931331475466862],[-67.17380123561074,-10.30681243249961],[-68.04819230820539,-10.712059014532485],[-68.27125362819326,-11.01452117273682],[-68.78615759954948,-11.03638030359628],[-69.52967810736496,-10.951734307502194],[-70.0937522040469,-11.123971856331012],[-70.54868567572841,-11.009146823778465],[-70.48189388699117,-9.490118096558845],[-71.30241227892154,-10.079436130415374],[-72.18489071316984,-10.053597914269432],[-72.56303300646564,-9.520193780152717],[-73.22671342639016,-9.462212823121234],[-73.01538265653254,-9.03283334720806],[-73.57105933296707,-8.424446709835834],[-73.98723548042966,-7.523829847853064],[-73.7234014553635,-7.340998630404414],[-73.72448666044164,-6.91859547285064],[-73.1200274319236,-6.629930922068239],[-73.21971126981461,-6.089188734566078],[-72.96450720894119,-5.741251315944893],[-72.89192765978726,-5.274561455916981],[-71.74840572781655,-4.593982842633011],[-70.92884334988358,-4.401591485210368],[-70.7947688463023,-4.251264743673303],[-69.89363521999663,-4.298186944194327],[-69.44410193548961,-1.556287123219818],[-69.42048580593223,-1.122618503426409],[-69.5770653957766,-0.549991957200163],[-70.02065589057005,-0.185156345219539],[-70.0155657619893,0.541414292804205],[-69.45239600287246,0.706158758950693],[-69.25243404811906,0.602650865070075],[-69.21863766140018,0.985676581217433],[-69.80459672715773,1.089081122233466],[-69.81697323269162,1.714805202639624],[-67.86856502955884,1.692455145673392],[-67.53781002467468,2.03716278727633],[-67.25999752467358,1.719998684084956],[-67.0650481838525,1.130112209473225],[-66.87632585312258,1.253360500489336],[-66.32576514348496,0.724452215982012],[-65.54826738143757,0.78925446207603],[-65.35471330428837,1.0952822941085],[-64.61101192895985,1.328730576987042],[-64.19930579289051,1.49285492594602],[-64.08308549666609,1.91636912679408],[-63.36878801131166,2.200899562993129],[-63.42286739770512,2.411067613124174],[-64.26999915226578,2.497005520025567],[-64.40882788761792,3.126786200366624],[-64.36849443221409,3.797210394705246],[-64.81606401229402,4.056445217297423],[-64.62865943058755,4.14848094320925],[-63.88834286157416,4.020530096854571],[-63.0931975978991,3.770571193858785],[-62.804533047116706,4.006965033377952],[-62.08542965355914,4.162123521334308],[-60.96689327660153,4.536467596856639],[-60.60117916527194,4.91809804933213],[-60.73357418480372,5.200277207861901],[-60.21368343773133,5.244486395687602],[-59.98095862490488,5.014061184098139],[-60.11100236676737,4.574966538914083],[-59.767405768458715,4.423502915866607],[-59.53803992373123,3.958802598481938],[-59.81541317405786,3.606498521332085],[-59.97452490908456,2.755232652188056],[-59.71854570172674,2.24963043864436],[-59.64604366722126,1.786893825686789],[-59.03086157900265,1.317697658692722],[-58.5400129868783,1.268088283692521],[-58.42947709820596,1.463941962078721],[-58.11344987652502,1.507195135907025],[-57.66097103537737,1.682584947105639],[-57.335822923396904,1.94853770589576],[-56.78270423036083,1.863710842288654],[-56.539385748914555,1.899522609866921],[-55.99569800477175,1.817667141116601],[-55.905600145070885,2.02199575439866],[-56.0733418442903,2.220794989425499],[-55.973322109589375,2.510363877773017],[-55.569755011606,2.421506252447131],[-55.09758744975514,2.523748073736613],[-54.52475419779971,2.311848863123785],[-54.08806250671724,2.105556545414629],[-53.77852067728892,2.376702785650082],[-53.55483924011354,2.334896551925951],[-53.4184651352953,2.053389187015981],[-52.939657151894956,2.124857692875636],[-52.55642473001842,2.504705308437053],[-52.249337531123956,3.241094468596245],[-51.65779741067888,4.156232408053029],[-51.31714636901086,4.203490505383954],[-51.069771287629656,3.650397650564031],[-50.508875291533656,1.901563828942457],[-49.97407589374506,1.736483465986069],[-49.947100796088705,1.046189683431223],[-50.699251268096916,0.222984117021682],[-50.38821082213214,-0.078444512536819],[-48.62056677915631,-0.235489190271821],[-48.58449662941659,-1.237805271005001],[-47.824956427590635,-0.5816179337628],[-46.566583624851226,-0.941027520352776],[-44.905703090990414,-1.551739597178134],[-44.417619187993665,-2.137750339367976],[-44.58158850765578,-2.691308282078524],[-43.418791266440195,-2.383110039889793],[-41.47265682632825,-2.912018324397116],[-39.97866533055404,-2.873054294449041],[-38.50038347019657,-3.700652357603395],[-37.2232521225352,-4.820945733258917],[-36.45293738457639,-5.109403578312153],[-35.59779578301047,-5.149504489770648],[-35.23538896334756,-5.464937432480247],[-34.89602983248683,-6.738193047719711],[-34.729993455533034,-7.343220716992966],[-35.12821204277422,-8.996401462442286],[-35.636966518687714,-9.649281508017815],[-37.046518724097,-11.040721123908801],[-37.68361161960736,-12.171194756725823],[-38.42387651218844,-13.038118584854288],[-38.673887091616514,-13.057652276260619],[-38.953275722802545,-13.793369642800023],[-38.88229814304965,-15.667053724838768],[-39.16109249526431,-17.208406670808472],[-39.2673392400564,-17.867746270420483],[-39.58352149103423,-18.262295830968938],[-39.76082333022764,-19.59911345792741],[-40.77474077001034,-20.904511814052423],[-40.94475623225061,-21.937316989837807],[-41.754164191238225,-22.370675551037458],[-41.98828426773655,-22.970070489190892],[-43.07470374202475,-22.96769337330547],[-44.64781185563781,-23.351959323827842],[-45.35213578955991,-23.796841729428582],[-46.47209326840554,-24.08896860117454],[-47.64897233742066,-24.885199069927722],[-48.4954581365777,-25.877024834905654],[-48.64100480812774,-26.623697605090932],[-48.47473588722865,-27.17591196056189],[-48.661520351747626,-28.186134535435716],[-48.88845740415739,-28.674115085567884],[-49.587329474472675,-29.224469089476337],[-50.696874152211485,-30.984465020472957],[-51.576226162306156,-31.77769825615321],[-52.256081305538046,-32.24536996839466],[-52.712099982297694,-33.19657805759118],[-53.373661668498244,-33.768377780900764],[-53.6505439927181,-33.20200408298183],[-53.209588995971544,-32.727666110974724],[-53.787951626182185,-32.047242526987624],[-54.57245154480512,-31.494511407193748],[-55.601510179249345,-30.853878676071393],[-55.97324459494093,-30.883075860316303],[-56.97602576356473,-30.109686374636127],[-57.62513342958296,-30.216294854454258]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Chile\",\"name\":\"Chile\",\"name_long\":\"Chile\",\"iso_a2\":\"CL\",\"iso_a3\":\"CHL\",\"iso_n3\":\"152\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-68.63401022758316,-52.63637045887437],[-68.6333499999999,-54.8695],[-67.56244,-54.87001],[-66.95992,-54.89681],[-67.29102999999989,-55.30124],[-68.14862999999986,-55.61183],[-68.63999081081181,-55.58001799908689],[-69.2321,-55.49906],[-69.95809,-55.19843],[-71.00568,-55.05383],[-72.2639,-54.49514],[-73.2852,-53.95751999999989],[-74.66253,-52.83749],[-73.8381,-53.04743],[-72.43418,-53.7154],[-71.10773,-54.07433],[-70.59177999999979,-53.61583],[-70.26748,-52.93123],[-69.34564999999989,-52.5183],[-68.63401022758316,-52.63637045887437]]],[[[-68.21991309271124,-21.494346612231837],[-67.82817989772266,-22.87291879648218],[-67.10667355006362,-22.7359245744764],[-66.98523393417764,-22.98634856536283],[-67.32844295924414,-24.02530323659091],[-68.41765296087613,-24.518554782816878],[-68.38600114609736,-26.185016371365233],[-68.59479977077268,-26.506908868111267],[-68.2955415513704,-26.89933969493579],[-69.00123491074828,-27.52121388113613],[-69.65613033718314,-28.459141127233693],[-70.01355038112987,-29.36792286551855],[-69.91900834825192,-30.336339206668313],[-70.53506893581945,-31.365010267870286],[-70.07439938015364,-33.09120981214803],[-69.81477698431921,-33.27388600029985],[-69.81730912950147,-34.193571465798286],[-70.38804948594908,-35.16968759535944],[-70.36476925320167,-36.005088799789945],[-71.1218806627098,-36.65812387466234],[-71.11862504747543,-37.5768274879472],[-70.81466427273472,-38.55299529394074],[-71.41351660834904,-38.916022230791114],[-71.68076127794646,-39.80816415787807],[-71.91573401557756,-40.83233936947073],[-71.74680375841547,-42.051386407235995],[-72.14889807807853,-42.25488819760139],[-71.91542395698391,-43.40856454851742],[-71.46405615913051,-43.78761117937833],[-71.79362260607195,-44.20717213315611],[-71.32980078803621,-44.40752166115169],[-71.22277889675973,-44.784242852559416],[-71.65931555854533,-44.97368865334144],[-71.55200944689125,-45.56073292417713],[-71.91725847033021,-46.8848381487918],[-72.44735531278027,-47.73853281025353],[-72.33116085477195,-48.244238376661826],[-72.64824744331494,-48.87861825947679],[-73.41543575712004,-49.31843637471296],[-73.32805091011448,-50.37878508890987],[-72.97574683296463,-50.74145029073431],[-72.30997351753237,-50.677009779666356],[-72.32940385607404,-51.42595631287241],[-71.91480383979635,-52.009022305865926],[-69.49836218939609,-52.14276091263725],[-68.57154537624135,-52.29944385534626],[-69.46128434922664,-52.29195077266393],[-69.94277950710614,-52.53793059037325],[-70.84510169135453,-52.899200528525725],[-71.00633216010525,-53.83325204220135],[-71.42979468452094,-53.85645476030039],[-72.55794287788486,-53.531410001184454],[-73.70275672066288,-52.83506926860725],[-73.70275672066288,-52.8350700760515],[-74.94676347522515,-52.26275358841903],[-75.2600260077785,-51.629354750373224],[-74.9766324530898,-51.04339568461569],[-75.4797541978835,-50.37837167745156],[-75.60801510283196,-48.6737728818718],[-75.18276974150213,-47.71191944762316],[-74.1265809801047,-46.9392534319951],[-75.64439531116545,-46.64764332457203],[-74.69215369332306,-45.76397633238098],[-74.35170935738427,-44.103044122087894],[-73.2403560045152,-44.454960625995625],[-72.71780392117978,-42.383355808278985],[-73.38889990913825,-42.11753224056957],[-73.70133561877486,-43.365776462579745],[-74.33194312203258,-43.22495818458441],[-74.01795711942717,-41.794812920906836],[-73.67709937202997,-39.942212823243125],[-73.21759253609068,-39.258688653318515],[-73.50555945503706,-38.28288258235107],[-73.58806087919109,-37.156284681956016],[-73.1667170884993,-37.12378020604435],[-72.55313696968173,-35.508840020491036],[-71.86173214383257,-33.90909270603153],[-71.43845048692992,-32.41889942803083],[-71.66872066922244,-30.92064462659252],[-71.37008256700773,-30.095682061485004],[-71.48989437527646,-28.861442152625912],[-70.90512386746158,-27.6403797340012],[-70.72495398627598,-25.70592416758721],[-70.40396582709505,-23.628996677344542],[-70.09124589708067,-21.393319187101223],[-70.16441972520599,-19.756468194256186],[-70.37257239447774,-18.347975355708883],[-69.85844356960581,-18.092693780187034],[-69.590423753524,-17.58001189541929],[-69.10024695501943,-18.260125420812656],[-68.96681840684184,-18.981683444904093],[-68.44222510443095,-19.405068454671422],[-68.75716712103372,-20.372657972904477],[-68.21991309271124,-21.494346612231837]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Col.\",\"name\":\"Colombia\",\"name_long\":\"Colombia\",\"iso_a2\":\"CO\",\"iso_a3\":\"COL\",\"iso_n3\":\"170\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-75.37322323271385,-0.15203175212045],[-75.80146582711659,0.084801337073202],[-76.29231441924097,0.416047268064119],[-76.57637976754938,0.256935533037435],[-77.4249843004304,0.395686753741117],[-77.66861284047044,0.825893052570961],[-77.85506140817952,0.809925034992773],[-78.85525875518871,1.380923773601822],[-78.99093522817103,1.691369940595251],[-78.61783138702371,1.766404120283056],[-78.66211808949785,2.267355454920477],[-78.42761043975732,2.629555568854215],[-77.93154252797149,2.696605739752926],[-77.51043128122501,3.325016994638247],[-77.12768978545526,3.849636135265357],[-77.49627193877703,4.087606105969428],[-77.3076012844794,4.667984117039452],[-77.53322058786573,5.582811997902496],[-77.31881507028675,5.84535411216136],[-77.47666073272228,6.691116441266303],[-77.88157141794525,7.223771267114785],[-77.75341386586139,7.709839789252142],[-77.43110795765699,7.638061224798735],[-77.24256649444008,7.935278225125444],[-77.47472286651133,8.524286200388218],[-77.35336076527385,8.67050466555807],[-76.83667395700357,8.638749497914716],[-76.08638383655786,9.336820583529487],[-75.67460018584005,9.443248195834599],[-75.66470414905618,9.774003200718738],[-75.48042599150335,10.618990383339309],[-74.90689510771197,11.083044745320322],[-74.27675269234489,11.102035834187587],[-74.1972226630477,11.310472723836865],[-73.41476396350029,11.22701528568548],[-72.62783525255963,11.731971543825523],[-72.23819495307892,11.955549628136326],[-71.75409013536864,12.437303168177309],[-71.3998223537917,12.376040757695293],[-71.13746110704588,12.112981879113505],[-71.3315836249503,11.776284084515808],[-71.97392167833829,11.60867157637712],[-72.22757544624294,11.10870209395324],[-72.61465776232521,10.821975409381778],[-72.9052860175347,10.450344346554772],[-73.02760413276957,9.736770331252444],[-73.30495154488005,9.151999823437606],[-72.7887298245004,9.085027167187334],[-72.6604947577681,8.625287787302682],[-72.43986223009796,8.405275376820029],[-72.36090064155596,8.002638454617895],[-72.47967892117885,7.632506008327354],[-72.44448727078807,7.423784898300481],[-72.19835242378188,7.340430813013682],[-71.96017574734864,6.991614895043538],[-70.67423356798152,7.087784735538719],[-70.09331295437242,6.96037649172311],[-69.38947994655712,6.099860541198836],[-68.98531856960236,6.206804917826858],[-68.26505245631823,6.153268133972475],[-67.69508724635502,6.267318020040647],[-67.34143958196557,6.095468044454023],[-67.52153194850275,5.556870428891969],[-67.74469662135522,5.221128648291668],[-67.82301225449355,4.503937282728899],[-67.62183590358127,3.839481716319994],[-67.33756384954368,3.542342230641722],[-67.30317318385345,3.31845408773718],[-67.8099381171237,2.820655015469569],[-67.44709204778631,2.600280869960869],[-67.18129431829307,2.250638129074062],[-66.87632585312258,1.253360500489336],[-67.0650481838525,1.130112209473225],[-67.25999752467358,1.719998684084956],[-67.53781002467468,2.03716278727633],[-67.86856502955884,1.692455145673392],[-69.81697323269162,1.714805202639624],[-69.80459672715773,1.089081122233466],[-69.21863766140018,0.985676581217433],[-69.25243404811906,0.602650865070075],[-69.45239600287246,0.706158758950693],[-70.0155657619893,0.541414292804205],[-70.02065589057005,-0.185156345219539],[-69.5770653957766,-0.549991957200163],[-69.42048580593223,-1.122618503426409],[-69.44410193548961,-1.556287123219818],[-69.89363521999663,-4.298186944194327],[-70.39404395209499,-3.766591485207825],[-70.69268205430971,-3.742872002785859],[-70.04770850287485,-2.725156345229699],[-70.81347571479196,-2.256864515800743],[-71.41364579942979,-2.342802422702128],[-71.7747607082854,-2.169789727388938],[-72.32578650581365,-2.434218031426454],[-73.07039221870724,-2.308954359550953],[-73.6595035468346,-1.260491224781134],[-74.12239518908906,-1.002832533373848],[-74.44160051135597,-0.530820000819887],[-75.10662451852008,-0.05720549886486],[-75.37322323271385,-0.15203175212045]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Ecu.\",\"name\":\"Ecuador\",\"name_long\":\"Ecuador\",\"iso_a2\":\"EC\",\"iso_a3\":\"ECU\",\"iso_n3\":\"218\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-80.30256059438722,-3.404856459164713],[-79.77029334178093,-2.65751189535964],[-79.98655921092241,-2.220794366061014],[-80.36878394236925,-2.685158786635788],[-80.96776546906436,-2.246942640800704],[-80.76480628123804,-1.965047702648533],[-80.9336590237517,-1.057454522306358],[-80.58337032746127,-0.906662692878683],[-80.39932471385376,-0.283703301600141],[-80.02089820018037,0.360340074053468],[-80.09060970734211,0.768428859862397],[-79.54276201039978,0.982937730305963],[-78.85525875518871,1.380923773601822],[-77.85506140817952,0.809925034992773],[-77.66861284047044,0.825893052570961],[-77.4249843004304,0.395686753741117],[-76.57637976754938,0.256935533037435],[-76.29231441924097,0.416047268064119],[-75.80146582711659,0.084801337073202],[-75.37322323271385,-0.15203175212045],[-75.23372270374193,-0.911416924649529],[-75.54499569365204,-1.56160979574588],[-76.63539425322672,-2.608677666843818],[-77.83790483265861,-3.003020521663103],[-78.45068396677564,-3.873096612161376],[-78.63989722361234,-4.547784112164074],[-79.20528906931771,-4.959128513207389],[-79.62497921417618,-4.454198093283494],[-80.02890804718561,-4.346090996928893],[-80.44224199087216,-4.425724379090674],[-80.46929460317695,-4.059286797708999],[-80.18401485870967,-3.821161797708044],[-80.30256059438722,-3.404856459164713]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Flk. Is.\",\"name\":\"Falkland Is.\",\"name_long\":\"Falkland Islands\",\"iso_a2\":\"FK\",\"iso_a3\":\"FLK\",\"iso_n3\":\"238\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-61.2,-51.85],[-60,-51.25],[-59.15,-51.5],[-58.55,-51.1],[-57.75,-51.55],[-58.05,-51.9],[-59.4,-52.2],[-59.85,-51.85],[-60.7,-52.3],[-61.2,-51.85]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Guy.\",\"name\":\"Guyana\",\"name_long\":\"Guyana\",\"iso_a2\":\"GY\",\"iso_a3\":\"GUY\",\"iso_n3\":\"328\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-59.758284878159195,8.367034816924047],[-59.101684129458654,7.999201971870492],[-58.482962205628056,7.347691351750697],[-58.45487606467743,6.832787380394463],[-58.07810319683737,6.809093736188643],[-57.542218593970645,6.321268215353356],[-57.14743648947688,5.973149929219161],[-57.307245856339506,5.073566595882227],[-57.91428890647214,4.812626451024414],[-57.86020952007869,4.57680105226045],[-58.04469438336068,4.060863552258382],[-57.60156897645786,3.334654649260685],[-57.2814334784097,3.333491929534119],[-57.150097825739905,2.768926906745406],[-56.539385748914555,1.899522609866921],[-56.78270423036083,1.863710842288654],[-57.335822923396904,1.94853770589576],[-57.66097103537737,1.682584947105639],[-58.11344987652502,1.507195135907025],[-58.42947709820596,1.463941962078721],[-58.5400129868783,1.268088283692521],[-59.03086157900265,1.317697658692722],[-59.64604366722126,1.786893825686789],[-59.71854570172674,2.24963043864436],[-59.97452490908456,2.755232652188056],[-59.81541317405786,3.606498521332085],[-59.53803992373123,3.958802598481938],[-59.767405768458715,4.423502915866607],[-60.11100236676737,4.574966538914083],[-59.98095862490488,5.014061184098139],[-60.21368343773133,5.244486395687602],[-60.73357418480372,5.200277207861901],[-61.410302903881956,5.959068101419618],[-61.13941504580795,6.234296779806144],[-61.15933631045648,6.696077378766319],[-60.54399919294098,6.856584377464883],[-60.29566809756239,7.043911444522919],[-60.637972785063766,7.414999904810855],[-60.55058793805819,7.779602972846178],[-59.758284878159195,8.367034816924047]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Para.\",\"name\":\"Paraguay\",\"name_long\":\"Paraguay\",\"iso_a2\":\"PY\",\"iso_a3\":\"PRY\",\"iso_n3\":\"600\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-62.685057135657885,-22.249029229422387],[-62.29117936872922,-21.05163461678739],[-62.26596126977079,-20.513734633061276],[-61.786326463453776,-19.633736667562964],[-60.04356462262649,-19.342746677327426],[-59.115042487206104,-19.356906019775398],[-58.183471442280506,-19.868399346600363],[-58.166392381408045,-20.176700941653678],[-57.87067399761779,-20.73268767668195],[-57.937155727761294,-22.090175876557172],[-56.8815095689029,-22.28215382252148],[-56.47331743022939,-22.086300144135283],[-55.79795813660691,-22.356929620047822],[-55.610682745981144,-22.655619398694842],[-55.517639329639636,-23.571997572526634],[-55.40074723979542,-23.956935316668805],[-55.02790178080954,-24.001273695575225],[-54.652834235235126,-23.83957813893396],[-54.29295956075451,-24.02101409271073],[-54.29347632507745,-24.570799655863965],[-54.42894609233059,-25.162184747012166],[-54.625290696823576,-25.73925546641551],[-54.78879492859505,-26.621785577096134],[-55.69584550639816,-27.38783700939086],[-56.48670162619299,-27.548499037386293],[-57.60975969097615,-27.395898532828387],[-58.61817359071974,-27.123718763947096],[-57.633660040911124,-25.60365650808164],[-57.77721716981794,-25.16233977630904],[-58.80712846539498,-24.77145924245331],[-60.02896603050402,-24.032796319273274],[-60.84656470400991,-23.880712579038292],[-62.685057135657885,-22.249029229422387]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Sur.\",\"name\":\"Suriname\",\"name_long\":\"Suriname\",\"iso_a2\":\"SR\",\"iso_a3\":\"SUR\",\"iso_n3\":\"740\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-57.14743648947688,5.973149929219161],[-55.9493184067898,5.772877915872002],[-55.841779751190415,5.953125311706059],[-55.033250291551774,6.025291449401664],[-53.958044603070896,5.756548163267765],[-54.47863298197923,4.896755682795586],[-54.399542202356514,4.212611395683466],[-54.00693050801901,3.620037746592558],[-54.181726040246275,3.189779771330421],[-54.2697051662232,2.732391669115046],[-54.52475419779971,2.311848863123785],[-55.09758744975514,2.523748073736613],[-55.569755011606,2.421506252447131],[-55.973322109589375,2.510363877773017],[-56.0733418442903,2.220794989425499],[-55.905600145070885,2.02199575439866],[-55.99569800477175,1.817667141116601],[-56.539385748914555,1.899522609866921],[-57.150097825739905,2.768926906745406],[-57.2814334784097,3.333491929534119],[-57.60156897645786,3.334654649260685],[-58.04469438336068,4.060863552258382],[-57.86020952007869,4.57680105226045],[-57.91428890647214,4.812626451024414],[-57.307245856339506,5.073566595882227],[-57.14743648947688,5.973149929219161]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Ury.\",\"name\":\"Uruguay\",\"name_long\":\"Uruguay\",\"iso_a2\":\"UY\",\"iso_a3\":\"URY\",\"iso_n3\":\"858\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-57.62513342958296,-30.216294854454258],[-56.97602576356473,-30.109686374636127],[-55.97324459494093,-30.883075860316303],[-55.601510179249345,-30.853878676071393],[-54.57245154480512,-31.494511407193748],[-53.787951626182185,-32.047242526987624],[-53.209588995971544,-32.727666110974724],[-53.6505439927181,-33.20200408298183],[-53.373661668498244,-33.768377780900764],[-53.806425950726535,-34.396814874002224],[-54.93586605489773,-34.952646579733624],[-55.67408972840329,-34.75265878676407],[-56.21529700379607,-34.85983570733742],[-57.139685024633096,-34.430456231424245],[-57.8178606838155,-34.4625472958775],[-58.42707414410439,-33.90945444105757],[-58.34961117209887,-33.26318897881541],[-58.13264767112144,-33.040566908502015],[-58.14244035504076,-32.044503676076154],[-57.87493730328188,-31.016556084926208],[-57.62513342958296,-30.216294854454258]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Peru\",\"name\":\"Peru\",\"name_long\":\"Peru\",\"iso_a2\":\"PE\",\"iso_a3\":\"PER\",\"iso_n3\":\"604\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-69.59042375352405,-17.580011895419332],[-69.85844356960587,-18.092693780187012],[-70.37257239447771,-18.34797535570887],[-71.37525021023691,-17.773798516513857],[-71.46204077827112,-17.363487644116383],[-73.44452958850042,-16.359362888252996],[-75.23788265654144,-15.265682875227782],[-76.00920508492995,-14.649286390850321],[-76.42346920439775,-13.82318694423243],[-76.25924150257416,-13.535039157772943],[-77.10619238962184,-12.22271615972082],[-78.09215287953464,-10.377712497604065],[-79.03695309112695,-8.386567884965892],[-79.44592037628485,-7.93083342858386],[-79.76057817251004,-7.194340915560083],[-80.53748165558608,-6.541667575713717],[-81.24999630402642,-6.136834405139183],[-80.92634680858244,-5.690556735866565],[-81.41094255239946,-4.736764825055459],[-81.09966956248937,-4.036394138203697],[-80.30256059438722,-3.404856459164713],[-80.18401485870967,-3.821161797708044],[-80.46929460317695,-4.059286797708999],[-80.44224199087216,-4.425724379090674],[-80.02890804718561,-4.346090996928893],[-79.62497921417618,-4.454198093283494],[-79.20528906931771,-4.959128513207389],[-78.63989722361234,-4.547784112164074],[-78.45068396677564,-3.873096612161376],[-77.83790483265861,-3.003020521663103],[-76.63539425322672,-2.608677666843818],[-75.54499569365204,-1.56160979574588],[-75.23372270374193,-0.911416924649529],[-75.37322323271385,-0.15203175212045],[-75.10662451852008,-0.05720549886486],[-74.44160051135597,-0.530820000819887],[-74.12239518908906,-1.002832533373848],[-73.6595035468346,-1.260491224781134],[-73.07039221870724,-2.308954359550953],[-72.32578650581365,-2.434218031426454],[-71.7747607082854,-2.169789727388938],[-71.41364579942979,-2.342802422702128],[-70.81347571479196,-2.256864515800743],[-70.04770850287485,-2.725156345229699],[-70.69268205430971,-3.742872002785859],[-70.39404395209499,-3.766591485207825],[-69.89363521999663,-4.298186944194327],[-70.7947688463023,-4.251264743673303],[-70.92884334988358,-4.401591485210368],[-71.74840572781655,-4.593982842633011],[-72.89192765978726,-5.274561455916981],[-72.96450720894119,-5.741251315944893],[-73.21971126981461,-6.089188734566078],[-73.1200274319236,-6.629930922068239],[-73.72448666044164,-6.91859547285064],[-73.7234014553635,-7.340998630404414],[-73.98723548042966,-7.523829847853064],[-73.57105933296707,-8.424446709835834],[-73.01538265653254,-9.03283334720806],[-73.22671342639016,-9.462212823121234],[-72.56303300646564,-9.520193780152717],[-72.18489071316984,-10.053597914269432],[-71.30241227892154,-10.079436130415374],[-70.48189388699117,-9.490118096558845],[-70.54868567572841,-11.009146823778465],[-70.0937522040469,-11.123971856331012],[-69.52967810736496,-10.951734307502194],[-68.66507971868961,-12.561300144097173],[-68.88007951523997,-12.899729099176653],[-68.92922380234954,-13.602683607643007],[-68.9488866848366,-14.453639418193283],[-69.33953467474701,-14.953195489158832],[-69.16034664577495,-15.323973890853019],[-69.38976416693471,-15.660129082911654],[-68.9596353827533,-16.50069793057127],[-69.59042375352405,-17.580011895419332]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Ven.\",\"name\":\"Venezuela\",\"name_long\":\"Venezuela\",\"iso_a2\":\"VE\",\"iso_a3\":\"VEN\",\"iso_n3\":\"862\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-71.3315836249503,11.776284084515808],[-71.36000566271082,11.53999359786121],[-71.94704993354651,11.423282375530022],[-71.62086829292019,10.969459947142795],[-71.63306393094108,10.446494452349027],[-72.07417395698451,9.865651353388373],[-71.69564409044654,9.072263088411248],[-71.26455929226773,9.137194525585983],[-71.03999935574339,9.859992784052407],[-71.35008378771079,10.211935126176215],[-71.40062333849224,10.968969021036015],[-70.1552988349065,11.37548167566004],[-70.29384334988103,11.846822414594214],[-69.94324459499683,12.162307033736099],[-69.58430009629747,11.459610907431212],[-68.88299923366445,11.443384507691563],[-68.23327145045873,10.885744126829946],[-68.19412655299763,10.554653225135922],[-67.29624854192633,10.54586823164631],[-66.227864142508,10.648626817258688],[-65.65523759628175,10.200798855017323],[-64.89045223657817,10.0772146671913],[-64.32947872583374,10.38959870039568],[-64.31800655786495,10.64141795495398],[-63.07932247582874,10.7017243514386],[-61.880946010980196,10.715625311725104],[-62.73011898461641,10.420268662960906],[-62.388511928950976,9.94820445397464],[-61.58876746280193,9.873066921422264],[-60.83059668643172,9.38133982994894],[-60.67125240745973,8.580174261911878],[-60.15009558779618,8.602756862823426],[-59.758284878159195,8.367034816924047],[-60.55058793805819,7.779602972846178],[-60.637972785063766,7.414999904810855],[-60.29566809756239,7.043911444522919],[-60.54399919294098,6.856584377464883],[-61.15933631045648,6.696077378766319],[-61.13941504580795,6.234296779806144],[-61.410302903881956,5.959068101419618],[-60.73357418480372,5.200277207861901],[-60.60117916527194,4.91809804933213],[-60.96689327660153,4.536467596856639],[-62.08542965355914,4.162123521334308],[-62.804533047116706,4.006965033377952],[-63.0931975978991,3.770571193858785],[-63.88834286157416,4.020530096854571],[-64.62865943058755,4.14848094320925],[-64.81606401229402,4.056445217297423],[-64.36849443221409,3.797210394705246],[-64.40882788761792,3.126786200366624],[-64.26999915226578,2.497005520025567],[-63.42286739770512,2.411067613124174],[-63.36878801131166,2.200899562993129],[-64.08308549666609,1.91636912679408],[-64.19930579289051,1.49285492594602],[-64.61101192895985,1.328730576987042],[-65.35471330428837,1.0952822941085],[-65.54826738143757,0.78925446207603],[-66.32576514348496,0.724452215982012],[-66.87632585312258,1.253360500489336],[-67.18129431829307,2.250638129074062],[-67.44709204778631,2.600280869960869],[-67.8099381171237,2.820655015469569],[-67.30317318385345,3.31845408773718],[-67.33756384954368,3.542342230641722],[-67.62183590358127,3.839481716319994],[-67.82301225449355,4.503937282728899],[-67.74469662135522,5.221128648291668],[-67.52153194850275,5.556870428891969],[-67.34143958196557,6.095468044454023],[-67.69508724635502,6.267318020040647],[-68.26505245631823,6.153268133972475],[-68.98531856960236,6.206804917826858],[-69.38947994655712,6.099860541198836],[-70.09331295437242,6.96037649172311],[-70.67423356798152,7.087784735538719],[-71.96017574734864,6.991614895043538],[-72.19835242378188,7.340430813013682],[-72.44448727078807,7.423784898300481],[-72.47967892117885,7.632506008327354],[-72.36090064155596,8.002638454617895],[-72.43986223009796,8.405275376820029],[-72.6604947577681,8.625287787302682],[-72.7887298245004,9.085027167187334],[-73.30495154488005,9.151999823437606],[-73.02760413276957,9.736770331252444],[-72.9052860175347,10.450344346554772],[-72.61465776232521,10.821975409381778],[-72.22757544624294,11.10870209395324],[-71.97392167833829,11.60867157637712],[-71.3315836249503,11.776284084515808]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Afg.\",\"name\":\"Afghanistan\",\"name_long\":\"Afghanistan\",\"iso_a2\":\"AF\",\"iso_a3\":\"AFG\",\"iso_n3\":\"004\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[61.21081709172574,35.650072333309225],[62.230651483005886,35.270663967422294],[62.9846623065766,35.40404083916762],[63.19353844590035,35.857165635718914],[63.9828959491587,36.0079574651466],[64.5464791197339,36.31207326918427],[64.7461051776774,37.111817735333304],[65.58894778835784,37.30521678318564],[65.74563073106681,37.66116404881207],[66.21738488145932,37.39379018813392],[66.51860680528867,37.36278432875879],[67.07578209825962,37.35614390720929],[67.82999962755952,37.14499400486468],[68.13556237170138,37.0231151393043],[68.85944583524594,37.344335842430596],[69.19627282092438,37.15114350030743],[69.51878543485796,37.60899669041341],[70.11657840361033,37.58822276463209],[70.27057417184014,37.735164699854025],[70.3763041523093,38.13839590102752],[70.80682050973289,38.486281643216415],[71.34813113799026,38.258905341132156],[71.23940392444817,37.95326508234188],[71.54191775908478,37.905774441065645],[71.44869347523024,37.06564484308051],[71.8446382994506,36.73817129164692],[72.1930408059624,36.948287665345674],[72.63688968291729,37.047558091778356],[73.26005577992501,37.495256862939],[73.9486959166465,37.4215662704908],[74.98000247589542,37.419990139305895],[75.15802778514092,37.13303091078912],[74.57589277537298,37.02084137628346],[74.06755171091783,36.83617564548845],[72.92002485544447,36.72000702569632],[71.84629194528392,36.50994232842986],[71.26234826038575,36.074387518857804],[71.49876793812109,35.650563259416],[71.6130762063507,35.153203436822864],[71.11501875192162,34.733125718722235],[71.15677330921346,34.34891144463215],[70.8818030129884,33.98885590263851],[69.9305432473596,34.02012014417511],[70.3235941913716,33.35853261975839],[69.68714725126485,33.105498969041236],[69.26252200712256,32.5019440780883],[69.31776411324255,31.90141225842444],[68.92667687365767,31.620189113892064],[68.55693200060932,31.713310044882018],[67.79268924344478,31.58293040620963],[67.68339358914747,31.30315420178142],[66.93889122911847,31.304911200479353],[66.38145755398602,30.73889923758645],[66.34647260932442,29.887943427036177],[65.0468620136161,29.472180691031905],[64.35041873561852,29.560030625928093],[64.14800215033125,29.340819200145972],[63.55026085801117,29.468330796826162],[62.54985680527278,29.31857249604431],[60.874248488208785,29.829238999952604],[61.781221551363444,30.735850328081234],[61.699314406180825,31.37950613049267],[60.94194461451113,31.548074652628753],[60.863654819588966,32.18291962333443],[60.536077915290775,32.98126882581157],[60.963700392506006,33.52883230237625],[60.52842980331158,33.676446031218006],[60.80319339380745,34.40410187431986],[61.21081709172574,35.650072333309225]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"U.A.E.\",\"name\":\"United Arab Emirates\",\"name_long\":\"United Arab Emirates\",\"iso_a2\":\"AE\",\"iso_a3\":\"ARE\",\"iso_n3\":\"784\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[51.57951867046327,24.245497137951105],[51.757440626844186,24.29407298430547],[51.794389275932865,24.019826158132506],[52.57708051942561,24.177439276622707],[53.404006788960146,24.15131684009917],[54.00800092958758,24.121757920828212],[54.69302371604863,24.79789236093509],[55.43902469261414,25.43914520924494],[56.07082075381456,26.055464178973978],[56.261041701080956,25.71460643157677],[56.396847365144005,24.924732163995486],[55.88623253766801,24.920830593357444],[55.804118686756226,24.269604193615265],[55.981213820220454,24.130542914317825],[55.52863162620823,23.933604030853502],[55.525841098864475,23.524869289640932],[55.234489373602884,23.11099274341532],[55.20834109886319,22.708329982997046],[55.0068030129249,22.496947536707136],[52.000733270074335,23.00115448657894],[51.61770755392698,24.014219265228828],[51.57951867046327,24.245497137951105]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Arm.\",\"name\":\"Armenia\",\"name_long\":\"Armenia\",\"iso_a2\":\"AM\",\"iso_a3\":\"ARM\",\"iso_n3\":\"051\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[43.582745802592726,41.09214325618257],[44.97248009621808,41.248128567055595],[45.17949588397934,40.98535390885141],[45.56035118997045,40.812289537105926],[45.35917483905817,40.56150381119346],[45.89190717955509,40.21847565364],[45.61001224140293,39.89999380142518],[46.034534132680676,39.628020738273065],[46.48349897643246,39.464154771475535],[46.50571984231797,38.770605373686294],[46.14362308124881,38.74120148371222],[45.73537926614301,39.31971914321974],[45.73997846861698,39.47399913182713],[45.298144972521456,39.471751207022436],[45.00198733905674,39.740003567049555],[44.79398969908195,39.71300263117705],[44.4000085792887,40.00500031184228],[43.65643639504094,40.253563951166186],[43.75265791196841,40.74020091405876],[43.582745802592726,41.09214325618257]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Aze.\",\"name\":\"Azerbaijan\",\"name_long\":\"Azerbaijan\",\"iso_a2\":\"AZ\",\"iso_a3\":\"AZE\",\"iso_n3\":\"031\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[45.0019873390568,39.7400035670496],[45.29814497252144,39.471751207022436],[45.739978468617004,39.473999131827156],[45.73537926614309,39.3197191432198],[46.14362308124881,38.74120148371222],[45.457721795438744,38.874139105783115],[44.95268802265028,39.33576467544643],[44.793989699082005,39.713002631177034],[45.0019873390568,39.7400035670496]]],[[[47.373315464066216,41.219732367511256],[47.81566572448472,41.151416124021345],[47.98728315612604,41.40581920019423],[48.58435265482629,41.808869533854676],[49.11026370626067,41.282286688800525],[49.6189148293096,40.57292430272997],[50.0848295428531,40.52615713150578],[50.39282107931271,40.256561184239104],[49.5692021014448,40.17610097916071],[49.39525923035043,39.39948171646225],[49.223228387250714,39.04921885838792],[48.85653242370759,38.81548635513178],[48.88324913920255,38.32024526626264],[48.634375441284845,38.27037750910094],[48.010744256386516,38.794014797514535],[48.355529412637935,39.28876496027689],[48.06009524922527,39.582235419262446],[47.685079380083124,39.50836395930119],[46.50571984231797,38.770605373686266],[46.48349897643246,39.464154771475535],[46.034534132680704,39.62802073827305],[45.61001224140293,39.89999380142518],[45.89190717955515,40.218475653639985],[45.35917483905817,40.56150381119349],[45.560351189970476,40.81228953710595],[45.1794958839794,40.98535390885143],[44.972480096218156,41.24812856705562],[45.21742638528164,41.41145193131405],[45.962600538930445,41.1238725856098],[46.501637404166985,41.06444468847411],[46.637908156120574,41.181672675128226],[46.14543175637899,41.72280243587264],[46.404950799348825,41.86067515722735],[46.68607059101666,41.827137152669906],[47.373315464066216,41.219732367511256]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Brunei\",\"name\":\"Brunei\",\"name_long\":\"Brunei Darussalam\",\"iso_a2\":\"BN\",\"iso_a3\":\"BRN\",\"iso_n3\":\"096\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[114.20401655482837,4.525873928236805],[114.59996137904872,4.900011298029966],[115.45071048386981,5.447729803891534],[115.4057003113436,4.955227565933839],[115.34746097215067,4.316636053887009],[114.8695573263154,4.348313706881925],[114.65959598191353,4.007636826997754],[114.20401655482837,4.525873928236805]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Bang.\",\"name\":\"Bangladesh\",\"name_long\":\"Bangladesh\",\"iso_a2\":\"BD\",\"iso_a3\":\"BGD\",\"iso_n3\":\"050\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[92.67272098182556,22.041238918541254],[92.65225711463799,21.324047552978485],[92.30323449093868,21.47548533780982],[92.36855350135562,20.670883287025347],[92.08288618364612,21.19219513598577],[92.02521528520839,21.701569729086767],[91.83489098507744,22.18293569588556],[91.41708702999766,22.76501902922122],[90.49600630082728,22.80501658781513],[90.58695682166098,22.392793687422866],[90.27297081905554,21.83636770272011],[89.84746707556428,22.039146023033425],[89.70204959509493,21.857115790285306],[89.41886274613549,21.9661789006373],[89.03196129756623,22.055708319582976],[88.87631188350309,22.879146429937826],[88.52976972855377,23.631141872649163],[88.69994022009092,24.23371491138856],[88.08442223506242,24.501657212821925],[88.30637251175602,24.866079413344206],[88.93155398962308,25.238692328384776],[88.2097892598025,25.76806570078271],[88.56304935094977,26.44652558034272],[89.35509402868729,26.014407253518073],[89.83248091019962,25.96508209889548],[89.92069258012185,25.26974986419218],[90.8722107279121,25.132600612889547],[91.79959598182207,25.147431748957317],[92.37620161333481,24.976692816664965],[91.91509280799443,24.13041372323711],[91.46772993364367,24.072639471934792],[91.15896325069971,23.50352692310439],[91.70647505083211,22.985263983649183],[91.86992760617132,23.624346421802784],[92.14603478390681,23.627498684172593],[92.67272098182556,22.041238918541254]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"China\",\"name\":\"China\",\"name_long\":\"China\",\"iso_a2\":\"CN\",\"iso_a3\":\"CHN\",\"iso_n3\":\"156\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[110.33918786015154,18.678395087147607],[109.4752095886637,18.197700913968614],[108.65520796105616,18.5076819930714],[108.62621748254045,19.367887885001977],[109.11905561730802,19.821038519769385],[110.21159874882285,20.101253973872076],[110.78655073450223,20.07753449145008],[111.01005130416465,19.695929877190736],[110.57064660038682,19.255879218009312],[110.33918786015154,18.678395087147607]]],[[[127.6574072612624,49.76027049417294],[129.39781782442046,49.44060008401544],[130.5822933289824,48.72968740497612],[130.98728152885386,47.790132351261406],[132.50667199109952,47.78896963153488],[133.37359581922803,48.18344167743493],[135.02631147678673,48.47822988544391],[134.50081383681064,47.57843984637785],[134.11236209527263,47.21246735288673],[133.7696439963129,46.11692698829907],[133.09712690646646,45.14406647397217],[131.8834542176596,45.32116160743644],[131.0252120301561,44.96795319272157],[131.28855512911557,44.111519680348266],[131.14468794161488,42.92998973242695],[130.6338664084098,42.90301463477056],[130.64001590385246,42.39500946712528],[129.99426720593326,42.98538686784379],[129.5966687358795,42.42498179785459],[128.05221520397234,41.99428457291799],[128.20843305879075,41.46677155208254],[127.34378299368305,41.50315176041596],[126.86908328664988,41.81656932226616],[126.18204511932946,41.10733612727637],[125.07994184784062,40.569823716792456],[124.26562462778534,39.92849335383414],[122.86757042856104,39.63778758397626],[122.13138797413094,39.17045176854464],[121.05455447803288,38.89747101496291],[121.5859949077225,39.36085358332414],[121.37675703337267,39.750261338859524],[122.16859500538104,40.422442531896046],[121.64035851449356,40.94638987890332],[120.76862877816197,40.5933881699176],[119.63960208544907,39.89805593521421],[119.02346398323304,39.2523330755111],[118.04274865119793,39.20427399347969],[117.53270226447708,38.7376358098841],[118.0596985209897,38.06147553156105],[118.87814985562838,37.8973253443859],[118.91163618375353,37.44846385349874],[119.70280236214207,37.15638865818508],[120.82345747282366,37.87042776137798],[121.71125857959797,37.48112335870718],[122.35793745329848,37.45448415786069],[122.51999474496584,36.930614325501836],[121.10416385303303,36.65132904718044],[120.6370089051146,36.111439520811125],[119.6645618022461,35.609790554337735],[119.1512081238586,34.909859117160465],[120.22752485563375,34.36033193616862],[120.6203690939166,33.37672272392513],[121.22901411345023,32.46031871187719],[121.90814578663006,31.69217438407469],[121.89191938689035,30.949351508095102],[121.26425744027331,30.67626740164872],[121.50351932178475,30.142914943964257],[122.09211388558911,29.832520453403163],[121.93842817595308,29.018022365834806],[121.68443851123847,28.225512600206685],[121.12566124886645,28.135673122667185],[120.39547326058234,27.053206895449392],[119.58549686083958,25.740780544532612],[118.65687137255453,24.547390855400238],[117.28160647997086,23.624501451099718],[115.89073530483515,22.782873236578098],[114.76382734584624,22.66807404224167],[114.15254682826568,22.223760077396207],[113.80677981980075,22.54833974862143],[113.24107791550162,22.05136749927047],[111.84359215703248,21.550493679281512],[110.78546552942414,21.397143866455334],[110.44403934127169,20.341032619706397],[109.88986128137357,20.282457383703445],[109.62765506392466,21.008227037026728],[109.86448815311834,21.395050970947523],[108.52281294152444,21.71521230721183],[108.050180291783,21.552379869060104],[107.04342003787266,21.811898912029903],[106.56727339073537,22.218204860924743],[106.7254032735485,22.79426788989838],[105.81124718630521,22.976892401617903],[105.32920942588666,23.352063300056983],[104.4768583516645,22.81915009204692],[103.50451460166053,22.70375661873922],[102.70699222210018,22.708795070887703],[102.17043582561355,22.464753119389343],[101.65201785686158,22.31819875740956],[101.80311974488292,21.174366766845054],[101.27002566936002,21.20165192309517],[101.18000532430759,21.43657298429406],[101.15003299357826,21.84998444262902],[100.41653771362738,21.558839423096657],[99.98348921102158,21.74293671313646],[99.24089887898722,22.118314317304566],[99.53199222208744,22.949038804612595],[98.89874922078283,23.142722072842588],[98.6602624857558,24.063286037690006],[97.60471967976203,23.897404690033056],[97.72460900267916,25.083637193293043],[98.67183800658924,25.918702500913497],[98.71209394734458,26.74353587494025],[98.68269005737054,27.50881216075066],[98.24623091023338,27.74722138112918],[97.91198774616944,28.335945136014374],[97.32711388549004,28.26158274994634],[96.24883344928784,28.411030992134467],[96.58659061074755,28.83097951915437],[96.11767866413103,29.452802028922516],[95.40480228066465,29.031716620392164],[94.56599043170294,29.277438055939964],[93.41334760943268,28.640629380807237],[92.50311893104364,27.89687632904645],[91.6966565286967,27.771741848251622],[91.25885379431989,28.04061432546635],[90.73051395056783,28.064953925075738],[90.01582889197121,28.296438503527185],[89.47581017452116,28.042758897406372],[88.8142484883206,27.299315904239393],[88.73032596227856,28.08686473236756],[88.12044070836994,27.876541652939576],[86.95451704300065,27.97426178640353],[85.82331994013154,28.20357595469875],[85.01163821812307,28.642773952747376],[84.23457970575018,28.839893703724698],[83.89899295444675,29.32022614187764],[83.33711510613719,29.463731594352193],[82.32751264845089,30.115268052688208],[81.5258044778748,30.422716986608663],[81.11125613802928,30.18348094331341],[79.72136681510712,30.882714748654735],[78.73889448437401,31.51590607352705],[78.45844648632604,32.61816437431273],[79.17612877799556,32.483779812137755],[79.20889163606856,32.99439463961374],[78.81108646028574,33.506198025032404],[78.91226891471322,34.32193634697577],[77.83745079947462,35.49400950778781],[76.19284834178572,35.89840342868786],[75.89689741405019,36.66680613865188],[75.158027785141,37.13303091078916],[74.98000247589542,37.419990139305895],[74.82998579295216,37.99000702570146],[74.8648157083168,38.3788463404816],[74.2575142760227,38.60650686294349],[73.9288521666464,38.50581533462272],[73.67537926625485,39.43123688410557],[73.96001305531846,39.660008449861714],[73.82224368682833,39.89397349706314],[74.77686242055606,40.36642527929163],[75.46782799673073,40.56207225194868],[76.52636803579745,40.42794607193513],[76.90448449087712,41.06648590754966],[78.18719689322606,41.185315863604814],[78.54366092317528,41.58224254003871],[80.11943037305142,42.123940741538235],[80.25999026888533,42.34999929459909],[80.18015018099439,42.92006785742686],[80.86620649610123,43.180362046881015],[79.96610639844144,44.91751699480462],[81.9470707539181,45.31702749285316],[82.45892581576905,45.539649563166506],[83.18048383986054,47.33003123635075],[85.16429039911324,47.00095571551611],[85.7204838398707,47.45296946877309],[85.76823286330838,48.4557506373969],[86.59877648310336,48.54918162698061],[87.35997033076269,49.21498078062916],[87.75126427607668,49.29719798440547],[88.0138322285517,48.5994627956006],[88.85429772334678,48.069081732773014],[90.28082563676392,47.693549099307916],[90.97080936072499,46.888146063822944],[90.58576826371834,45.7197160914875],[90.94553958533433,45.28607330991025],[92.13389082231825,45.115075995456436],[93.48073367714133,44.975472113620015],[94.68892866412537,44.35233185482846],[95.30687544147153,44.24133087826547],[95.7624548685567,43.31944916439462],[96.34939578652782,42.72563528092866],[97.451757440178,42.74888967546008],[99.51581749878002,42.524691473961695],[100.8458655131083,42.663804429691425],[101.83304039917995,42.51487295182628],[103.31227827353482,41.90746816666763],[104.52228193564903,41.90834666601663],[104.96499393109346,41.59740957291635],[106.12931562706169,42.1343277044289],[107.744772576938,42.481515814781915],[109.24359581913146,42.51944631608416],[110.4121033061153,42.87123362891103],[111.12968224492023,43.40683401140018],[111.8295878438814,43.74311839453949],[111.66773725794323,44.07317576758771],[111.34837690637946,44.45744171811006],[111.87330610560028,45.10207937273512],[112.43606245325887,45.01164561622426],[113.46390669154422,44.80889313412711],[114.46033165899607,45.33981679949389],[115.98509647020013,45.72723501238602],[116.71786828009888,46.388202419615254],[117.42170128791425,46.67273285581421],[118.87432579963873,46.80541209572365],[119.66326989143877,46.69267995867895],[119.77282392789756,47.04805878355015],[118.86657433479498,47.74706004494621],[118.06414269416675,48.06673045510374],[117.29550744025747,47.6977090521074],[116.30895267137325,47.853410142602826],[115.74283735561575,47.72654450132629],[115.48528201707305,48.135382595403456],[116.19180219936761,49.13459809019906],[116.67880089728621,49.888531399121405],[117.87924441942639,49.51098338479696],[119.28846072802585,50.14288279886205],[119.27936567594239,50.582907619827296],[120.18204959521695,51.64356639261803],[120.738191359542,51.964115302124554],[120.725789015792,52.516226304730814],[120.1770886577169,52.75388621684121],[121.00308475147024,53.25140106873124],[122.24574791879289,53.43172597921369],[123.57150678924087,53.45880442973464],[125.06821129771045,53.161044826868846],[125.94634891164618,52.79279857035695],[126.56439904185699,51.7842554795327],[126.93915652883769,51.3538941514059],[127.28745568248493,50.73979726826545],[127.6574072612624,49.76027049417294]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Bhutan\",\"name\":\"Bhutan\",\"name_long\":\"Bhutan\",\"iso_a2\":\"BT\",\"iso_a3\":\"BTN\",\"iso_n3\":\"064\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[91.69665652869668,27.77174184825166],[92.10371178585973,27.452614040633208],[92.03348351437509,26.83831045176356],[91.21751264848643,26.808648179628022],[90.37327477413407,26.87572418874288],[89.74452762243884,26.719402981059957],[88.83564253128938,27.098966376243762],[88.81424848832054,27.29931590423936],[89.47581017452111,28.042758897406397],[90.01582889197118,28.296438503527217],[90.7305139505678,28.064953925075756],[91.25885379431992,28.040614325466294],[91.69665652869668,27.77174184825166]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"N. Cy.\",\"name\":\"N. Cyprus\",\"name_long\":\"Northern Cyprus\",\"iso_a2\":null,\"iso_a3\":null,\"iso_n3\":null},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[32.73178022637745,35.14002594658844],[32.80247358575275,35.14550364841138],[32.946960890440806,35.3867033961337],[33.667227003724946,35.37321584730551],[34.57647382990046,35.67159556735879],[33.900804477684204,35.245755927057616],[33.97361657078346,35.058506374648],[33.86643965021011,35.09359467217419],[33.675391880027064,35.01786286065045],[33.5256852556775,35.03868846286407],[33.475817498515845,35.000344550103506],[33.45592207208347,35.10142365166641],[33.3838334490363,35.16271190036457],[33.19097700372305,35.17312470147138],[32.919572381326134,35.08783274997364],[32.73178022637745,35.14002594658844]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Cyp.\",\"name\":\"Cyprus\",\"name_long\":\"Cyprus\",\"iso_a2\":\"CY\",\"iso_a3\":\"CYP\",\"iso_n3\":\"196\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[33.97361657078346,35.058506374648],[34.00488081232004,34.97809784600186],[32.97982710137845,34.57186941175544],[32.49029625827753,34.701654771456475],[32.25666710788596,35.10323232679663],[32.73178022637745,35.14002594658844],[32.919572381326134,35.08783274997364],[33.19097700372305,35.17312470147138],[33.3838334490363,35.16271190036457],[33.45592207208347,35.10142365166641],[33.475817498515845,35.000344550103506],[33.5256852556775,35.03868846286407],[33.675391880027064,35.01786286065045],[33.86643965021011,35.09359467217419],[33.97361657078346,35.058506374648]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Geo.\",\"name\":\"Georgia\",\"name_long\":\"Georgia\",\"iso_a2\":\"GE\",\"iso_a3\":\"GEO\",\"iso_n3\":\"268\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[41.55408410011065,41.53565623632757],[41.70317060727271,41.96294281673292],[41.45347008643839,42.64512339941794],[40.87546919125379,43.013628038091284],[40.32139448422032,43.128633938156845],[39.955008579270924,43.43499766699922],[40.07696495947977,43.55310415300231],[40.922184686045625,43.38215851498079],[42.39439456560882,43.22030792904263],[43.75601688006739,42.74082815202249],[43.931199985536836,42.55497386328477],[44.537622918481986,42.71199270280363],[45.47027916848572,42.50278066666998],[45.77641035338277,42.09244395605636],[46.404950799348825,41.860675157227305],[46.14543175637902,41.72280243587258],[46.63790815612058,41.181672675128226],[46.50163740416693,41.06444468847411],[45.96260053893039,41.123872585609774],[45.217426385281584,41.41145193131405],[44.97248009621808,41.248128567055595],[43.582745802592726,41.09214325618257],[42.61954878110449,41.58317271581994],[41.55408410011065,41.53565623632757]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Indo.\",\"name\":\"Indonesia\",\"name_long\":\"Indonesia\",\"iso_a2\":\"ID\",\"iso_a3\":\"IDN\",\"iso_n3\":\"360\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[120.71560875863044,-10.239581394087864],[120.2950142762069,-10.258649997603525],[118.96780846565471,-9.557969252158031],[119.90030968636161,-9.361340427287516],[120.42575564990543,-9.665921319215798],[120.77550174365675,-9.969675388227456],[120.71560875863044,-10.239581394087864]]],[[[124.43595014861941,-10.140000909061442],[123.57998172413673,-10.359987481327963],[123.45998904835503,-10.239994805546175],[123.55000939340746,-9.90001555749798],[123.98000898650811,-9.290026950724695],[124.96868248911622,-8.892790215697048],[125.07001997284064,-9.089987481322837],[125.08852013560109,-9.393173109579322],[124.43595014861941,-10.140000909061442]]],[[[117.90001834520777,-8.095681247594925],[118.2606164897405,-8.362383314653329],[118.87845991422215,-8.28068287519983],[119.12650678922309,-8.705824883665073],[117.9704016459893,-8.906639499551261],[117.27773074754903,-9.040894870645559],[116.74014082241663,-9.03293670007264],[117.08373742072533,-8.457157891476541],[117.63202436734215,-8.449303073768192],[117.90001834520777,-8.095681247594925]]],[[[122.90353722543611,-8.094234307490737],[122.75698286345632,-8.64980763106064],[121.25449059457013,-8.933666273639943],[119.9243909038096,-8.810417982623875],[119.92092858284613,-8.444858900591072],[120.71509199430757,-8.236964613480865],[121.34166873584658,-8.536739597206022],[122.00736453663043,-8.460620212440162],[122.90353722543611,-8.094234307490737]]],[[[108.62347863162894,-6.777673841990676],[110.53922732955331,-6.877357679881683],[110.75957563684594,-6.465186455921752],[112.61481123255638,-6.946035658397591],[112.97876834518812,-7.59421314863458],[114.47893517462117,-7.776527601760279],[115.70552697150109,-8.370806573116866],[114.56451134649652,-8.751816908404834],[113.4647335144609,-8.348947442257426],[112.55967247930103,-8.376180922075164],[111.52206139531248,-8.302128594600957],[110.58614953007432,-8.122604668819022],[109.4276672709552,-7.740664157749761],[108.69365522668133,-7.641600437046221],[108.27776329959632,-7.766657403192581],[106.45410200401615,-7.354899590690947],[106.28062422081231,-6.924899997590202],[105.36548628135554,-6.85141611087117],[106.05164594932707,-5.8959188777945],[107.2650085795402,-5.954985039904059],[108.0720910990747,-6.345762220895239],[108.48684614464926,-6.421984958525768],[108.62347863162894,-6.777673841990676]]],[[[134.72462446506668,-6.214400730009287],[134.21013390516893,-6.895237725454706],[134.112775506731,-6.142467136259014],[134.2903357280858,-5.783057549669039],[134.49962527886788,-5.445042006047899],[134.72700158095213,-5.73758228925216],[134.72462446506668,-6.214400730009287]]],[[[127.24921512258892,-3.45906503663889],[126.87492272349888,-3.79098276124958],[126.18380211802733,-3.607376397316556],[125.98903364471929,-3.177273451351325],[127.00065148326499,-3.12931772218441],[127.24921512258892,-3.45906503663889]]],[[[130.4713440288518,-3.09376433676762],[130.8348360535928,-3.858472181822762],[129.99054650280814,-3.446300957862817],[129.15524865124243,-3.362636813982249],[128.59068362845366,-3.428679294451257],[127.89889122936236,-3.393435967628193],[128.1358793478528,-2.843650404474914],[129.37099775606092,-2.802154229344552],[130.4713440288518,-3.09376433676762]]],[[[134.1433679546478,-1.151867364103595],[134.42262739475305,-2.769184665542383],[135.4576029806947,-3.367752780779114],[136.2933142437188,-2.30704233155609],[137.44073774632753,-1.703513278819372],[138.3297274110448,-1.70268645590265],[139.18492068904297,-2.051295668143638],[139.92668419816042,-2.409051608900284],[141.00021040259188,-2.600151055515624],[141.01705691951904,-5.859021905138022],[141.0338517600139,-9.117892754760419],[140.14341515519257,-8.297167657100957],[139.12776655492812,-8.096042982620942],[138.88147667862498,-8.380935153846096],[137.61447391169284,-8.411682631059762],[138.0390991558352,-7.597882175327356],[138.6686214540148,-7.320224704623072],[138.40791385310237,-6.232849216337484],[137.92783979711086,-5.393365573756],[135.98925011611348,-4.546543877789048],[135.16459760959972,-4.462931410340772],[133.6628804871979,-3.538853448097527],[133.3677047059468,-4.024818617370315],[132.98395551974735,-4.112978610860281],[132.756940952689,-3.74628264731713],[132.75378869031923,-3.311787204607072],[131.9898043153162,-2.820551039240456],[133.0668445171435,-2.460417982598443],[133.78003095920351,-2.47984832114021],[133.69621178602614,-2.214541517753688],[132.23237348849423,-2.212526136894326],[131.8362219585447,-1.617161960459597],[130.94283979708283,-1.432522067880797],[130.51955814018007,-0.937720228686075],[131.86753787651364,-0.695461114101818],[132.3801164084168,-0.369537855636977],[133.98554813042844,-0.780210463060442],[134.1433679546478,-1.151867364103595]]],[[[125.24050052297159,1.419836127117605],[124.43703535369737,0.427881171058971],[123.68550499887672,0.235593166500877],[122.72308312387288,0.431136786293337],[121.0567248881891,0.381217352699451],[120.18308312386276,0.23724681233422],[120.04086958219548,-0.519657891444851],[120.93590538949073,-1.408905938323372],[121.4758207540762,-0.955962009285116],[123.34056481332848,-0.615672702643081],[123.2583992859845,-1.076213067228338],[122.82271528533161,-0.930950616055881],[122.38852990121539,-1.516858005381124],[121.50827355355548,-1.904482924002423],[122.4545723816843,-3.186058444840882],[122.27189619353257,-3.529500013852697],[123.17096276254657,-4.683693129091708],[123.16233279835379,-5.340603936385961],[122.62851525277871,-5.634591159694494],[122.23639448454806,-5.282933037948283],[122.71956912647707,-4.46417164471579],[121.73823367725439,-4.8513314754465],[121.48946333220127,-4.574552504091216],[121.61917117725388,-4.188477878438674],[120.89818159391771,-3.602105401222829],[120.97238895068877,-2.62764291749491],[120.30545291552991,-2.931603692235726],[120.39004723519176,-4.097579034037224],[120.43071658740539,-5.528241062037779],[119.79654341031952,-5.673400160345651],[119.36690555224496,-5.379878024927805],[119.65360639860013,-4.459417412944958],[119.49883548388597,-3.49441171632651],[119.078344354327,-3.487021986508765],[118.7677689962529,-2.801999200047689],[119.18097374885869,-2.147103773612798],[119.32339399625508,-1.353147067880471],[119.82599897672586,0.154254462073496],[120.03570193896637,0.566477362465804],[120.8857792501677,1.309222723796836],[121.666816847827,1.013943589681077],[122.92756676645185,0.875192368977466],[124.07752241424285,0.917101955566139],[125.06598921112183,1.643259182131558],[125.24050052297159,1.419836127117605]]],[[[128.68824873262074,1.132385972494106],[128.63595218314137,0.258485826006179],[128.12016971243617,0.356412665199286],[127.96803429576887,-0.252077325037533],[128.37999881399972,-0.780003757331286],[128.10001590384232,-0.899996433112975],[127.69647464407504,-0.266598402511505],[127.39949018769377,1.011721503092573],[127.60051150930907,1.810690822757181],[127.93237755748751,2.174596258956555],[128.00415612194084,1.628531398928331],[128.59455936087548,1.540810655112864],[128.68824873262074,1.132385972494106]]],[[[117.87562706916603,1.827640692548911],[118.99674726773819,0.902219143066048],[117.81185835171779,0.784241848143722],[117.47833865770608,0.102474676917026],[117.52164350796662,-0.803723239753211],[116.56004845587952,-1.487660821136231],[116.53379682827519,-2.483517347832901],[116.14808393764864,-4.012726332214015],[116.0008577820491,-3.657037448749008],[114.86480309454454,-4.106984144714417],[114.46865156459509,-3.495703627133821],[113.75567182826413,-3.43916961020652],[113.25699425664757,-3.118775729996855],[112.06812625534067,-3.478392022316072],[111.70329064336002,-2.994442233902632],[111.04824018762824,-3.049425957861189],[110.223846063276,-2.934032484553484],[110.07093550012436,-1.592874037282414],[109.57194786991406,-1.314906507984489],[109.09187381392253,-0.459506524257051],[108.95265750532816,0.415375474444346],[109.06913618371404,1.341933905437642],[109.66326012577375,2.006466986494985],[109.83022667850886,1.338135687664192],[110.51406090702713,0.773131415200993],[111.15913781132659,0.976478176269509],[111.79754845586044,0.904441229654651],[112.38025190638368,1.410120957846758],[112.8598091980522,1.497790025229946],[113.80584964401956,1.217548732911041],[114.6213554220175,1.430688177898887],[115.13403730678523,2.821481838386219],[115.51907840379201,3.169238389494396],[115.86551720587677,4.306559149590157],[117.01521447150637,4.306094061699469],[117.88203494677019,4.137551377779488],[117.31323245653354,3.234428208830579],[118.04832970588538,2.287690131027361],[117.87562706916603,1.827640692548911]]],[[[105.81765506390936,-5.852355645372413],[104.71038414919151,-5.873284600450646],[103.86821333213074,-5.037314955264975],[102.58426069540693,-4.220258884298204],[102.15617313030103,-3.614146009946765],[101.39911339722508,-2.799777113459172],[100.90250288290017,-2.05026213949786],[100.14198082886062,-0.650347588710957],[99.26373986206025,0.183141587724663],[98.97001102091333,1.042882391764536],[98.60135135294311,1.823506577965617],[97.6995976094499,2.453183905442117],[97.1769421732499,3.30879059489861],[96.42401655475734,3.868859768077911],[95.38087609251347,4.970782172053673],[95.29302615761733,5.479820868344817],[95.93686282754176,5.439513251157109],[97.4848820332771,5.246320909034011],[98.36916914265569,4.268370266126368],[99.14255862833582,3.590349636240916],[99.69399783732243,3.174328518075157],[100.64143354696168,2.099381211755798],[101.65801232300734,2.083697414555189],[102.49827111207324,1.398700466310217],[103.07684044801303,0.561361395668854],[103.83839603069835,0.104541734208667],[103.43764529827497,-0.711945896002845],[104.01078860882402,-1.059211521004229],[104.3699914896849,-1.084843031421016],[104.53949018760218,-1.782371514496716],[104.88789269411402,-2.340425306816655],[105.622111444117,-2.42884368246807],[106.10859337771271,-3.06177662517895],[105.85744591677414,-4.305524997579724],[105.81765506390936,-5.852355645372413]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"India\",\"name\":\"India\",\"name_long\":\"India\",\"iso_a2\":\"IN\",\"iso_a3\":\"IND\",\"iso_n3\":\"356\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[77.83745079947457,35.494009507787766],[78.91226891471322,34.32193634697579],[78.81108646028574,33.50619802503242],[79.20889163606857,32.994394639613716],[79.17612877799553,32.48377981213771],[78.45844648632601,32.61816437431273],[78.73889448437401,31.515906073527063],[79.7213668151071,30.882714748654728],[81.11125613802932,30.183480943313402],[80.4767212259174,29.72986522065534],[80.08842451367627,28.79447011974014],[81.05720258985203,28.416095282499043],[81.99998742058497,27.925479234319994],[83.30424889519955,27.36450572357556],[84.6750179381738,27.234901231387536],[85.25177859898338,26.72619843190634],[86.02439293817918,26.63098460540857],[87.22747195836628,26.397898057556077],[88.06023766474982,26.41461538340249],[88.17480431514092,26.81040517832595],[88.04313276566123,27.445818589786825],[88.12044070836987,27.876541652939594],[88.73032596227856,28.086864732367516],[88.81424848832054,27.29931590423936],[88.83564253128938,27.098966376243762],[89.74452762243884,26.719402981059957],[90.37327477413407,26.87572418874288],[91.21751264848643,26.808648179628022],[92.03348351437509,26.83831045176356],[92.10371178585973,27.452614040633208],[91.69665652869668,27.77174184825166],[92.50311893104364,27.89687632904645],[93.41334760943268,28.640629380807226],[94.56599043170294,29.277438055939985],[95.40480228066464,29.03171662039213],[96.11767866413103,29.452802028922466],[96.58659061074749,28.830979519154344],[96.24883344928779,28.41103099213444],[97.32711388549004,28.26158274994634],[97.40256147663612,27.88253611908544],[97.0519885599681,27.69905894623315],[97.1339990580153,27.083773505149964],[96.41936567585097,27.264589341739224],[95.12476769407496,26.5735720891323],[95.1551534362626,26.001307277932085],[94.60324913938538,25.162495428970402],[94.55265791217164,24.675238348890332],[94.10674197792505,23.85074087167348],[93.3251876159428,24.078556423432204],[93.28632693885928,23.043658352139005],[93.06029422401463,22.70311066333557],[93.16612755734836,22.278459580977103],[92.67272098182556,22.041238918541254],[92.14603478390681,23.627498684172593],[91.86992760617132,23.624346421802784],[91.70647505083211,22.985263983649183],[91.15896325069971,23.50352692310439],[91.46772993364367,24.072639471934792],[91.91509280799443,24.13041372323711],[92.37620161333481,24.976692816664965],[91.79959598182207,25.147431748957317],[90.8722107279121,25.132600612889547],[89.92069258012185,25.26974986419218],[89.83248091019962,25.96508209889548],[89.35509402868729,26.014407253518073],[88.56304935094977,26.44652558034272],[88.2097892598025,25.76806570078271],[88.93155398962308,25.238692328384776],[88.30637251175602,24.866079413344206],[88.08442223506242,24.501657212821925],[88.69994022009092,24.23371491138856],[88.52976972855377,23.631141872649163],[88.87631188350309,22.879146429937826],[89.03196129756623,22.055708319582976],[88.88876590368542,21.690588487224748],[88.20849734899521,21.703171698487807],[86.97570438024027,21.49556163175521],[87.03316857294887,20.743307806882413],[86.49935102737378,20.151638495356607],[85.0602657409097,19.4785788029711],[83.94100589390001,18.302009792549725],[83.18921715691785,17.67122142177898],[82.19279218946592,17.016636053937813],[82.19124189649719,16.556664130107848],[81.69271935417748,16.310219224507904],[80.79199913933014,15.951972357644491],[80.32489586784388,15.899184882058348],[80.02506920768644,15.136414903214147],[80.2332735533904,13.835770778859981],[80.28629357292186,13.006260687710833],[79.8625468281285,12.056215318240888],[79.85799930208682,10.35727509199711],[79.340511509116,10.30885427493962],[78.88534549348918,9.546135972527722],[79.18971967968828,9.216543687370148],[78.2779407083305,8.933046779816934],[77.94116539908435,8.252959092639742],[77.53989790233794,7.965534776232333],[76.59297895702167,8.89927623131419],[76.13006147655108,10.299630031775521],[75.74646731964849,11.308250637248307],[75.39610110870957,11.781245022015824],[74.86481570831681,12.741935736537897],[74.61671715688354,13.99258291264968],[74.44385949086723,14.617221787977696],[73.5341992532334,15.99065216721496],[73.11990929554943,17.928570054592498],[72.82090945830865,19.208233547436166],[72.8244751321368,20.419503282141534],[72.6305334817454,21.356009426351008],[71.17527347197395,20.757441311114235],[70.4704586119451,20.877330634031384],[69.16413008003883,22.0892980005727],[69.64492760608239,22.450774644454338],[69.34959679553435,22.84317963306269],[68.1766451353734,23.69196503345671],[68.84259931831878,24.35913361256094],[71.04324018746823,24.3565239527302],[70.84469933460284,25.21510203704352],[70.28287316272558,25.72222870533983],[70.16892662952202,26.491871649678842],[69.51439293811312,26.940965684511372],[70.61649620960193,27.989196275335868],[71.77766564320032,27.913180243434525],[72.8237516620847,28.961591701772054],[73.45063846221743,29.97641347911987],[74.42138024282026,30.979814764931177],[74.40592898956501,31.69263947196528],[75.25864179881322,32.2711054550405],[74.45155927927871,32.7648996038055],[74.10429365427734,33.44147329358685],[73.74994835805195,34.31769887952785],[74.24020267120497,34.74888703057125],[75.75706098826834,34.50492259372132],[76.87172163280403,34.65354401299274],[77.83745079947457,35.494009507787766]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Iran\",\"name\":\"Iran\",\"name_long\":\"Iran\",\"iso_a2\":\"IR\",\"iso_a3\":\"IRN\",\"iso_n3\":\"364\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[53.92159793479556,37.19891836196126],[54.800303989486565,37.392420762678185],[55.51157840355191,37.96411713312317],[56.18037479027333,37.93512665460742],[56.61936608259282,38.121394354803485],[57.33043379092898,38.02922943781094],[58.4361544126782,37.522309475243794],[59.23476199731681,37.412987982730336],[60.37763797388387,36.52738312432837],[61.123070509694145,36.49159719496624],[61.21081709172574,35.650072333309225],[60.80319339380745,34.40410187431986],[60.52842980331158,33.676446031218006],[60.963700392506006,33.52883230237625],[60.536077915290775,32.98126882581157],[60.863654819588966,32.18291962333443],[60.94194461451113,31.548074652628753],[61.699314406180825,31.37950613049267],[61.781221551363444,30.735850328081234],[60.874248488208785,29.829238999952604],[61.36930870956494,29.303276272085924],[61.77186811711863,28.699333807890795],[62.72783043808598,28.25964488373539],[62.755425652929866,27.378923448184985],[63.233897739520295,27.21704702403071],[63.31663170761959,26.756532497661667],[61.87418745305655,26.239974880472104],[61.49736290878419,25.0782370061185],[59.61613406763084,25.380156561783778],[58.5257613462723,25.60996165618573],[57.397251417882394,25.73990204518364],[56.970765822177555,26.96610626882136],[56.492138706290206,27.143304755150197],[55.72371015811006,26.96463349050104],[54.71508955263727,26.480657863871514],[53.49309695823135,26.81236888275305],[52.48359785340961,27.580849107365495],[51.52076256694741,27.865689602158298],[50.85294803243954,28.814520575469384],[50.115008579311585,30.147772528599717],[49.576850213423995,29.985715236932407],[48.94133344909855,30.317090359004037],[48.567971225789755,29.926778265903522],[48.0145683123761,30.452456773392598],[48.004698113808324,30.985137437457244],[47.68528608581227,30.98485321707963],[47.8492037290421,31.70917593029867],[47.3346614927119,32.469155381799105],[46.10936160663932,33.017287299119005],[45.41669070819904,33.967797756479584],[45.64845950702809,34.748137722303014],[46.15178795755093,35.09325877536429],[46.0763403664048,35.67738332777549],[45.4206181170532,35.97754588474282],[44.77267,37.17045],[44.22575564960053,37.97158437758935],[44.421402622257546,38.28128123631454],[44.10922529478234,39.4281362981681],[44.79398969908195,39.71300263117705],[44.95268802265031,39.33576467544637],[45.45772179543877,38.87413910578306],[46.14362308124881,38.74120148371222],[46.50571984231797,38.770605373686294],[47.685079380083096,39.508363959301214],[48.06009524922524,39.58223541926246],[48.35552941263788,39.28876496027691],[48.01074425638648,38.79401479751452],[48.63437544128481,38.27037750910097],[48.88324913920249,38.32024526626262],[49.19961225769334,37.58287425388988],[50.14777143738462,37.37456655532134],[50.84235436381971,36.8728142359834],[52.264024692601424,36.7004216578577],[53.82578982932642,36.965030829408235],[53.92159793479556,37.19891836196126]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Jord.\",\"name\":\"Jordan\",\"name_long\":\"Jordan\",\"iso_a2\":\"JO\",\"iso_a3\":\"JOR\",\"iso_n3\":\"400\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[35.54566531753454,32.393992011030576],[35.71991824722275,32.709192409794866],[36.834062127435544,32.312937526980775],[38.792340529136084,33.378686428352225],[39.19546837744497,32.16100881604267],[39.00488569515255,32.01021698661498],[37.00216556168101,31.508412990844743],[37.998848911294374,30.50849986421313],[37.66811974462638,30.3386652694859],[37.503581984209035,30.003776150018403],[36.74052778498725,29.86528331147619],[36.50121422704358,29.5052536076987],[36.06894087092206,29.197494615184457],[34.95603722508426,29.35655467377884],[34.92260257339142,29.501326198844524],[35.420918409981965,31.100065822874356],[35.397560662586045,31.48908600516758],[35.5452519060762,31.78250478772084],[35.54566531753454,32.393992011030576]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Isr.\",\"name\":\"Israel\",\"name_long\":\"Israel\",\"iso_a2\":\"IL\",\"iso_a3\":\"ISR\",\"iso_n3\":\"376\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[35.71991824722275,32.709192409794866],[35.54566531753454,32.393992011030576],[35.183930291491436,32.53251068778894],[34.97464074070933,31.86658234305972],[35.22589155451242,31.754341132121766],[34.970506626125996,31.616778469360806],[34.92740848159457,31.353435370401414],[35.397560662586045,31.48908600516758],[35.420918409981965,31.100065822874356],[34.92260257339142,29.501326198844524],[34.26543338393568,31.219360866820153],[34.55637169773891,31.548823960896996],[34.48810713068136,31.60553884533732],[34.752587111151165,32.07292633720117],[34.95541710789677,32.82737641044638],[35.098457472480675,33.080539252244265],[35.126052687324545,33.09090037691878],[35.460709262846706,33.08904002535628],[35.55279666519081,33.26427480725802],[35.82110070165024,33.2774264592763],[35.836396925608625,32.86812327730851],[35.700797967274745,32.71601369885738],[35.71991824722275,32.709192409794866]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Japan\",\"name\":\"Japan\",\"name_long\":\"Japan\",\"iso_a2\":\"JP\",\"iso_a3\":\"JPN\",\"iso_n3\":\"392\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[134.63842817600388,34.14923371025642],[134.7663790223585,33.80633474378368],[134.20341596897086,33.20117788342964],[133.7929500672765,33.5219851750976],[133.28026818250888,33.28957042086495],[133.01485802625788,32.70456736910478],[132.3631148621927,32.98938202568137],[132.37117638563018,33.46364248304007],[132.9243725933148,34.06029857028204],[133.49296837782222,33.9446208765967],[133.90410607313638,34.36493113864262],[134.63842817600388,34.14923371025642]]],[[[140.9763875673053,37.14207428644016],[140.59976972876214,36.343983466124534],[140.77407433488264,35.84287710219024],[140.25327925024513,35.13811391859365],[138.97552778539622,34.66760000257611],[137.21759891169123,34.60628591566186],[135.7929830262689,33.46480520276663],[135.12098270074543,33.84907115328906],[135.07943484918272,34.59654490817482],[133.340316196832,34.37593821872076],[132.15677086805132,33.90493337659652],[130.98614464734348,33.88576142021628],[132.00003624891005,33.149992377244615],[131.33279015515737,31.450354519164843],[130.68631798718596,31.029579169228242],[130.20241987520498,31.418237616495418],[130.44767622286216,32.319474595665724],[129.8146916037189,32.61030955660439],[129.40846316947258,33.29605581311759],[130.35393517468466,33.6041507024417],[130.87845096244715,34.232742824840045],[131.88422936414392,34.74971385348791],[132.61767296766251,35.43339305270942],[134.6083008159778,35.73161774346582],[135.67753787652893,35.527134100886826],[136.72383060114245,37.30498423924038],[137.3906116070045,36.827390651998826],[138.85760216690628,37.82748464614346],[139.4264046571429,38.21596222589764],[140.0547900738121,39.438807481436385],[139.88337934789988,40.563312486323696],[140.30578250545372,41.19500519465956],[141.3689734234267,41.37855988216029],[141.91426313697048,39.99161611587868],[141.884600864835,39.180864569651504],[140.9594893739458,38.17400096287658],[140.9763875673053,37.14207428644016]]],[[[143.9101619813795,44.17409983985373],[144.61342654843963,43.960882880217525],[145.3208252300831,44.38473297787544],[145.54313724180278,43.262088324550604],[144.0596618999999,42.98835826270056],[143.18384972551732,41.9952147486992],[141.61149092017249,42.67879059505608],[141.06728641170665,41.58459381770799],[139.95510623592108,41.569555975911044],[139.81754357315995,42.5637588567744],[140.31208703019323,43.33327261003265],[141.38054894426003,43.388824774746496],[141.67195234595394,44.77212535255148],[141.967644891528,45.55148346616135],[143.14287031470982,44.510358384776964],[143.9101619813795,44.17409983985373]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Iraq\",\"name\":\"Iraq\",\"name_long\":\"Iraq\",\"iso_a2\":\"IQ\",\"iso_a3\":\"IRQ\",\"iso_n3\":\"368\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[45.4206181170532,35.97754588474282],[46.0763403664048,35.67738332777549],[46.15178795755093,35.09325877536429],[45.64845950702809,34.748137722303014],[45.41669070819904,33.967797756479584],[46.10936160663932,33.017287299119005],[47.3346614927119,32.469155381799105],[47.8492037290421,31.70917593029867],[47.68528608581227,30.98485321707963],[48.004698113808324,30.985137437457244],[48.0145683123761,30.452456773392598],[48.567971225789755,29.926778265903522],[47.974519077349896,29.9758192001485],[47.30262210469096,30.05906993257072],[46.568713413281756,29.09902517345229],[44.70949873228474,29.178891099559383],[41.889980910007836,31.190008653278365],[40.399994337736246,31.889991766887935],[39.19546837744497,32.16100881604267],[38.792340529136084,33.378686428352225],[41.006158888519934,34.41937226006212],[41.383965285005814,35.628316555314356],[41.289707472505455,36.35881460219227],[41.83706424334096,36.605853786763575],[42.34959109881177,37.2298725449041],[42.77912560402182,37.385263576805755],[43.9422587420473,37.25622752537295],[44.29345177590286,37.0015143906063],[44.772699008977696,37.170444647768434],[45.4206181170532,35.97754588474282]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Kaz.\",\"name\":\"Kazakhstan\",\"name_long\":\"Kazakhstan\",\"iso_a2\":\"KZ\",\"iso_a3\":\"KAZ\",\"iso_n3\":\"398\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[70.96231489449929,42.26615428320554],[70.3889648782208,42.081307684897524],[69.07002729683524,41.38424428971234],[68.63248294462005,40.66868073176687],[68.25989586779565,40.6623245305949],[67.98585574735182,41.135990708982206],[66.7140470722166,41.168443508461564],[66.51064863471572,41.987644151368556],[66.02339155463562,41.99464630794404],[66.0980123228652,42.99766002051308],[64.90082441595933,43.728080552742654],[63.185786981056594,43.650074978198006],[62.01330040878628,43.50447663021566],[61.0583199400325,44.40581696225058],[60.23997195825847,44.784036770194746],[58.6899890480958,45.50001373959873],[58.50312706892844,45.586804307632974],[55.92891727074118,44.99585846615918],[55.968191359283026,41.30864166926938],[55.45525109235381,41.25985911718584],[54.75534549339267,42.04397146256662],[54.07941775901497,42.32410940202084],[52.94429324729174,42.11603424739758],[52.50245975119628,41.78331553808647],[52.44633914572722,42.027150783855575],[52.692112257707265,42.44389537207337],[52.50142622255032,42.7922978785852],[51.342427199108215,43.132974758469345],[50.89129194520024,44.03103363705378],[50.339129266161365,44.284015611338475],[50.305642938036264,44.609835516938915],[51.278503452363225,44.51485423438646],[51.316899041556034,45.2459982366679],[52.16738976421573,45.40839142514511],[53.0408764992452,45.25904653582177],[53.220865512917726,46.234645901059935],[53.042736850807785,46.853006089864486],[52.04202273947561,46.80463694923924],[51.191945428274266,47.048704738953916],[50.03408328634248,46.60898997658222],[49.10116000000011,46.399330000000134],[48.593241001180495,46.56103424741547],[48.694733514201744,47.07562816017793],[48.05725304544927,47.74375275327952],[47.31523115417024,47.715847479841955],[46.46644575377627,48.39415233010493],[47.043671502476506,49.152038886097614],[46.75159630716274,49.35600576435377],[47.5494804217493,50.454698391311126],[48.57784142435752,49.87475962991567],[48.70238162618102,50.60512848571284],[50.76664839051215,51.6927623561599],[52.32872358583097,51.718652248738124],[54.532878452376224,51.02623973245932],[55.716940545479815,50.62171662047853],[56.777961053296565,51.04355133727705],[58.36329064314674,51.06365346943858],[59.6422823423706,50.545442206415714],[59.93280724471549,50.842194118851864],[61.337424350840934,50.79907013610426],[61.58800337102417,51.272658799843214],[59.96753380721554,51.9604204372157],[60.92726850774027,52.44754832621504],[60.73999311711459,52.71998647725775],[61.6999861998006,52.97999644633427],[60.97806644068316,53.66499339457914],[61.436591424409066,54.00626455343479],[65.17853356309593,54.35422781027211],[65.666875848254,54.60126699484345],[68.16910037625883,54.97039175070432],[69.06816694527288,55.38525014914353],[70.86526655465514,55.169733588270105],[71.18013105660941,54.133285224008254],[72.22415001820218,54.376655381886735],[73.5085160663844,54.035616766976595],[73.42567874542043,53.489810289109755],[74.38484500519007,53.54686107036008],[76.89110029491343,54.49052440044193],[76.52517947785473,54.177003485727134],[77.80091556184425,53.404414984747575],[80.03555952344169,50.86475088154725],[80.56844689323549,51.38833649352847],[81.94598554883993,50.81219594990637],[83.38300377801238,51.069182847693924],[83.93511478061885,50.88924551045358],[84.41637739455308,50.311399644565824],[85.11555952346203,50.11730296487763],[85.54126997268247,49.69285858824816],[86.82935672398963,49.82667470966817],[87.35997033076268,49.21498078062916],[86.59877648310339,48.54918162698061],[85.7682328633083,48.45575063739698],[85.72048383987072,47.45296946877312],[85.16429039911338,47.00095571551611],[83.18048383986047,47.330031236350855],[82.45892581576913,45.539649563166506],[81.94707075391813,45.31702749285324],[79.96610639844141,44.91751699480466],[80.86620649610137,43.18036204688104],[80.1801501809943,42.92006785742694],[80.25999026888536,42.349999294599115],[79.64364546094015,42.496682847659656],[79.1421773619798,42.856092434249604],[77.6583919615832,42.960685533208334],[76.00035363149857,42.98802236589063],[75.6369649596221,42.87789988867678],[74.21286583852259,43.29833934180351],[73.64530358266092,43.09127187760987],[73.48975752146237,42.50089447689129],[71.84463829945065,42.845395412765185],[71.18628055205227,42.70429291439223],[70.96231489449929,42.26615428320554]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Kgz.\",\"name\":\"Kyrgyzstan\",\"name_long\":\"Kyrgyzstan\",\"iso_a2\":\"KG\",\"iso_a3\":\"KGZ\",\"iso_n3\":\"417\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[70.96231489449914,42.26615428320549],[71.18628055205212,42.70429291439214],[71.8446382994506,42.84539541276509],[73.48975752146237,42.50089447689132],[73.64530358266092,43.09127187760982],[74.21286583852256,43.29833934180337],[75.636964959622,42.87789988867668],[76.00035363149846,42.98802236589067],[77.6583919615832,42.96068553320826],[79.14217736197978,42.85609243424952],[79.64364546094012,42.49668284765953],[80.2599902688853,42.34999929459906],[80.11943037305139,42.12394074153825],[78.54366092317531,41.58224254003869],[78.18719689322597,41.18531586360481],[76.90448449087708,41.06648590754964],[76.52636803579745,40.42794607193512],[75.4678279967307,40.56207225194867],[74.77686242055606,40.36642527929163],[73.8222436868283,39.893973497063186],[73.96001305531843,39.660008449861735],[73.6753792662548,39.4312368841056],[71.784693637992,39.27946320246437],[70.54916181832562,39.6041979029865],[69.46488691597753,39.5266832545487],[69.55960981636852,40.10321137141298],[70.64801883329997,39.93575389257117],[71.01419803252017,40.24436554621823],[71.77487511585656,40.14584442805378],[73.05541710804917,40.866033026689465],[71.87011478057047,41.392900092121266],[71.1578585142916,41.14358714452912],[70.42002241402821,41.51999827734314],[71.25924767444822,42.16771067968946],[70.96231489449914,42.26615428320549]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"S.K.\",\"name\":\"Korea\",\"name_long\":\"Republic of Korea\",\"iso_a2\":\"KR\",\"iso_a3\":\"KOR\",\"iso_n3\":\"410\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[128.34971642467661,38.61224294692785],[129.21291954968007,37.43239248305595],[129.46044966035817,36.78418915460282],[129.4683044780665,35.63214061130395],[129.0913765809296,35.082484239231434],[128.1858504578791,34.89037710218639],[127.38651940318839,34.47567373304412],[126.48574751190874,34.39004588473648],[126.37391971242913,34.934560451795946],[126.5592313986278,35.6845405136479],[126.11739790253229,36.72548472751926],[126.86014326386339,36.893924058574626],[126.17475874237624,37.74968577732804],[126.23733890188176,37.84037791600028],[126.68371992401892,37.80477285415118],[127.07330854706737,38.2561148137884],[127.780035435091,38.30453563084589],[128.20574588431145,38.37039724380189],[128.34971642467661,38.61224294692785]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Camb.\",\"name\":\"Cambodia\",\"name_long\":\"Cambodia\",\"iso_a2\":\"KH\",\"iso_a3\":\"KHM\",\"iso_n3\":\"116\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[103.4972799011397,10.632555446815928],[103.09068973186724,11.153660590047165],[102.5849324890267,12.186594956913282],[102.348099399833,13.394247341358223],[102.98842207236163,14.225721136934467],[104.28141808473661,14.416743068901367],[105.21877689007887,14.273211778210694],[106.04394616091552,13.881091009979954],[106.49637332563087,14.570583807834282],[107.38272749230109,14.202440904186972],[107.61454796756243,13.535530707244206],[107.49140302941089,12.337205918827946],[105.81052371625313,11.567614650921227],[106.24967003786946,10.961811835163587],[105.19991499229235,10.889309800658097],[104.33433475140347,10.48654368737523],[103.4972799011397,10.632555446815928]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Kwt.\",\"name\":\"Kuwait\",\"name_long\":\"Kuwait\",\"iso_a2\":\"KW\",\"iso_a3\":\"KWT\",\"iso_n3\":\"414\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[47.974519077349896,29.9758192001485],[48.18318851094448,29.534476630159766],[48.09394331237642,29.306299343375002],[48.416094191283946,28.55200429942667],[47.708850538937384,28.526062730416143],[47.45982181172283,29.002519436147224],[46.568713413281756,29.09902517345229],[47.30262210469096,30.05906993257072],[47.974519077349896,29.9758192001485]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Laos\",\"name\":\"Lao PDR\",\"name_long\":\"Lao PDR\",\"iso_a2\":\"LA\",\"iso_a3\":\"LAO\",\"iso_n3\":\"418\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[105.21877689007887,14.273211778210694],[105.54433841351769,14.723933620660416],[105.58903852745016,15.570316066952858],[104.7793205098688,16.44186493577145],[104.7169470560925,17.42885895433008],[103.95647667848529,18.24095408779688],[103.20019209189373,18.309632066312773],[102.9987056823877,17.9616946476916],[102.41300499879162,17.932781683824288],[102.11359175009248,18.109101670804165],[101.05954756063517,17.51249725999449],[101.03593143107777,18.408928330961615],[101.2820146016517,19.462584947176765],[100.60629357300316,19.508344427971224],[100.54888105672688,20.109237982661128],[100.11598758341783,20.417849636308187],[100.32910119018952,20.786121731036232],[101.18000532430754,21.436572984294024],[101.27002566935997,21.201651923095184],[101.80311974488292,21.17436676684507],[101.65201785686152,22.318198757409547],[102.17043582561358,22.464753119389304],[102.75489627483466,21.675137233969465],[103.20386111858645,20.766562201413745],[104.43500044150805,20.75873322192153],[104.8225736836971,19.886641750563882],[104.18338789267894,19.62466807706022],[103.8965320170267,19.265180975821806],[105.09459842328152,18.66697459561108],[105.92576216026403,17.48531545660896],[106.55600792849569,16.604283962464805],[107.3127059265456,15.90853831630318],[107.5645251811039,15.202173163305558],[107.38272749230109,14.202440904186972],[106.49637332563087,14.570583807834282],[106.04394616091552,13.881091009979954],[105.21877689007887,14.273211778210694]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Leb.\",\"name\":\"Lebanon\",\"name_long\":\"Lebanon\",\"iso_a2\":\"LB\",\"iso_a3\":\"LBN\",\"iso_n3\":\"422\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[35.82110070165024,33.2774264592763],[35.55279666519081,33.26427480725802],[35.460709262846706,33.08904002535628],[35.126052687324545,33.09090037691878],[35.48220665868013,33.90545014091944],[35.9795923194894,34.61005829521913],[35.99840254084364,34.644914048800004],[36.4481942075121,34.59393524834407],[36.61175011571589,34.201788641897174],[36.066460402172055,33.82491242119255],[35.82110070165024,33.2774264592763]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Sri L.\",\"name\":\"Sri Lanka\",\"name_long\":\"Sri Lanka\",\"iso_a2\":\"LK\",\"iso_a3\":\"LKA\",\"iso_n3\":\"144\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[81.7879590188914,7.523055324733164],[81.63732221876059,6.481775214051921],[81.21801964714433,6.197141424988288],[80.34835696810441,5.968369859232155],[79.87246870312853,6.76346344647493],[79.69516686393513,8.200843410673386],[80.14780073437964,9.824077663609557],[80.83881798698656,9.268426825391188],[81.30431928907177,8.56420624433369],[81.7879590188914,7.523055324733164]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Myan.\",\"name\":\"Myanmar\",\"name_long\":\"Myanmar\",\"iso_a2\":\"MM\",\"iso_a3\":\"MMR\",\"iso_n3\":\"104\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[99.54330936075931,20.186597601802063],[98.95967573445488,19.752980658440947],[98.25372399291561,19.708203029860044],[97.7977828308044,18.627080389881755],[97.37589643757354,18.445437730375815],[97.85912275593486,17.567946071843664],[98.49376102091135,16.83783559820793],[98.90334842325676,16.177824204976115],[98.53737592976572,15.308497422746084],[98.1920740091914,15.12370250087035],[98.43081912637987,14.622027696180835],[99.09775516153876,13.827502549693278],[99.21201175333609,13.269293728076464],[99.19635379435167,12.80474843998867],[99.58728600463972,11.892762762901697],[99.03812055867398,10.960545762572437],[98.55355065307305,9.932959906448545],[98.45717410684871,10.67526601810515],[98.76454552612077,11.441291612183749],[98.42833865762985,12.032986761925683],[98.50957400919268,13.122377631070677],[98.1036039571077,13.640459703012851],[97.77773237507517,14.837285874892642],[97.59707156778276,16.10056793869977],[97.1645398294998,16.928734442609336],[96.505768670643,16.42724050543285],[95.3693522481124,15.7143899601826],[94.80840457558412,15.80345429123764],[94.18880415240454,16.037936102762018],[94.53348595579135,17.277240301985728],[94.32481652219674,18.2135139022499],[93.54098839719364,19.36649262133002],[93.66325483599621,19.726961574781996],[93.07827762245219,19.855144965081976],[92.36855350135562,20.670883287025347],[92.30323449093868,21.47548533780982],[92.65225711463799,21.324047552978485],[92.67272098182556,22.041238918541254],[93.16612755734836,22.278459580977103],[93.06029422401463,22.70311066333557],[93.28632693885928,23.043658352139005],[93.3251876159428,24.078556423432204],[94.10674197792505,23.85074087167348],[94.55265791217164,24.675238348890332],[94.60324913938538,25.162495428970402],[95.1551534362626,26.001307277932085],[95.12476769407496,26.5735720891323],[96.41936567585097,27.264589341739224],[97.1339990580153,27.083773505149964],[97.0519885599681,27.69905894623315],[97.40256147663612,27.88253611908544],[97.32711388549004,28.26158274994634],[97.91198774616944,28.335945136014345],[98.2462309102333,27.74722138112918],[98.68269005737046,27.50881216075062],[98.71209394734451,26.743535874940264],[98.67183800658916,25.918702500913525],[97.72460900267914,25.083637193293],[97.60471967976198,23.897404690033042],[98.66026248575577,24.063286037689966],[98.89874922078276,23.14272207284253],[99.5319922220874,22.94903880461258],[99.24089887898725,22.11831431730458],[99.98348921102149,21.7429367131364],[100.41653771362738,21.558839423096614],[101.15003299357825,21.84998444262902],[101.18000532430754,21.436572984294024],[100.32910119018952,20.786121731036232],[100.11598758341783,20.417849636308187],[99.54330936075931,20.186597601802063]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Mong.\",\"name\":\"Mongolia\",\"name_long\":\"Mongolia\",\"iso_a2\":\"MN\",\"iso_a3\":\"MNG\",\"iso_n3\":\"496\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[87.7512642760767,49.29719798440548],[88.80556684769552,49.47052073831242],[90.71366743364067,50.33181183532109],[92.23471154171968,50.80217072204172],[93.10421919146269,50.49529022887643],[94.14756635943561,50.48053660745709],[94.81594933469873,50.01343333597085],[95.81402794798399,49.977466539095715],[97.25972781778141,49.72606069599574],[98.23176150919156,50.422400621128745],[97.8257397806743,51.01099518493318],[98.86149051310034,52.047366034546684],[99.98173221232354,51.63400625264399],[100.88948042196262,51.51685578063832],[102.06522260946733,51.259920559283124],[102.25590864462431,50.51056061461868],[103.67654544476022,50.089966132195116],[104.6215523620817,50.275329494826074],[105.88659142458675,50.406019192092224],[106.88880415245534,50.27429596618023],[107.86817589725094,49.793705145865815],[108.47516727095127,49.28254771585074],[109.40244917199666,49.29296051695755],[110.66201053267876,49.13012807880587],[111.58123091028662,49.37796824807769],[112.89773969935439,49.54356537535699],[114.36245649623527,50.24830272073741],[114.96210981655018,50.140247300815126],[115.48569542853141,49.805177313834605],[116.67880089728618,49.88853139912139],[116.19180219936757,49.134598090199106],[115.48528201707305,48.13538259540344],[115.74283735561578,47.72654450132629],[116.30895267137323,47.85341014260284],[117.29550744025741,47.69770905210743],[118.06414269416672,48.06673045510368],[118.86657433479495,47.74706004494617],[119.7728239278975,47.048058783550125],[119.66326989143874,46.69267995867892],[118.87432579963873,46.80541209572365],[117.42170128791419,46.67273285581426],[116.71786828009886,46.38820241961521],[115.98509647020008,45.727235012386004],[114.46033165899607,45.339816799493825],[113.46390669154417,44.80889313412711],[112.43606245325881,45.01164561622429],[111.87330610560029,45.10207937273506],[111.34837690637946,44.45744171811009],[111.66773725794323,44.07317576758771],[111.82958784388137,43.743118394539515],[111.12968224492022,43.40683401140015],[110.41210330611528,42.87123362891103],[109.24359581913146,42.5194463160841],[107.74477257693795,42.48151581478187],[106.12931562706169,42.13432770442891],[104.96499393109347,41.59740957291635],[104.52228193564899,41.908346666016556],[103.31227827353482,41.9074681666676],[101.83304039917994,42.51487295182628],[100.84586551310827,42.66380442969145],[99.51581749878004,42.52469147396172],[97.45175744017801,42.74888967546002],[96.34939578652781,42.725635280928685],[95.76245486855669,43.319449164394605],[95.30687544147153,44.24133087826547],[94.68892866412533,44.352331854828414],[93.4807336771413,44.975472113619965],[92.13389082231822,45.11507599545646],[90.9455395853343,45.28607330991028],[90.58576826371828,45.71971609148753],[90.97080936072501,46.88814606382293],[90.28082563676392,47.69354909930793],[88.85429772334676,48.06908173277296],[88.01383222855173,48.599462795600616],[87.7512642760767,49.29719798440548]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Malay.\",\"name\":\"Malaysia\",\"name_long\":\"Malaysia\",\"iso_a2\":\"MY\",\"iso_a3\":\"MYS\",\"iso_n3\":\"458\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[101.07551557821333,6.204867051615892],[101.15421878459384,5.691384182147715],[101.81428185425804,5.810808417174228],[102.14118696493645,6.221636053894656],[102.37114708863524,6.12820506431096],[102.9617053568667,5.524495144061078],[103.38121463421217,4.855001125503748],[103.4385754740562,4.181605536308382],[103.33212202353488,3.726697902842971],[103.42942874554055,3.38286876058902],[103.50244754436889,2.791018581550205],[103.85467410687036,2.515454006353763],[104.24793175661151,1.631141058759056],[104.22881147666354,1.293048000489534],[103.51970747275443,1.226333726400682],[102.57361535035479,1.967115383304744],[101.39063846232918,2.760813706875624],[101.27353966675585,3.270291652841181],[100.6954354187067,3.93913971599487],[100.55740766805509,4.76728038168828],[100.19670617065773,5.31249258058368],[100.30626020711652,6.040561835143876],[100.08575687052709,6.46448944745029],[100.25959638875692,6.642824815289572],[101.07551557821333,6.204867051615892]]],[[[118.61832075406485,4.478202419447541],[117.88203494677019,4.137551377779488],[117.01521447150637,4.306094061699469],[115.86551720587677,4.306559149590157],[115.51907840379201,3.169238389494396],[115.13403730678523,2.821481838386219],[114.6213554220175,1.430688177898887],[113.80584964401956,1.217548732911041],[112.8598091980522,1.497790025229946],[112.38025190638368,1.410120957846758],[111.79754845586044,0.904441229654651],[111.15913781132659,0.976478176269509],[110.51406090702713,0.773131415200993],[109.83022667850886,1.338135687664192],[109.66326012577375,2.006466986494985],[110.39613528853707,1.663774725751395],[111.1688529805975,1.850636704918784],[111.3700810079421,2.697303371588873],[111.79692833867287,2.885896511238073],[112.99561486211527,3.102394924324869],[113.71293541875873,3.893509426281128],[114.20401655482843,4.52587392823682],[114.65959598191355,4.00763682699781],[114.8695573263154,4.348313706881952],[115.34746097215069,4.316636053887009],[115.40570031134362,4.955227565933825],[115.45071048386981,5.447729803891561],[116.22074100145099,6.143191229675621],[116.72510298061978,6.924771429873998],[117.12962609260049,6.928052883324567],[117.64339318244633,6.422166449403306],[117.68907514859237,5.987490139180181],[118.3476912781522,5.708695786965464],[119.18190392463994,5.407835598162251],[119.11069380094172,5.016128241389865],[118.43972700406411,4.96651886638962],[118.61832075406485,4.478202419447541]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Nepal\",\"name\":\"Nepal\",\"name_long\":\"Nepal\",\"iso_a2\":\"NP\",\"iso_a3\":\"NPL\",\"iso_n3\":\"524\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[88.12044070836987,27.876541652939594],[88.04313276566123,27.445818589786825],[88.17480431514092,26.81040517832595],[88.06023766474982,26.41461538340249],[87.22747195836628,26.397898057556077],[86.02439293817918,26.63098460540857],[85.25177859898338,26.72619843190634],[84.6750179381738,27.234901231387536],[83.30424889519955,27.36450572357556],[81.99998742058497,27.925479234319994],[81.05720258985203,28.416095282499043],[80.08842451367627,28.79447011974014],[80.4767212259174,29.72986522065534],[81.11125613802932,30.183480943313402],[81.52580447787474,30.42271698660863],[82.32751264845088,30.115268052688133],[83.33711510613719,29.463731594352193],[83.89899295444673,29.320226141877658],[84.23457970575015,28.839893703724698],[85.01163821812304,28.642773952747344],[85.82331994013151,28.203575954698703],[86.9545170430006,27.974261786403517],[88.12044070836987,27.876541652939594]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Oman\",\"name\":\"Oman\",\"name_long\":\"Oman\",\"iso_a2\":\"OM\",\"iso_a3\":\"OMN\",\"iso_n3\":\"512\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[58.86114139184659,21.114034532144302],[58.48798587426696,20.42898590746711],[58.034318475176605,20.48143748624335],[57.82637251163411,20.24300242764863],[57.665762160070955,19.736004950433113],[57.788700392493375,19.06757029873765],[57.69439090356068,18.944709580963803],[57.234263950433814,18.947991034414258],[56.609650913321985,18.57426707607948],[56.512189162019496,18.087113348863937],[56.28352094912801,17.87606679938395],[55.6614917336307,17.88412832282154],[55.2699394061552,17.632309068263197],[55.274900343655105,17.228354397037663],[54.79100223167413,16.950696926333364],[54.239252964093765,17.044980577049984],[53.570508253804604,16.707662665264678],[53.10857262554751,16.65105113368898],[52.78218427919207,17.349742336491232],[52.00000980002224,19.000003363516072],[54.99998172386242,19.99999400479612],[55.66665937685988,22.00000112557231],[55.2083410988632,22.70832998299701],[55.234489373602884,23.11099274341535],[55.5258410988645,23.524869289640918],[55.52863162620829,23.933604030853502],[55.98121382022052,24.130542914317854],[55.80411868675625,24.269604193615294],[55.886232537668064,24.920830593357493],[56.396847365144,24.924732163995515],[56.84514041527606,24.241673081961494],[57.40345258975744,23.87859446867884],[58.13694786970834,23.74793060962884],[58.72921146020544,23.565667832935418],[59.18050174341036,22.99239533130546],[59.45009769067703,22.6602709009656],[59.80806033716286,22.533611965418203],[59.8061483091681,22.31052480721419],[59.44219119653641,21.714540513592084],[59.282407667889885,21.433885809814882],[58.86114139184659,21.114034532144302]]],[[[56.39142133975341,25.89599070892126],[56.26104170108093,25.71460643157675],[56.07082075381456,26.05546417897395],[56.36201744977936,26.395934353128947],[56.48567915225382,26.309117946878672],[56.39142133975341,25.89599070892126]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Pak.\",\"name\":\"Pakistan\",\"name_long\":\"Pakistan\",\"iso_a2\":\"PK\",\"iso_a3\":\"PAK\",\"iso_n3\":\"586\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[75.15802778514092,37.13303091078912],[75.89689741405013,36.666806138651836],[76.19284834178569,35.89840342868782],[77.83745079947457,35.494009507787766],[76.87172163280403,34.65354401299274],[75.75706098826834,34.50492259372132],[74.24020267120497,34.74888703057125],[73.74994835805195,34.31769887952785],[74.10429365427734,33.44147329358685],[74.45155927927871,32.7648996038055],[75.25864179881322,32.2711054550405],[74.40592898956501,31.69263947196528],[74.42138024282026,30.979814764931177],[73.45063846221743,29.97641347911987],[72.8237516620847,28.961591701772054],[71.77766564320032,27.913180243434525],[70.61649620960193,27.989196275335868],[69.51439293811312,26.940965684511372],[70.16892662952202,26.491871649678842],[70.28287316272558,25.72222870533983],[70.84469933460284,25.21510203704352],[71.04324018746823,24.3565239527302],[68.84259931831878,24.35913361256094],[68.1766451353734,23.69196503345671],[67.44366661974547,23.94484365487699],[67.14544192898907,24.663611151624647],[66.37282758979326,25.425140896093847],[64.53040774929113,25.23703868255143],[62.9057007180346,25.21840932871021],[61.49736290878419,25.0782370061185],[61.87418745305655,26.239974880472104],[63.31663170761959,26.756532497661667],[63.233897739520295,27.21704702403071],[62.755425652929866,27.378923448184985],[62.72783043808598,28.25964488373539],[61.77186811711863,28.699333807890795],[61.36930870956494,29.303276272085924],[60.874248488208785,29.829238999952604],[62.54985680527278,29.31857249604431],[63.55026085801117,29.468330796826162],[64.14800215033125,29.340819200145972],[64.35041873561852,29.560030625928093],[65.0468620136161,29.472180691031905],[66.34647260932442,29.887943427036177],[66.38145755398602,30.73889923758645],[66.93889122911847,31.304911200479353],[67.68339358914747,31.30315420178142],[67.79268924344478,31.58293040620963],[68.55693200060932,31.713310044882018],[68.92667687365767,31.620189113892064],[69.31776411324255,31.90141225842444],[69.26252200712256,32.5019440780883],[69.68714725126485,33.105498969041236],[70.3235941913716,33.35853261975839],[69.9305432473596,34.02012014417511],[70.8818030129884,33.98885590263851],[71.15677330921346,34.34891144463215],[71.11501875192162,34.733125718722235],[71.6130762063507,35.153203436822864],[71.49876793812109,35.650563259416],[71.26234826038575,36.074387518857804],[71.84629194528392,36.50994232842986],[72.92002485544447,36.72000702569632],[74.06755171091783,36.83617564548845],[74.57589277537298,37.02084137628346],[75.15802778514092,37.13303091078912]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Phil.\",\"name\":\"Philippines\",\"name_long\":\"Philippines\",\"iso_a2\":\"PH\",\"iso_a3\":\"PHL\",\"iso_n3\":\"608\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[126.37681359263748,8.414706325713354],[126.4785128113879,7.750354112168978],[126.53742394420063,7.189380601424574],[126.19677290253256,6.27429433840004],[125.83142052622911,7.293715318221857],[125.3638521668523,6.786485297060992],[125.68316084198372,6.049656887227258],[125.39651167206064,5.58100332277229],[124.21978763234236,6.161355495626182],[123.93871951710695,6.885135606306122],[124.24366214406135,7.360610459823661],[123.61021243702757,7.833527329942754],[123.2960714051252,7.418875637232787],[122.82550581267542,7.457374579290217],[122.08549930225577,6.899424139834849],[121.91992801319263,7.192119452336072],[122.31235884001714,8.034962063016508],[122.94239790251966,8.316236883981174],[123.48768761606352,8.693009751821194],[123.84115441293984,8.240324204944386],[124.60146976125023,8.514157619659017],[124.76461225799564,8.96040945071546],[125.47139082245157,8.986996975129642],[125.41211795461278,9.760334784377548],[126.22271447154318,9.28607432701885],[126.3066369975851,8.782487494334575],[126.37681359263748,8.414706325713354]]],[[[123.98243777882583,10.278778591345812],[123.62318322153278,9.950090643753299],[123.30992068897936,9.318268744336676],[122.99588300994164,9.0221886255204],[122.38005496631948,9.713360907424203],[122.5860889018671,9.981044826696104],[122.83708133350873,10.261156927934238],[122.94741051645192,10.881868394408029],[123.49884972543848,10.940624497923949],[123.33777428598475,10.267383938025446],[124.07793582570125,11.23272553145371],[123.98243777882583,10.278778591345812]]],[[[118.50458092659035,9.31638255455809],[117.1742745301007,8.367499904814665],[117.66447716682138,9.066888739452935],[118.38691369026175,9.684499619989225],[118.98734215706108,10.376292019080509],[119.51149620979756,11.369668077027214],[119.68967654833992,10.554291490109875],[119.029458449379,10.003653265823871],[118.50458092659035,9.31638255455809]]],[[[121.88354780485913,11.89175507247198],[122.48382124236147,11.582187404827508],[123.12021650603597,11.58366018314787],[123.10083784392647,11.16593374271649],[122.6377136577267,10.741308498574227],[122.00261030485957,10.441016750526087],[121.96736697803655,10.905691229694623],[122.03837039600555,11.41584096928004],[121.88354780485913,11.89175507247198]]],[[[125.50255171112352,12.162694606978349],[125.78346479706218,11.046121934447768],[125.01188398651229,11.31145457605038],[125.03276126515814,10.975816148314706],[125.27744917206027,10.358722032101312],[124.80181928924573,10.134678859899893],[124.76016808481849,10.837995103392302],[124.45910119028606,10.889929917845635],[124.30252160044172,11.495370998577227],[124.8910128113816,11.415582587118593],[124.87799035044398,11.79418996830499],[124.26676150929572,12.557760931849685],[125.22711632700785,12.535720933477194],[125.50255171112352,12.162694606978349]]],[[[121.52739383350351,13.06959015548452],[121.26219038298157,12.205560207564403],[120.83389611214656,12.70449616134242],[120.3234363139675,13.46641347905387],[121.18012820850217,13.429697373910443],[121.52739383350351,13.06959015548452]]],[[[121.32130822152358,18.504064642811016],[121.9376013530364,18.218552354398383],[122.24600630095429,18.478949896717097],[122.336956821788,18.224882717354177],[122.1742794129332,17.810282701076375],[122.51565392465336,17.093504746971973],[122.2523108256939,16.262444362854126],[121.66278608610828,15.931017564350128],[121.5050696147534,15.124813544164622],[121.72882856657728,14.328376369682246],[122.25892540902734,14.218202216035976],[122.70127566944566,14.33654124598442],[123.95029503794026,13.78213064214107],[123.85510704965863,13.237771104378467],[124.1812886902849,12.997527370653472],[124.07741906137825,12.536676947474575],[123.29803510955227,13.027525539598981],[122.92865197152993,13.552919826710408],[122.67135501514869,13.185836289925135],[122.03464969288055,13.784481919810347],[121.1263847189186,13.63668732345556],[120.62863732308331,13.857655747935652],[120.67938357959385,14.271015529838323],[120.99181928923055,14.525392767795083],[120.69333621631272,14.756670640517285],[120.564145135583,14.396279201713822],[120.0704285014664,14.970869452367097],[119.92092858284613,15.406346747290739],[119.88377322802826,16.363704331929966],[120.28648766487882,16.03462881109533],[120.39004723519176,17.59908112229951],[120.71586714079191,18.50522736253754],[121.32130822152358,18.504064642811016]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"N.K.\",\"name\":\"Dem. Rep. Korea\",\"name_long\":\"Dem. Rep. Korea\",\"iso_a2\":\"KP\",\"iso_a3\":\"PRK\",\"iso_n3\":\"408\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[130.6400159038524,42.39500946712528],[130.78000735893113,42.22000722916885],[130.40003055228902,42.28000356705971],[129.96594852103726,41.94136790625105],[129.66736209525482,41.60110443782523],[129.70518924369247,40.88282786718433],[129.18811486218,40.66180776627199],[129.01039961152821,40.485436102859815],[128.63336836152672,40.18984691015031],[127.96741417858135,40.02541250259756],[127.53343550019417,39.7568500839767],[127.5021195822253,39.32393077245153],[127.38543419811029,39.213472398427655],[127.78334272675772,39.05089834243742],[128.34971642467661,38.61224294692785],[128.20574588431145,38.37039724380189],[127.780035435091,38.30453563084589],[127.07330854706737,38.2561148137884],[126.68371992401892,37.80477285415118],[126.23733890188176,37.84037791600028],[126.17475874237624,37.74968577732804],[125.6891036316972,37.940010077459014],[125.56843916229569,37.75208873142962],[125.2753304383362,37.669070542952724],[125.24008711151315,37.85722443292744],[124.98103315643398,37.94882090916478],[124.71216067921938,38.10834605564979],[124.98599409393398,38.54847422947968],[125.2219486837787,38.66585724543067],[125.13285851450752,38.84855927179859],[125.3865897970606,39.387957872061165],[125.3211157573468,39.5513845891842],[124.7374821310424,39.66034434667162],[124.26562462778531,39.928493353834156],[125.07994184784063,40.56982371679245],[126.18204511932943,41.10733612727637],[126.86908328664985,41.81656932226619],[127.34378299368302,41.50315176041597],[128.20843305879066,41.46677155208249],[128.0522152039723,41.99428457291795],[129.59666873587952,42.42498179785456],[129.99426720593323,42.985386867843786],[130.6400159038524,42.39500946712528]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Pal.\",\"name\":\"Palestine\",\"name_long\":\"Palestine\",\"iso_a2\":\"PS\",\"iso_a3\":\"PSE\",\"iso_n3\":\"275\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[35.54566531753454,32.393992011030576],[35.5452519060762,31.78250478772084],[35.397560662586045,31.48908600516758],[34.92740848159457,31.353435370401414],[34.970506626125996,31.616778469360806],[35.22589155451242,31.754341132121766],[34.97464074070933,31.86658234305972],[35.183930291491436,32.53251068778894],[35.54566531753454,32.393992011030576]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Qatar\",\"name\":\"Qatar\",\"name_long\":\"Qatar\",\"iso_a2\":\"QA\",\"iso_a3\":\"QAT\",\"iso_n3\":\"634\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[50.81010827006958,24.754742539971378],[50.74391076030369,25.482424221289396],[51.01335167827349,26.00699168548419],[51.28646162293606,26.11458201751587],[51.58907881043726,25.80111277923338],[51.60670047384881,25.21567047779874],[51.38960778179063,24.62738597258806],[51.11241539897702,24.556330878186724],[50.81010827006958,24.754742539971378]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Syria\",\"name\":\"Syria\",\"name_long\":\"Syria\",\"iso_a2\":\"SY\",\"iso_a3\":\"SYR\",\"iso_n3\":\"760\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[38.792340529136084,33.378686428352225],[36.834062127435544,32.312937526980775],[35.71991824722275,32.709192409794866],[35.700797967274745,32.71601369885738],[35.836396925608625,32.86812327730851],[35.82110070165024,33.2774264592763],[36.066460402172055,33.82491242119255],[36.61175011571589,34.201788641897174],[36.4481942075121,34.59393524834407],[35.99840254084364,34.644914048800004],[35.905023227692226,35.410009467097325],[36.149762811026534,35.82153473565367],[36.417550083163036,36.04061697035506],[36.6853890317318,36.25969920505646],[36.7394942563414,36.81752045343109],[37.06676110204583,36.62303620050062],[38.1677274920242,36.90121043552777],[38.6998913917659,36.71292735447234],[39.52258019385255,36.71605377862599],[40.67325931169569,37.09127635349729],[41.21208947120305,37.074352321921694],[42.34959109881177,37.2298725449041],[41.83706424334096,36.605853786763575],[41.289707472505455,36.35881460219227],[41.383965285005814,35.628316555314356],[41.006158888519934,34.41937226006212],[38.792340529136084,33.378686428352225]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Saud.\",\"name\":\"Saudi Arabia\",\"name_long\":\"Saudi Arabia\",\"iso_a2\":\"SA\",\"iso_a3\":\"SAU\",\"iso_n3\":\"682\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[42.77933230975097,16.347891343648683],[42.649572788266084,16.774635321514964],[42.347989129410706,17.075805568912003],[42.270887892431226,17.474721787989125],[41.75438195167396,17.833046169500975],[41.22139122901558,18.671599636301206],[40.93934126156654,19.486485297111756],[40.247652215339826,20.17463450772649],[39.80168460466095,20.338862209550054],[39.139399448408284,21.29190481209293],[39.023695916506796,21.986875311770195],[39.06632897314759,22.57965566659027],[38.49277225114008,23.688451036060854],[38.02386030452362,24.07868561451293],[37.483634881344386,24.285494696545015],[37.154817742671185,24.85848297779731],[37.209491408036,25.084541530858104],[36.93162723160259,25.602959499610176],[36.63960371272122,25.82622752532722],[36.249136590323815,26.57013560638488],[35.64018151219639,27.37652049408342],[35.13018680190788,28.06335195567472],[34.63233605320798,28.058546047471566],[34.787778761541944,28.6074272730597],[34.832220493312946,28.957483425404845],[34.95603722508426,29.35655467377884],[36.06894087092206,29.197494615184457],[36.50121422704358,29.5052536076987],[36.74052778498725,29.86528331147619],[37.503581984209035,30.003776150018403],[37.66811974462638,30.3386652694859],[37.998848911294374,30.50849986421313],[37.00216556168101,31.508412990844743],[39.00488569515255,32.01021698661498],[39.19546837744497,32.16100881604267],[40.399994337736246,31.889991766887935],[41.889980910007836,31.190008653278365],[44.70949873228474,29.178891099559383],[46.568713413281756,29.09902517345229],[47.45982181172283,29.002519436147224],[47.708850538937384,28.526062730416143],[48.416094191283946,28.55200429942667],[48.80759484232718,27.689627997339883],[49.29955447774582,27.46121816660981],[49.470913527225655,27.10999929453808],[50.15242231629088,26.689663194275997],[50.212935418504685,26.277026882425375],[50.11330325704594,25.943972276304248],[50.239858839728754,25.608049628190926],[50.52738650900073,25.3278083358721],[50.66055667501689,24.99989553476402],[50.81010827006958,24.754742539971378],[51.11241539897702,24.556330878186724],[51.38960778179063,24.62738597258806],[51.57951867046327,24.245497137951105],[51.61770755392698,24.014219265228828],[52.000733270074335,23.00115448657894],[55.0068030129249,22.496947536707136],[55.20834109886319,22.708329982997046],[55.666659376859826,22.00000112557234],[54.99998172386236,19.999994004796108],[52.00000980002224,19.000003363516058],[49.11667158386487,18.616667588774945],[48.18334354024134,18.166669216377315],[47.46669477721763,17.116681626854884],[47.000004917189756,16.949999294497445],[46.74999433776165,17.283338120996177],[46.366658563020536,17.233315334537636],[45.39999922056875,17.333335069238558],[45.21665123879718,17.43332896572333],[44.06261315285508,17.410358791569593],[43.79151858905192,17.31997671149111],[43.380794305196105,17.57998668056767],[43.11579756040335,17.088440456607373],[43.21837527850275,16.66688996018641],[42.77933230975097,16.347891343648683]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"B.H\",\"name\":\"Bahrain\",\"name_long\":\"Kingdom of Bahrain\",\"iso_a2\":\"BH\",\"iso_a3\":\"BHR\",\"iso_n3\":\"48\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[50.441498315672,26.079486759594],[50.444990826448,26.155368387131],[50.494398435257,26.240297244012],[50.565645056344,26.252673936522],[50.58600454793,26.247660903832],[50.628002395199,25.943018578555],[50.610418895937,25.857704991003],[50.5840692813,25.815491188858],[50.508839915777,25.868375997347],[50.459210656002,25.954651764437],[50.441498315672,26.079486759594]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Thai.\",\"name\":\"Thailand\",\"name_long\":\"Thailand\",\"iso_a2\":\"TH\",\"iso_a3\":\"THA\",\"iso_n3\":\"764\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102.5849324890267,12.186594956913282],[101.68715783081996,12.645740057826572],[100.83180952352487,12.627084865769206],[100.9784672383692,13.412721665902566],[100.0977974792511,13.406856390837433],[100.01873253784456,12.307001044153354],[99.47892052612363,10.846366685423547],[99.15377241414316,9.963061428258554],[99.22239871622676,9.239255479362427],[99.87383182169813,9.20786204674512],[100.27964684448622,8.295152899606052],[100.45927412313276,7.429572658717177],[101.01732791545273,6.856868597842478],[101.62307905477806,6.74062246340192],[102.14118696493638,6.221636053894628],[101.81428185425798,5.810808417174242],[101.15421878459387,5.691384182147715],[101.07551557821336,6.204867051615921],[100.25959638875696,6.642824815289543],[100.0857568705271,6.46448944745029],[99.69069054565574,6.848212795433597],[99.51964155476963,7.34345388430276],[98.9882528015123,7.907993068875327],[98.503786248776,8.382305202666288],[98.339661899817,7.794511623562386],[98.15000939330581,8.350007432483878],[98.25915001830624,8.973922837759801],[98.55355065307305,9.932959906448545],[99.03812055867398,10.960545762572437],[99.58728600463972,11.892762762901697],[99.19635379435167,12.80474843998867],[99.21201175333609,13.269293728076464],[99.09775516153876,13.827502549693278],[98.43081912637987,14.622027696180835],[98.1920740091914,15.12370250087035],[98.53737592976572,15.308497422746084],[98.90334842325676,16.177824204976115],[98.49376102091135,16.83783559820793],[97.85912275593486,17.567946071843664],[97.37589643757354,18.445437730375815],[97.7977828308044,18.627080389881755],[98.25372399291561,19.708203029860044],[98.95967573445488,19.752980658440947],[99.54330936075931,20.186597601802063],[100.11598758341783,20.417849636308187],[100.54888105672688,20.109237982661128],[100.60629357300316,19.508344427971224],[101.2820146016517,19.462584947176765],[101.03593143107777,18.408928330961615],[101.05954756063517,17.51249725999449],[102.11359175009248,18.109101670804165],[102.41300499879162,17.932781683824288],[102.9987056823877,17.9616946476916],[103.20019209189373,18.309632066312773],[103.95647667848529,18.24095408779688],[104.7169470560925,17.42885895433008],[104.7793205098688,16.44186493577145],[105.58903852745016,15.570316066952858],[105.54433841351769,14.723933620660416],[105.21877689007887,14.273211778210694],[104.28141808473661,14.416743068901367],[102.98842207236163,14.225721136934467],[102.348099399833,13.394247341358223],[102.5849324890267,12.186594956913282]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Tjk.\",\"name\":\"Tajikistan\",\"name_long\":\"Tajikistan\",\"iso_a2\":\"TJ\",\"iso_a3\":\"TJK\",\"iso_n3\":\"762\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[71.01419803252017,40.24436554621823],[70.64801883329997,39.93575389257117],[69.55960981636852,40.10321137141298],[69.46488691597753,39.5266832545487],[70.54916181832562,39.6041979029865],[71.784693637992,39.27946320246437],[73.6753792662548,39.4312368841056],[73.92885216664644,38.50581533462274],[74.25751427602273,38.60650686294345],[74.86481570831681,38.3788463404816],[74.8299857929521,37.9900070257014],[74.98000247589542,37.419990139305895],[73.9486959166465,37.4215662704908],[73.26005577992501,37.495256862939],[72.63688968291729,37.047558091778356],[72.1930408059624,36.948287665345674],[71.8446382994506,36.73817129164692],[71.44869347523024,37.06564484308051],[71.54191775908478,37.905774441065645],[71.23940392444817,37.95326508234188],[71.34813113799026,38.258905341132156],[70.80682050973289,38.486281643216415],[70.3763041523093,38.13839590102752],[70.27057417184014,37.735164699854025],[70.11657840361033,37.58822276463209],[69.51878543485796,37.60899669041341],[69.19627282092438,37.15114350030743],[68.85944583524594,37.344335842430596],[68.13556237170138,37.0231151393043],[67.82999962755952,37.14499400486468],[68.39203250516596,38.157025254868735],[68.17602501818592,38.901553453113905],[67.44221967964131,39.140143541005486],[67.70142866401736,39.58047842056453],[68.53641645698941,39.53345286717894],[69.0116329283455,40.08615814875666],[69.32949466337283,40.72782440852485],[70.66662234892505,40.96021332454141],[70.45815962105962,40.49649485937029],[70.60140669137269,40.21852733007229],[71.01419803252017,40.24436554621823]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Turkm.\",\"name\":\"Turkmenistan\",\"name_long\":\"Turkmenistan\",\"iso_a2\":\"TM\",\"iso_a3\":\"TKM\",\"iso_n3\":\"795\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[61.21081709172574,35.650072333309225],[61.123070509694145,36.49159719496624],[60.37763797388387,36.52738312432837],[59.23476199731681,37.412987982730336],[58.4361544126782,37.522309475243794],[57.33043379092898,38.02922943781094],[56.61936608259282,38.121394354803485],[56.18037479027333,37.93512665460742],[55.51157840355191,37.96411713312317],[54.800303989486565,37.392420762678185],[53.92159793479556,37.19891836196126],[53.73551110211252,37.90613617609169],[53.880928582581845,38.95209300389536],[53.101027866432894,39.29057363540713],[53.35780805849123,39.97528636327445],[52.69397260926982,40.03362905533197],[52.915251092343624,40.87652334244473],[53.858139275941134,40.63103445084218],[54.73684533063215,40.95101491959346],[54.008310988181314,41.55121084244742],[53.72171349469059,42.12319143327003],[52.916749708880076,41.86811656347733],[52.81468875510361,41.13537059179471],[52.50245975119615,41.78331553808637],[52.944293247291654,42.11603424739759],[54.07941775901495,42.32410940202083],[54.75534549339263,42.043971462566574],[55.45525109235377,41.25985911718584],[55.96819135928291,41.30864166926936],[57.0963912290791,41.32231008561057],[56.932215203687804,41.826026109375604],[57.78652998233708,42.17055288346552],[58.62901085799146,42.75155101172305],[59.97642215356978,42.22308197689021],[60.08334069198167,41.425146185871405],[60.46595299667069,41.22032664648255],[61.54717898951356,41.266370347654615],[61.88271406438469,41.084856879229406],[62.374260288345,40.05388621679039],[63.51801476426103,39.36325653742564],[64.17022301621677,38.892406724598246],[65.2159989765074,38.40269501398429],[66.54615034370022,37.97468496352687],[66.51860680528867,37.36278432875879],[66.21738488145932,37.39379018813392],[65.74563073106681,37.66116404881207],[65.58894778835784,37.30521678318564],[64.7461051776774,37.111817735333304],[64.5464791197339,36.31207326918427],[63.9828959491587,36.0079574651466],[63.19353844590035,35.857165635718914],[62.9846623065766,35.40404083916762],[62.230651483005886,35.270663967422294],[61.21081709172574,35.650072333309225]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"T.L.\",\"name\":\"Timor-Leste\",\"name_long\":\"Timor-Leste\",\"iso_a2\":\"TL\",\"iso_a3\":\"TLS\",\"iso_n3\":\"626\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[124.96868248911622,-8.892790215697083],[125.08624637258026,-8.65688730228468],[125.94707238169826,-8.432094821815035],[126.64470421763855,-8.398246758663852],[126.95724328013982,-8.273344821814398],[127.33592817597464,-8.397316582882603],[126.96799197805655,-8.668256117388893],[125.9258850444586,-9.106007175333351],[125.08852013560109,-9.393173109579294],[125.07001997284061,-9.089987481322872],[124.96868248911622,-8.892790215697083]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Tur.\",\"name\":\"Turkey\",\"name_long\":\"Turkey\",\"iso_a2\":\"TR\",\"iso_a3\":\"TUR\",\"iso_n3\":\"792\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[36.91312706884215,41.335358384764305],[38.34766482926452,40.94858612727572],[39.51260664242025,41.102762763018575],[40.373432651538245,41.01367259374734],[41.554084100110714,41.53565623632761],[42.619548781104555,41.58317271581993],[43.58274580259271,41.09214325618257],[43.7526579119685,40.74020091405882],[43.65643639504096,40.25356395116617],[44.400008579288766,40.00500031184231],[44.793989699082005,39.713002631177034],[44.109225294782355,39.428136298168056],[44.4214026222576,38.28128123631453],[44.22575564960053,37.97158437758935],[44.77269900897775,37.17044464776845],[44.29345177590286,37.00151439060635],[43.94225874204736,37.256227525372935],[42.77912560402186,37.38526357680581],[42.34959109881177,37.22987254490411],[41.21208947120303,37.07435232192173],[40.673259311695716,37.09127635349736],[39.52258019385252,36.71605377862602],[38.69989139176593,36.71292735447233],[38.16772749202416,36.90121043552779],[37.06676110204583,36.62303620050062],[36.739494256341374,36.817520453431115],[36.68538903173183,36.259699205056506],[36.41755008316309,36.0406169703551],[36.14976281102659,35.82153473565367],[35.782084995269855,36.274995429014915],[36.160821567537056,36.650605577128374],[35.55093631362834,36.56544281671134],[34.714553256984374,36.795532131490916],[34.02689497247647,36.21996002862397],[32.5091581560641,36.1075637883892],[31.699595167779567,36.64427521417261],[30.62162479017107,36.677864895162315],[30.39109622571712,36.26298065850698],[29.699975620245567,36.144357408181],[28.73290286633539,36.67683136651644],[27.64118655773737,36.658822129862756],[27.048767937943296,37.65336090753601],[26.318218214633045,38.208133246405396],[26.804700148228733,38.98576019953356],[26.17078535330438,39.463612168936464],[27.280019972449395,40.42001373957831],[28.819977654747216,40.46001129817221],[29.24000369641558,41.21999074967269],[31.145933872204434,41.08762156835706],[32.34797936374579,41.73626414648464],[33.51328291192752,42.018960069337304],[35.16770389175187,42.04022492122544],[36.91312706884215,41.335358384764305]]],[[[27.192376743282406,40.690565700842455],[26.35800906749779,40.15199392349649],[26.04335127127254,40.61775360774317],[26.056942172965336,40.82412344010075],[26.29460208507569,40.93626129817417],[26.604195590936282,41.56211456966102],[26.117041863720825,41.82690460872456],[27.135739373490505,42.14148489030131],[27.996720411905414,42.00735871028777],[28.115524529744444,41.622886054036286],[28.988442824018787,41.29993419042819],[28.80643842948675,41.05496206314854],[27.61901736828412,40.99982330989312],[27.192376743282406,40.690565700842455]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Taiwan\",\"name\":\"Taiwan\",\"name_long\":\"Taiwan\",\"iso_a2\":\"TW\",\"iso_a3\":\"TWN\",\"iso_n3\":\"158\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[121.77781782438993,24.3942735865194],[121.17563235889274,22.790857245367167],[120.74707970589621,21.970571397382113],[120.22008344938367,22.81486094816674],[120.1061885926124,23.556262722258236],[120.69467980355225,24.538450832613737],[121.49504438688876,25.295458889257386],[121.95124393116144,24.997595933527034],[121.77781782438993,24.3942735865194]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Viet.\",\"name\":\"Vietnam\",\"name_long\":\"Vietnam\",\"iso_a2\":\"VN\",\"iso_a3\":\"VNM\",\"iso_n3\":\"704\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[108.05018029178294,21.55237986906012],[106.7150679870901,20.69685069425202],[105.88168216351903,19.752050482659698],[105.66200564984631,19.05816518806057],[106.426816847766,18.004120998603227],[107.36195356651974,16.69745656988705],[108.26949507042963,16.07974233648615],[108.87710656131748,15.27669057867044],[109.33526981001721,13.426028347217722],[109.20013593957398,11.666859239137764],[108.36612999881545,11.008320624226272],[107.22092858279524,10.364483954301832],[106.40511274620343,9.53083974856932],[105.15826378786511,8.599759629750494],[104.79518517458237,9.241038316276502],[105.0762016133856,9.918490505406808],[104.33433475140347,10.48654368737523],[105.19991499229235,10.889309800658097],[106.24967003786946,10.961811835163587],[105.81052371625313,11.567614650921227],[107.49140302941089,12.337205918827946],[107.61454796756243,13.535530707244206],[107.38272749230109,14.202440904186972],[107.5645251811039,15.202173163305558],[107.3127059265456,15.90853831630318],[106.55600792849569,16.604283962464805],[105.92576216026403,17.48531545660896],[105.09459842328152,18.66697459561108],[103.8965320170267,19.265180975821806],[104.18338789267894,19.62466807706022],[104.8225736836971,19.886641750563882],[104.43500044150805,20.75873322192153],[103.20386111858645,20.766562201413745],[102.75489627483466,21.675137233969465],[102.17043582561358,22.464753119389304],[102.7069922221001,22.708795070887675],[103.50451460166056,22.70375661873921],[104.47685835166448,22.819150092046968],[105.32920942588663,23.352063300056912],[105.81124718630521,22.976892401617903],[106.72540327354845,22.79426788989842],[106.56727339073532,22.21820486092477],[107.04342003787262,21.811898912029914],[108.05018029178294,21.55237986906012]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Uzb.\",\"name\":\"Uzbekistan\",\"name_long\":\"Uzbekistan\",\"iso_a2\":\"UZ\",\"iso_a3\":\"UZB\",\"iso_n3\":\"860\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[66.51860680528867,37.36278432875879],[66.54615034370022,37.97468496352687],[65.2159989765074,38.40269501398429],[64.17022301621677,38.892406724598246],[63.51801476426103,39.36325653742564],[62.374260288345,40.05388621679039],[61.88271406438469,41.084856879229406],[61.54717898951356,41.266370347654615],[60.46595299667069,41.22032664648255],[60.08334069198167,41.425146185871405],[59.97642215356978,42.22308197689021],[58.62901085799146,42.75155101172305],[57.78652998233708,42.17055288346552],[56.932215203687804,41.826026109375604],[57.0963912290791,41.32231008561057],[55.96819135928291,41.30864166926936],[55.928917270741096,44.99585846615911],[58.50312706892847,45.586804307632825],[58.689989048095896,45.50001373959862],[60.23997195825833,44.78403677019473],[61.05831994003245,44.40581696225051],[62.01330040878625,43.50447663021565],[63.18578698105657,43.650074978198006],[64.90082441595928,43.72808055274258],[66.09801232286509,42.99766002051309],[66.02339155463562,41.99464630794398],[66.51064863471572,41.987644151368436],[66.7140470722165,41.1684435084615],[67.98585574735182,41.13599070898222],[68.25989586779562,40.6623245305949],[68.63248294462001,40.66868073176681],[69.07002729683532,41.38424428971237],[70.3889648782208,42.08130768489745],[70.96231489449914,42.26615428320549],[71.25924767444822,42.16771067968946],[70.42002241402821,41.51999827734314],[71.1578585142916,41.14358714452912],[71.87011478057047,41.392900092121266],[73.05541710804917,40.866033026689465],[71.77487511585656,40.14584442805378],[71.01419803252017,40.24436554621823],[70.60140669137269,40.21852733007229],[70.45815962105962,40.49649485937029],[70.66662234892505,40.96021332454141],[69.32949466337283,40.72782440852485],[69.0116329283455,40.08615814875666],[68.53641645698941,39.53345286717894],[67.70142866401736,39.58047842056453],[67.44221967964131,39.140143541005486],[68.17602501818592,38.901553453113905],[68.39203250516596,38.157025254868735],[67.82999962755952,37.14499400486468],[67.07578209825962,37.35614390720929],[66.51860680528867,37.36278432875879]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Yem.\",\"name\":\"Yemen\",\"name_long\":\"Yemen\",\"iso_a2\":\"YE\",\"iso_a3\":\"YEM\",\"iso_n3\":\"887\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[53.10857262554751,16.65105113368895],[52.38520592632588,16.382411200419654],[52.19172936382509,15.93843313238402],[52.1681649107,15.597420355689948],[51.172515089732485,15.175249742081492],[49.57457645040315,14.70876658778275],[48.67923058451416,14.00320241948566],[48.23894738138742,13.948089504446372],[47.938914015500785,14.007233181204427],[47.354453566279716,13.592219753468383],[46.717076450391744,13.39969920496502],[45.87759280781027,13.347764390511685],[45.62505008319987,13.290946153206763],[45.406458774605255,13.026905422411433],[45.14435591002086,12.95393830001531],[44.98953331887441,12.699586900274708],[44.49457645038285,12.721652736863346],[44.175112745954486,12.585950425664876],[43.48295861183713,12.636800035040084],[43.22287112811213,13.220950425667425],[43.25144819516953,13.767583726450852],[43.08794396339806,14.06263031662131],[42.89224531430872,14.802249253798749],[42.60487267433362,15.213335272680595],[42.80501549660005,15.261962795467255],[42.70243777850066,15.718885809791999],[42.823670688657415,15.911742255105265],[42.77933230975097,16.347891343648683],[43.21837527850275,16.66688996018641],[43.11579756040335,17.088440456607373],[43.380794305196105,17.57998668056767],[43.79151858905192,17.31997671149111],[44.06261315285508,17.410358791569593],[45.21665123879718,17.43332896572333],[45.39999922056875,17.333335069238558],[46.366658563020536,17.233315334537636],[46.74999433776165,17.283338120996177],[47.000004917189756,16.949999294497445],[47.46669477721763,17.116681626854884],[48.18334354024134,18.166669216377315],[49.11667158386487,18.616667588774945],[52.00000980002224,19.000003363516058],[52.78218427919205,17.349742336491232],[53.10857262554751,16.65105113368895]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Ang.\",\"name\":\"Angola\",\"name_long\":\"Angola\",\"iso_a2\":\"AO\",\"iso_a3\":\"AGO\",\"iso_n3\":\"024\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[16.326528354567046,-5.877470391466218],[16.57317996589614,-6.622644545115094],[16.86019087084523,-7.222297865429979],[17.08999596524717,-7.545688978712476],[17.472970004962292,-8.068551120641656],[18.13422163256905,-7.987677504104866],[18.464175652752687,-7.847014255406477],[19.01675174324967,-7.98824594486014],[19.166613396896082,-7.738183688999725],[19.417502475673217,-7.155428562044278],[20.037723016040218,-7.11636117923166],[20.09162153492062,-6.943090101756951],[20.601822950938327,-6.939317722199689],[20.51474816252653,-7.299605808138665],[21.728110792739756,-7.290872491081316],[21.746455926203367,-7.920084730667114],[21.94913089365204,-8.305900974158305],[21.801801385187957,-8.908706556842986],[21.875181919042404,-9.523707777548566],[22.208753289486424,-9.89479623783653],[22.155268182064333,-11.084801120653779],[22.402798292742432,-10.99307545333569],[22.83734541188477,-11.017621758674338],[23.456790805767465,-10.867863457892483],[23.912215203555746,-10.926826267137542],[24.017893507592614,-11.237298272347118],[23.90415368011824,-11.722281589406336],[24.079905226342902,-12.191296888887308],[23.930922072045377,-12.565847670138822],[24.016136508894704,-12.911046237848552],[21.933886346125945,-12.898437188369357],[21.887842644953878,-16.080310153876894],[22.56247846852429,-16.898451429921835],[23.215048455506093,-17.523116143465952],[21.377176141045595,-17.93063648851971],[18.95618696460363,-17.789094740472237],[18.26330936043422,-17.309950860262006],[14.209706658595051,-17.353100681225712],[14.058501417709039,-17.423380629142656],[13.462362094789967,-16.97121184658874],[12.814081251688407,-16.941342868724078],[12.215461460019384,-17.111668389558062],[11.734198846085148,-17.3018893368245],[11.64009606288161,-16.67314218512921],[11.778537224991567,-15.79381601325069],[12.123580763404448,-14.878316338767931],[12.175618930722266,-14.449143568583892],[12.500095249083017,-13.547699883684402],[12.738478631245442,-13.137905775609935],[13.312913852601838,-12.483630466362513],[13.633721144269828,-12.038644707897191],[13.738727654686926,-11.297863050993143],[13.686379428775297,-10.731075941615842],[13.38732791510216,-10.373578383020728],[13.120987583069875,-9.766897067914115],[12.875369500386569,-9.166933689005488],[12.929061313537801,-8.959091078327575],[13.236432732809874,-8.562629489784342],[12.933040398824316,-7.596538588087753],[12.72829837408392,-6.927122084178805],[12.227347039446443,-6.294447523629372],[12.322431674863566,-6.100092461779653],[12.735171339578699,-5.965682061388478],[13.02486941900699,-5.984388929878108],[13.375597364971895,-5.864241224799557],[16.326528354567046,-5.877470391466218]]],[[[12.436688266660923,-5.684303887559224],[12.18233686692028,-5.789930515163803],[11.914963006242116,-5.037986748884734],[12.318607618873926,-4.606230157086158],[12.62075971848455,-4.438023369976121],[12.995517205465205,-4.781103203961919],[12.631611769265845,-4.991271254092936],[12.468004184629763,-5.248361504744992],[12.436688266660923,-5.684303887559224]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Bur.\",\"name\":\"Burundi\",\"name_long\":\"Burundi\",\"iso_a2\":\"BI\",\"iso_a3\":\"BDI\",\"iso_n3\":\"108\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[29.339997592900346,-4.499983412294092],[29.276383904749053,-3.293907159034063],[29.024926385216787,-2.839257907730158],[29.632176141078588,-2.917857761246097],[29.938359002407942,-2.348486830254238],[30.469696079232985,-2.413857517103458],[30.527677036264464,-2.807631931167535],[30.7430127296247,-3.034284763199686],[30.75226281100495,-3.35932952231557],[30.505559523243566,-3.568567396665365],[30.116332635221173,-4.090137627787243],[29.753512404099922,-4.452389418153281],[29.339997592900346,-4.499983412294092]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"B.F.\",\"name\":\"Burkina Faso\",\"name_long\":\"Burkina Faso\",\"iso_a2\":\"BF\",\"iso_a3\":\"BFA\",\"iso_n3\":\"854\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-2.827496303712707,9.642460842319778],[-3.511898972986273,9.90032623945622],[-3.980449184576685,9.8623440617217],[-4.330246954760383,9.61083486575714],[-4.779883592131966,9.821984768101743],[-4.954653286143099,10.152713934769736],[-5.404341599946974,10.370736802609146],[-5.470564947929006,10.951269842976048],[-5.197842576508648,11.37514577885014],[-5.220941941743121,11.713858954307227],[-4.427166103523803,12.542645575404295],[-4.28040503581488,13.228443508349741],[-4.006390753587226,13.472485459848116],[-3.522802700199861,13.337661647998615],[-3.10370683431276,13.541266791228594],[-2.967694464520577,13.79815033615151],[-2.191824510090385,14.246417548067356],[-2.001035122068771,14.559008287000891],[-1.066363491205664,14.973815009007765],[-0.515854458000348,15.116157741755726],[-0.26625729003058,14.924308986872148],[0.374892205414682,14.928908189346132],[0.295646396495101,14.444234930880654],[0.429927605805517,13.988733018443924],[0.993045688490071,13.335749620003824],[1.024103224297477,12.851825669806574],[2.177107781593776,12.625017808477534],[2.154473504249921,11.940150051313337],[1.935985548519881,11.641150214072553],[1.447178175471066,11.547719224488858],[1.243469679376489,11.110510769083461],[0.899563022474069,10.99733938236426],[0.023802524423701,11.018681748900804],[-0.438701544588582,11.09834096927872],[-0.761575893548183,10.936929633015055],[-1.203357713211431,11.009819240762738],[-2.940409308270461,10.962690334512558],[-2.963896246747112,10.395334784380083],[-2.827496303712707,9.642460842319778]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Bwa.\",\"name\":\"Botswana\",\"name_long\":\"Botswana\",\"iso_a2\":\"BW\",\"iso_a3\":\"BWA\",\"iso_n3\":\"072\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[25.649163445750162,-18.53602589281899],[25.85039147309473,-18.714412937090536],[26.164790887158485,-19.29308562589494],[27.296504754350508,-20.391519870691],[27.724747348753255,-20.499058526290387],[27.72722781750326,-20.851801853114715],[28.021370070108617,-21.485975030200585],[28.794656202924212,-21.63945403410745],[29.43218834810904,-22.091312758067588],[28.01723595552525,-22.827753594659075],[27.119409620886245,-23.574323011979775],[26.786406691197413,-24.240690606383485],[26.4857532081233,-24.616326592713104],[25.94165205252216,-24.69637338633322],[25.76584882986521,-25.174845472923675],[25.66466637543772,-25.486816094669713],[25.025170525825786,-25.7196700985769],[24.211266717228792,-25.670215752873574],[23.73356977712271,-25.390129489851613],[23.312096795350186,-25.26868987396572],[22.8242712745149,-25.500458672794768],[22.57953169118059,-25.979447523708146],[22.105968865657868,-26.280256036079138],[21.60589603036939,-26.726533705351756],[20.88960900237174,-26.828542982695915],[20.66647016773544,-26.477453301704923],[20.758609246511835,-25.86813648855145],[20.165725538827186,-24.917961928000768],[19.895767856534434,-24.767790215760588],[19.89545779794068,-21.84915699634787],[20.881134067475866,-21.814327080983148],[20.910641310314535,-18.252218926672022],[21.655040317478978,-18.219146010005225],[23.1968583513393,-17.869038181227786],[23.579005568137717,-18.28126108162006],[24.217364536239213,-17.88934701911849],[24.520705193792537,-17.887124932529936],[25.08444339366457,-17.661815687737374],[25.264225701608012,-17.736539808831417],[25.649163445750162,-18.53602589281899]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Benin\",\"name\":\"Benin\",\"name_long\":\"Benin\",\"iso_a2\":\"BJ\",\"iso_a3\":\"BEN\",\"iso_n3\":\"204\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[2.691701694356254,6.258817246928629],[1.865240512712319,6.142157701029731],[1.618950636409238,6.832038072126237],[1.664477573258381,9.12859039960938],[1.46304284018467,9.334624335157088],[1.425060662450136,9.825395412633],[1.077795037448738,10.175606594275024],[0.772335646171484,10.470808213742359],[0.899563022474069,10.99733938236426],[1.243469679376489,11.110510769083461],[1.447178175471066,11.547719224488858],[1.935985548519881,11.641150214072553],[2.154473504249921,11.940150051313337],[2.49016360841793,12.233052069543675],[2.848643019226671,12.235635891158267],[3.611180454125559,11.660167141155966],[3.572216424177469,11.327939357951518],[3.797112257511714,10.734745591673105],[3.600070021182801,10.332186184119408],[3.705438266625919,10.063210354040208],[3.220351596702101,9.4441525333997],[2.912308383810256,9.13760793704432],[2.723792758809509,8.50684540448971],[2.74906253420022,7.870734361192888],[2.691701694356254,6.258817246928629]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"C.A.R.\",\"name\":\"Central African Rep.\",\"name_long\":\"Central African Republic\",\"iso_a2\":\"CF\",\"iso_a3\":\"CAF\",\"iso_n3\":\"140\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[15.279460483469109,7.421924546737969],[16.10623172370677,7.497087917506505],[16.290561557691888,7.754307359239306],[16.456184523187346,7.734773667832968],[16.705988396886255,7.508327541529979],[17.964929640380888,7.890914008002866],[18.38955488452322,8.281303615751824],[18.911021762780507,8.630894680206353],[18.81200971850927,8.982914536978598],[19.09400800952602,9.07484691002584],[20.05968549976427,9.012706000194854],[21.000868361096167,9.475985215691509],[21.723821648859452,10.567055568885976],[22.231129184668788,10.97188873946051],[22.864165480244225,11.142395127807546],[22.97754357269261,10.71446259199854],[23.554304233502194,10.089255275915308],[23.557249790142826,9.681218166538684],[23.394779087017184,9.265067857292223],[23.459012892355986,8.954285793488893],[23.805813429466752,8.666318874542426],[24.567369012152085,8.229187933785468],[25.11493248871679,7.825104071479174],[25.124130893664727,7.500085150579436],[25.79664798351118,6.979315904158071],[26.213418409945117,6.546603298362072],[26.46590945812323,5.94671743410187],[27.21340905122517,5.550953477394557],[27.37422610851749,5.233944403500061],[27.04406538260471,5.127852688004836],[26.402760857862543,5.150874538590871],[25.650455356557472,5.256087754737123],[25.278798455514302,5.170408229997192],[25.12883344900328,4.927244777847789],[24.805028924262416,4.89724660890235],[24.410531040146253,5.108784084489129],[23.29721398285014,4.609693101414223],[22.841479526468106,4.710126247573484],[22.70412356943629,4.633050848810157],[22.405123732195538,4.029160061047321],[21.659122755630023,4.22434194581372],[20.927591180106276,4.322785549329737],[20.290679152108936,4.691677761245288],[19.467783644293146,5.03152781821278],[18.93231245288476,4.709506130385975],[18.54298221199778,4.201785183118318],[18.45306521980993,3.504385891123349],[17.809900343505262,3.56019643799857],[17.133042433346304,3.728196519379452],[16.537058139724135,3.198254706226279],[16.012852410555354,2.267639675298085],[15.907380812247652,2.557389431158612],[15.86273237474748,3.013537298998983],[15.405395948964383,3.33530060466434],[15.036219516671252,3.851367295747124],[14.950953403389661,4.210389309094921],[14.478372430080467,4.732605495620447],[14.558935988023505,5.03059764243153],[14.459407179429348,5.4517605656103],[14.536560092841112,6.22695872642069],[14.776545444404576,6.408498033062045],[15.279460483469109,7.421924546737969]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"I.C.\",\"name\":\"Côte d'Ivoire\",\"name_long\":\"Côte d'Ivoire\",\"iso_a2\":\"CI\",\"iso_a3\":\"CIV\",\"iso_n3\":\"384\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-2.856125047202397,4.994475816259509],[-3.311084357100071,4.984295559098015],[-4.008819545904942,5.179813340674315],[-4.649917364917911,5.168263658057086],[-5.834496222344526,4.993700669775137],[-6.528769090185847,4.705087795425015],[-7.518941209330436,4.338288479017308],[-7.71215938966975,4.364565944837722],[-7.63536821128403,5.188159084489456],[-7.539715135111762,5.313345241716519],[-7.570152553731688,5.707352199725904],[-7.993692592795881,6.126189683451543],[-8.311347622094019,6.193033148621083],[-8.60288021486862,6.46756419517166],[-8.385451626000574,6.911800645368742],[-8.48544552248535,7.39520783124307],[-8.439298468448698,7.686042792181738],[-8.280703497744938,7.687179673692156],[-8.221792364932199,8.123328762235573],[-8.299048631208564,8.316443589710303],[-8.20349890790088,8.455453192575447],[-7.832100389019188,8.575704250518626],[-8.079113735374348,9.376223863152035],[-8.30961646161225,9.789531968622441],[-8.229337124046822,10.1290202905639],[-8.029943610048619,10.206534939001713],[-7.899589809592372,10.297382106970828],[-7.622759161804809,10.147236232946796],[-6.850506557635057,10.138993841996239],[-6.666460944027548,10.430810655148447],[-6.493965013037267,10.411302801958271],[-6.205222947606431,10.524060777219134],[-6.050452032892267,10.096360785355444],[-5.816926235365287,10.222554633012194],[-5.404341599946974,10.370736802609146],[-4.954653286143099,10.152713934769736],[-4.779883592131966,9.821984768101743],[-4.330246954760383,9.61083486575714],[-3.980449184576685,9.8623440617217],[-3.511898972986273,9.90032623945622],[-2.827496303712707,9.642460842319778],[-2.562189500326241,8.219627793811483],[-2.983584967450327,7.379704901555512],[-3.244370083011262,6.250471503113502],[-2.81070146321784,5.38905121502411],[-2.856125047202397,4.994475816259509]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Cam.\",\"name\":\"Cameroon\",\"name_long\":\"Cameroon\",\"iso_a2\":\"CM\",\"iso_a3\":\"CMR\",\"iso_n3\":\"120\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[13.075822381246752,2.267097072759015],[12.951333855855609,2.32161570882694],[12.359380323952221,2.19281220133945],[11.75166548019979,2.326757513839993],[11.276449008843713,2.261050930180872],[9.649158155972628,2.283866075037736],[9.795195753629457,3.073404445809117],[9.404366896206,3.734526882335202],[8.948115675501072,3.904128933117136],[8.744923943729418,4.35221527751996],[8.488815545290889,4.495617377129918],[8.500287713259695,4.771982937026849],[8.757532993208628,5.479665839047911],[9.233162876023044,6.444490668153334],[9.522705926154401,6.453482367372117],[10.118276808318257,7.038769639509879],[10.497375115611417,7.055357774275564],[11.05878787603035,6.644426784690594],[11.74577436691851,6.981382961449753],[11.839308709366803,7.397042344589436],[12.063946160539558,7.799808457872302],[12.218872104550599,8.305824082874324],[12.753671502339214,8.717762762888995],[12.955467970438974,9.417771714714704],[13.167599724997103,9.640626328973411],[13.308676385153918,10.160362046748928],[13.572949659894562,10.798565985553566],[14.415378859116686,11.572368882692075],[14.468192172918975,11.904751695193411],[14.577177768622533,12.085360826053503],[14.181336297266792,12.483656927943116],[14.213530714584634,12.802035427293347],[14.495787387762844,12.85939626713733],[14.893385857816526,12.219047756392584],[14.9601518083376,11.555574042197224],[14.923564894274959,10.891325181517473],[15.467872755605269,9.98233673750343],[14.909353875394716,9.99212942142273],[14.62720055508106,9.920919297724538],[14.171466098699028,10.021378282099931],[13.954218377344006,9.549494940626685],[14.544466586981768,8.965861314322268],[14.97999555833769,8.796104234243472],[15.120865512765334,8.382150173369423],[15.436091749745769,7.692812404811973],[15.279460483469109,7.421924546737969],[14.776545444404576,6.408498033062045],[14.536560092841112,6.22695872642069],[14.459407179429348,5.4517605656103],[14.558935988023505,5.03059764243153],[14.478372430080467,4.732605495620447],[14.950953403389661,4.210389309094921],[15.036219516671252,3.851367295747124],[15.405395948964383,3.33530060466434],[15.86273237474748,3.013537298998983],[15.907380812247652,2.557389431158612],[16.012852410555354,2.267639675298085],[15.940918816805064,1.727672634280295],[15.146341993885244,1.964014797367184],[14.33781253424658,2.227874660649491],[13.075822381246752,2.267097072759015]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"D.R.C.\",\"name\":\"Dem. Rep. Congo\",\"name_long\":\"Democratic Republic of the Congo\",\"iso_a2\":\"CD\",\"iso_a3\":\"COD\",\"iso_n3\":\"180\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[30.833859897593808,3.509165961110341],[30.773346795380043,2.339883327642127],[31.174149204235814,2.204465236821264],[30.852670118948055,1.849396470543809],[30.468507521290295,1.58380544677972],[30.086153598762703,1.062312730306289],[29.875778842902495,0.597379868976304],[29.819503208136638,-0.205310153813372],[29.58783776217217,-0.587405694179481],[29.579466180140884,-1.341313164885626],[29.29188683443661,-1.620055840667987],[29.25483483248334,-2.215109958508911],[29.117478875451553,-2.292211195488385],[29.024926385216787,-2.839257907730158],[29.276383904749053,-3.293907159034063],[29.339997592900346,-4.499983412294092],[29.519986606572925,-5.419978936386314],[29.419992710088167,-5.939998874539433],[29.62003217949001,-6.520015150583426],[30.199996779101696,-7.079980970898163],[30.740015496551788,-8.340007419470915],[30.346086053190813,-8.238256524288218],[29.002912225060467,-8.407031752153472],[28.7348665707625,-8.526559340044578],[28.449871046672826,-9.164918308146085],[28.67368167492893,-9.605924981324932],[28.49606977714177,-10.789883721564044],[28.372253045370428,-11.793646742401393],[28.642417433392353,-11.971568698782315],[29.34154788586909,-12.360743910372413],[29.61600141777123,-12.178894545137311],[29.69961388521949,-13.257226657771831],[28.934285922976837,-13.248958428605135],[28.523561639121027,-12.698604424696683],[28.155108676879987,-12.272480564017897],[27.38879886242378,-12.132747491100666],[27.164419793412463,-11.608748467661075],[26.553087599399618,-11.924439792532127],[25.752309604604733,-11.784965101776358],[25.418118116973204,-11.330935967659961],[24.78316979340295,-11.238693536018964],[24.31451622894795,-11.26282642989927],[24.25715538910399,-10.951992689663657],[23.912215203555718,-10.926826267137514],[23.45679080576744,-10.867863457892483],[22.83734541188474,-11.01762175867433],[22.402798292742375,-10.99307545333569],[22.155268182064308,-11.084801120653772],[22.208753289486395,-9.894796237836509],[21.875181919042348,-9.523707777548566],[21.8018013851879,-8.908706556842978],[21.949130893652043,-8.305900974158277],[21.74645592620331,-7.920084730667149],[21.7281107927397,-7.290872491081302],[20.514748162526498,-7.299605808138629],[20.6018229509383,-6.939317722199682],[20.091621534920648,-6.943090101756993],[20.037723016040218,-7.116361179231646],[19.417502475673157,-7.155428562044298],[19.16661339689611,-7.738183688999754],[19.01675174324967,-7.988245944860132],[18.464175652752687,-7.847014255406444],[18.13422163256905,-7.987677504104922],[17.472970004962235,-8.0685511206417],[17.08999596524717,-7.545688978712526],[16.8601908708452,-7.222297865429986],[16.57317996589614,-6.622644545115087],[16.326528354567046,-5.877470391466267],[13.375597364971895,-5.864241224799549],[13.02486941900696,-5.984388929878157],[12.735171339578699,-5.965682061388499],[12.32243167486351,-6.10009246177966],[12.182336866920252,-5.789930515163839],[12.436688266660868,-5.684303887559246],[12.468004184629736,-5.248361504745005],[12.63161176926579,-4.991271254092936],[12.995517205465177,-4.781103203961884],[13.258240187237048,-4.882957452009165],[13.600234816144678,-4.50013844159097],[14.144956088933299,-4.510008640158715],[14.209034864975223,-4.793092136253598],[14.58260379401318,-4.97023894615014],[15.170991652088444,-4.343507175314301],[15.75354007331475,-3.855164890156096],[16.0062895036543,-3.535132744972529],[15.972803175529151,-2.712392266453612],[16.407091912510054,-1.740927015798682],[16.865306837642123,-1.225816338713287],[17.523716261472856,-0.743830254726987],[17.638644646889986,-0.424831638189247],[17.66355268725468,-0.058083998213817],[17.826540154703252,0.288923244626105],[17.774191928791566,0.855658677571085],[17.898835483479587,1.741831976728278],[18.094275750407434,2.365721543788055],[18.39379235197114,2.90044342692822],[18.45306521980993,3.504385891123349],[18.54298221199778,4.201785183118318],[18.93231245288476,4.709506130385975],[19.467783644293146,5.03152781821278],[20.290679152108936,4.691677761245288],[20.927591180106276,4.322785549329737],[21.659122755630023,4.22434194581372],[22.405123732195538,4.029160061047321],[22.70412356943629,4.633050848810157],[22.841479526468106,4.710126247573484],[23.29721398285014,4.609693101414223],[24.410531040146253,5.108784084489129],[24.805028924262416,4.89724660890235],[25.12883344900328,4.927244777847789],[25.278798455514302,5.170408229997192],[25.650455356557472,5.256087754737123],[26.402760857862543,5.150874538590871],[27.04406538260471,5.127852688004836],[27.37422610851749,5.233944403500061],[27.979977247842807,4.408413397637375],[28.428993768026913,4.287154649264494],[28.696677687298802,4.455077215996937],[29.1590784034465,4.389267279473231],[29.71599531425602,4.600804755060025],[29.953500197069474,4.173699042167683],[30.833859897593808,3.509165961110341]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Rep. Congo\",\"name\":\"Congo\",\"name_long\":\"Republic of Congo\",\"iso_a2\":\"CG\",\"iso_a3\":\"COG\",\"iso_n3\":\"178\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[12.995517205465177,-4.781103203961884],[12.620759718484491,-4.438023369976136],[12.318607618873926,-4.606230157086188],[11.91496300624209,-5.037986748884791],[11.093772820691925,-3.978826592630547],[11.855121697648114,-3.426870619321051],[11.478038771214303,-2.765618991714241],[11.820963575903193,-2.514161472181982],[12.495702752338161,-2.391688327650243],[12.575284458067642,-1.948511244315135],[13.109618767965628,-2.428740329603514],[13.99240726080771,-2.4708049454891],[14.299210239324564,-1.998275648612214],[14.425455763413593,-1.333406670744971],[14.316418491277743,-0.552627455247048],[13.843320753645655,0.038757635901149],[14.276265903386957,1.196929836426619],[14.026668735417218,1.395677395021153],[13.282631463278818,1.31418366129688],[13.003113641012078,1.83089630778332],[13.075822381246752,2.267097072759015],[14.33781253424658,2.227874660649491],[15.146341993885244,1.964014797367184],[15.940918816805064,1.727672634280295],[16.012852410555354,2.267639675298085],[16.537058139724135,3.198254706226279],[17.133042433346304,3.728196519379452],[17.809900343505262,3.56019643799857],[18.45306521980993,3.504385891123349],[18.39379235197114,2.90044342692822],[18.094275750407434,2.365721543788055],[17.898835483479587,1.741831976728278],[17.774191928791566,0.855658677571085],[17.826540154703252,0.288923244626105],[17.66355268725468,-0.058083998213817],[17.638644646889986,-0.424831638189247],[17.523716261472856,-0.743830254726987],[16.865306837642123,-1.225816338713287],[16.407091912510054,-1.740927015798682],[15.972803175529151,-2.712392266453612],[16.0062895036543,-3.535132744972529],[15.75354007331475,-3.855164890156096],[15.170991652088444,-4.343507175314301],[14.58260379401318,-4.97023894615014],[14.209034864975223,-4.793092136253598],[14.144956088933299,-4.510008640158715],[13.600234816144678,-4.50013844159097],[13.258240187237048,-4.882957452009165],[12.995517205465177,-4.781103203961884]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Dji.\",\"name\":\"Djibouti\",\"name_long\":\"Djibouti\",\"iso_a2\":\"DJ\",\"iso_a3\":\"DJI\",\"iso_n3\":\"262\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[43.08122602720015,12.699638576707116],[43.31785241066467,12.390148423711025],[43.286381463398925,11.974928290245884],[42.715873650896526,11.735640570518342],[43.14530480324214,11.462039699748857],[42.77685184100096,10.92687856693442],[42.55493000000013,11.105110000000195],[42.31414000000012,11.0342],[41.755570000000205,11.050910000000101],[41.73959000000019,11.355110000000138],[41.66176000000013,11.6312],[42.000000000000114,12.100000000000136],[42.35156000000012,12.542230000000131],[42.77964236834475,12.455415757695675],[43.08122602720015,12.699638576707116]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Alg.\",\"name\":\"Algeria\",\"name_long\":\"Algeria\",\"iso_a2\":\"DZ\",\"iso_a3\":\"DZA\",\"iso_n3\":\"012\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[11.999505649471613,23.47166840259645],[8.572893100629784,21.565660712159143],[5.677565952180686,19.601206976799716],[4.267419467800039,19.155265204337],[3.158133172222705,19.057364203360038],[3.1466610042539,19.69357859952144],[2.683588494486429,19.856230170160114],[2.06099083823392,20.142233384679486],[1.823227573259032,20.610809434486043],[-1.550054897457613,22.792665920497384],[-4.92333736817423,24.974574082941],[-8.68439978680905,27.395744126896005],[-8.665124477564191,27.589479071558227],[-8.665589565454809,27.656425889592356],[-8.674116176782974,28.84128896739658],[-7.059227667661928,29.57922842052453],[-6.060632290053774,29.731699734001694],[-5.242129278982787,30.000443020135588],[-4.859646165374471,30.501187649043842],[-3.690441046554696,30.896951605751152],[-3.647497931320146,31.63729401298067],[-3.068980271812648,31.724497992473214],[-2.616604783529567,32.09434621838615],[-1.30789913573787,32.2628889023061],[-1.124551153966308,32.65152151135713],[-1.388049282222568,32.86401500094131],[-1.733454555661467,33.919712836231994],[-1.792985805661687,34.527918606091205],[-2.169913702798624,35.16839630791668],[-1.208602871089056,35.7148487411871],[-0.127454392894606,35.888662421200806],[0.503876580415209,36.30127289483528],[1.466918572606545,36.605647081034405],[3.161698846050825,36.78390493422522],[4.81575809084913,36.86503693292346],[5.320120070017793,36.71651886651662],[6.261819695672613,37.11065501560674],[7.33038496260397,37.118380642234364],[7.737078484741004,36.885707505840216],[8.420964389691676,36.94642731378316],[8.217824334352315,36.433176988260286],[8.376367628623768,35.47987600355594],[8.140981479534304,34.65514598239379],[7.524481642292244,34.09737641045146],[7.612641635782182,33.34411489514896],[8.430472853233368,32.74833730725595],[8.439102817426118,32.50628489840082],[9.055602654668148,32.10269196220129],[9.482139926805274,30.307556057246188],[9.805634392952411,29.42463837332339],[9.859997999723447,28.959989732371014],[9.683884718472767,28.1441738957792],[9.756128370816782,27.68825857188415],[9.629056023811074,27.14095347748092],[9.716285841519749,26.512206325785698],[9.319410841518163,26.094324856057455],[9.910692579801776,25.36545461679674],[9.94826134607797,24.936953640232517],[10.303846876678362,24.379313259370917],[10.771363559622927,24.56253205006175],[11.560669386449005,24.097909247325518],[11.999505649471613,23.47166840259645]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Egypt\",\"name\":\"Egypt\",\"name_long\":\"Egypt\",\"iso_a2\":\"EG\",\"iso_a3\":\"EGY\",\"iso_n3\":\"818\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[34.9226,29.50133],[34.64174,29.09942],[34.42655,28.34399],[34.15451,27.8233],[33.92136,27.6487],[33.58811,27.97136],[33.13676,28.41765],[32.42323,29.85108],[32.32046,29.76043],[32.73482,28.70523],[33.34876,27.69989],[34.10455,26.14227],[34.47387,25.59856],[34.79507,25.03375],[35.69241,23.92671],[35.49372,23.75237],[35.52598,23.10244],[36.69069,22.20485],[36.86623,22],[32.9,22],[29.02,22],[25,22],[25,25.682499996361],[25,29.23865452953346],[24.70007,30.04419],[24.95762,30.6616],[24.80287,31.08929],[25.16482,31.56915],[26.49533,31.58568],[27.45762,31.32126],[28.45048,31.02577],[28.91353,30.87005],[29.68342,31.18686],[30.09503,31.4734],[30.97693,31.55586],[31.68796,31.4296],[31.96041,30.9336],[32.19247,31.26034],[32.99392,31.02407],[33.7734,30.96746],[34.26544,31.21936],[34.9226,29.50133]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Gabon\",\"name\":\"Gabon\",\"name_long\":\"Gabon\",\"iso_a2\":\"GA\",\"iso_a3\":\"GAB\",\"iso_n3\":\"266\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[11.093772820691925,-3.978826592630547],[10.06613528813574,-2.969482517105681],[9.40524539555497,-2.144313246269043],[8.79799563969317,-1.111301364754496],[8.830086704146423,-0.779073581550037],[9.048419630579588,-0.459351494960217],[9.29135053878369,0.268666083167687],[9.492888624721985,1.010119533691494],[9.830284051155644,1.067893784993799],[11.285078973036462,1.057661851400013],[11.276449008843713,2.261050930180872],[11.75166548019979,2.326757513839993],[12.359380323952221,2.19281220133945],[12.951333855855609,2.32161570882694],[13.075822381246752,2.267097072759015],[13.003113641012078,1.83089630778332],[13.282631463278818,1.31418366129688],[14.026668735417218,1.395677395021153],[14.276265903386957,1.196929836426619],[13.843320753645655,0.038757635901149],[14.316418491277743,-0.552627455247048],[14.425455763413593,-1.333406670744971],[14.299210239324564,-1.998275648612214],[13.99240726080771,-2.4708049454891],[13.109618767965628,-2.428740329603514],[12.575284458067642,-1.948511244315135],[12.495702752338161,-2.391688327650243],[11.820963575903193,-2.514161472181982],[11.478038771214303,-2.765618991714241],[11.855121697648114,-3.426870619321051],[11.093772820691925,-3.978826592630547]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Erit.\",\"name\":\"Eritrea\",\"name_long\":\"Eritrea\",\"iso_a2\":\"ER\",\"iso_a3\":\"ERI\",\"iso_n3\":\"232\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[42.35156000000012,12.542230000000131],[42.00975,12.86582],[41.59856,13.452090000000112],[41.15519371924984,13.773319810435224],[40.8966,14.118640000000141],[40.026218702969175,14.519579169162284],[39.34061,14.53155],[39.0994,14.74064],[38.51295,14.50547],[37.90607000000011,14.959430000000168],[37.59377,14.2131],[36.42951,14.42211],[36.32318891779812,14.82248057704106],[36.75386030451858,16.29187409104429],[36.852530000000115,16.95655],[37.16747,17.263140000000135],[37.90400000000011,17.42754],[38.410089959473225,17.99830739997031],[38.990622999840014,16.84062612555169],[39.26611006038803,15.922723496967249],[39.814293654140215,15.435647284400318],[41.17927493669765,14.491079616753211],[41.73495161313235,13.921036892141558],[42.27683068214486,13.343992010954423],[42.58957645037526,13.000421250861905],[43.08122602720015,12.699638576707116],[42.77964236834475,12.455415757695675],[42.35156000000012,12.542230000000131]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Eth.\",\"name\":\"Ethiopia\",\"name_long\":\"Ethiopia\",\"iso_a2\":\"ET\",\"iso_a3\":\"ETH\",\"iso_n3\":\"231\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[37.90607000000011,14.959430000000168],[38.51295,14.50547],[39.0994,14.74064],[39.34061,14.53155],[40.02625000000012,14.51959],[40.8966,14.118640000000141],[41.1552,13.77333],[41.59856,13.452090000000112],[42.00975,12.86582],[42.35156000000012,12.542230000000131],[42.000000000000114,12.100000000000136],[41.66176000000013,11.6312],[41.73959000000019,11.355110000000138],[41.755570000000205,11.050910000000101],[42.31414000000012,11.0342],[42.55493000000013,11.105110000000195],[42.77685184100096,10.92687856693442],[42.55876,10.57258000000013],[42.92812,10.021940000000143],[43.29699000000011,9.540480000000173],[43.67875,9.18358000000012],[46.94834,7.99688],[47.78942,8.003],[44.9636,5.001620000000116],[43.66087,4.95755],[42.76967000000013,4.252590000000225],[42.12861,4.234130000000164],[41.85508309264412,3.918911920483765],[41.17180000000013,3.91909],[40.76848000000012,4.257020000000125],[39.85494000000011,3.838790000000131],[39.55938425876593,3.422060000000215],[38.89251,3.50074],[38.67114,3.61607],[38.436970000000144,3.58851],[38.12091500000014,3.598605],[36.85509323800824,4.447864127672858],[36.15907863285565,4.447864127672858],[35.81744766235362,4.776965663462022],[35.81744766235362,5.338232082790853],[35.298007118233095,5.506],[34.70702,6.59422000000012],[34.25032,6.82607],[34.07510000000019,7.22595],[33.568290000000104,7.71334],[32.954180000000235,7.784970000000102],[33.29480000000012,8.35458],[33.82550000000015,8.37916],[33.97498,8.684560000000147],[33.96162,9.58358],[34.25745,10.63009],[34.73115000000013,10.910170000000107],[34.83163000000013,11.318960000000118],[35.26049,12.08286],[35.863630000000164,12.57828],[36.27022,13.563330000000121],[36.42951,14.42211],[37.59377,14.2131],[37.90607000000011,14.959430000000168]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Ghana\",\"name\":\"Ghana\",\"name_long\":\"Ghana\",\"iso_a2\":\"GH\",\"iso_a3\":\"GHA\",\"iso_n3\":\"288\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[1.060121697604927,5.928837388528876],[-0.507637905265938,5.343472601742675],[-1.063624640294194,5.000547797053812],[-1.964706590167594,4.710462144383371],[-2.856125047202397,4.994475816259509],[-2.81070146321784,5.38905121502411],[-3.244370083011262,6.250471503113502],[-2.983584967450327,7.379704901555512],[-2.562189500326241,8.219627793811483],[-2.827496303712707,9.642460842319778],[-2.963896246747112,10.395334784380083],[-2.940409308270461,10.962690334512558],[-1.203357713211431,11.009819240762738],[-0.761575893548183,10.936929633015055],[-0.438701544588582,11.09834096927872],[0.023802524423701,11.018681748900804],[-0.049784715159944,10.706917832883931],[0.367579990245389,10.19121287682718],[0.365900506195885,9.465003973829482],[0.461191847342121,8.677222601756014],[0.712029249686878,8.31246450442383],[0.490957472342245,7.411744289576475],[0.570384148774849,6.914358628767189],[0.836931186536333,6.279978745952149],[1.060121697604927,5.928837388528876]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Gin.\",\"name\":\"Guinea\",\"name_long\":\"Guinea\",\"iso_a2\":\"GN\",\"iso_a3\":\"GIN\",\"iso_n3\":\"324\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-8.439298468448698,7.686042792181738],[-8.722123582382123,7.71167430259851],[-8.926064622422004,7.309037380396375],[-9.208786383490844,7.313920803247953],[-9.40334815106975,7.526905218938907],[-9.337279832384581,7.928534450711353],[-9.755342169625834,8.541055202666925],[-10.016566534861255,8.42850393313523],[-10.23009355309128,8.406205552601293],[-10.505477260774668,8.348896389189605],[-10.494315151399633,8.715540676300435],[-10.654770473665891,8.977178452994194],[-10.622395188835041,9.267910061068278],[-10.8391519840833,9.688246161330369],[-11.117481248407328,10.045872911006285],[-11.917277390988659,10.046983954300558],[-12.150338100625005,9.858571682164381],[-12.425928514037565,9.835834051955956],[-12.59671912276221,9.62018830000197],[-12.71195756677308,9.342711696810767],[-13.246550258832515,8.903048610871508],[-13.685153977909792,9.49474376061346],[-14.074044969122282,9.886166897008252],[-14.33007585291237,10.015719712763966],[-14.579698859098258,10.214467271358515],[-14.693231980843505,10.656300767454042],[-14.839553798877944,10.876571560098139],[-15.130311245168171,11.040411688679526],[-14.685687221728898,11.527823798056488],[-14.382191534878729,11.509271958863692],[-14.121406419317779,11.677117010947697],[-13.900799729863776,11.678718980348748],[-13.743160773157411,11.811269029177412],[-13.828271857142125,12.142644151249044],[-13.718743658899513,12.24718557377551],[-13.700476040084325,12.586182969610194],[-13.217818162478238,12.575873521367967],[-12.499050665730564,12.332089952031057],[-12.278599005573438,12.354440008997285],[-12.203564825885634,12.465647691289405],[-11.65830095055793,12.386582749882834],[-11.51394283695059,12.442987575729418],[-11.456168585648271,12.076834214725338],[-11.29757361494451,12.077971096235771],[-11.036555955438258,12.211244615116515],[-10.870829637078215,12.17788747807211],[-10.593223842806282,11.92397532800598],[-10.165213792348837,11.844083563682744],[-9.890992804392013,12.060478623904972],[-9.567911749703214,12.194243068892476],[-9.327616339546012,12.334286200403454],[-9.127473517279583,12.308060411015331],[-8.90526485842453,12.088358059126437],[-8.786099005559464,11.812560939984706],[-8.376304897484914,11.393645941610629],[-8.581305304386774,11.136245632364805],[-8.620321010767128,10.810890814655183],[-8.407310756860028,10.909256903522762],[-8.282357143578281,10.792597357623846],[-8.33537716310974,10.494811916541934],[-8.029943610048619,10.206534939001713],[-8.229337124046822,10.1290202905639],[-8.30961646161225,9.789531968622441],[-8.079113735374348,9.376223863152035],[-7.832100389019188,8.575704250518626],[-8.20349890790088,8.455453192575447],[-8.299048631208564,8.316443589710303],[-8.221792364932199,8.123328762235573],[-8.280703497744938,7.687179673692156],[-8.439298468448698,7.686042792181738]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Gambia\",\"name\":\"Gambia\",\"name_long\":\"The Gambia\",\"iso_a2\":\"GM\",\"iso_a3\":\"GMB\",\"iso_n3\":\"270\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-16.841524624081273,13.15139394780256],[-16.713728807023468,13.594958604379853],[-15.62459632003994,13.62358734786956],[-15.39877031092446,13.86036876063092],[-15.08173539881382,13.876491807505984],[-14.687030808968487,13.630356960499784],[-14.376713833055788,13.625680243377372],[-14.046992356817482,13.79406789800045],[-13.844963344772408,13.505041612192002],[-14.277701788784553,13.280585028532242],[-14.712197231494626,13.298206691943777],[-15.141163295949466,13.509511623585238],[-15.511812506562935,13.278569647672867],[-15.691000535534995,13.270353094938455],[-15.931295945692211,13.130284125211332],[-16.841524624081273,13.15139394780256]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"GnB.\",\"name\":\"Guinea-Bissau\",\"name_long\":\"Guinea-Bissau\",\"iso_a2\":\"GW\",\"iso_a3\":\"GNB\",\"iso_n3\":\"624\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-15.130311245168171,11.040411688679526],[-15.664180467175527,11.458474025920795],[-16.085214199273565,11.52459402103824],[-16.314786749730203,11.806514797406548],[-16.30894731288123,11.95870189050612],[-16.613838263403277,12.170911159712702],[-16.677451951554573,12.384851589401052],[-16.147716844130585,12.547761542201187],[-15.816574266004254,12.515567124883345],[-15.548476935274008,12.628170070847347],[-13.700476040084325,12.586182969610194],[-13.718743658899513,12.24718557377551],[-13.828271857142125,12.142644151249044],[-13.743160773157411,11.811269029177412],[-13.900799729863776,11.678718980348748],[-14.121406419317779,11.677117010947697],[-14.382191534878729,11.509271958863692],[-14.685687221728898,11.527823798056488],[-15.130311245168171,11.040411688679526]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Ken.\",\"name\":\"Kenya\",\"name_long\":\"Kenya\",\"iso_a2\":\"KE\",\"iso_a3\":\"KEN\",\"iso_n3\":\"404\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[40.993,-0.85829],[41.58513,-1.68325],[40.88477,-2.08255],[40.63785,-2.49979],[40.26304,-2.57309],[40.12119,-3.27768],[39.80006,-3.68116],[39.60489,-4.34653],[39.20222,-4.67677],[37.7669,-3.67712],[37.69869,-3.09699],[34.07262,-1.05982],[33.90371119710453,-0.95],[33.89356896966694,0.109813537861896],[34.18,0.515],[34.6721,1.17694],[35.03599,1.90584],[34.59607,3.05374],[34.47913,3.5556],[34.005,4.249884947362048],[34.62019626785388,4.847122742081988],[35.298007118232974,5.506],[35.817447662353516,5.338232082790797],[35.817447662353516,4.77696566346189],[36.159078632855646,4.447864127672769],[36.85509323800812,4.447864127672769],[38.120915,3.598605],[38.43697,3.58851],[38.67114,3.61607],[38.89251,3.50074],[39.55938425876585,3.42206],[39.85494,3.83879],[40.76848,4.25702],[41.1718,3.91909],[41.85508309264397,3.918911920483727],[40.98105,2.78452],[40.993,-0.85829]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Eq. G.\",\"name\":\"Eq. Guinea\",\"name_long\":\"Equatorial Guinea\",\"iso_a2\":\"GQ\",\"iso_a3\":\"GNQ\",\"iso_n3\":\"226\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[9.492888624721985,1.010119533691494],[9.305613234096256,1.160911363119183],[9.649158155972628,2.283866075037736],[11.276449008843713,2.261050930180872],[11.285078973036462,1.057661851400013],[9.830284051155644,1.067893784993799],[9.492888624721985,1.010119533691494]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Liberia\",\"name\":\"Liberia\",\"name_long\":\"Liberia\",\"iso_a2\":\"LR\",\"iso_a3\":\"LBR\",\"iso_n3\":\"430\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-7.71215938966975,4.364565944837722],[-7.974107224957251,4.355755113131963],[-9.004793667018674,4.8324185245922],[-9.913420376006684,5.593560695819207],[-10.765383876986643,6.140710760925558],[-11.438779466182055,6.785916856305747],[-11.19980180504828,7.105845648624737],[-11.146704270868383,7.396706447779536],[-10.69559485517648,7.939464016141087],[-10.23009355309128,8.406205552601293],[-10.016566534861255,8.42850393313523],[-9.755342169625834,8.541055202666925],[-9.337279832384581,7.928534450711353],[-9.40334815106975,7.526905218938907],[-9.208786383490844,7.313920803247953],[-8.926064622422004,7.309037380396375],[-8.722123582382123,7.71167430259851],[-8.439298468448698,7.686042792181738],[-8.48544552248535,7.39520783124307],[-8.385451626000574,6.911800645368742],[-8.60288021486862,6.46756419517166],[-8.311347622094019,6.193033148621083],[-7.993692592795881,6.126189683451543],[-7.570152553731688,5.707352199725904],[-7.539715135111762,5.313345241716519],[-7.63536821128403,5.188159084489456],[-7.71215938966975,4.364565944837722]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Libya\",\"name\":\"Libya\",\"name_long\":\"Libya\",\"iso_a2\":\"LY\",\"iso_a3\":\"LBY\",\"iso_n3\":\"434\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[14.8513,22.862950000000126],[14.143870883855243,22.49128896737113],[13.581424594790462,23.04050608976928],[11.9995056494717,23.471668402596432],[11.560669386449035,24.097909247325617],[10.771363559622955,24.562532050061748],[10.303846876678449,24.379313259370974],[9.948261346078027,24.936953640232616],[9.910692579801776,25.365454616796796],[9.31941084151822,26.094324856057483],[9.716285841519664,26.51220632578565],[9.629056023811074,27.140953477481048],[9.756128370816782,27.688258571884205],[9.683884718472882,28.144173895779314],[9.859997999723475,28.95998973237107],[9.805634392952356,29.424638373323376],[9.482139926805417,30.307556057246188],[9.970017124072967,30.539324856075382],[10.056575148161699,30.961831366493524],[9.950225050505196,31.376069647745283],[10.636901482799487,31.761420803345683],[10.944789666394513,32.081814683555365],[11.432253452203781,32.36890310315283],[11.48878746913101,33.13699575452324],[12.66331,32.79278],[13.08326,32.87882],[13.91868,32.71196],[15.24563,32.26508],[15.71394,31.37626],[16.61162,31.18218],[18.02109,30.76357],[19.08641,30.26639],[19.57404,30.52582],[20.05335,30.98576],[19.82033,31.75179000000014],[20.13397,32.2382],[20.85452,32.7068],[21.54298,32.8432],[22.89576,32.63858],[23.2368,32.19149],[23.609130000000107,32.18726],[23.9275,32.01667],[24.92114,31.89936],[25.16482,31.56915],[24.80287,31.08929],[24.95762,30.6616],[24.70007,30.04419],[25.00000000000011,29.23865452953356],[25.00000000000011,25.682499996361003],[25.00000000000011,22],[25.00000000000011,20.00304],[23.850000000000136,20],[23.837660000000138,19.580470000000105],[19.84926,21.49509],[15.86085,23.40972],[14.8513,22.862950000000126]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Mor.\",\"name\":\"Morocco\",\"name_long\":\"Morocco\",\"iso_a2\":\"MA\",\"iso_a3\":\"MAR\",\"iso_n3\":\"504\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-5.193863491222032,35.75518219659085],[-4.591006232105143,35.33071198174565],[-3.640056525070008,35.39985504815198],[-2.604305792644112,35.17909332940112],[-2.169913702798624,35.16839630791671],[-1.792985805661658,34.527918606091305],[-1.73345455566141,33.91971283623212],[-1.388049282222596,32.86401500094137],[-1.124551153966195,32.6515215113572],[-1.30789913573787,32.26288890230603],[-2.616604783529567,32.094346218386164],[-3.068980271812649,31.72449799247329],[-3.647497931320146,31.63729401298082],[-3.690441046554667,30.896951605751152],[-4.859646165374443,30.50118764904388],[-5.242129278982787,30.000443020135574],[-6.060632290053746,29.731699734001808],[-7.059227667661901,29.579228420524657],[-8.674116176782832,28.84128896739665],[-8.665589565454836,27.65642588959247],[-8.817809007940525,27.65642588959247],[-8.817828334986643,27.65642588959247],[-8.794883999049034,27.12069631602256],[-9.413037482124508,27.088476060488546],[-9.735343390328751,26.860944729107416],[-10.189424200877452,26.860944729107416],[-10.55126257978526,26.990807603456886],[-11.392554897496948,26.883423977154393],[-11.718219773800342,26.104091701760808],[-12.030758836301658,26.03086619720312],[-12.50096269372537,24.770116278578143],[-13.891110398809047,23.691009019459386],[-14.221167771857154,22.310163072188345],[-14.630832688850946,21.860939846274874],[-14.750954555713404,21.500600083903805],[-17.00296179856107,21.420734157796687],[-17.020428432675768,21.422310288981635],[-16.973247849993186,21.885744533774954],[-16.58913692876763,22.15823436125009],[-16.261921759495664,22.679339504481277],[-16.3264139469959,23.017768459560898],[-15.982610642958063,23.723358466074103],[-15.426003790742186,24.35913361256104],[-15.089331834360733,24.52026072844697],[-14.824645148161691,25.103532619725314],[-14.800925665739667,25.63626496022229],[-14.439939947964831,26.254418443297652],[-13.773804897506464,26.618892320252286],[-13.139941779014292,27.640147813420494],[-13.121613369914712,27.654147671719812],[-12.618836635783111,28.038185533148663],[-11.688919236690765,28.148643907172584],[-10.900956997104402,28.83214223888092],[-10.399592251008642,29.098585923777787],[-9.564811163765626,29.933573716749862],[-9.814718390329174,31.17773550060906],[-9.434793260119363,32.038096421836485],[-9.300692918321829,32.564679266890636],[-8.65747636558504,33.2402452662424],[-7.654178432638218,33.69706492770251],[-6.91254411460136,34.11047638603745],[-6.244342006851411,35.145865383437524],[-5.929994269219833,35.75998810479399],[-5.193863491222032,35.75518219659085]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Les.\",\"name\":\"Lesotho\",\"name_long\":\"Lesotho\",\"iso_a2\":\"LS\",\"iso_a3\":\"LSO\",\"iso_n3\":\"426\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[28.97826256685724,-28.955596612261715],[29.325166456832587,-29.257386976846252],[29.018415154748023,-29.74376555757737],[28.84839969250774,-30.070050551068253],[28.29106937023991,-30.2262167294543],[28.107204624145425,-30.54573211031495],[27.749397006956485,-30.64510588961222],[26.999261915807637,-29.875953871379984],[27.532511020627478,-29.24271087007536],[28.07433841320778,-28.851468601193588],[28.541700066855498,-28.64750172293757],[28.97826256685724,-28.955596612261715]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Mad.\",\"name\":\"Madagascar\",\"name_long\":\"Madagascar\",\"iso_a2\":\"MG\",\"iso_a3\":\"MDG\",\"iso_n3\":\"450\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[49.54351891459575,-12.469832858940554],[49.80898074727909,-12.895284925999555],[50.056510857957164,-13.555761407121985],[50.21743126811407,-14.758788750876795],[50.47653689962553,-15.226512139550541],[50.377111443895956,-15.706069431219126],[50.20027469259318,-16.000263360256767],[49.86060550313868,-15.414252618066916],[49.67260664246086,-15.710203545802479],[49.863344354050156,-16.451036879138776],[49.77456424337271,-16.875042006093597],[49.49861209493412,-17.106035658438273],[49.435618523970305,-17.953064060134366],[49.041792433473944,-19.118781019774445],[48.54854088724801,-20.496888116134127],[47.93074913919867,-22.391501153251085],[47.54772342305131,-23.781958916928517],[47.095761346226595,-24.941629733990453],[46.282477654817086,-25.178462823184105],[45.409507684110444,-25.60143442149309],[44.83357384621755,-25.34610116953894],[44.03972049334976,-24.988345228782308],[43.76376834491117,-24.460677178649988],[43.697777540874455,-23.574116306250602],[43.345654331237625,-22.776903985283873],[43.254187046081,-22.057413018484123],[43.43329756040464,-21.336475111580185],[43.893682895692926,-21.163307386970125],[43.896370070172104,-20.830459486578174],[44.37432539243966,-20.07236622485639],[44.46439741392439,-19.435454196859048],[44.23242190936617,-18.961994724200906],[44.04297610858415,-18.33138722094317],[43.96308434426091,-17.409944756746782],[44.31246870298628,-16.850495700754955],[44.4465173683514,-16.216219170804507],[44.94493655780653,-16.1793738745804],[45.50273196796499,-15.97437346767854],[45.87299360533626,-15.793454278224685],[46.31224327981721,-15.780018405828798],[46.882182651564285,-15.210182386946313],[47.70512983581235,-14.594302666891764],[48.005214878131255,-14.091232598530375],[47.869047479042166,-13.663868503476586],[48.29382775248138,-13.784067884987486],[48.84506025573878,-13.089174899958664],[48.86350874206698,-12.48786793381042],[49.194651320193316,-12.04055673589197],[49.54351891459575,-12.469832858940554]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Mali\",\"name\":\"Mali\",\"name_long\":\"Mali\",\"iso_a2\":\"ML\",\"iso_a3\":\"MLI\",\"iso_n3\":\"466\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-12.170750291380301,14.616834214735505],[-11.834207526079467,14.79909699142894],[-11.666078253617854,15.388208319556298],[-11.349095017939504,15.411256008358478],[-10.650791388379417,15.132745876521426],[-10.086846482778212,15.330485744686273],[-9.700255092802706,15.264107367407362],[-9.55023840985939,15.486496893775437],[-5.537744309908447,15.501689764869257],[-5.315277268891933,16.20185374599184],[-5.488522508150438,16.325102037007966],[-5.971128709324248,20.64083344164763],[-6.453786586930335,24.956590684503425],[-4.92333736817423,24.974574082941],[-1.550054897457613,22.792665920497384],[1.823227573259032,20.610809434486043],[2.06099083823392,20.142233384679486],[2.683588494486429,19.856230170160114],[3.1466610042539,19.69357859952144],[3.158133172222705,19.057364203360038],[4.267419467800039,19.155265204337],[4.270209995143801,16.852227484601215],[3.723421665063483,16.184283759012615],[3.638258904646477,15.568119818580454],[2.749992709981484,15.409524847876696],[1.385528191746858,15.323561102759172],[1.01578331869851,14.968182277887948],[0.374892205414682,14.928908189346132],[-0.26625729003058,14.924308986872148],[-0.515854458000348,15.116157741755726],[-1.066363491205664,14.973815009007765],[-2.001035122068771,14.559008287000891],[-2.191824510090385,14.246417548067356],[-2.967694464520577,13.79815033615151],[-3.10370683431276,13.541266791228594],[-3.522802700199861,13.337661647998615],[-4.006390753587226,13.472485459848116],[-4.28040503581488,13.228443508349741],[-4.427166103523803,12.542645575404295],[-5.220941941743121,11.713858954307227],[-5.197842576508648,11.37514577885014],[-5.470564947929006,10.951269842976048],[-5.404341599946974,10.370736802609146],[-5.816926235365287,10.222554633012194],[-6.050452032892267,10.096360785355444],[-6.205222947606431,10.524060777219134],[-6.493965013037267,10.411302801958271],[-6.666460944027548,10.430810655148447],[-6.850506557635057,10.138993841996239],[-7.622759161804809,10.147236232946796],[-7.899589809592372,10.297382106970828],[-8.029943610048619,10.206534939001713],[-8.33537716310974,10.494811916541934],[-8.282357143578281,10.792597357623846],[-8.407310756860028,10.909256903522762],[-8.620321010767128,10.810890814655183],[-8.581305304386774,11.136245632364805],[-8.376304897484914,11.393645941610629],[-8.786099005559464,11.812560939984706],[-8.90526485842453,12.088358059126437],[-9.127473517279583,12.308060411015331],[-9.327616339546012,12.334286200403454],[-9.567911749703214,12.194243068892476],[-9.890992804392013,12.060478623904972],[-10.165213792348837,11.844083563682744],[-10.593223842806282,11.92397532800598],[-10.870829637078215,12.17788747807211],[-11.036555955438258,12.211244615116515],[-11.29757361494451,12.077971096235771],[-11.456168585648271,12.076834214725338],[-11.51394283695059,12.442987575729418],[-11.467899135778524,12.754518947800975],[-11.55339779300543,13.141213690641067],[-11.927716030311615,13.422075100147394],[-12.12488745772126,13.994727484589788],[-12.170750291380301,14.616834214735505]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Moz.\",\"name\":\"Mozambique\",\"name_long\":\"Mozambique\",\"iso_a2\":\"MZ\",\"iso_a3\":\"MOZ\",\"iso_n3\":\"508\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[34.55998904799935,-11.520020033415925],[35.31239790216904,-11.439146416879147],[36.51408165868426,-11.720938002166733],[36.775150994622805,-11.594537448780805],[37.47128421402661,-11.56875090906716],[37.82764489111139,-11.268769219612835],[38.42755659358775,-11.285202325081656],[39.521029900883775,-10.896853936408226],[40.31658857601719,-10.317096042525698],[40.47838748552303,-10.765440769089993],[40.437253045418686,-11.761710707245015],[40.56081139502857,-12.639176527561027],[40.59962039567975,-14.201975192931862],[40.775475294768995,-14.691764418194241],[40.4772506040126,-15.406294447493972],[40.08926395036522,-16.10077402106446],[39.45255862809705,-16.72089120856694],[38.53835086442152,-17.101023044505958],[37.41113284683888,-17.586368096591237],[36.28127933120936,-18.65968759529345],[35.89649661636406,-18.842260430580634],[35.198399692533144,-19.552811374593894],[34.78638349787005,-19.784011732667736],[34.70189253107284,-20.49704314543101],[35.176127150215365,-21.25436126066841],[35.37342776870574,-21.840837090748877],[35.385848253705404,-22.14],[35.562545536369086,-22.09],[35.533934767404304,-23.070787855727758],[35.37177412287238,-23.5353589820317],[35.60747033055563,-23.706563002214683],[35.45874555841962,-24.12260995859655],[35.04073489761066,-24.478350518493805],[34.21582400893547,-24.81631438568266],[33.01321007663901,-25.357573337507738],[32.574632195777866,-25.72731821055609],[32.66036339695009,-26.148584486599443],[32.91595503106569,-26.215867201443466],[32.830120477028885,-26.742191664336197],[32.07166548028107,-26.73382008230491],[31.98577924981197,-26.291779880480227],[31.837777947728064,-25.84333180105135],[31.75240848158188,-25.484283949487413],[31.93058882012425,-24.369416599222536],[31.670397983534652,-23.658969008073864],[31.191409132621285,-22.2515096981724],[32.244988234188014,-21.116488539313693],[32.50869306817344,-20.395292250248307],[32.65974327976258,-20.304290052982317],[32.772707960752626,-19.715592136313298],[32.61199425632489,-19.419382826416275],[32.65488569512715,-18.672089939043495],[32.84986087416439,-17.97905730557718],[32.847638787575846,-16.713398125884616],[32.32823896661022,-16.392074069893752],[31.8520406430406,-16.319417006091378],[31.636498243951195,-16.071990248277885],[31.173063999157677,-15.860943698797872],[30.338954705534544,-15.880839125230244],[30.274255812305107,-15.507786960515212],[30.17948123548183,-14.796099134991527],[33.214024692525214,-13.971860039936153],[33.789700148256685,-14.451830743063072],[34.064825473778626,-14.359950046448121],[34.45963341648854,-14.613009535381424],[34.51766604995231,-15.013708591372612],[34.307291294092096,-15.478641452702595],[34.38129194513405,-16.183559665596043],[35.033810255683534,-16.801299737213093],[35.33906294123164,-16.10744028083011],[35.77190473810836,-15.896858819240725],[35.68684533055594,-14.611045830954332],[35.26795617039801,-13.887834161029566],[34.907151320136165,-13.565424899960568],[34.55998904799935,-13.579997653866876],[34.28000613784198,-12.280025323132504],[34.55998904799935,-11.520020033415925]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Nam.\",\"name\":\"Namibia\",\"name_long\":\"Namibia\",\"iso_a2\":\"NA\",\"iso_a3\":\"NAM\",\"iso_n3\":\"516\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[16.344976840895242,-28.576705010697697],[15.601818068105814,-27.8212472470228],[15.21047244635946,-27.090955905874047],[14.989710727608552,-26.117371921495156],[14.743214145576331,-25.39292001719538],[14.408144158595833,-23.853014011329847],[14.385716586981149,-22.65665292734069],[14.257714064194175,-22.111208184499954],[13.86864220546866,-21.699036960539978],[13.35249799973744,-20.872834161057504],[12.826845330464492,-19.673165785401665],[12.608564080463621,-19.0453488094877],[11.794918654028066,-18.069129327061916],[11.734198846085121,-17.301889336824473],[12.215461460019355,-17.111668389558083],[12.814081251688407,-16.94134286872407],[13.462362094789967,-16.971211846588773],[14.05850141770901,-17.423380629142663],[14.209706658595024,-17.35310068122572],[18.26330936043416,-17.309950860262006],[18.956186964603603,-17.789094740472258],[21.377176141045567,-17.930636488519696],[23.215048455506064,-17.52311614346598],[24.033861525170778,-17.295843194246324],[24.682349074001507,-17.353410739819473],[25.07695031098226,-17.57882333747662],[25.08444339366457,-17.661815687737374],[24.520705193792537,-17.887124932529936],[24.217364536239213,-17.88934701911849],[23.579005568137717,-18.28126108162006],[23.1968583513393,-17.869038181227786],[21.655040317478978,-18.219146010005225],[20.910641310314535,-18.252218926672022],[20.881134067475866,-21.814327080983148],[19.89545779794068,-21.84915699634787],[19.895767856534434,-24.767790215760588],[19.894734327888614,-28.461104831660776],[19.002127312911085,-28.972443129188864],[18.464899122804752,-29.04546192801728],[17.83615197110953,-28.85637786226132],[17.387497185951503,-28.78351409272978],[17.218928663815404,-28.35594329194681],[16.824017368240902,-28.082161553664466],[16.344976840895242,-28.576705010697697]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Niger\",\"name\":\"Niger\",\"name_long\":\"Niger\",\"iso_a2\":\"NE\",\"iso_a3\":\"NER\",\"iso_n3\":\"562\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[2.15447350424995,11.940150051313424],[2.177107781593918,12.625017808477537],[1.024103224297619,12.851825669806601],[0.993045688490156,13.335749620003865],[0.429927605805517,13.988733018443893],[0.295646396495215,14.444234930880667],[0.374892205414767,14.928908189346147],[1.015783318698482,14.968182277887989],[1.385528191746971,15.32356110275924],[2.749992709981541,15.409524847876753],[3.638258904646591,15.568119818580442],[3.723421665063597,16.184283759012658],[4.270209995143887,16.852227484601315],[4.267419467800096,19.155265204337127],[5.677565952180714,19.6012069767998],[8.57289310062987,21.565660712159225],[11.9995056494717,23.471668402596432],[13.581424594790462,23.04050608976928],[14.143870883855243,22.49128896737113],[14.8513,22.862950000000126],[15.096887648181848,21.30851878507491],[15.471076694407316,21.048457139565983],[15.487148064850146,20.730414537025638],[15.903246697664313,20.387618923417506],[15.685740594147774,19.957180080642384],[15.30044111497972,17.927949937405003],[15.247731154041846,16.627305813050782],[13.972201775781684,15.684365953021143],[13.540393507550789,14.367133693901222],[13.956698846094127,13.996691189016929],[13.95447675950561,13.353448798063766],[14.595781284247607,13.330426947477859],[14.495787387762903,12.859396267137356],[14.21353071458475,12.802035427293333],[14.18133629726691,12.483656927943171],[13.995352817448293,12.461565253138303],[13.318701613018561,13.556356309457954],[13.083987257548813,13.596147162322495],[12.30207116054055,13.037189032437539],[11.527803175511508,13.32898000737356],[10.989593133191532,13.387322699431195],[10.701031935273818,13.246917832894042],[10.114814487354748,13.277251898649467],[9.52492801274309,12.851102199754564],[9.014933302454438,12.826659247280418],[7.804671258178871,13.343526923063735],[7.330746697630047,13.098038031461215],[6.820441928747812,13.115091254117601],[6.445426059605722,13.492768459522722],[5.443058302440135,13.865923977102225],[4.368343540066007,13.747481594289411],[4.107945997747379,13.531215725147945],[3.967282749048934,12.956108710171577],[3.680633579125925,12.55290334721417],[3.611180454125587,11.660167141155966],[2.848643019226586,12.23563589115821],[2.490163608418015,12.233052069543588],[2.15447350424995,11.940150051313424]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Mal.\",\"name\":\"Malawi\",\"name_long\":\"Malawi\",\"iso_a2\":\"MW\",\"iso_a3\":\"MWI\",\"iso_n3\":\"454\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[34.55998904799935,-11.520020033415925],[34.28000613784198,-12.280025323132504],[34.55998904799935,-13.579997653866876],[34.907151320136165,-13.565424899960568],[35.26795617039801,-13.887834161029566],[35.68684533055594,-14.611045830954332],[35.77190473810836,-15.896858819240725],[35.33906294123164,-16.10744028083011],[35.033810255683534,-16.801299737213093],[34.38129194513405,-16.183559665596043],[34.307291294092096,-15.478641452702595],[34.51766604995231,-15.013708591372612],[34.45963341648854,-14.613009535381424],[34.064825473778626,-14.359950046448121],[33.789700148256685,-14.451830743063072],[33.214024692525214,-13.971860039936153],[32.68816531752313,-13.712857761289275],[32.991764357237884,-12.783870537978272],[33.306422153463075,-12.435778090060218],[33.11428917820191,-11.607198174692314],[33.315310499817286,-10.796549981329697],[33.48568769708359,-10.525558770391115],[33.2313879737753,-9.6767216935648],[32.75937544122132,-9.230599053589058],[33.73972903823045,-9.417150974162723],[33.94083772409653,-9.693673841980294],[34.28000613784198,-10.159999688358404],[34.55998904799935,-11.520020033415925]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Mrt.\",\"name\":\"Mauritania\",\"name_long\":\"Mauritania\",\"iso_a2\":\"MR\",\"iso_a3\":\"MRT\",\"iso_n3\":\"478\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-12.170750291380301,14.616834214735505],[-12.830658331747516,15.303691514542946],[-13.43573767745306,16.03938304286619],[-14.099521450242179,16.304302273010492],[-14.577347581428981,16.59826365810281],[-15.135737270558817,16.587282416240782],[-15.62366614425869,16.369337063049812],[-16.12069007004193,16.455662543193384],[-16.463098110407884,16.13503611903846],[-16.549707810929064,16.67389211676196],[-16.270551723688357,17.166962795474873],[-16.14634741867485,18.108481553616656],[-16.256883307347167,19.096715806550307],[-16.37765112961327,19.593817246981985],[-16.277838100641517,20.0925206568147],[-16.536323614965468,20.567866319251493],[-17.063423224342568,20.999752102130827],[-16.845193650773993,21.33332347257488],[-12.929101935263532,21.32707062426756],[-13.118754441774712,22.771220201096256],[-12.874221564169575,23.284832261645178],[-11.937224493853321,23.374594224536168],[-11.96941891117116,25.933352769468268],[-8.6872936670174,25.881056219988906],[-8.68439978680905,27.395744126896005],[-4.92333736817423,24.974574082941],[-6.453786586930335,24.956590684503425],[-5.971128709324248,20.64083344164763],[-5.488522508150438,16.325102037007966],[-5.315277268891933,16.20185374599184],[-5.537744309908447,15.501689764869257],[-9.55023840985939,15.486496893775437],[-9.700255092802706,15.264107367407362],[-10.086846482778212,15.330485744686273],[-10.650791388379417,15.132745876521426],[-11.349095017939504,15.411256008358478],[-11.666078253617854,15.388208319556298],[-11.834207526079467,14.79909699142894],[-12.170750291380301,14.616834214735505]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Nigeria\",\"name\":\"Nigeria\",\"name_long\":\"Nigeria\",\"iso_a2\":\"NG\",\"iso_a3\":\"NGA\",\"iso_n3\":\"566\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[8.500287713259695,4.771982937026849],[7.46210818851594,4.412108262546241],[7.082596469764439,4.464689032403228],[6.6980721370806,4.240594183769517],[5.898172641634687,4.262453314628985],[5.362804803090881,4.887970689305959],[5.033574252959368,5.611802476418234],[4.325607130560683,6.270651149923467],[3.574180128604553,6.258300482605719],[2.691701694356254,6.258817246928629],[2.74906253420022,7.870734361192888],[2.723792758809509,8.50684540448971],[2.912308383810256,9.13760793704432],[3.220351596702101,9.4441525333997],[3.705438266625919,10.063210354040208],[3.600070021182801,10.332186184119408],[3.797112257511714,10.734745591673105],[3.572216424177469,11.327939357951518],[3.611180454125559,11.660167141155966],[3.680633579125811,12.552903347214226],[3.967282749048849,12.956108710171575],[4.107945997747322,13.531215725147831],[4.368343540066064,13.747481594289324],[5.443058302440164,13.865923977102298],[6.445426059605637,13.492768459522678],[6.820441928747754,13.115091254117518],[7.330746697630018,13.0980380314612],[7.804671258178786,13.343526923063745],[9.014933302454466,12.82665924728043],[9.524928012742945,12.851102199754479],[10.114814487354693,13.27725189864941],[10.701031935273704,13.246917832894084],[10.989593133191535,13.38732269943111],[11.527803175511394,13.328980007373588],[12.302071160540523,13.037189032437524],[13.08398725754887,13.596147162322566],[13.318701613018561,13.556356309457826],[13.99535281744835,12.461565253138346],[14.181336297266792,12.483656927943116],[14.577177768622533,12.085360826053503],[14.468192172918975,11.904751695193411],[14.415378859116686,11.572368882692075],[13.572949659894562,10.798565985553566],[13.308676385153918,10.160362046748928],[13.167599724997103,9.640626328973411],[12.955467970438974,9.417771714714704],[12.753671502339214,8.717762762888995],[12.218872104550599,8.305824082874324],[12.063946160539558,7.799808457872302],[11.839308709366803,7.397042344589436],[11.74577436691851,6.981382961449753],[11.05878787603035,6.644426784690594],[10.497375115611417,7.055357774275564],[10.118276808318257,7.038769639509879],[9.522705926154401,6.453482367372117],[9.233162876023044,6.444490668153334],[8.757532993208628,5.479665839047911],[8.500287713259695,4.771982937026849]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Rwa.\",\"name\":\"Rwanda\",\"name_long\":\"Rwanda\",\"iso_a2\":\"RW\",\"iso_a3\":\"RWA\",\"iso_n3\":\"646\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[30.41910485201924,-1.134659112150416],[30.81613488131771,-1.698914076345389],[30.75830895358311,-2.287250257988369],[30.469696079232985,-2.413857517103458],[29.938359002407942,-2.348486830254238],[29.632176141078588,-2.917857761246097],[29.024926385216787,-2.839257907730158],[29.117478875451553,-2.292211195488385],[29.25483483248334,-2.215109958508911],[29.29188683443661,-1.620055840667987],[29.579466180140884,-1.341313164885626],[29.82151858899601,-1.443322442229785],[30.41910485201924,-1.134659112150416]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"W. Sah.\",\"name\":\"W. Sahara\",\"name_long\":\"Western Sahara\",\"iso_a2\":\"EH\",\"iso_a3\":\"ESH\",\"iso_n3\":\"732\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-8.794883999049077,27.120696316022503],[-8.817828334986672,27.656425889592356],[-8.665589565454809,27.656425889592356],[-8.665124477564191,27.589479071558227],[-8.68439978680905,27.395744126896005],[-8.6872936670174,25.881056219988906],[-11.96941891117116,25.933352769468268],[-11.937224493853321,23.374594224536168],[-12.874221564169575,23.284832261645178],[-13.118754441774712,22.771220201096256],[-12.929101935263532,21.32707062426756],[-16.845193650773993,21.33332347257488],[-17.063423224342568,20.999752102130827],[-17.020428432675743,21.42231028898148],[-17.00296179856109,21.420734157796577],[-14.750954555713534,21.500600083903663],[-14.630832688851072,21.8609398462749],[-14.221167771857251,22.31016307218816],[-13.891110398809047,23.691009019459305],[-12.50096269372537,24.7701162785782],[-12.030758836301615,26.030866197203043],[-11.718219773800357,26.104091701760623],[-11.392554897496979,26.883423977154365],[-10.551262579785273,26.990807603456886],[-10.189424200877582,26.860944729107405],[-9.735343390328879,26.860944729107405],[-9.413037482124466,27.088476060488517],[-8.794883999049077,27.120696316022503]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"S. Sud.\",\"name\":\"S. Sudan\",\"name_long\":\"South Sudan\",\"iso_a2\":\"SS\",\"iso_a3\":\"SSD\",\"iso_n3\":\"728\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[33.963392794971185,9.464285229420625],[33.97498,8.68456],[33.82550000000015,8.37916],[33.29480000000012,8.35458],[32.95418,7.784970000000102],[33.568290000000104,7.71334],[34.0751,7.22595],[34.25032,6.82607],[34.70702,6.59422000000012],[35.298007118233095,5.506],[34.62019626785394,4.847122742082036],[34.005,4.249884947362147],[33.3900000000001,3.79],[32.68642,3.79232],[31.881450000000143,3.55827],[31.24556,3.7819],[30.83385,3.50917],[29.95349,4.1737],[29.71599531425602,4.600804755060153],[29.159078403446642,4.389267279473245],[28.696677687298802,4.455077215996994],[28.428993768027,4.287154649264608],[27.979977247842953,4.408413397637388],[27.374226108517632,5.233944403500175],[27.213409051225256,5.550953477394614],[26.465909458123292,5.946717434101856],[26.21341840994512,6.546603298362129],[25.796647983511264,6.97931590415817],[25.124130893664812,7.500085150579424],[25.114932488716875,7.825104071479245],[24.5673690121522,8.229187933785454],[23.886979580860665,8.619729712933065],[24.19406772118765,8.728696472403897],[24.53741516360202,8.91753756573172],[24.794925745412684,9.810240916008695],[25.069603699343986,10.273759963267992],[25.790633328413946,10.411098940233728],[25.962307049621018,10.136420986302424],[26.477328213242515,9.552730334198088],[26.752006167173818,9.466893473594496],[27.112520981708883,9.638567194801624],[27.833550610778783,9.60423245056029],[27.970889587744352,9.398223985111654],[28.966597170745782,9.398223985111654],[29.000931914987174,9.60423245056029],[29.515953078608614,9.793073543888056],[29.61895731133285,10.084918869940225],[29.99663949798855,10.290927335388687],[30.83784073190338,9.70723668328452],[31.35286189552488,9.810240916008695],[31.850715687025517,10.531270545078826],[32.400071594888345,11.080626452941488],[32.31423473428475,11.68148447716652],[32.073891524594785,11.973329803218519],[32.67474954881965,12.02483191958072],[32.743419037302544,12.248007757149992],[33.20693808456178,12.179338268667093],[33.086766479716744,11.441141267476496],[33.20693808456178,10.720111638406593],[33.72195924818311,10.325262079630193],[33.842130853028145,9.981914637215993],[33.82496348090751,9.484060845715362],[33.963392794971185,9.464285229420625]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"S.L.\",\"name\":\"Sierra Leone\",\"name_long\":\"Sierra Leone\",\"iso_a2\":\"SL\",\"iso_a3\":\"SLE\",\"iso_n3\":\"694\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-11.438779466182055,6.785916856305747],[-11.70819454593574,6.860098374860726],[-12.428098924193819,7.26294200279203],[-12.949049038128194,7.798645738145738],[-13.124025437868482,8.163946438016978],[-13.246550258832515,8.903048610871508],[-12.71195756677308,9.342711696810767],[-12.59671912276221,9.62018830000197],[-12.425928514037565,9.835834051955956],[-12.150338100625005,9.858571682164381],[-11.917277390988659,10.046983954300558],[-11.117481248407328,10.045872911006285],[-10.8391519840833,9.688246161330369],[-10.622395188835041,9.267910061068278],[-10.654770473665891,8.977178452994194],[-10.494315151399633,8.715540676300435],[-10.505477260774668,8.348896389189605],[-10.23009355309128,8.406205552601293],[-10.69559485517648,7.939464016141087],[-11.146704270868383,7.396706447779536],[-11.19980180504828,7.105845648624737],[-11.438779466182055,6.785916856305747]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Sen.\",\"name\":\"Senegal\",\"name_long\":\"Senegal\",\"iso_a2\":\"SN\",\"iso_a3\":\"SEN\",\"iso_n3\":\"686\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-16.713728807023468,13.594958604379853],[-17.126106736712615,14.373515733289224],[-17.62504269049066,14.729540513564071],[-17.18517289882223,14.919477240452863],[-16.700706346085923,15.621527411354108],[-16.463098110407884,16.13503611903846],[-16.12069007004193,16.455662543193384],[-15.62366614425869,16.369337063049812],[-15.135737270558817,16.587282416240782],[-14.577347581428981,16.59826365810281],[-14.099521450242179,16.304302273010492],[-13.43573767745306,16.03938304286619],[-12.830658331747516,15.303691514542946],[-12.170750291380301,14.616834214735505],[-12.12488745772126,13.994727484589788],[-11.927716030311615,13.422075100147394],[-11.55339779300543,13.141213690641067],[-11.467899135778524,12.754518947800975],[-11.51394283695059,12.442987575729418],[-11.65830095055793,12.386582749882834],[-12.203564825885634,12.465647691289405],[-12.278599005573438,12.354440008997285],[-12.499050665730564,12.332089952031057],[-13.217818162478238,12.575873521367967],[-13.700476040084325,12.586182969610194],[-15.548476935274008,12.628170070847347],[-15.816574266004254,12.515567124883345],[-16.147716844130585,12.547761542201187],[-16.677451951554573,12.384851589401052],[-16.841524624081273,13.15139394780256],[-15.931295945692211,13.130284125211332],[-15.691000535534995,13.270353094938455],[-15.511812506562935,13.278569647672867],[-15.141163295949466,13.509511623585238],[-14.712197231494626,13.298206691943777],[-14.277701788784553,13.280585028532242],[-13.844963344772408,13.505041612192002],[-14.046992356817482,13.79406789800045],[-14.376713833055788,13.625680243377372],[-14.687030808968487,13.630356960499784],[-15.08173539881382,13.876491807505984],[-15.39877031092446,13.86036876063092],[-15.62459632003994,13.62358734786956],[-16.713728807023468,13.594958604379853]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Solnd.\",\"name\":\"Somaliland\",\"name_long\":\"Somaliland\",\"iso_a2\":null,\"iso_a3\":null,\"iso_n3\":null},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[48.938129510296505,9.451748968946674],[48.48673587422701,8.83762624758998],[47.78942,8.003],[46.94832848489796,7.996876532417388],[43.67875,9.18358000000012],[43.29697513201876,9.540477403191744],[42.92812,10.021940000000143],[42.55876,10.57258000000013],[42.77685184100096,10.92687856693442],[43.14530480324214,11.462039699748857],[43.470659620951665,11.27770986576388],[43.66666832863484,10.864169216348158],[44.11780358254282,10.445538438351605],[44.614259067570856,10.442205308468942],[45.55694054543915,10.698029486529776],[46.645401238803004,10.816549383991173],[47.525657586462785,11.12722809492999],[48.02159630716778,11.193063869669743],[48.37878380716927,11.375481675660126],[48.94820641459347,11.41062164961852],[48.94200524271844,11.394266058798166],[48.93849124532261,10.982327378783452],[48.93823286316109,9.973500067581483],[48.938129510296505,9.451748968946674]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Sudan\",\"name\":\"Sudan\",\"name_long\":\"Sudan\",\"iso_a2\":\"SD\",\"iso_a3\":\"SDN\",\"iso_n3\":\"729\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[33.963392794971185,9.464285229420625],[33.82496348090751,9.484060845715362],[33.842130853028145,9.981914637215993],[33.72195924818311,10.325262079630193],[33.20693808456178,10.720111638406593],[33.086766479716744,11.441141267476496],[33.20693808456178,12.179338268667093],[32.743419037302544,12.248007757149992],[32.67474954881965,12.02483191958072],[32.073891524594785,11.973329803218519],[32.31423473428475,11.68148447716652],[32.400071594888345,11.080626452941488],[31.850715687025517,10.531270545078826],[31.35286189552488,9.810240916008695],[30.83784073190338,9.70723668328452],[29.99663949798855,10.290927335388687],[29.61895731133285,10.084918869940225],[29.515953078608614,9.793073543888056],[29.000931914987174,9.60423245056029],[28.966597170745782,9.398223985111654],[27.970889587744352,9.398223985111654],[27.833550610778783,9.60423245056029],[27.112520981708883,9.638567194801624],[26.752006167173818,9.466893473594496],[26.477328213242515,9.552730334198088],[25.962307049621018,10.136420986302424],[25.790633328413946,10.411098940233728],[25.069603699343986,10.273759963267992],[24.794925745412684,9.810240916008695],[24.53741516360202,8.91753756573172],[24.19406772118765,8.728696472403897],[23.886979580860665,8.619729712933065],[23.805813429466752,8.666318874542526],[23.459012892355986,8.95428579348902],[23.394779087017298,9.26506785729225],[23.55724979014292,9.68121816653877],[23.554304233502194,10.08925527591532],[22.97754357269275,10.71446259199854],[22.864165480244253,11.142395127807617],[22.87622,11.384610000000123],[22.50869,11.67936],[22.49762,12.26024],[22.28801,12.64605],[21.93681,12.588180000000136],[22.03759,12.95546],[22.29658,13.37232],[22.18329,13.78648],[22.51202,14.09318],[22.30351,14.32682],[22.56795000000011,14.944290000000137],[23.024590000000103,15.68072],[23.886890000000108,15.61084],[23.837660000000138,19.580470000000105],[23.850000000000136,20],[25.00000000000011,20.00304],[25.00000000000011,22],[29.02,22],[32.9,22],[36.86623,22],[37.1887200000001,21.01885],[36.96941,20.83744000000013],[37.11470000000014,19.80796],[37.4817900000001,18.61409],[37.86276,18.36786],[38.410089959473225,17.99830739997031],[37.90400000000011,17.42754],[37.16747,17.263140000000135],[36.852530000000115,16.95655],[36.75389,16.29186],[36.32322,14.82249],[36.42951,14.42211],[36.27022,13.563330000000121],[35.86363,12.57828],[35.26049,12.08286],[34.83163000000013,11.318960000000118],[34.73115000000013,10.910170000000107],[34.25745,10.63009],[33.96162,9.58358],[33.963392794971185,9.464285229420625]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Som.\",\"name\":\"Somalia\",\"name_long\":\"Somalia\",\"iso_a2\":\"SO\",\"iso_a3\":\"SOM\",\"iso_n3\":\"706\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[49.72862,11.5789],[50.25878,11.67957],[50.73202,12.0219],[51.1112,12.02464],[51.13387,11.74815],[51.04153,11.16651],[51.04531,10.6409],[50.83418,10.27972],[50.55239,9.19874],[50.07092,8.08173],[49.4527,6.80466],[48.59455,5.33911],[47.74079,4.2194],[46.56476,2.85529],[45.56399,2.04576],[44.06815,1.05283],[43.13597,0.2922],[42.04157,-0.91916],[41.81095,-1.44647],[41.58513,-1.68325],[40.993,-0.85829],[40.98105,2.78452],[41.85508309264397,3.918911920483727],[42.12861,4.23413],[42.76967,4.25259],[43.66087,4.95755],[44.9636,5.00162],[47.78942,8.003],[48.48673587422695,8.837626247589995],[48.93812951029645,9.451748968946617],[48.93823286316103,9.973500067581512],[48.938491245322496,10.982327378783467],[48.94200524271835,11.394266058798138],[48.94820475850974,11.410617281697963],[49.26776,11.43033],[49.72862,11.5789]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Swz.\",\"name\":\"Swaziland\",\"name_long\":\"Swaziland\",\"iso_a2\":\"SZ\",\"iso_a3\":\"SWZ\",\"iso_n3\":\"748\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[32.07166548028107,-26.73382008230491],[31.868060337051077,-27.177927341421277],[31.282773064913325,-27.285879408478998],[30.68596194837448,-26.74384531016953],[30.676608514129637,-26.398078301704608],[30.949666782359913,-26.022649021104147],[31.04407962415715,-25.731452325139443],[31.333157586397906,-25.66019052500895],[31.837777947728064,-25.84333180105135],[31.98577924981197,-26.291779880480227],[32.07166548028107,-26.73382008230491]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Chad\",\"name\":\"Chad\",\"name_long\":\"Chad\",\"iso_a2\":\"TD\",\"iso_a3\":\"TCD\",\"iso_n3\":\"148\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[14.495787387762903,12.859396267137356],[14.595781284247607,13.330426947477859],[13.95447675950561,13.353448798063766],[13.956698846094127,13.996691189016929],[13.540393507550789,14.367133693901222],[13.97217,15.68437],[15.247731154041846,16.627305813050782],[15.30044111497972,17.927949937405003],[15.685740594147774,19.957180080642384],[15.903246697664313,20.387618923417506],[15.487148064850146,20.730414537025638],[15.47106,21.04845],[15.096887648181848,21.30851878507491],[14.8513,22.862950000000126],[15.86085,23.40972],[19.84926,21.49509],[23.837660000000138,19.580470000000105],[23.886890000000108,15.61084],[23.024590000000103,15.68072],[22.56795000000011,14.944290000000137],[22.30351,14.32682],[22.51202,14.09318],[22.18329,13.78648],[22.29658,13.37232],[22.03759,12.95546],[21.93681,12.588180000000136],[22.28801,12.64605],[22.49762,12.26024],[22.50869,11.67936],[22.87622,11.384610000000123],[22.864165480244253,11.142395127807617],[22.23112918466876,10.97188873946061],[21.72382164885954,10.567055568885962],[21.00086836109631,9.47598521569148],[20.05968549976427,9.01270600019484],[19.09400800952608,9.07484691002577],[18.81200971850927,8.982914536978626],[18.911021762780592,8.630894680206438],[18.389554884523303,8.281303615751881],[17.964929640380888,7.890914008002994],[16.70598839688637,7.508327541529979],[16.456184523187403,7.734773667832939],[16.290561557691888,7.754307359239418],[16.106231723706742,7.497087917506462],[15.279460483469164,7.421924546738012],[15.43609174974574,7.692812404811889],[15.120865512765306,8.382150173369437],[14.97999555833769,8.796104234243444],[14.544466586981855,8.96586131432224],[13.954218377344091,9.549494940626685],[14.171466098699113,10.021378282100045],[14.62720055508106,9.920919297724595],[14.9093538753948,9.99212942142276],[15.467872755605244,9.982336737503543],[14.923564894275046,10.891325181517516],[14.960151808337683,11.555574042197236],[14.89336,12.21905],[14.495787387762903,12.859396267137356]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Togo\",\"name\":\"Togo\",\"name_long\":\"Togo\",\"iso_a2\":\"TG\",\"iso_a3\":\"TGO\",\"iso_n3\":\"768\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[1.865240512712319,6.142157701029731],[1.060121697604927,5.928837388528876],[0.836931186536333,6.279978745952149],[0.570384148774849,6.914358628767189],[0.490957472342245,7.411744289576475],[0.712029249686878,8.31246450442383],[0.461191847342121,8.677222601756014],[0.365900506195885,9.465003973829482],[0.367579990245389,10.19121287682718],[-0.049784715159944,10.706917832883931],[0.023802524423701,11.018681748900804],[0.899563022474069,10.99733938236426],[0.772335646171484,10.470808213742359],[1.077795037448738,10.175606594275024],[1.425060662450136,9.825395412633],[1.46304284018467,9.334624335157088],[1.664477573258381,9.12859039960938],[1.618950636409238,6.832038072126237],[1.865240512712319,6.142157701029731]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Tun.\",\"name\":\"Tunisia\",\"name_long\":\"Tunisia\",\"iso_a2\":\"TN\",\"iso_a3\":\"TUN\",\"iso_n3\":\"788\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[9.482139926805274,30.307556057246188],[9.055602654668148,32.10269196220129],[8.439102817426118,32.50628489840082],[8.430472853233368,32.74833730725595],[7.612641635782182,33.34411489514896],[7.524481642292244,34.09737641045146],[8.140981479534304,34.65514598239379],[8.376367628623768,35.47987600355594],[8.217824334352315,36.433176988260286],[8.420964389691676,36.94642731378316],[9.509993523810607,37.349994411766545],[10.210002475636317,37.230001735984814],[10.18065026209453,36.724037787415085],[11.028867221733348,37.09210317641396],[11.100025668999251,36.899996039368915],[10.600004510143094,36.410000108377375],[10.593286573945136,35.94744436293281],[10.939518670300687,35.698984076473494],[10.807847120821009,34.83350718844919],[10.149592726287125,34.3307730168977],[10.339658644256616,33.78574168551532],[10.856836378633687,33.76874013929128],[11.108500603895122,33.293342800422195],[11.48878746913101,33.13699575452314],[11.432253452203696,32.368903103152874],[10.944789666394456,32.081814683555365],[10.636901482799487,31.761420803345754],[9.950225050505082,31.376069647745254],[10.056575148161755,30.9618313664936],[9.970017124072854,30.539324856075243],[9.482139926805274,30.307556057246188]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Tanz.\",\"name\":\"Tanzania\",\"name_long\":\"Tanzania\",\"iso_a2\":\"TZ\",\"iso_a3\":\"TZA\",\"iso_n3\":\"834\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[33.9037111971046,-0.95],[34.07262,-1.05982],[37.69869,-3.09699],[37.7669,-3.67712],[39.20222,-4.67677],[38.74054,-5.90895],[38.79977,-6.47566],[39.44,-6.839999999999861],[39.470000000000134,-7.1],[39.19469,-7.7039],[39.25203,-8.00781],[39.18652,-8.48551],[39.53574,-9.112369999999883],[39.9496,-10.0984],[40.31659,-10.317099999999868],[39.521,-10.89688],[38.42755659358778,-11.285202325081627],[37.82764,-11.26879],[37.47129,-11.56876],[36.77515099462289,-11.594537448780784],[36.514081658684404,-11.720938002166747],[35.31239790216915,-11.439146416879169],[34.559989047999466,-11.520020033415847],[34.28,-10.16],[33.940837724096525,-9.693673841980285],[33.73972,-9.41715],[32.75937544122138,-9.230599053589003],[32.19186486179194,-8.930358981973257],[31.556348097466635,-8.762048841998647],[31.15775133695007,-8.594578747317314],[30.74,-8.34],[30.2,-7.08],[29.62,-6.52],[29.419992710088305,-5.939998874539299],[29.51998660657307,-5.419978936386258],[29.33999759290037,-4.499983412294114],[29.753512404099865,-4.452389418153302],[30.11632,-4.09012],[30.50554,-3.56858],[30.75224,-3.35931],[30.74301,-3.03431],[30.52766,-2.80762],[30.46967,-2.41383],[30.758308953583136,-2.287250257988376],[30.81613488131785,-1.698914076345375],[30.4191048520193,-1.134659112150416],[30.769860000000108,-1.01455],[31.86617,-1.02736],[33.9037111971046,-0.95]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Uga.\",\"name\":\"Uganda\",\"name_long\":\"Uganda\",\"iso_a2\":\"UG\",\"iso_a3\":\"UGA\",\"iso_n3\":\"800\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[31.86617,-1.02736],[30.769860000000108,-1.01455],[30.4191048520193,-1.134659112150416],[29.821518588996124,-1.443322442229771],[29.579466180141022,-1.341313164885605],[29.58783776217217,-0.587405694179381],[29.8195,-0.2053],[29.875778842902434,0.597379868976361],[30.08615359876279,1.062312730306417],[30.46850752129029,1.583805446779706],[30.852670118948136,1.849396470543752],[31.17414920423596,2.204465236821306],[30.77332,2.339890000000139],[30.83385,3.50917],[31.24556,3.7819],[31.88145,3.55827],[32.68642,3.79232],[33.3900000000001,3.79],[34.005,4.249884947362147],[34.47913,3.5556],[34.59607,3.053740000000118],[35.03599,1.90584],[34.6721,1.17694],[34.18,0.515],[33.893568969666994,0.109813537861839],[33.9037111971046,-0.95],[31.86617,-1.02736]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"S.Af.\",\"name\":\"South Africa\",\"name_long\":\"South Africa\",\"iso_a2\":\"ZA\",\"iso_a3\":\"ZAF\",\"iso_n3\":\"710\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[31.521001417778876,-29.257386976846252],[31.325561150851,-29.401977634398914],[30.901762729625343,-29.90995696382804],[30.622813348113823,-30.42377573010613],[30.05571618014278,-31.140269463832958],[28.925552605919535,-32.17204111097249],[28.2197558936771,-32.771952813448856],[27.464608188595975,-33.2269637997788],[26.419452345492825,-33.61495045342619],[25.90966434093349,-33.6670402971764],[25.780628289500697,-33.944646091448334],[25.172861769315972,-33.796851495093584],[24.677853224392123,-33.98717579522455],[23.594043409934642,-33.794474379208154],[22.988188917744733,-33.91643075941698],[22.574157342222236,-33.864082533505304],[21.542799106541025,-34.258838799782936],[20.689052768647002,-34.417175388325234],[20.071261020597632,-34.79513681410799],[19.61640506356457,-34.81916635512371],[19.193278435958717,-34.46259897230979],[18.85531456876987,-34.444305515278465],[18.42464318204938,-33.99787281670896],[18.377410922934615,-34.13652068454807],[18.244499139079917,-33.86775156019802],[18.250080193767445,-33.28143075941444],[17.92519046394844,-32.61129078545343],[18.247909783611192,-32.42913136162456],[18.22176150887148,-31.66163298922567],[17.56691775886887,-30.725721123987547],[17.064416131262703,-29.87864104585916],[17.062917514726223,-29.875953871379984],[16.344976840895242,-28.576705010697697],[16.824017368240902,-28.082161553664466],[17.218928663815404,-28.35594329194681],[17.387497185951503,-28.78351409272978],[17.83615197110953,-28.85637786226132],[18.464899122804752,-29.04546192801728],[19.002127312911085,-28.972443129188864],[19.894734327888614,-28.461104831660776],[19.895767856534434,-24.767790215760588],[20.165725538827186,-24.917961928000768],[20.758609246511835,-25.86813648855145],[20.66647016773544,-26.477453301704923],[20.88960900237174,-26.828542982695915],[21.60589603036939,-26.726533705351756],[22.105968865657868,-26.280256036079138],[22.57953169118059,-25.979447523708146],[22.8242712745149,-25.500458672794768],[23.312096795350186,-25.26868987396572],[23.73356977712271,-25.390129489851613],[24.211266717228792,-25.670215752873574],[25.025170525825786,-25.7196700985769],[25.66466637543772,-25.486816094669713],[25.76584882986521,-25.174845472923675],[25.94165205252216,-24.69637338633322],[26.4857532081233,-24.616326592713104],[26.786406691197413,-24.240690606383485],[27.119409620886245,-23.574323011979775],[28.01723595552525,-22.827753594659075],[29.43218834810904,-22.091312758067588],[29.839036899542972,-22.102216485281176],[30.322883335091774,-22.27161183033393],[30.65986535006709,-22.151567478119915],[31.191409132621285,-22.2515096981724],[31.670397983534652,-23.658969008073864],[31.93058882012425,-24.369416599222536],[31.75240848158188,-25.484283949487413],[31.837777947728064,-25.84333180105135],[31.333157586397906,-25.66019052500895],[31.04407962415715,-25.731452325139443],[30.949666782359913,-26.022649021104147],[30.676608514129637,-26.398078301704608],[30.68596194837448,-26.74384531016953],[31.282773064913325,-27.285879408478998],[31.868060337051077,-27.177927341421277],[32.07166548028107,-26.73382008230491],[32.830120477028885,-26.742191664336197],[32.580264926897684,-27.470157566031816],[32.46213260267845,-28.301011244420557],[32.20338870619304,-28.752404880490072],[31.521001417778876,-29.257386976846252]],[[28.97826256685724,-28.955596612261715],[28.541700066855498,-28.64750172293757],[28.07433841320778,-28.851468601193588],[27.532511020627478,-29.24271087007536],[26.999261915807637,-29.875953871379984],[27.749397006956485,-30.64510588961222],[28.107204624145425,-30.54573211031495],[28.29106937023991,-30.2262167294543],[28.84839969250774,-30.070050551068253],[29.018415154748023,-29.74376555757737],[29.325166456832587,-29.257386976846252],[28.97826256685724,-28.955596612261715]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Zambia\",\"name\":\"Zambia\",\"name_long\":\"Zambia\",\"iso_a2\":\"ZM\",\"iso_a3\":\"ZMB\",\"iso_n3\":\"894\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[32.75937544122132,-9.230599053589058],[33.2313879737753,-9.6767216935648],[33.48568769708359,-10.525558770391115],[33.315310499817286,-10.796549981329697],[33.11428917820191,-11.607198174692314],[33.306422153463075,-12.435778090060218],[32.991764357237884,-12.783870537978272],[32.68816531752313,-13.712857761289275],[33.214024692525214,-13.971860039936153],[30.17948123548183,-14.796099134991527],[30.274255812305107,-15.507786960515212],[29.516834344203147,-15.644677829656388],[28.947463413211263,-16.04305144619444],[28.825868768028496,-16.389748630440614],[28.467906121542683,-16.468400160388846],[27.598243442502756,-17.290830580314008],[27.04442711763073,-17.938026218337434],[26.70677330903564,-17.961228936436484],[26.381935255648926,-17.8460421688579],[25.264225701608012,-17.736539808831417],[25.08444339366457,-17.661815687737374],[25.07695031098226,-17.57882333747662],[24.682349074001507,-17.353410739819473],[24.033861525170778,-17.295843194246324],[23.215048455506064,-17.52311614346598],[22.56247846852426,-16.898451429921813],[21.887842644953874,-16.08031015387688],[21.933886346125917,-12.898437188369359],[24.016136508894675,-12.911046237848574],[23.930922072045377,-12.565847670138854],[24.079905226342845,-12.191296888887365],[23.904153680118185,-11.722281589406322],[24.017893507592586,-11.23729827234709],[23.912215203555718,-10.926826267137514],[24.25715538910399,-10.951992689663657],[24.31451622894795,-11.26282642989927],[24.78316979340295,-11.238693536018964],[25.418118116973204,-11.330935967659961],[25.752309604604733,-11.784965101776358],[26.553087599399618,-11.924439792532127],[27.164419793412463,-11.608748467661075],[27.38879886242378,-12.132747491100666],[28.155108676879987,-12.272480564017897],[28.523561639121027,-12.698604424696683],[28.934285922976837,-13.248958428605135],[29.69961388521949,-13.257226657771831],[29.61600141777123,-12.178894545137311],[29.34154788586909,-12.360743910372413],[28.642417433392353,-11.971568698782315],[28.372253045370428,-11.793646742401393],[28.49606977714177,-10.789883721564044],[28.67368167492893,-9.605924981324932],[28.449871046672826,-9.164918308146085],[28.7348665707625,-8.526559340044578],[29.002912225060467,-8.407031752153472],[30.346086053190813,-8.238256524288218],[30.740015496551788,-8.340007419470915],[31.15775133695005,-8.594578747317366],[31.556348097466497,-8.762048841998642],[32.19186486179197,-8.930358981973278],[32.75937544122132,-9.230599053589058]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Zimb.\",\"name\":\"Zimbabwe\",\"name_long\":\"Zimbabwe\",\"iso_a2\":\"ZW\",\"iso_a3\":\"ZWE\",\"iso_n3\":\"716\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[31.191409132621285,-22.2515096981724],[30.65986535006709,-22.151567478119915],[30.322883335091774,-22.27161183033393],[29.839036899542972,-22.102216485281176],[29.43218834810904,-22.091312758067588],[28.794656202924212,-21.63945403410745],[28.021370070108617,-21.485975030200585],[27.72722781750326,-20.851801853114715],[27.724747348753255,-20.499058526290387],[27.296504754350508,-20.391519870691],[26.164790887158485,-19.29308562589494],[25.85039147309473,-18.714412937090536],[25.649163445750162,-18.53602589281899],[25.264225701608012,-17.736539808831417],[26.381935255648926,-17.8460421688579],[26.70677330903564,-17.961228936436484],[27.04442711763073,-17.938026218337434],[27.598243442502756,-17.290830580314008],[28.467906121542683,-16.468400160388846],[28.825868768028496,-16.389748630440614],[28.947463413211263,-16.04305144619444],[29.516834344203147,-15.644677829656388],[30.274255812305107,-15.507786960515212],[30.338954705534544,-15.880839125230244],[31.173063999157677,-15.860943698797872],[31.636498243951195,-16.071990248277885],[31.8520406430406,-16.319417006091378],[32.32823896661022,-16.392074069893752],[32.847638787575846,-16.713398125884616],[32.84986087416439,-17.97905730557718],[32.65488569512715,-18.672089939043495],[32.61199425632489,-19.419382826416275],[32.772707960752626,-19.715592136313298],[32.65974327976258,-20.304290052982317],[32.50869306817344,-20.395292250248307],[32.244988234188014,-21.116488539313693],[31.191409132621285,-22.2515096981724]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Alb.\",\"name\":\"Albania\",\"name_long\":\"Albania\",\"iso_a2\":\"AL\",\"iso_a3\":\"ALB\",\"iso_n3\":\"008\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[20.590247430104906,41.855404161133606],[20.463175083099202,41.51508901627533],[20.605181919037364,41.08622630468522],[21.0200403174764,40.84272695572588],[20.999989861747224,40.58000397395397],[20.674996779063633,40.43499990494303],[20.615000441172754,40.11000682225938],[20.15001590341052,39.62499766698397],[19.980000441170144,39.69499339452341],[19.960001661873207,39.91500580500605],[19.406081984136733,40.250773423822466],[19.319058872157143,40.72723012955356],[19.40354983895429,41.40956574153546],[19.540027296637106,41.71998607031276],[19.37176883309496,41.877547512370654],[19.304486118250793,42.19574514420782],[19.738051385179627,42.688247382165564],[19.801613396898688,42.50009349219084],[20.0707,42.58863],[20.283754510181893,42.32025950781508],[20.52295,42.21787],[20.590247430104906,41.855404161133606]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Aust.\",\"name\":\"Austria\",\"name_long\":\"Austria\",\"iso_a2\":\"AT\",\"iso_a3\":\"AUT\",\"iso_n3\":\"040\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[16.979666782304037,48.123497015976305],[16.90375410326726,47.71486562762833],[16.340584344150415,47.71290192320123],[16.534267612380376,47.49617096616912],[16.202298211337364,46.85238597267696],[16.011663852612656,46.6836107448117],[15.137091912504985,46.65870270444703],[14.63247155117483,46.43181732846955],[13.806475457421527,46.509306138691215],[12.376485223040817,46.76755910906985],[12.153088006243054,47.11539317482645],[11.16482791509327,46.94157949481273],[11.048555942436536,46.75135854754634],[10.44270145024663,46.89354625099743],[9.932448357796659,46.92072805438296],[9.479969516649021,47.10280996356337],[9.632931756232978,47.34760122332999],[9.59422610844635,47.52505809182027],[9.896068149463188,47.580196845075704],[10.402083774465211,47.30248769793916],[10.544504021861627,47.56639923765377],[11.426414015354737,47.523766181012974],[12.141357456112788,47.703083401065776],[12.620759718484491,47.67238760028441],[12.932626987365948,47.467645575544],[13.02585127122049,47.637583523135824],[12.884102817443903,48.28914581968792],[13.243357374737,48.416114813829054],[13.595945672264437,48.87717194273715],[14.33889773932472,48.5553052842072],[14.901447381254057,48.964401760445824],[15.253415561593982,49.039074205107575],[16.02964725105022,48.73389903420793],[16.49928266771877,48.78580801044511],[16.960288120194576,48.5969823268506],[16.879982944413,48.47001333270947],[16.979666782304037,48.123497015976305]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Belg.\",\"name\":\"Belgium\",\"name_long\":\"Belgium\",\"iso_a2\":\"BE\",\"iso_a3\":\"BEL\",\"iso_n3\":\"056\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[3.314971144228537,51.345780951536085],[4.047071160507527,51.26725861266857],[4.973991326526914,51.475023708698124],[5.606975945670001,51.037298488969775],[6.156658155958779,50.80372101501058],[6.043073357781111,50.128051662794235],[5.782417433300906,50.09032786722122],[5.674051954784829,49.529483547557504],[4.799221632515809,49.985373033236385],[4.286022983425084,49.907496649772554],[3.588184441755686,50.37899241800358],[3.123251580425801,50.780363267614575],[2.658422071960274,50.79684804951574],[2.513573032246143,51.14850617126183],[3.314971144228537,51.345780951536085]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Bulg.\",\"name\":\"Bulgaria\",\"name_long\":\"Bulgaria\",\"iso_a2\":\"BG\",\"iso_a3\":\"BGR\",\"iso_n3\":\"100\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[22.65714969248299,44.23492300066128],[22.944832391051847,43.82378530534713],[23.33230228037632,43.89701080990471],[24.100679152124172,43.74105133724785],[25.569271681426926,43.68844472917472],[26.065158725699746,43.94349376075126],[27.242399529740908,44.175986029632405],[27.970107049275075,43.81246816667521],[28.558081495891997,43.70746165625813],[28.03909508638472,43.293171698574184],[27.67389773937805,42.577892361006214],[27.99672041190539,42.00735871028779],[27.135739373490477,42.14148489030134],[26.117041863720797,41.82690460872456],[26.106138136507212,41.32889883072778],[25.197201368925445,41.23448598893053],[24.49264489105803,41.583896185872035],[23.692073601992348,41.30908091894385],[22.952377150166452,41.33799388281115],[22.88137373219743,41.99929718685026],[22.380525750424592,42.32025950781509],[22.54501183440962,42.46136200618804],[22.43659467946128,42.580321153323936],[22.60480146657133,42.898518785161144],[22.986018507588483,43.211161200526966],[22.50015669118028,43.64281443946099],[22.410446404721597,44.00806346289995],[22.65714969248299,44.23492300066128]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Bela.\",\"name\":\"Belarus\",\"name_long\":\"Belarus\",\"iso_a2\":\"BY\",\"iso_a3\":\"BLR\",\"iso_n3\":\"112\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[23.48412763844985,53.91249766704114],[24.450683628037037,53.905702216194754],[25.536353794056993,54.28242340760253],[25.7684326514798,54.84696259217509],[26.58827924979039,55.16717560487167],[26.494331495883753,55.615106919977634],[27.10245975109453,55.783313707087686],[28.176709425577993,56.16912995057881],[29.229513380660308,55.918344224666356],[29.371571893030673,55.670090643936184],[29.896294386522356,55.78946320253041],[30.873909132620007,55.55097646750341],[30.971835971813135,55.08154775656404],[30.757533807098717,54.81177094178432],[31.38447228366374,54.157056382862436],[31.79142418796224,53.97463857687212],[31.731272820774507,53.79402944601202],[32.405598585751164,53.618045355842035],[32.69364301934604,53.35142080343212],[32.304519484188226,53.1327261419729],[31.49764367038293,53.1674268662569],[31.305200636528014,53.07399587667321],[31.54001834486226,52.74205231384636],[31.785998162571587,52.101677964885454],[30.927549269338982,52.04235342061438],[30.619454380014844,51.822806098022376],[30.555117221811457,51.31950348571566],[30.157363722460897,51.41613841410147],[29.254938185347925,51.368234361366895],[28.99283532076353,51.602044379271476],[28.61761274589225,51.42771393493484],[28.24161502453657,51.57222707783907],[27.454066196408434,51.59230337178447],[26.337958611768556,51.83228872334793],[25.327787713327005,51.91065603291855],[24.553106316839518,51.888461005249184],[24.00507775238421,51.61744395609446],[23.52707075368437,51.57845408793023],[23.508002150168693,52.02364655212473],[23.199493849386187,52.486977444053664],[23.799198846133375,52.69109935160657],[23.80493493011778,53.089731350306074],[23.527535841575002,53.470121568406555],[23.48412763844985,53.91249766704114]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Switz.\",\"name\":\"Switzerland\",\"name_long\":\"Switzerland\",\"iso_a2\":\"CH\",\"iso_a3\":\"CHE\",\"iso_n3\":\"756\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[9.59422610844635,47.52505809182027],[9.632931756232978,47.34760122332999],[9.479969516649021,47.10280996356337],[9.932448357796659,46.92072805438296],[10.44270145024663,46.89354625099743],[10.363378126678612,46.48357127540986],[9.92283654139038,46.31489940040919],[9.182881707403055,46.44021474871698],[8.966305779667806,46.036931871111186],[8.489952426801324,46.005150865251686],[8.31662967289438,46.16364248309086],[7.755992058959833,45.82449005795931],[7.273850945676656,45.776947740250776],[6.843592970414504,45.99114655210061],[6.500099724970425,46.42967275652944],[6.022609490593537,46.27298981382047],[6.037388950229001,46.725778713561866],[6.768713820023606,47.2877082383037],[6.736571079138059,47.541801255882845],[7.192202182655507,47.44976552997102],[7.46675906742223,47.62058197691181],[8.317301466514152,47.61357982033626],[8.522611932009765,47.830827541691285],[9.59422610844635,47.52505809182027]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"B.H.\",\"name\":\"Bosnia and Herz.\",\"name_long\":\"Bosnia and Herzegovina\",\"iso_a2\":\"BA\",\"iso_a3\":\"BIH\",\"iso_n3\":\"070\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[19.00548628101012,44.86023366960916],[19.36803,44.863],[19.11761,44.42307000000011],[19.59976,44.03847],[19.454,43.56810000000013],[19.21852,43.52384],[19.03165,43.43253],[18.70648,43.20011],[18.56,42.65],[17.674921502358984,43.02856252702361],[17.297373488034452,43.44634064388736],[16.91615644701733,43.66772247982567],[16.456442905348865,44.04123973243128],[16.23966027188453,44.35114329688571],[15.750026075918981,44.81871165626256],[15.959367303133376,45.233776760430935],[16.318156772535872,45.00412669532591],[16.534939406000206,45.21160757097772],[17.002146030351014,45.233776760430935],[17.861783481526402,45.067740383477144],[18.553214145591653,45.08158966733145],[19.00548628101012,44.86023366960916]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Cz. Rep.\",\"name\":\"Czech Rep.\",\"name_long\":\"Czech Republic\",\"iso_a2\":\"CZ\",\"iso_a3\":\"CZE\",\"iso_n3\":\"203\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[16.960288120194576,48.5969823268506],[16.49928266771877,48.78580801044511],[16.02964725105022,48.73389903420793],[15.253415561593982,49.039074205107575],[14.901447381254057,48.964401760445824],[14.33889773932472,48.5553052842072],[13.595945672264437,48.87717194273715],[13.031328973043431,49.30706818297324],[12.521024204161192,49.547415269562734],[12.415190870827445,49.96912079528057],[12.240111118222558,50.266337795607285],[12.966836785543194,50.484076443069085],[13.338131951560285,50.73323436136435],[14.056227654688172,50.92691762959429],[14.307013380600637,51.117267767941414],[14.570718214586066,51.002339382524276],[15.01699588385867,51.10667409932158],[15.490972120839727,50.78472992614321],[16.23862674323857,50.69773265237984],[16.176253289462267,50.42260732685791],[16.719475945714436,50.21574656839354],[16.868769158605655,50.47397370055603],[17.55456709155112,50.36214590107641],[17.64944502123899,50.049038397819956],[18.392913852622172,49.98862864847075],[18.853144158613617,49.49622976337764],[18.554971144289482,49.49501536721878],[18.399993523846177,49.31500051533004],[18.170498488037964,49.271514797556435],[18.104972771891852,49.04398346617531],[17.913511590250465,48.996492824899086],[17.88648481616181,48.90347524677371],[17.545006951577108,48.80001902932537],[17.101984897538898,48.81696889911711],[16.960288120194576,48.5969823268506]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Ger.\",\"name\":\"Germany\",\"name_long\":\"Germany\",\"iso_a2\":\"DE\",\"iso_a3\":\"DEU\",\"iso_n3\":\"276\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[9.921906365609232,54.983104153048025],[9.9395797054529,54.596641954153256],[10.950112338920519,54.363607082733154],[10.939466993868448,54.00869334575258],[11.956252475643282,54.19648550070116],[12.518440382546714,54.47037059184799],[13.647467075259499,54.0755109727059],[14.119686313542559,53.75702912049103],[14.353315463934168,53.248171291713106],[14.074521111719434,52.98126251892535],[14.4375997250022,52.624850165408304],[14.685026482815713,52.089947414755216],[14.607098422919648,51.745188096719964],[15.016995883858781,51.10667409932171],[14.570718214586122,51.00233938252438],[14.307013380600665,51.11726776794137],[14.056227654688314,50.92691762959435],[13.338131951560397,50.73323436136428],[12.96683678554325,50.48407644306917],[12.240111118222671,50.26633779560723],[12.415190870827473,49.96912079528062],[12.521024204161336,49.54741526956275],[13.031328973043514,49.30706818297324],[13.595945672264577,48.877171942737164],[13.243357374737116,48.41611481382903],[12.884102817443873,48.28914581968786],[13.025851271220517,47.63758352313595],[12.932626987366064,47.467645575544],[12.620759718484521,47.672387600284424],[12.141357456112871,47.70308340106578],[11.426414015354851,47.52376618101306],[10.544504021861597,47.5663992376538],[10.402083774465325,47.30248769793916],[9.896068149463188,47.580196845075704],[9.594226108446376,47.5250580918202],[8.522611932009795,47.83082754169135],[8.317301466514095,47.61357982033627],[7.466759067422288,47.62058197691192],[7.593676385131062,48.33301911070373],[8.099278598674855,49.01778351500343],[6.658229607783709,49.20195831969164],[6.186320428094177,49.463802802114515],[6.242751092156993,49.90222565367873],[6.043073357781111,50.128051662794235],[6.156658155958779,50.80372101501058],[5.988658074577813,51.851615709025054],[6.589396599970826,51.852029120483394],[6.842869500362383,52.22844025329755],[7.092053256873896,53.14404328064489],[6.905139601274129,53.48216217713064],[7.100424838905268,53.69393219666267],[7.936239454793962,53.74829580343379],[8.121706170289485,53.52779246684429],[8.800734490604668,54.020785630908904],[8.572117954145368,54.39564647075405],[8.526229282270208,54.96274363872516],[9.282048780971136,54.83086538351631],[9.921906365609232,54.983104153048025]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Den.\",\"name\":\"Denmark\",\"name_long\":\"Denmark\",\"iso_a2\":\"DK\",\"iso_a3\":\"DNK\",\"iso_n3\":\"208\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[12.69000613775563,55.609990953180784],[12.089991082414741,54.80001455343793],[11.043543328504228,55.364863796604254],[10.903913608451631,55.77995473898875],[12.370904168353292,56.111407375708836],[12.69000613775563,55.609990953180784]]],[[[10.912181837618363,56.458621324277914],[10.667803989309988,56.08138336854722],[10.369992710011985,56.19000722922473],[9.649984978889307,55.469999498102055],[9.921906365609175,54.98310415304806],[9.282048780971136,54.83086538351616],[8.526229282270236,54.96274363872499],[8.120310906617588,55.517722683323626],[8.08997684086225,56.540011705137594],[8.256581658571264,56.8099693874303],[8.543437534223386,57.110002753316905],[9.42446902836761,57.17206614849948],[9.775558709358563,57.447940782289656],[10.580005730846153,57.73001658795485],[10.546105991262692,57.215732733786155],[10.250000034230226,56.89001618105047],[10.369992710011985,56.609981594460834],[10.912181837618363,56.458621324277914]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Sp.\",\"name\":\"Spain\",\"name_long\":\"Spain\",\"iso_a2\":\"ES\",\"iso_a3\":\"ESP\",\"iso_n3\":\"724\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-9.034817674180246,41.88057058365967],[-8.984433152695672,42.59277517350627],[-9.392883673530648,43.0266246608127],[-7.978189663108309,43.748337714200986],[-6.754491746436756,43.567909450853925],[-5.411886359061596,43.574239813809676],[-4.347842779955783,43.40344920508504],[-3.51753170410609,43.4559007838613],[-1.901351284177764,43.42280202897834],[-1.502770961910528,43.03401439063043],[0.338046909190581,42.57954600683954],[0.701590610363894,42.7957343613326],[1.826793247087153,42.34338471126569],[2.985998976258458,42.47301504166986],[3.039484083680549,41.892120266276905],[2.091841668312185,41.22608856868309],[0.810524529635188,41.01473196060934],[0.721331007499401,40.678318386389236],[0.106691521819869,40.12393362076202],[-0.278711310212941,39.30997813573272],[0.111290724293838,38.73851430923303],[-0.467123582349103,38.29236583104115],[-0.683389451490598,37.642353827457825],[-1.438382127274849,37.443063666324214],[-2.146452602538119,36.67414419203728],[-3.415780808923387,36.65889964451118],[-4.368900926114719,36.677839056946155],[-4.995219285492211,36.32470815687964],[-5.377159796561457,35.946850083961465],[-5.866432257500904,36.02981659600606],[-6.236693894872175,36.367677110330334],[-6.520190802425404,36.94291331638732],[-7.453725551778092,37.09778758396607],[-7.537105475281024,37.42890432387623],[-7.166507941099865,37.803894354802225],[-7.029281175148796,38.07576406508977],[-7.374092169616318,38.37305858006492],[-7.098036668313128,39.03007274022378],[-7.498632371439725,39.62957103124181],[-7.066591559263529,39.71189158788277],[-7.026413133156595,40.184524237624245],[-6.864019944679385,40.33087189387483],[-6.851126674822552,41.11108266861753],[-6.389087693700915,41.381815497394655],[-6.668605515967656,41.883386949219584],[-7.251308966490824,41.91834605566505],[-7.422512986673795,41.79207469335983],[-8.013174607769912,41.790886135417125],[-8.263856980817792,42.28046865495034],[-8.67194576662672,42.13468943945496],[-9.034817674180246,41.88057058365967]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Est.\",\"name\":\"Estonia\",\"name_long\":\"Estonia\",\"iso_a2\":\"EE\",\"iso_a3\":\"EST\",\"iso_n3\":\"233\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[24.312862583114622,57.79342357037698],[24.42892785004216,58.38341339785328],[24.061198357853186,58.25737457949341],[23.42656009287668,58.612753404364625],[23.339795363058645,59.18724030215338],[24.604214308376182,59.46585378685502],[25.86418908051664,59.61109039981134],[26.949135776484525,59.445803331125774],[27.981114129353244,59.47538808861287],[28.13169925305175,59.30082510033092],[27.42016645682494,58.72458120384424],[27.71668582531572,57.79189911562437],[27.28818484875151,57.47452830670383],[26.463532342237787,57.47638865826633],[25.602809685984365,57.84752879498657],[25.16459354014927,57.97015696881519],[24.312862583114622,57.79342357037698]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Fin.\",\"name\":\"Finland\",\"name_long\":\"Finland\",\"iso_a2\":\"FI\",\"iso_a3\":\"FIN\",\"iso_n3\":\"246\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[28.591929559043194,69.06477692328666],[28.445943637818658,68.36461294216404],[29.977426385220607,67.69829702419266],[29.054588657352326,66.94428620062193],[30.21765,65.80598],[29.544429559046986,64.94867157659048],[30.44468468600371,64.20445343693909],[30.035872430142714,63.55281362573855],[31.51609215671112,62.86768748641288],[31.139991082490894,62.35769277612441],[30.211107212044446,61.78002777774969],[28.069997592895277,60.503516547275844],[26.25517296723697,60.4239606797625],[24.496623976344523,60.05731639265165],[22.869694858499457,59.846373196036225],[22.290763787533592,60.39192129174154],[21.322244093519316,60.72016998965952],[21.544866163832694,61.7053294948718],[21.05921105315369,62.60739329695874],[21.536029493910803,63.18973501245587],[22.442744174903993,63.81781037053129],[24.730511508897536,64.90234365504082],[25.398067661243942,65.11142650009373],[25.294043003040404,65.53434642197045],[23.903378533633802,66.00692739527962],[23.565879754335583,66.39605093043743],[23.53947309743444,67.93600861273525],[21.978534783626117,68.6168456081807],[20.645592889089528,69.10624726020087],[21.244936150810673,69.37044302029307],[22.356237827247412,68.84174144151491],[23.66204959483076,68.89124746365054],[24.735679152126725,68.64955678982146],[25.68921268077636,69.09211375596904],[26.17962202322624,69.82529897732614],[27.732292107867863,70.16419302029625],[29.01557295097197,69.76649119737799],[28.591929559043194,69.06477692328666]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Fr.\",\"name\":\"France\",\"name_long\":\"France\",\"iso_a2\":\"FR\",\"iso_a3\":\"FRA\",\"iso_n3\":\"250\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-52.55642473001839,2.504705308437053],[-52.93965715189498,2.124857692875622],[-53.418465135295264,2.053389187016037],[-53.554839240113495,2.334896551925965],[-53.77852067728889,2.376702785650053],[-54.08806250671728,2.105556545414629],[-54.52475419779975,2.311848863123785],[-54.27122962097578,2.738747870286943],[-54.18428402364474,3.194172268075235],[-54.01150387227682,3.622569891774858],[-54.399542202356514,4.212611395683481],[-54.47863298197922,4.896755682795643],[-53.95804460307093,5.756548163267809],[-53.618452928264844,5.646529038918402],[-52.88214128275408,5.409850979021599],[-51.82334286152593,4.565768133966145],[-51.65779741067888,4.156232408053029],[-52.24933753112398,3.241094468596287],[-52.55642473001839,2.504705308437053]]],[[[9.560016310269134,42.15249197037957],[9.229752231491773,41.38000682226445],[8.77572309737536,41.58361196549444],[8.54421268070783,42.25651662858308],[8.746009148807588,42.62812185319396],[9.390000848028905,43.00998484961474],[9.560016310269134,42.15249197037957]]],[[[3.588184441755715,50.37899241800358],[4.28602298342514,49.907496649772554],[4.799221632515753,49.98537303323633],[5.674051954784885,49.52948354755745],[5.897759230176376,49.44266714130717],[6.186320428094206,49.46380280211446],[6.658229607783539,49.20195831969155],[8.099278598674772,49.01778351500337],[7.593676385131062,48.33301911070373],[7.46675906742223,47.620581976911865],[7.192202182655535,47.44976552997099],[6.736571079138088,47.54180125588289],[6.768713820023634,47.28770823830368],[6.037388950228972,46.72577871356191],[6.022609490593567,46.272989813820516],[6.500099724970454,46.42967275652944],[6.843592970414562,45.99114655210067],[6.802355177445662,45.70857982032867],[7.096652459347837,45.333098863295874],[6.749955275101711,45.02851797136759],[7.007562290076663,44.25476675066139],[7.549596388386163,44.12790110938482],[7.435184767291843,43.69384491634918],[6.529245232783068,43.12889232031836],[4.556962517931396,43.39965098731158],[3.10041059735272,43.075200507167125],[2.985998976258486,42.47301504166989],[1.826793247087181,42.34338471126566],[0.701590610363922,42.79573436133265],[0.338046909190581,42.579546006839564],[-1.502770961910471,43.03401439063049],[-1.901351284177735,43.42280202897834],[-1.384225226232957,44.02261037859017],[-1.193797573237362,46.014917710954876],[-2.225724249673789,47.06436269793821],[-2.963276129559574,47.570326646507965],[-4.491554938159481,47.95495433205642],[-4.592349819344747,48.68416046812695],[-3.295813971357745,48.901692409859635],[-1.616510789384932,48.644421291694584],[-1.933494025063254,49.776341864615766],[-0.98946895995536,49.347375800160876],[1.338761020522753,50.12717316344526],[1.6390010921385,50.946606350297515],[2.513573032246171,51.14850617126185],[2.658422071960331,50.79684804951566],[3.123251580425716,50.78036326761452],[3.588184441755715,50.37899241800358]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"U.K.\",\"name\":\"United Kingdom\",\"name_long\":\"United Kingdom\",\"iso_a2\":\"GB\",\"iso_a3\":\"GBR\",\"iso_n3\":\"826\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-5.661948614921897,54.55460317648385],[-6.197884894220977,53.86756500916334],[-6.953730231137996,54.073702297575636],[-7.572167934591079,54.05995636658599],[-7.366030646178785,54.595840969452695],[-7.572167934591079,55.1316222194549],[-6.733847011736145,55.1728600124238],[-5.661948614921897,54.55460317648385]]],[[[-3.005004848635281,58.63500010846633],[-4.073828497728016,57.55302480735525],[-3.055001796877661,57.69001902936095],[-1.959280564776918,57.68479970969951],[-2.219988165689301,56.87001740175353],[-3.119003058271118,55.973793036515474],[-2.085009324543023,55.90999848085127],[-2.005675679673857,55.80490285035023],[-1.11499101399221,54.62498647726539],[-0.4304849918542,54.46437612570216],[0.184981316742039,53.32501414653103],[0.469976840831777,52.92999949809197],[1.681530795914739,52.739520168664],[1.559987827164377,52.09999848083601],[1.050561557630914,51.806760565795685],[1.449865349950301,51.28942780212196],[0.550333693045502,50.765738837275876],[-0.78751746255864,50.77498891865622],[-2.489997524414377,50.50001862243124],[-2.956273972984036,50.696879991247016],[-3.617448085942328,50.22835561787272],[-4.542507900399244,50.34183706318566],[-5.245023159191135,49.95999990498108],[-5.776566941745301,50.15967763935682],[-4.309989793301838,51.21000112568916],[-3.414850633142123,51.42600861266925],[-3.422719467108323,51.42684816740609],[-4.984367234710874,51.593466091510976],[-5.267295701508885,51.99140045837458],[-4.222346564134853,52.301355699261364],[-4.770013393564113,52.840004991255626],[-4.579999152026915,53.49500377055517],[-3.093830673788659,53.404547400669685],[-3.092079637047106,53.404440822963544],[-2.945008510744344,53.984999701546684],[-3.614700825433034,54.600936773292574],[-3.63000545898933,54.615012925833014],[-4.844169073903004,54.790971177786844],[-5.082526617849226,55.06160065369937],[-4.719112107756644,55.50847260194348],[-5.047980922862109,55.78398550070752],[-5.586397670911139,55.31114614523682],[-5.644998745130181,56.275014960344805],[-6.149980841486354,56.78500967063354],[-5.786824713555291,57.81884837506465],[-5.009998745127575,58.63001333275005],[-4.211494513353557,58.55084503847917],[-3.005004848635281,58.63500010846633]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Cro.\",\"name\":\"Croatia\",\"name_long\":\"Croatia\",\"iso_a2\":\"HR\",\"iso_a3\":\"HRV\",\"iso_n3\":\"191\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[18.829838087650046,45.908877671891844],[19.072768995854176,45.52151113543209],[19.39047570158459,45.236515611342384],[19.00548628101012,44.86023366960916],[18.553214145591653,45.08158966733145],[17.861783481526402,45.067740383477144],[17.002146030351014,45.233776760430935],[16.534939406000206,45.21160757097772],[16.318156772535872,45.00412669532591],[15.959367303133376,45.233776760430935],[15.750026075918981,44.81871165626256],[16.23966027188453,44.35114329688571],[16.456442905348865,44.04123973243128],[16.91615644701733,43.66772247982567],[17.297373488034452,43.44634064388736],[17.674921502358984,43.02856252702361],[18.56,42.65],[18.450016310304818,42.47999136002932],[17.509970330483327,42.849994615239154],[16.930005730871642,43.20999848080038],[16.015384555737683,43.50721548112722],[15.174453973052096,44.243191229827914],[15.376250441151795,44.31791535092208],[14.920309279040508,44.73848399512946],[14.901602410550877,45.07606028907611],[14.258747592839995,45.233776760430935],[13.952254672917034,44.80212352149687],[13.656975538801191,45.13693512631596],[13.67940311041582,45.48414907488501],[13.715059848697251,45.500323798192426],[14.4119682145855,45.46616567644742],[14.595109490627918,45.63494090431282],[14.935243767972963,45.471695054702764],[15.327674594797427,45.452316392593325],[15.323953891672431,45.731782538427694],[15.671529575267641,45.8341535507979],[15.768732944408612,46.23810822202353],[16.564808383864943,46.50375092221981],[16.882515089595415,46.38063182228444],[17.630066359129557,45.9517691106941],[18.45606245288286,45.75948110613615],[18.829838087650046,45.908877671891844]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Ire.\",\"name\":\"Ireland\",\"name_long\":\"Ireland\",\"iso_a2\":\"IE\",\"iso_a3\":\"IRL\",\"iso_n3\":\"372\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-6.197884894220991,53.86756500916336],[-6.03298539877761,53.15316417094435],[-6.788856573910849,52.260117906292336],[-8.56161658368356,51.669301255899356],[-9.977085740590269,51.82045482035307],[-9.16628251793078,52.86462881124268],[-9.688524542672454,53.8813626165853],[-8.327987433292009,54.66451894796863],[-7.572167934591064,55.13162221945487],[-7.366030646178785,54.59584096945272],[-7.572167934591064,54.059956366586],[-6.953730231138067,54.073702297575636],[-6.197884894220991,53.86756500916336]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Greece\",\"name\":\"Greece\",\"name_long\":\"Greece\",\"iso_a2\":\"GR\",\"iso_a3\":\"GRC\",\"iso_n3\":\"300\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[23.699980096133004,35.70500438083553],[24.24666507334868,35.368022365860156],[25.02501549652888,35.42499563246198],[25.769207797964185,35.35401805270908],[25.745023227651586,35.179997666966216],[26.290002882601723,35.29999034274792],[26.16499759288766,35.004995429009796],[24.724982130642303,34.91998769788961],[24.735007358506945,35.08499054619759],[23.51497846852811,35.27999156345098],[23.699980096133004,35.70500438083553]]],[[[26.604195590936282,41.562114569661105],[26.29460208507578,40.93626129817426],[26.056942172965506,40.824123440100834],[25.447677036244187,40.85254547786147],[24.92584842296094,40.94706167252323],[23.714811232200816,40.687129218095116],[24.407998894964066,40.1249929876241],[23.899967889102584,39.96200552017558],[23.3429993018608,39.96099782974579],[22.81398766448896,40.476005153966554],[22.62629886240478,40.25656118423919],[22.849747755634805,39.65931081802577],[23.3500272966526,39.19001129816726],[22.973099399515547,38.97090322524966],[23.530016310324953,38.51000112563847],[24.025024855248944,38.21999298761645],[24.040011020613605,37.655014553369426],[23.115002882589152,37.92001129816222],[23.409971958111072,37.409990749657396],[22.774971958108633,37.30501007745656],[23.15422529469862,36.422505804992056],[22.490028110451107,36.41000010837746],[21.670026482843696,36.8449864771942],[21.295010613701574,37.644989325504696],[21.120034213961333,38.31032339126273],[20.730032179454582,38.769985256498785],[20.217712029712857,39.340234686839636],[20.15001590341052,39.62499766698403],[20.615000441172782,40.11000682225943],[20.674996779063633,40.434999904943055],[20.99998986174728,40.58000397395397],[21.02004031747643,40.84272695572588],[21.674160597426976,40.93127452245798],[22.05537763844427,41.14986583105269],[22.597308383889015,41.130487168943205],[22.76177,41.3048],[22.952377150166566,41.33799388281122],[23.692073601992462,41.30908091894386],[24.49264489105803,41.58389618587205],[25.197201368925533,41.23448598893066],[26.106138136507184,41.32889883072784],[26.117041863720914,41.82690460872473],[26.604195590936282,41.562114569661105]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Iceland\",\"name\":\"Iceland\",\"name_long\":\"Iceland\",\"iso_a2\":\"IS\",\"iso_a3\":\"ISL\",\"iso_n3\":\"352\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-14.508695441129236,66.45589223903141],[-14.739637417041605,65.8087482774403],[-13.60973222497981,65.12667104761987],[-14.909833746794902,64.36408193628868],[-17.794438035543422,63.67874909123385],[-18.656245896874992,63.49638296167582],[-19.97275468594276,63.64363495549153],[-22.762971971110158,63.960178941495386],[-21.778484259517683,64.40211579045551],[-23.95504391121911,64.89112986923348],[-22.184402635170358,65.0849681667603],[-22.227423265053332,65.37859365504272],[-24.326184047939336,65.61118927678847],[-23.65051469572309,66.26251902939522],[-22.134922451250883,66.41046865504687],[-20.57628373867955,65.73211212835143],[-19.05684160000159,66.27660085719477],[-17.79862382655905,65.99385325790978],[-16.167818976292125,66.52679230413587],[-14.508695441129236,66.45589223903141]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Hun.\",\"name\":\"Hungary\",\"name_long\":\"Hungary\",\"iso_a2\":\"HU\",\"iso_a3\":\"HUN\",\"iso_n3\":\"348\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[16.202298211337364,46.85238597267696],[16.534267612380376,47.49617096616912],[16.340584344150415,47.71290192320123],[16.90375410326726,47.71486562762833],[16.979666782304037,48.123497015976305],[17.48847293464982,47.86746613218621],[17.857132602620027,47.758428860050365],[18.696512892336926,47.880953681014404],[18.77702477384767,48.081768296900634],[19.17436486173989,48.11137889260387],[19.661363559658497,48.26661489520866],[19.769470656013112,48.202691148463614],[20.239054396249347,48.32756724709692],[20.473562045989866,48.56285004332181],[20.801293979584926,48.623854071642384],[21.872236362401736,48.31997081155002],[22.08560835133485,48.42226430927179],[22.640819939878753,48.15023956968735],[22.710531447040495,47.88219391538941],[22.099767693782834,47.6724392767167],[21.62651492685387,46.99423777931816],[21.02195234547125,46.3160879583519],[20.220192498462836,46.127468980486555],[19.596044549241583,46.17172984474454],[18.82983808764996,45.90887767189193],[18.45606245288286,45.759481106136136],[17.630066359129557,45.95176911069419],[16.8825150895953,46.38063182228444],[16.564808383864857,46.50375092221983],[16.370504998447416,46.8413272161665],[16.202298211337364,46.85238597267696]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Italy\",\"name\":\"Italy\",\"name_long\":\"Italy\",\"iso_a2\":\"IT\",\"iso_a3\":\"ITA\",\"iso_n3\":\"380\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[15.520376010813834,38.23115509699147],[15.160242954171736,37.44404551853782],[15.309897902089006,37.1342194687318],[15.099988234119449,36.6199872909954],[14.335228712632016,36.996630967754754],[13.826732618879928,37.10453135838019],[12.431003859108813,37.61294993748381],[12.570943637755136,38.12638113051968],[13.741156447004585,38.03496552179536],[14.76124922044616,38.143873602850505],[15.520376010813834,38.23115509699147]]],[[[9.210011834356266,41.20999136002422],[9.809975213264977,40.5000088567661],[9.669518670295673,39.177376410471794],[9.21481774255949,39.240473334300134],[8.80693566247973,38.90661774347847],[8.428302443077115,39.17184703221662],[8.38825320805094,40.378310858718805],[8.15999840661766,40.95000722916379],[8.709990675500109,40.89998444270523],[9.210011834356266,41.20999136002422]]],[[[12.376485223040843,46.76755910906987],[13.806475457421556,46.50930613869119],[13.698109978905478,46.016778062517375],[13.937630242578335,45.591015936864665],[13.141606479554298,45.73669179949541],[12.328581170306306,45.38177806251485],[12.383874952858605,44.88537425391908],[12.261453484759159,44.600482082694015],[12.589237094786483,44.091365871754476],[13.526905958722494,43.5877273626379],[14.029820997787027,42.76100779883248],[15.142569614327956,41.955139675456905],[15.926191033601896,41.96131500911574],[16.169897088290412,41.74029490820342],[15.889345737377797,41.5410822617182],[16.785001661860576,41.179605617836586],[17.519168735431208,40.87714345963224],[18.376687452882575,40.35562490494266],[18.4802470231954,40.168866278639825],[18.293385044028096,39.81077444107325],[17.738380161213286,40.2776710068303],[16.869595981522338,40.44223460546385],[16.448743116937322,39.79540070246648],[17.1714896989715,39.42469981542072],[17.05284061042934,38.9028712021373],[16.635088331781844,38.8435724960824],[16.100960727613057,37.98589874933418],[15.684086948314501,37.90884918878703],[15.687962680736321,38.214592800441864],[15.891981235424707,38.750942491199226],[16.109332309644312,38.96454702407769],[15.718813510814641,39.544072374014945],[15.413612501698822,40.04835683853517],[14.998495721098237,40.17294871679093],[14.70326826341477,40.604550279292624],[14.060671827865264,40.78634796809544],[13.627985060285397,41.188287258461656],[12.88808190273042,41.25308950455562],[12.10668257004491,41.70453481705741],[11.191906365614187,42.35542531998967],[10.511947869517797,42.931462510747224],[10.200028924204048,43.920006822274615],[9.702488234097814,44.03627879493132],[8.88894616052687,44.36633616797954],[8.428560825238577,44.23122813575242],[7.850766635783201,43.76714793555524],[7.435184767291843,43.69384491634918],[7.549596388386163,44.12790110938482],[7.007562290076663,44.25476675066139],[6.749955275101711,45.02851797136759],[7.096652459347837,45.333098863295874],[6.802355177445662,45.70857982032867],[6.843592970414562,45.99114655210067],[7.273850945676685,45.77694774025076],[7.755992058959833,45.82449005795928],[8.31662967289438,46.163642483090854],[8.489952426801295,46.00515086525175],[8.966305779667834,46.036931871111165],[9.182881707403112,46.44021474871698],[9.922836541390353,46.31489940040919],[10.363378126678668,46.483571275409844],[10.442701450246602,46.893546250997446],[11.048555942436508,46.7513585475464],[11.164827915093326,46.94157949481274],[12.153088006243081,47.11539317482644],[12.376485223040843,46.76755910906987]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Kos.\",\"name\":\"Kosovo\",\"name_long\":\"Kosovo\",\"iso_a2\":null,\"iso_a3\":null,\"iso_n3\":null},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[20.76216,42.05186],[20.71731000000011,41.84711],[20.59023,41.85541],[20.52295,42.21787],[20.28374,42.3202500000001],[20.0707,42.58863],[20.25758,42.81275000000011],[20.49679,42.88469],[20.63508,43.21671],[20.81448,43.27205],[20.95651,43.13094],[21.143395,43.06868500000012],[21.27421,42.90959],[21.43866,42.86255],[21.63302,42.67717],[21.77505,42.6827],[21.66292,42.43922],[21.54332,42.3202500000001],[21.57663598940212,42.24522439706186],[21.35270000000014,42.2068],[20.76216,42.05186]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Lith.\",\"name\":\"Lithuania\",\"name_long\":\"Lithuania\",\"iso_a2\":\"LT\",\"iso_a3\":\"LTU\",\"iso_n3\":\"440\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[22.731098667092652,54.327536932993326],[22.65105187347254,54.582740993866736],[22.75776370615526,54.85657440858138],[22.315723504330577,55.015298570365864],[21.268448927503467,55.190481675835315],[21.055800408622414,56.03107636171106],[22.201156853939494,56.33780182557948],[23.878263787539964,56.273671373105266],[24.860684441840757,56.37252838807963],[25.000934279080894,56.16453074810484],[25.533046502390334,56.10029694276603],[26.494331495883753,55.615106919977634],[26.58827924979039,55.16717560487167],[25.7684326514798,54.84696259217509],[25.536353794056993,54.28242340760253],[24.450683628037037,53.905702216194754],[23.48412763844985,53.91249766704114],[23.24398725758951,54.22056671814914],[22.731098667092652,54.327536932993326]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Lat.\",\"name\":\"Latvia\",\"name_long\":\"Latvia\",\"iso_a2\":\"LV\",\"iso_a3\":\"LVA\",\"iso_n3\":\"428\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[21.055800408622414,56.03107636171106],[21.09042361825797,56.78387278912293],[21.581866489353672,57.41187063254993],[22.524341261492875,57.75337433535076],[23.318452996522097,57.00623647727487],[24.12072960785343,57.02569265403277],[24.312862583114622,57.79342357037698],[25.16459354014927,57.97015696881519],[25.602809685984365,57.84752879498657],[26.463532342237787,57.47638865826633],[27.28818484875151,57.47452830670383],[27.77001590344093,57.24425812441123],[27.855282016722526,56.75932648378429],[28.176709425577993,56.16912995057881],[27.10245975109453,55.783313707087686],[26.494331495883753,55.615106919977634],[25.533046502390334,56.10029694276603],[25.000934279080894,56.16453074810484],[24.860684441840757,56.37252838807963],[23.878263787539964,56.273671373105266],[22.201156853939494,56.33780182557948],[21.055800408622414,56.03107636171106]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Lux.\",\"name\":\"Luxembourg\",\"name_long\":\"Luxembourg\",\"iso_a2\":\"LU\",\"iso_a3\":\"LUX\",\"iso_n3\":\"442\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[6.043073357781111,50.128051662794235],[6.242751092156993,49.90222565367873],[6.186320428094177,49.463802802114515],[5.897759230176405,49.44266714130703],[5.674051954784829,49.529483547557504],[5.782417433300906,50.09032786722122],[6.043073357781111,50.128051662794235]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Mkd.\",\"name\":\"Macedonia\",\"name_long\":\"Macedonia\",\"iso_a2\":\"MK\",\"iso_a3\":\"MKD\",\"iso_n3\":\"807\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[20.59023,41.85541],[20.71731000000011,41.84711],[20.76216,42.05186],[21.35270000000014,42.2068],[21.57663598940212,42.24522439706186],[21.917080000000112,42.30364],[22.38052575042468,42.32025950781508],[22.881373732197346,41.999297186850356],[22.952377150166512,41.33799388281119],[22.76177,41.3048],[22.597308383889015,41.130487168943205],[22.05537763844427,41.14986583105269],[21.674160597426976,40.93127452245795],[21.0200403174764,40.84272695572588],[20.60518,41.08622],[20.46315,41.5150900000001],[20.59023,41.85541]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Mda.\",\"name\":\"Moldova\",\"name_long\":\"Moldova\",\"iso_a2\":\"MD\",\"iso_a3\":\"MDA\",\"iso_n3\":\"498\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[26.619336785597795,48.22072622333347],[26.857823520624805,48.368210761094495],[27.522537469195154,48.467119452501116],[28.259546746541844,48.15556224221342],[28.670891147585163,48.1181485052341],[29.12269819511303,47.84909516050646],[29.05086795422733,47.51022695575249],[29.41513512545274,47.34664520933257],[29.559674106573112,46.928582872091326],[29.908851759569302,46.67436066343146],[29.838210076626297,46.52532583270169],[30.02465864433537,46.42393667254503],[29.75997195813639,46.34998769793536],[29.170653924279886,46.3792623968287],[29.072106967899295,46.517677720722496],[28.862972446414062,46.43788930926383],[28.93371748222162,46.2588304713725],[28.659987420371575,45.93998688413164],[28.485269402792767,45.5969070501459],[28.233553501099042,45.48828318946837],[28.0544429867754,45.944586086605625],[28.160017937947714,46.37156260841722],[28.128030226359044,46.810476386088254],[27.551166212684848,47.40511709247083],[27.233872918412743,47.82677094175638],[26.924176059687568,48.123264472030996],[26.619336785597795,48.22072622333347]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Mont.\",\"name\":\"Montenegro\",\"name_long\":\"Montenegro\",\"iso_a2\":\"ME\",\"iso_a3\":\"MNE\",\"iso_n3\":\"499\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[19.801613396898688,42.50009349219084],[19.738051385179627,42.688247382165564],[19.3044900000001,42.19574],[19.37177000000014,41.87755],[19.16246,41.95502],[18.88214,42.28151],[18.45,42.48],[18.56,42.65],[18.70648,43.20011],[19.03165,43.43253],[19.21852,43.52384],[19.48389,43.35229],[19.63,43.21377997027054],[19.95857,43.10604],[20.3398,42.89852],[20.25758,42.81275000000011],[20.0707,42.58863],[19.801613396898688,42.50009349219084]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Nor.\",\"name\":\"Norway\",\"name_long\":\"Norway\",\"iso_a2\":\"NO\",\"iso_a3\":\"NOR\",\"iso_n3\":\"578\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[28.165547316202915,71.18547435168051],[31.29341840996548,70.45378774685992],[30.005435011522792,70.1862588568849],[31.10107872897512,69.55808014594486],[29.399580519332886,69.15691600206307],[28.591929559043194,69.0647769232867],[29.01557295097197,69.76649119737797],[27.73229210786789,70.16419302029628],[26.1796220232263,69.82529897732616],[25.68921268077639,69.09211375596902],[24.73567915212672,68.64955678982145],[23.662049594830762,68.89124746365053],[22.356237827247412,68.84174144151494],[21.24493615081073,69.37044302029312],[20.64559288908958,69.10624726020085],[20.025268995857914,69.06513865831272],[19.878559604581255,68.40719432237262],[17.99386844246439,68.56739126247734],[17.729181756265348,68.01055186631623],[16.76887861498554,68.01393667263139],[16.108712192456835,67.3024555528369],[15.108411492583059,66.19386688909543],[13.55568973150909,64.78702769638147],[13.919905226302205,64.44542064071611],[13.57191613124877,64.04911408146967],[12.57993533697393,64.06621898055835],[11.93056928879423,63.128317572676984],[11.992064243221535,61.800362453856565],[12.631146681375242,61.29357168237009],[12.3003658382749,60.11793284773006],[11.468271925511175,59.432393296946],[11.027368605196926,58.856149400459394],[10.356556837616097,59.46980703392538],[8.382000359743643,58.31328847923328],[7.048748406613299,58.07888418235728],[5.665835402050419,58.58815542259367],[5.308234490590735,59.66323191999382],[4.992078077829007,61.970998033284275],[5.912900424837885,62.614472968182696],[8.553411085655766,63.45400828719647],[10.527709181366788,64.48603831649748],[12.358346795306375,65.87972585719316],[14.761145867581604,67.81064158799515],[16.43592736172897,68.56320547146169],[19.184028354578516,69.81744415961782],[21.378416375420613,70.25516937934606],[23.023742303161583,70.20207184516626],[24.546543409938522,71.03049673123724],[26.370049676221807,70.98626170519537],[28.165547316202915,71.18547435168051]]],[[[24.72412,77.85385],[22.49032,77.44493],[20.72601,77.67704],[21.41611,77.93504],[20.8119,78.25463],[22.88426,78.45494],[23.28134,78.07954],[24.72412,77.85385]]],[[[18.25183,79.70175],[21.54383,78.95611],[19.02737,78.5626],[18.47172,77.82669],[17.59441,77.63796],[17.1182,76.80941],[15.91315,76.77045],[13.76259,77.38035],[14.66956,77.73565],[13.1706,78.02493],[11.22231,78.8693],[10.44453,79.65239],[13.17077,80.01046],[13.71852,79.66039],[15.14282,79.67431],[15.52255,80.01608],[16.99085,80.05086],[18.25183,79.70175]]],[[[25.447625359811894,80.40734039989451],[27.4075057309135,80.05640574820046],[25.92465050629818,79.51783397085455],[23.02446577321362,79.4000117052291],[20.075188429451885,79.56682322866726],[19.897266473070914,79.84236196564751],[18.462263624757924,79.85988027619442],[17.368015170977458,80.31889618602702],[20.455992059010697,80.59815562613224],[21.9079447771154,80.35767934846209],[22.919252557067438,80.65714427359349],[25.447625359811894,80.40734039989451]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Neth.\",\"name\":\"Netherlands\",\"name_long\":\"Netherlands\",\"iso_a2\":\"NL\",\"iso_a3\":\"NLD\",\"iso_n3\":\"528\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[6.074182570020923,53.510403347378144],[6.905139601274129,53.48216217713064],[7.092053256873896,53.14404328064489],[6.842869500362383,52.22844025329755],[6.589396599970826,51.852029120483394],[5.988658074577813,51.851615709025054],[6.156658155958779,50.80372101501058],[5.606975945670001,51.037298488969775],[4.973991326526914,51.475023708698124],[4.047071160507527,51.26725861266857],[3.314971144228537,51.34575511331991],[3.830288527043137,51.62054454203195],[4.705997348661185,53.09179840759776],[6.074182570020923,53.510403347378144]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Pol.\",\"name\":\"Poland\",\"name_long\":\"Poland\",\"iso_a2\":\"PL\",\"iso_a3\":\"POL\",\"iso_n3\":\"616\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[15.01699588385867,51.10667409932158],[14.607098422919535,51.745188096719964],[14.685026482815688,52.0899474147552],[14.4375997250022,52.62485016540838],[14.074521111719491,52.98126251892543],[14.353315463934138,53.24817129171297],[14.119686313542587,53.75702912049103],[14.802900424873458,54.05070628520575],[16.36347700365573,54.513158677785725],[17.622831658608675,54.85153595643291],[18.62085859546164,54.68260569927078],[18.696254510175464,54.43871877706929],[19.660640089606403,54.42608388937393],[20.892244500418624,54.31252492941253],[22.731098667092652,54.327536932993326],[23.24398725758951,54.22056671814914],[23.48412763844985,53.91249766704114],[23.527535841575002,53.470121568406555],[23.80493493011778,53.089731350306074],[23.799198846133375,52.69109935160657],[23.199493849386187,52.486977444053664],[23.508002150168693,52.02364655212473],[23.52707075368437,51.57845408793023],[24.029985792748903,50.70540660257518],[23.922757195743262,50.42488108987875],[23.426508416444392,50.30850576435745],[22.518450148211603,49.47677358661974],[22.776418898212626,49.02739533140962],[22.558137648211755,49.085738023467144],[21.607808058364213,49.47010732685409],[20.887955356538413,49.32877228453583],[20.41583947111985,49.43145335549977],[19.825022820726872,49.21712535256923],[19.320712517990472,49.571574001659194],[18.909574822676316,49.435845852244576],[18.853144158613617,49.49622976337764],[18.392913852622172,49.98862864847075],[17.64944502123899,50.049038397819956],[17.55456709155112,50.36214590107641],[16.868769158605655,50.47397370055603],[16.719475945714436,50.21574656839354],[16.176253289462267,50.42260732685791],[16.23862674323857,50.69773265237984],[15.490972120839727,50.78472992614321],[15.01699588385867,51.10667409932158]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Port.\",\"name\":\"Portugal\",\"name_long\":\"Portugal\",\"iso_a2\":\"PT\",\"iso_a3\":\"PRT\",\"iso_n3\":\"620\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-9.034817674180246,41.88057058365967],[-8.67194576662672,42.13468943945496],[-8.263856980817792,42.28046865495034],[-8.013174607769912,41.790886135417125],[-7.422512986673795,41.79207469335983],[-7.251308966490824,41.91834605566505],[-6.668605515967656,41.883386949219584],[-6.389087693700915,41.381815497394655],[-6.851126674822552,41.11108266861753],[-6.864019944679385,40.33087189387483],[-7.026413133156595,40.184524237624245],[-7.066591559263529,39.71189158788277],[-7.498632371439725,39.62957103124181],[-7.098036668313128,39.03007274022378],[-7.374092169616318,38.37305858006492],[-7.029281175148796,38.07576406508977],[-7.166507941099865,37.803894354802225],[-7.537105475281024,37.42890432387623],[-7.453725551778092,37.09778758396607],[-7.855613165711985,36.83826854099627],[-8.382816127953689,36.97888011326246],[-8.898856980820327,36.86880931248078],[-8.746101446965554,37.65134552667661],[-8.839997524439879,38.26624339451761],[-9.287463751655224,38.3584858261586],[-9.526570603869715,38.73742910415491],[-9.446988898140232,39.39206614842837],[-9.048305223008427,39.75509308527877],[-8.977353481471681,40.15930613866581],[-8.768684047877102,40.76063894303019],[-8.79085323733031,41.18433401139126],[-8.99078935386757,41.54345937760364],[-9.034817674180246,41.88057058365967]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Rom.\",\"name\":\"Romania\",\"name_long\":\"Romania\",\"iso_a2\":\"RO\",\"iso_a3\":\"ROU\",\"iso_n3\":\"642\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[22.710531447040495,47.88219391538941],[23.142236362406802,48.09634105080695],[23.76095828623741,47.985598456405455],[24.40205610525038,47.98187775328042],[24.866317172960578,47.73752574318831],[25.20774336111299,47.89105642352747],[25.9459411964024,47.987148749374214],[26.19745039236693,48.22088125263035],[26.619336785597795,48.22072622333347],[26.924176059687568,48.123264472030996],[27.233872918412743,47.82677094175638],[27.551166212684848,47.40511709247083],[28.128030226359044,46.810476386088254],[28.160017937947714,46.37156260841722],[28.0544429867754,45.944586086605625],[28.233553501099042,45.48828318946837],[28.679779493939378,45.304030870131704],[29.149724969201653,45.46492544207245],[29.603289015427432,45.293308010431126],[29.626543409958767,45.03539093686239],[29.141611769331835,44.82021027279904],[28.837857700320203,44.913873806328056],[28.558081495891997,43.70746165625813],[27.970107049275075,43.81246816667521],[27.242399529740908,44.175986029632405],[26.065158725699746,43.94349376075126],[25.569271681426926,43.68844472917472],[24.100679152124172,43.74105133724785],[23.33230228037632,43.89701080990471],[22.944832391051847,43.82378530534713],[22.65714969248299,44.23492300066128],[22.4740084164406,44.40922760678177],[22.705725538837356,44.57800283464702],[22.459022251075936,44.7025171982543],[22.14508792490281,44.47842234962059],[21.562022739353605,44.7689472519655],[21.483526238702233,45.18117015235778],[20.874312778413355,45.416375433934235],[20.762174920339987,45.734573065771436],[20.220192498462836,46.127468980486555],[21.02195234547125,46.3160879583519],[21.62651492685387,46.99423777931816],[22.099767693782834,47.6724392767167],[22.710531447040495,47.88219391538941]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Rus.\",\"name\":\"Russia\",\"name_long\":\"Russian Federation\",\"iso_a2\":\"RU\",\"iso_a3\":\"RUS\",\"iso_n3\":\"643\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[143.64800744036287,50.74760040954151],[144.65414757708564,48.976390692737596],[143.17392785051723,49.30655141865037],[142.5586682476501,47.861575018904915],[143.53349246640406,46.83672801369249],[143.5052771343726,46.13790761980948],[142.74770063697392,46.74076487892657],[142.0920300640545,45.96675527605879],[141.90692508358504,46.80592886004655],[142.0184428244709,47.780132961612935],[141.90444461483506,48.85918854429956],[142.13580000220568,49.61516307229746],[142.1799833518153,50.95234243428192],[141.59407596249002,51.93543488220254],[141.68254601457366,53.30196645772878],[142.60693403541075,53.762145087287905],[142.2097489768154,54.22547597921687],[142.654786411713,54.36588084575388],[142.91461551327657,53.704577541714734],[143.26084760963207,52.74076040303905],[143.23526777564766,51.75666026468875],[143.64800744036287,50.74760040954151]]],[[[22.731098667092652,54.327536932993326],[20.892244500418652,54.312524929412575],[19.660640089606403,54.426083889373984],[19.888481479581344,54.8661603867715],[21.2684489275035,55.19048167583528],[22.315723504330606,55.0152985703659],[22.757763706155288,54.85657440858142],[22.651051873472568,54.58274099386671],[22.731098667092652,54.327536932993326]]],[[[-175.01425,66.58435],[-174.33983,66.33556],[-174.57182,67.06219],[-171.85731,66.91308],[-169.89958,65.97724],[-170.89107,65.54139],[-172.53025,65.43791],[-172.555,64.46079],[-172.95533,64.25269],[-173.89184,64.2826],[-174.65392,64.63125],[-175.98353,64.92288],[-176.20716,65.35667],[-177.22266,65.52024],[-178.35993,65.39052],[-178.90332,65.74044],[-178.68611,66.11211],[-179.88377,65.87456],[-179.43268,65.40411],[-180,64.97970870219837],[-180,68.96363636363635],[-177.55,68.2],[-174.92825,67.20589],[-175.01425,66.58435]]],[[[180.00000000000014,70.83219920854668],[178.9034250000001,70.78114],[178.7253,71.0988],[180.00000000000014,71.51571433642826],[180.00000000000014,70.83219920854668]]],[[[-178.69378,70.89302],[-180,70.83219920854668],[-180,71.51571433642826],[-179.871875,71.55762],[-179.02433,71.55553],[-177.577945,71.26948],[-177.663575,71.13277],[-178.69378,70.89302]]],[[[143.60385,73.21244],[142.08763,73.20544],[140.038155,73.31692],[139.86312,73.36983],[140.81171,73.76506],[142.06207,73.85758],[143.48283,73.47525],[143.60385,73.21244]]],[[[150.73167,75.08406],[149.575925,74.68892],[147.977465,74.778355],[146.11919,75.17298],[146.358485,75.49682],[148.22223,75.345845],[150.73167,75.08406]]],[[[145.086285,75.562625],[144.3,74.82],[140.61381,74.84768],[138.95544,74.61148],[136.97439,75.26167],[137.51176,75.94917],[138.831075,76.13676],[141.471615,76.09289],[145.086285,75.562625]]],[[[57.5356925799924,70.72046397570216],[56.94497928246395,70.63274323188668],[53.6773751157842,70.76265778266847],[53.41201663596539,71.2066616889202],[51.60189456564572,71.47475901965049],[51.45575361512422,72.01488108996514],[52.47827518088357,72.22944163684096],[52.444168735570855,72.77473135038485],[54.42761355979766,73.62754751249759],[53.50828982932515,73.74981395130015],[55.90245893740766,74.62748647734533],[55.631932814359715,75.08141225859717],[57.86864383324885,75.60939036732321],[61.170044386647504,76.25188345000814],[64.49836836127022,76.43905548776928],[66.2109770038551,76.80978221303124],[68.15705976753483,76.93969676381292],[68.85221113472512,76.54481130645462],[68.18057254422766,76.23364166940911],[64.637326287703,75.73775462513623],[61.58350752141476,75.2608845079468],[58.47708214705338,74.30905630156283],[56.98678551618801,73.33304352486624],[55.419335971910954,72.37126760526598],[55.622837762276305,71.54059479439033],[57.5356925799924,70.72046397570216]]],[[[106.97013000000013,76.97419],[107.24000000000015,76.48],[108.1538,76.72335000000015],[111.07726000000017,76.71],[113.33151,76.22224],[114.13417,75.84764],[113.88539,75.32779000000014],[112.77918,75.03186],[110.1512500000002,74.47673],[109.4,74.18],[110.64,74.04],[112.11919,73.78774000000011],[113.01954000000026,73.97693000000015],[113.52958000000032,73.33505000000011],[113.96881,73.59488],[115.56782,73.75285],[118.77633000000023,73.58772],[119.02,73.12],[123.20066000000011,72.97122],[123.25777000000018,73.73503000000011],[125.38000000000018,73.56],[126.97644,73.56549],[128.59126,73.03871],[129.05157,72.39872],[128.46000000000012,71.98],[129.7159900000002,71.19304],[131.28858000000028,70.78699000000012],[132.25350000000017,71.83630000000011],[133.85766000000032,71.38642000000016],[135.56193,71.65525000000014],[137.49755,71.34763],[138.23409000000018,71.62803],[139.86983000000012,71.48783000000014],[139.14791,72.4161900000001],[140.46817,72.84941000000013],[149.5,72.2],[150.3511800000002,71.60643],[152.96890000000022,70.84222],[157.00688,71.03141],[158.99779,70.86672],[159.83031000000025,70.45324],[159.70866,69.72198],[160.94053000000034,69.4372800000001],[162.27907000000013,69.64204],[164.05248000000014,69.66823],[165.94037000000023,69.47199],[167.83567,69.58269],[169.5776300000002,68.6938],[170.81688000000028,69.01363],[170.0082000000002,69.65276],[170.4534500000003,70.09703],[173.64391000000026,69.81743],[175.72403000000023,69.87725000000023],[178.6,69.4],[180.00000000000014,68.96363636363657],[180.00000000000014,64.97970870219848],[179.99281,64.97433],[178.70720000000026,64.53493],[177.41128000000018,64.60821],[178.31300000000024,64.07593],[178.9082500000002,63.251970000000135],[179.37034,62.982620000000104],[179.48636,62.56894],[179.22825000000014,62.30410000000015],[177.3643,62.5219],[174.56929000000022,61.76915],[173.68013,61.65261],[172.15,60.95],[170.6985000000001,60.33618],[170.3308500000003,59.88177],[168.90046,60.57355],[166.29498000000032,59.788550000000214],[165.84000000000023,60.16],[164.87674,59.7316],[163.53929000000014,59.86871],[163.21711000000025,59.21101],[162.0173300000001,58.24328],[162.05297,57.83912],[163.19191,57.61503000000011],[163.05794000000017,56.159240000000125],[162.12958000000023,56.12219],[161.70146,55.285680000000156],[162.11749000000017,54.85514],[160.36877000000032,54.34433],[160.02173000000022,53.20257],[158.5309400000002,52.958680000000236],[158.23118,51.94269],[156.7897900000003,51.01105],[156.42000000000016,51.7],[155.99182,53.15895],[155.43366000000012,55.38103000000012],[155.91442000000032,56.767920000000146],[156.75815,57.3647],[156.8103500000001,57.83204],[158.3643300000002,58.05575],[160.15064000000012,59.31477000000012],[161.87204,60.34300000000013],[163.66969,61.1409],[164.47355000000013,62.55061],[163.2584200000002,62.46627],[162.65791,61.6425],[160.1214800000001,60.54423],[159.30232,61.77396],[156.7206800000001,61.43442],[154.21806000000035,59.75818000000013],[155.04375,59.14495],[152.81185,58.88385],[151.26573000000025,58.78089],[151.33815000000013,59.50396],[149.78371,59.65573000000014],[148.54481,59.16448],[145.48722,59.33637],[142.19782000000018,59.03998],[138.95848000000032,57.08805],[135.12619,54.72959],[136.70171,54.603550000000126],[137.19342,53.97732],[138.1647,53.755010000000254],[138.80463,54.25455000000011],[139.90151,54.18968000000018],[141.34531,53.08957000000012],[141.37923,52.23877],[140.5974200000002,51.2396700000001],[140.51308,50.04553000000013],[140.06193000000022,48.44671000000017],[138.5547200000002,46.99965],[138.21971,46.30795],[136.86232,45.14350000000019],[135.5153500000002,43.989],[134.86939000000027,43.39821],[133.53687000000028,42.81147],[132.90627000000015,42.79849],[132.27807000000027,43.28456000000011],[130.93587000000014,42.55274],[130.78,42.22000000000019],[130.64000000000019,42.395],[130.6338664084098,42.90301463477056],[131.144687941615,42.92998973242695],[131.28855512911562,44.111519680348266],[131.02519000000026,44.96796],[131.8834542176596,45.32116160743652],[133.09712000000022,45.14409],[133.7696439963132,46.116926988299156],[134.1123500000002,47.21248000000014],[134.50081,47.578450000000146],[135.0263114767868,48.47822988544391],[133.37359581922803,48.18344167743484],[132.50669000000013,47.78896],[130.98726000000013,47.79013],[130.58229332898267,48.729687404976204],[129.3978178244205,49.440600084015614],[127.65740000000038,49.76027],[127.28745568248493,50.73979726826545],[126.93915652883786,51.3538941514059],[126.56439904185699,51.7842554795327],[125.94634891164647,52.79279857035695],[125.06821129771045,53.161044826868924],[123.57147,53.4588],[122.24574791879307,53.43172597921369],[121.00308475147037,53.25140106873124],[120.1770886577169,52.75388621684121],[120.725789015792,52.51622630473091],[120.7382,51.96411],[120.18208000000018,51.64355],[119.27939,50.58292],[119.28846072802585,50.14288279886196],[117.8792444194265,49.51098338479704],[116.67880089728621,49.888531399121405],[115.48569542853144,49.80517731383475],[114.96210981655038,50.14024730081513],[114.36245649623534,50.248302720737485],[112.89773969935439,49.54356537535699],[111.58123091028668,49.37796824807767],[110.66201053267886,49.13012807880585],[109.40244917199672,49.29296051695769],[108.47516727095127,49.28254771585071],[107.86817589725112,49.79370514586588],[106.88880415245532,50.27429596618029],[105.8865914245869,50.406019192092174],[104.62158,50.275320000000164],[103.67654544476036,50.089966132195144],[102.25589000000011,50.51056000000011],[102.06521,51.25991],[100.88948042196265,51.51685578063842],[99.98173221232356,51.63400625264395],[98.8614905131005,52.04736603454671],[97.82573978067452,51.01099518493325],[98.23176150919173,50.42240062112873],[97.25976000000023,49.72605],[95.81402000000017,49.977460000000114],[94.81594933469879,50.01343333597088],[94.14756635943561,50.48053660745716],[93.10421,50.49529],[92.23471154171969,50.80217072204175],[90.71366743364078,50.331811835321105],[88.80556684769559,49.47052073831247],[87.75126427607685,49.29719798440556],[87.35997033076269,49.21498078062916],[86.82935672398966,49.82667470966813],[85.5412699726825,49.69285858824816],[85.11555952346211,50.11730296487763],[84.41637739455304,50.311399644565824],[83.93511478061893,50.88924551045358],[83.38300377801247,51.069182847693895],[81.94598554883994,50.81219594990633],[80.56844689323546,51.38833649352844],[80.03555952344172,50.864750881547224],[77.80091556184433,53.40441498474754],[76.52517947785478,54.177003485727134],[76.89110029491346,54.49052440044193],[74.38482000000013,53.54685000000011],[73.42567874542053,53.489810289109755],[73.50851606638437,54.035616766976595],[72.22415001820221,54.37665538188679],[71.1801310566095,54.133285224008254],[70.86526655465516,55.169733588270105],[69.06816694527289,55.3852501491435],[68.1691003762589,54.97039175070438],[65.6668700000001,54.601250000000164],[65.17853356309595,54.35422781027208],[61.43660000000013,54.00625],[60.97806644068325,53.66499339457914],[61.699986199800634,52.97999644633427],[60.739993117114544,52.71998647725775],[60.92726850774025,52.447548326215006],[59.96753380721557,51.960420437215674],[61.58800337102414,51.272658799843185],[61.33742435084101,50.79907013610426],[59.93280724471557,50.842194118851836],[59.64228234237057,50.545442206415714],[58.36332000000013,51.06364],[56.77798,51.04355],[55.71694000000011,50.62171000000015],[54.532878452376195,51.02623973245937],[52.32872358583106,51.718652248738096],[50.76664839051219,51.69276235615987],[48.702381626181044,50.60512848571284],[48.577841424357615,49.874759629915644],[47.549480421749394,50.454698391311126],[46.75159630716277,49.35600576435374],[47.0436715024766,49.152038886097586],[46.46644575377629,48.39415233010493],[47.31524000000016,47.71585],[48.05725,47.74377],[48.694733514201886,47.0756281601779],[48.593250000000154,46.561040000000105],[49.101160000000135,46.39933],[48.64541000000011,45.80629],[47.67591,45.64149000000012],[46.68201,44.6092000000001],[47.59094,43.66016000000013],[47.49252,42.98658],[48.58437000000018,41.80888],[47.98728315612604,41.4058192001944],[47.81566572448466,41.151416124021345],[47.373315464066394,41.21973236751114],[46.686070591016716,41.827137152669906],[46.40495079934894,41.860675157227426],[45.7764,42.09244000000024],[45.470279168485916,42.50278066667005],[44.53762291848207,42.711992702803684],[43.93121000000011,42.5549600000001],[43.755990000000196,42.74083],[42.39440000000016,43.2203],[40.92219000000014,43.38215000000014],[40.07696495947985,43.553104153002494],[39.955008579271095,43.434997666999294],[38.68,44.28],[37.53912000000011,44.65721],[36.67546000000013,45.24469],[37.40317,45.4045100000001],[38.23295,46.24087],[37.67372,46.63657],[39.14767,47.04475000000013],[39.12120000000013,47.26336],[38.22353803889948,47.10218984637598],[38.25511233902981,47.54640045835697],[38.77057,47.82562000000024],[39.738277622238996,47.89893707945208],[39.89562000000015,48.23241],[39.67465,48.783820000000134],[40.08078901546949,49.30742991799937],[40.069040000000115,49.60105],[38.59498823421356,49.92646190042373],[38.010631137857075,49.91566152607473],[37.39345950699524,50.38395335550368],[36.626167840325394,50.225590928745135],[35.35611616388812,50.57719737405915],[35.37791,50.77394],[35.02218305841794,51.2075723333715],[34.2248157081544,51.255993150428935],[34.14197838719061,51.566413479206204],[34.39173058445723,51.768881740925906],[33.75269982273588,52.33507457133166],[32.71576053236716,52.23846548116216],[32.41205813978777,52.28869497334977],[32.15944000000022,52.061250000000115],[31.78597,52.10168],[31.54001834486226,52.74205231384644],[31.30520063652798,53.0739958766733],[31.49764,53.16743000000014],[32.304519484188376,53.132726141972846],[32.693643019346126,53.35142080343214],[32.405598585751164,53.618045355842014],[31.731272820774592,53.79402944601202],[31.791424187962406,53.974638576872195],[31.384472283663822,54.15705638286238],[30.75753380709878,54.811770941784395],[30.971835971813245,55.081547756564134],[30.87390913262007,55.55097646750351],[29.89629438652244,55.7894632025305],[29.37157189303079,55.67009064393628],[29.22951338066039,55.91834422466641],[28.17670942557794,56.16912995057879],[27.855282016722526,56.75932648378438],[27.770015903440992,57.244258124411196],[27.288184848751655,57.47452830670392],[27.71668582531578,57.79189911562445],[27.420150000000202,58.72457000000014],[28.131699253051863,59.300825100331],[27.98112,59.47537],[29.1177,60.02805000000012],[28.07,60.50352000000015],[30.211107212044652,61.78002777774969],[31.139991082491036,62.357692776124445],[31.51609215671127,62.867687486412905],[30.035872430142803,63.552813625738565],[30.444684686003736,64.20445343693908],[29.544429559047018,64.94867157659056],[30.21765,65.80598],[29.054588657352383,66.94428620062203],[29.977426385220696,67.69829702419275],[28.44594363781877,68.36461294216399],[28.591929559043365,69.0647769232867],[29.39955,69.15692000000018],[31.10108000000011,69.55811],[32.13272000000026,69.90595000000025],[33.77547,69.30142000000012],[36.51396,69.06342],[40.292340000000166,67.9324],[41.059870000000124,67.4571300000001],[41.12595000000019,66.79158000000012],[40.01583,66.26618000000013],[38.38295,65.9995300000001],[33.918710000000175,66.75961],[33.18444,66.63253],[34.81477,65.90015000000014],[34.87857425307877,65.4362128770482],[34.94391000000015,64.41437000000016],[36.23129,64.10945],[37.01273000000012,63.84983000000011],[37.14197000000016,64.33471],[36.539579035089815,64.76446],[37.17604000000014,65.14322000000013],[39.59345,64.52079000000018],[40.43560000000011,64.76446],[39.76260000000016,65.49682],[42.0930900000001,66.47623],[43.01604000000012,66.41858],[43.94975000000014,66.06908],[44.53226,66.75634000000014],[43.69839,67.35245],[44.18795000000014,67.95051],[43.45282,68.57079],[46.25000000000014,68.25],[46.82134000000016,67.68997],[45.55517,67.56652],[45.5620200000001,67.01005000000019],[46.34915000000015,66.6676700000001],[47.894160000000255,66.88455000000016],[48.13876,67.52238],[50.22766000000016,67.99867000000013],[53.71743000000018,68.85738000000012],[54.47171,68.80815],[53.48582000000013,68.20131],[54.72628,68.09702],[55.44268000000014,68.43866],[57.317020000000156,68.46628],[58.80200000000021,68.88082],[59.94142000000019,68.2784400000001],[61.07784000000018,68.94069],[60.03,69.52],[60.55,69.85],[63.50400000000016,69.54739],[64.888115,69.23483500000013],[68.51216000000014,68.09233000000017],[69.18068,68.61563000000012],[68.16444,69.14436],[68.13522,69.35649],[66.93008000000012,69.45461000000012],[67.25976,69.92873],[66.72492000000014,70.70889000000014],[66.69466,71.02897000000024],[68.54006000000011,71.93450000000024],[69.19636000000011,72.84336000000016],[69.94,73.04000000000013],[72.58754,72.7762900000001],[72.79603,72.22006],[71.8481100000001,71.40898],[72.47011,71.09019],[72.79188,70.39114],[72.56470000000022,69.02085],[73.66787,68.4079],[73.2387,67.7404],[71.28000000000011,66.32000000000016],[72.42301000000018,66.17267000000018],[72.82077,66.53267],[73.92099000000016,66.78946000000013],[74.1865100000002,67.28429],[75.052,67.76047000000017],[74.46926000000016,68.32899],[74.93584000000013,68.98918],[73.84236,69.07146],[73.60187000000022,69.62763],[74.3998,70.63175],[73.1011,71.44717000000026],[74.89082000000022,72.12119],[74.65926,72.83227],[75.15801000000019,72.85497000000011],[75.68351,72.30056000000013],[75.28898000000012,71.33556],[76.35911,71.15287000000015],[75.90313000000017,71.87401],[77.57665000000011,72.26717],[79.65202000000014,72.32011],[81.5,71.75],[80.61071000000013,72.58285000000012],[80.51109,73.6482],[82.25,73.85000000000011],[84.65526,73.80591000000018],[86.82230000000024,73.93688],[86.00956,74.45967000000014],[87.16682000000017,75.11643],[88.31571000000011,75.14393],[90.26,75.64],[92.90058,75.77333],[93.23421000000016,76.0472],[95.86000000000016,76.14],[96.67821,75.91548],[98.92254000000023,76.44689],[100.75967000000023,76.43028],[101.03532,76.86189],[101.99084000000013,77.2875400000002],[104.3516000000001,77.69792],[106.06664000000013,77.37389],[104.70500000000024,77.1274],[106.97013000000013,76.97419]]],[[[105.07547,78.30689],[99.43814,77.921],[101.2649,79.23399],[102.08635,79.34641],[102.837815,79.28129],[105.37243,78.71334],[105.07547,78.30689]]],[[[51.13618655783128,80.54728017854093],[49.79368452332071,80.4154277615482],[48.89441124857754,80.3395667589437],[48.754936557821765,80.17546824820084],[47.586119012244154,80.01018117951533],[46.502825962109654,80.24724681265437],[47.07245527526291,80.55942414012945],[44.846958042181114,80.58980988231718],[46.79913862487123,80.77191762971364],[48.318477410684665,80.78400991486996],[48.522806023966695,80.51456899690015],[49.09718956889091,80.75398590770843],[50.03976769389462,80.91888540315182],[51.52293297710369,80.6997256538019],[51.13618655783128,80.54728017854093]]],[[[99.93976,78.88094],[97.75794,78.7562],[94.97259,79.044745],[93.31288,79.4265],[92.5454,80.14379],[91.18107,80.34146],[93.77766,81.0246],[95.940895,81.2504],[97.88385,80.746975],[100.186655,79.780135],[99.93976,78.88094]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Serb.\",\"name\":\"Serbia\",\"name_long\":\"Serbia\",\"iso_a2\":\"RS\",\"iso_a3\":\"SRB\",\"iso_n3\":\"688\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[20.87431277841341,45.41637543393432],[21.48352623870221,45.18117015235788],[21.562022739353722,44.76894725196564],[22.145087924902896,44.47842234962059],[22.459022251075965,44.70251719825444],[22.70572553883744,44.57800283464701],[22.474008416440654,44.40922760678177],[22.657149692483074,44.234923000661354],[22.410446404721597,44.008063462900054],[22.500156691180223,43.642814439461006],[22.986018507588483,43.2111612005271],[22.60480146657136,42.898518785161116],[22.43659467946139,42.58032115332394],[22.54501183440965,42.46136200618804],[22.38052575042468,42.32025950781508],[21.917080000000112,42.30364],[21.57663598940212,42.24522439706186],[21.54332,42.3202500000001],[21.66292,42.43922],[21.77505,42.6827],[21.63302,42.67717],[21.43866,42.86255],[21.27421,42.90959],[21.143395,43.06868500000012],[20.95651,43.13094],[20.81448,43.27205],[20.63508,43.21671],[20.49679,42.88469],[20.25758,42.81275000000011],[20.3398,42.89852],[19.95857,43.10604],[19.63,43.21377997027054],[19.48389,43.35229],[19.21852,43.52384],[19.454,43.56810000000013],[19.59976,44.03847],[19.11761,44.42307000000011],[19.36803,44.863],[19.00548,44.86023],[19.39047570158459,45.236515611342384],[19.072768995854176,45.52151113543209],[18.82982,45.90888],[19.59604454924164,46.171729844744554],[20.220192498462893,46.12746898048658],[20.762174920339987,45.734573065771485],[20.87431277841341,45.41637543393432]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Slo.\",\"name\":\"Slovenia\",\"name_long\":\"Slovenia\",\"iso_a2\":\"SI\",\"iso_a3\":\"SVN\",\"iso_n3\":\"705\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[13.806475457421527,46.509306138691215],[14.63247155117483,46.43181732846955],[15.137091912504985,46.65870270444703],[16.011663852612656,46.6836107448117],[16.202298211337364,46.85238597267696],[16.370504998447416,46.8413272161665],[16.564808383864857,46.50375092221983],[15.768732944408551,46.23810822202345],[15.671529575267556,45.83415355079788],[15.323953891672403,45.73178253842768],[15.327674594797427,45.45231639259323],[14.935243767972935,45.471695054702685],[14.595109490627804,45.634940904312714],[14.411968214585414,45.46616567644746],[13.715059848697221,45.50032379819237],[13.937630242578306,45.59101593686462],[13.698109978905478,46.01677806251735],[13.806475457421527,46.509306138691215]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Svk.\",\"name\":\"Slovakia\",\"name_long\":\"Slovakia\",\"iso_a2\":\"SK\",\"iso_a3\":\"SVK\",\"iso_n3\":\"703\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[18.853144158613617,49.49622976337764],[18.909574822676316,49.435845852244576],[19.320712517990472,49.571574001659194],[19.825022820726872,49.21712535256923],[20.41583947111985,49.43145335549977],[20.887955356538413,49.32877228453583],[21.607808058364213,49.47010732685409],[22.558137648211755,49.085738023467144],[22.28084191253356,48.82539215758067],[22.08560835133485,48.42226430927179],[21.872236362401736,48.31997081155002],[20.801293979584926,48.623854071642384],[20.473562045989866,48.56285004332181],[20.239054396249347,48.32756724709692],[19.769470656013112,48.202691148463614],[19.661363559658497,48.26661489520866],[19.17436486173989,48.11137889260387],[18.77702477384767,48.081768296900634],[18.696512892336926,47.880953681014404],[17.857132602620027,47.758428860050365],[17.48847293464982,47.86746613218621],[16.979666782304037,48.123497015976305],[16.879982944413,48.47001333270947],[16.960288120194576,48.5969823268506],[17.101984897538898,48.81696889911711],[17.545006951577108,48.80001902932537],[17.88648481616181,48.90347524677371],[17.913511590250465,48.996492824899086],[18.104972771891852,49.04398346617531],[18.170498488037964,49.271514797556435],[18.399993523846177,49.31500051533004],[18.554971144289482,49.49501536721878],[18.853144158613617,49.49622976337764]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Ukr.\",\"name\":\"Ukraine\",\"name_long\":\"Ukraine\",\"iso_a2\":\"UA\",\"iso_a3\":\"UKR\",\"iso_n3\":\"804\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[31.785998162571587,52.101677964885454],[32.15941206231267,52.06126699483322],[32.41205813978763,52.28869497334975],[32.71576053236697,52.23846548116205],[33.7526998227357,52.335074571331695],[34.39173058445701,51.76888174092579],[34.14197838719039,51.56641347920623],[34.22481570815427,51.25599315042895],[35.02218305841788,51.20757233337145],[35.37792361831512,50.77395539001034],[35.35611616388795,50.577197374059054],[36.62616784032534,50.225590928745135],[37.39345950699507,50.38395335550359],[38.010631137856905,49.91566152607463],[38.59498823421342,49.92646190042363],[40.06905846533911,49.6010554062817],[40.08078901546935,49.307429917999286],[39.67466393408753,48.78381846780187],[39.89563235856758,48.23240509703143],[39.738277622238826,47.89893707945198],[38.7705847511412,47.825608222029814],[38.25511233902975,47.546400458356814],[38.22353803889942,47.10218984637588],[37.42513715998999,47.022220567404204],[36.75985477066439,46.698700263040934],[35.82368452326483,46.64596446388707],[34.96234174982388,46.27319651954964],[35.020787794745985,45.65121898048466],[35.51000857925317,45.40999339454619],[36.52999799983016,45.46998973243706],[36.33471276219916,45.113215643893966],[35.23999922052812,44.939996242851606],[33.882511020652885,44.36147858334407],[33.326420932760044,44.56487702084489],[33.54692426934946,45.03477081967489],[32.4541744321055,45.32746613217608],[32.630804477679135,45.519185695978905],[33.58816206231839,45.85156850848024],[33.29856733575471,46.080598456397844],[31.74414025241518,46.333347886737386],[31.675307244602408,46.70624502215554],[30.748748813609097,46.583100084004],[30.377608676888883,46.03241018328567],[29.603289015427432,45.293308010431126],[29.149724969201653,45.46492544207245],[28.679779493939378,45.304030870131704],[28.233553501099042,45.48828318946837],[28.485269402792767,45.5969070501459],[28.659987420371575,45.93998688413164],[28.93371748222162,46.2588304713725],[28.862972446414062,46.43788930926383],[29.072106967899295,46.517677720722496],[29.170653924279886,46.3792623968287],[29.75997195813639,46.34998769793536],[30.02465864433537,46.42393667254503],[29.838210076626297,46.52532583270169],[29.908851759569302,46.67436066343146],[29.559674106573112,46.928582872091326],[29.41513512545274,47.34664520933257],[29.05086795422733,47.51022695575249],[29.12269819511303,47.84909516050646],[28.670891147585163,48.1181485052341],[28.259546746541844,48.15556224221342],[27.522537469195154,48.467119452501116],[26.857823520624805,48.368210761094495],[26.619336785597795,48.22072622333347],[26.19745039236693,48.22088125263035],[25.9459411964024,47.987148749374214],[25.20774336111299,47.89105642352747],[24.866317172960578,47.73752574318831],[24.40205610525038,47.98187775328042],[23.76095828623741,47.985598456405455],[23.142236362406802,48.09634105080695],[22.710531447040495,47.88219391538941],[22.640819939878753,48.15023956968735],[22.08560835133485,48.42226430927179],[22.28084191253356,48.82539215758067],[22.558137648211755,49.085738023467144],[22.776418898212626,49.02739533140962],[22.518450148211603,49.47677358661974],[23.426508416444392,50.30850576435745],[23.922757195743262,50.42488108987875],[24.029985792748903,50.70540660257518],[23.52707075368437,51.57845408793023],[24.00507775238421,51.61744395609446],[24.553106316839518,51.888461005249184],[25.327787713327005,51.91065603291855],[26.337958611768556,51.83228872334793],[27.454066196408434,51.59230337178447],[28.24161502453657,51.57222707783907],[28.61761274589225,51.42771393493484],[28.99283532076353,51.602044379271476],[29.254938185347925,51.368234361366895],[30.157363722460897,51.41613841410147],[30.555117221811457,51.31950348571566],[30.619454380014844,51.822806098022376],[30.927549269338982,52.04235342061438],[31.785998162571587,52.101677964885454]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Swe.\",\"name\":\"Sweden\",\"name_long\":\"Sweden\",\"iso_a2\":\"SE\",\"iso_a3\":\"SWE\",\"iso_n3\":\"752\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[22.183173455501926,65.72374054632017],[21.21351687997722,65.02600535751527],[21.369631381930958,64.41358795842429],[19.77887576669022,63.60955434839504],[17.84777916837521,62.74940013289681],[17.119554884518124,61.34116567651097],[17.83134606290639,60.63658336042741],[18.78772179533209,60.081914374422595],[17.86922488777634,58.9537661810587],[16.829185011470088,58.71982697207339],[16.447709588291474,57.041118069071885],[15.879785597403783,56.10430186626866],[14.666681349352075,56.200885118222175],[14.100721062891465,55.40778107362265],[12.942910597392057,55.36173737245058],[12.625100538797028,56.30708018658197],[11.787942335668674,57.44181712506307],[11.027368605196866,58.85614940045936],[11.468271925511146,59.43239329694604],[12.3003658382749,60.11793284773003],[12.631146681375183,61.293571682370136],[11.992064243221563,61.80036245385656],[11.93056928879423,63.12831757267698],[12.579935336973932,64.06621898055833],[13.571916131248711,64.04911408146971],[13.919905226302204,64.44542064071608],[13.55568973150909,64.78702769638151],[15.108411492583002,66.19386688909547],[16.108712192456778,67.30245555283689],[16.768878614985482,68.01393667263139],[17.729181756265348,68.01055186631628],[17.993868442464333,68.56739126247736],[19.878559604581255,68.40719432237258],[20.025268995857886,69.0651386583127],[20.645592889089528,69.10624726020087],[21.978534783626117,68.6168456081807],[23.53947309743444,67.93600861273525],[23.565879754335583,66.39605093043743],[23.903378533633802,66.00692739527962],[22.183173455501926,65.72374054632017]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Auz.\",\"name\":\"Australia\",\"name_long\":\"Australia\",\"iso_a2\":\"AU\",\"iso_a3\":\"AUS\",\"iso_n3\":\"036\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[145.39797814349484,-40.79254851660589],[146.36412072162372,-41.13769540788334],[146.90858361225085,-41.00054615658068],[147.68925947488415,-40.80825815202269],[148.28906782449602,-40.87543751400213],[148.35986453673584,-42.06244516374644],[148.0173014670731,-42.407023614268624],[147.9140519553538,-43.211522312188485],[147.564564243764,-42.93768889747386],[146.87034305235494,-43.634597263362096],[146.66332726459368,-43.58085377377856],[146.04837772032042,-43.54974456153889],[145.43192955951056,-42.69377613705627],[145.2950903668017,-42.03360971452756],[144.71807132383063,-41.162551771815714],[144.74375451067968,-40.70397511165771],[145.39797814349484,-40.79254851660589]]],[[[143.56181115129996,-13.763655694232213],[143.92209923723894,-14.548310642152003],[144.56371382057486,-14.171176039285882],[144.89490807513354,-14.594457696188625],[145.37472374896348,-14.984976495018286],[145.27199100156727,-15.428205254785695],[145.48525963763578,-16.285672295804773],[145.63703331927695,-16.784918308176614],[145.88890425026767,-16.90692636481765],[146.1603088726645,-17.761654554925244],[146.0636739442787,-18.28007252367732],[146.3874784690196,-18.95827402107591],[147.47108157774792,-19.48072275154668],[148.1776017600425,-19.95593922290277],[148.84841352762322,-20.39120981209726],[148.7174654481956,-20.633468926681516],[149.28942020080206,-21.260510756111103],[149.67833703023067,-22.342511895438392],[150.07738244038862,-22.12278370533332],[150.48293908101516,-22.556142266533012],[150.72726525289121,-22.40240488046466],[150.89955447815228,-23.462236830338682],[151.60917524638424,-24.076256198830762],[152.07353966695908,-24.457886651306197],[152.85519738180594,-25.267501316023015],[153.13616214417678,-26.07117319102619],[153.16194868389042,-26.641319268502443],[153.0929089703486,-27.26029957449451],[153.5694690289442,-28.1100668271021],[153.51210818910022,-28.995077406532758],[153.33909549378706,-29.458201592732447],[153.06924116435889,-30.350240166954816],[153.0896016786818,-30.923641859665448],[152.8915775901394,-31.640445651985956],[152.45000247620536,-32.550002536755244],[151.70911746643682,-33.041342054986345],[151.34397179586242,-33.81602345147385],[151.01055545471516,-34.310360202777886],[150.71413943908905,-35.17345997491681],[150.32821984273326,-35.67187916437193],[150.07521203023228,-36.42020558039051],[149.94612430236717,-37.10905242284123],[149.99728397033616,-37.42526051203514],[149.42388227762555,-37.77268116633346],[148.30462243061592,-37.80906137466688],[147.3817330263153,-38.21921721776755],[146.92212283751135,-38.60653207779512],[146.3179219911548,-39.03575652441144],[145.48965213438058,-38.59376799901905],[144.87697635312816,-38.41744801203912],[145.03221235573298,-37.896187839510986],[144.48568240781404,-38.08532358169927],[143.6099735861961,-38.80946542740533],[142.745426873953,-38.538267510737526],[142.178329705982,-38.38003427505984],[141.6065816591047,-38.30851409276788],[140.63857872941324,-38.019332777662555],[139.99215823787435,-37.40293629328511],[139.80658816951407,-36.64360279718828],[139.57414757706525,-36.13836231867067],[139.0828080588341,-35.73275400161178],[138.12074791885632,-35.612296237939404],[138.44946170466503,-35.127261244447894],[138.2075643251067,-34.38472258884593],[137.71917036351616,-35.07682504653103],[136.82940555231474,-35.26053476332862],[137.3523710471085,-34.70733855564409],[137.50388634658836,-34.130267836240776],[137.89011600153768,-33.640478610978334],[137.81032759007914,-32.90000701266811],[136.99683719294038,-33.752771498348636],[136.37206912653167,-34.09476612725619],[135.98904341038437,-34.89011809666049],[135.20821251845413,-34.47867034275261],[135.23921837782916,-33.94795338311498],[134.6134167827746,-33.22277800876314],[134.08590376193914,-32.848072198214766],[134.27390262261704,-32.61723357516696],[132.99077680880984,-32.011224053680195],[132.2880806825049,-31.98264698662277],[131.32633060112093,-31.495803318001048],[129.5357938986397,-31.590422865527483],[128.24093753470223,-31.948488864877856],[127.10286746633831,-32.28226694105105],[126.14871382050116,-32.21596607842061],[125.08862348846561,-32.728751316052836],[124.22164798390494,-32.95948658623607],[124.02894656788854,-33.483847344701715],[123.65966678273072,-33.89017913181273],[122.81103641163364,-33.91446705498984],[122.18306440642286,-34.003402194964224],[121.2991907085026,-33.82103606540613],[120.58026818245814,-33.930176690406626],[119.89369510302824,-33.976065362281815],[119.2988993673488,-34.50936614353397],[119.007340936358,-34.464149265278536],[118.5057178081008,-34.7468193499151],[118.02497195848953,-35.064732761374714],[117.29550744025747,-35.02545867283287],[116.62510908413495,-35.02509693780683],[115.56434695847972,-34.386427911111554],[115.02680870977954,-34.196517022438925],[115.04861616420679,-33.62342538832203],[115.5451233256671,-33.48725798923296],[115.71467370001668,-33.25957162855495],[115.6793786967614,-32.90036874769413],[115.80164513556397,-32.20506235120703],[115.68961063035513,-31.61243702568379],[115.16090905157697,-30.60159433362246],[114.99704308477945,-30.030724786094165],[115.04003787644628,-29.461095472940798],[114.64197431850201,-28.810230808224713],[114.61649783738217,-28.516398614213042],[114.17357913620847,-28.11807667410733],[114.04888390508816,-27.334765313427127],[113.4774975932369,-26.543134047147902],[113.3389530782625,-26.116545098578484],[113.77835778204026,-26.54902516042918],[113.44096235560662,-25.621278171493156],[113.93690107631167,-25.911234633082884],[114.23285200404732,-26.298446140245872],[114.21616051641703,-25.786281019801105],[113.72125532435771,-24.998938897402127],[113.62534386602405,-24.683971042583153],[113.39352339076267,-24.38476449961327],[113.50204389857564,-23.806350192970257],[113.70699262904517,-23.560215345964068],[113.8434184102957,-23.059987481378737],[113.7365515483161,-22.47547535572538],[114.1497563009219,-21.755881036061012],[114.22530724493268,-22.517488295178634],[114.64776207891869,-21.829519952076904],[115.46016727097933,-21.495173435148544],[115.94737267462702,-21.06868783944371],[116.71161543179156,-20.70168181730682],[117.16631635952771,-20.623598728113805],[117.44154503791427,-20.746898695562162],[118.22955895393298,-20.374208265873236],[118.83608523974273,-20.26331064217483],[118.98780724495177,-20.044202569257322],[119.25249393115065,-19.95294198982984],[119.80522505094457,-19.976506442954985],[120.85622033089666,-19.68370777758919],[121.39985639860723,-19.239755547769732],[121.65513797412909,-18.705317885007133],[122.24166548064177,-18.19764861417177],[122.28662397673567,-17.798603204013915],[122.31277225147544,-17.25496713630345],[123.01257449757193,-16.405199883695857],[123.43378909718304,-17.268558037996225],[123.85934451710662,-17.069035332917252],[123.50324222218326,-16.596506036040367],[123.81707319549193,-16.111316013251994],[124.25828657439988,-16.327943617419564],[124.37972619028582,-15.567059828353976],[124.92615278534005,-15.075100192935324],[125.16727501841389,-14.680395603090004],[125.67008670461385,-14.510070082256021],[125.6857963400305,-14.230655612853838],[126.12514936737611,-14.347340996968953],[126.14282270721989,-14.095986830301213],[126.58258914602376,-13.95279143642041],[127.06586714081735,-13.817967624570926],[127.80463341686195,-14.276906019755046],[128.35968997610897,-14.869169610252257],[128.98554324759593,-14.875990899314742],[129.62147342337963,-14.969783623924556],[129.409600050983,-14.420669854391035],[129.88864057832862,-13.618703301653483],[130.33946577364296,-13.357375583553477],[130.183506300986,-13.107520033422304],[130.617795037967,-12.536392103732467],[131.22349450086003,-12.183648776908115],[131.73509118054952,-12.302452894747162],[132.57529829318312,-12.114040622611014],[132.55721154188106,-11.603012383676685],[131.82469811414367,-11.273781833545101],[132.35722374891142,-11.128519382372644],[133.01956058159644,-11.376411228076847],[133.55084598198906,-11.786515394745138],[134.393068475482,-12.042365411022175],[134.67863244032705,-11.9411829565947],[135.29849124566803,-12.248606052299051],[135.88269331272764,-11.962266940969798],[136.25838097548947,-12.04934172938161],[136.49247521377166,-11.857208754120393],[136.95162031468502,-12.351958916882737],[136.68512495335577,-12.887223402562057],[136.30540652887512,-13.291229750219898],[135.96175825413414,-13.324509372615893],[136.07761681533256,-13.724278252825783],[135.78383629775325,-14.223989353088214],[135.42866417861123,-14.7154322241839],[135.50018436090318,-14.99774057379443],[136.29517459528137,-15.550264987859123],[137.0653601421595,-15.870762220933356],[137.58047081924482,-16.215082289294084],[138.303217401279,-16.807604261952658],[138.5851640158634,-16.806622409739177],[139.1085429221155,-17.06267913174537],[139.26057498591823,-17.371600843986187],[140.2152453960783,-17.710804945550066],[140.87546349503927,-17.369068698803943],[141.0711104676963,-16.832047214426723],[141.27409549373883,-16.388870131091608],[141.3982222841038,-15.840531508042588],[141.70218305884467,-15.04492115647693],[141.5633801617087,-14.56133310308951],[141.63552046118812,-14.270394789286284],[141.51986860571898,-13.698078301653808],[141.650920038011,-12.944687595270565],[141.84269127824624,-12.74154753993119],[141.6869901877508,-12.407614434461138],[141.92862918514757,-11.87746591557878],[142.118488397388,-11.32804208745162],[142.14370649634637,-11.042736504768143],[142.51526004452498,-10.668185723516643],[142.79731001197408,-11.157354831591519],[142.8667631369743,-11.784706719614931],[143.1159468934857,-11.905629571177911],[143.1586316265588,-12.325655612846191],[143.5221236512999,-12.834358412327433],[143.5971578309877,-13.400422051652598],[143.56181115129996,-13.763655694232213]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Fiji\",\"name\":\"Fiji\",\"name_long\":\"Fiji\",\"iso_a2\":\"FJ\",\"iso_a3\":\"FJI\",\"iso_n3\":\"242\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[178.3736,-17.33992],[178.71806,-17.62846],[178.55271,-18.15059],[177.93266,-18.28799],[177.38146,-18.16432],[177.28504,-17.72465],[177.67087,-17.38114],[178.12557,-17.50481],[178.3736,-17.33992]]],[[[179.36414266196428,-16.80135407694685],[178.7250593629971,-17.01204167436802],[178.59683859511708,-16.63915],[179.09660936299716,-16.43398427754742],[179.41350936299713,-16.379054277547397],[180.00000000000014,-16.06713266364244],[180.00000000000014,-16.55521656663916],[179.36414266196428,-16.80135407694685]]],[[[-179.91736938476524,-16.50178313564936],[-180,-16.55521656663916],[-180,-16.06713266364244],[-179.79332010904858,-16.02088225674123],[-179.91736938476524,-16.50178313564936]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"New C.\",\"name\":\"New Caledonia\",\"name_long\":\"New Caledonia\",\"iso_a2\":\"NC\",\"iso_a3\":\"NCL\",\"iso_n3\":\"540\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[165.77998986232637,-21.08000497811563],[166.59999148993384,-21.700018812753523],[167.1200114280869,-22.159990736583488],[166.74003462144478,-22.39997608814695],[166.18973229396866,-22.12970834726045],[165.47437544175222,-21.679606621998232],[164.82981530177568,-21.14981983814195],[164.16799523341365,-20.444746595951628],[164.029605747736,-20.105645847252354],[164.45996707586272,-20.1200118954295],[165.02003624904205,-20.45999114347773],[165.46000939357512,-20.80002206795826],[165.77998986232637,-21.08000497811563]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"N.Z.\",\"name\":\"New Zealand\",\"name_long\":\"New Zealand\",\"iso_a2\":\"NZ\",\"iso_a3\":\"NZL\",\"iso_n3\":\"554\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[173.02037479074076,-40.919052422856424],[173.24723432850206,-41.331998793300784],[173.95840538970288,-40.92670053483562],[174.24758670480813,-41.34915536882167],[174.2485168805895,-41.770008233406756],[173.8764465680879,-42.233184096038826],[173.22273969959568,-42.970038344088564],[172.71124637277077,-43.372287693048506],[173.0801127464702,-43.85334360125358],[172.30858361235252,-43.865694268571346],[171.45292524646365,-44.24251881284372],[171.18513797432726,-44.89710418068489],[170.61669721911662,-45.90892872495971],[169.8314221540093,-46.3557748349876],[169.33233117093428,-46.641235446967855],[168.41135379462858,-46.61994475686359],[167.76374474514685,-46.29019744240921],[166.67688602118423,-46.21991749449224],[166.5091443219647,-45.85270476662622],[167.04642418850327,-45.11094125750867],[168.3037634625969,-44.12397307716613],[168.94940880765157,-43.93581918719142],[169.66781456937318,-43.55532561622634],[170.52491987536618,-43.03168832781283],[171.125089960004,-42.51275359473778],[171.56971398344322,-41.767424411792135],[171.94870893787194,-41.51441659929115],[172.09722700427878,-40.95610442480968],[172.798579543344,-40.493962090823466],[173.02037479074076,-40.919052422856424]]],[[[174.61200890533055,-36.156397393540544],[175.3366158389272,-37.20909799575826],[175.35759647043753,-36.52619394302113],[175.8088867536425,-36.79894215265769],[175.9584900251275,-37.55538176854606],[176.76319542877658,-37.8812533505787],[177.4388131045605,-37.961248467766495],[178.0103544457087,-37.57982472102013],[178.51709354076283,-37.6953732236248],[178.27473107331386,-38.58281259537309],[177.97046023997936,-39.166342868812976],[177.20699262929915,-39.145775648760846],[176.93998050364704,-39.44973642350158],[177.0329464053401,-39.87994272233148],[176.88582360260526,-40.065977878582174],[176.50801720611938,-40.60480803808959],[176.0124402204403,-41.28962411882151],[175.239567499083,-41.68830779395324],[175.0678983910094,-41.42589487077508],[174.65097293527847,-41.28182097754545],[175.22763024322367,-40.459235528323404],[174.90015669179,-39.90893320084723],[173.82404666574402,-39.50885426204351],[173.85226199777534,-39.14660247167746],[174.5748018740804,-38.797683200842755],[174.74347374908106,-38.027807712558385],[174.69701663645063,-37.38112883885796],[174.29202843657922,-36.71109221776144],[174.31900353423555,-36.53482390721389],[173.84099653553582,-36.121980889634116],[173.0541711774596,-35.23712533950034],[172.63600548735374,-34.52910654066939],[173.00704227120949,-34.45066171645034],[173.55129845610747,-35.006183363587965],[174.3293904971263,-35.26549570082862],[174.61200890533055,-36.156397393540544]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"P.N.G.\",\"name\":\"Papua New Guinea\",\"name_long\":\"Papua New Guinea\",\"iso_a2\":\"PG\",\"iso_a3\":\"PNG\",\"iso_n3\":\"598\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[155.88002566957843,-6.81999684003776],[155.5999910829888,-6.919990736522493],[155.16699425681512,-6.535931491729301],[154.72919152243836,-5.900828138862209],[154.51411421123967,-5.139117526880014],[154.65250369691736,-5.042430922061839],[154.7599906760844,-5.339983819198494],[155.06291792217937,-5.566791680527487],[155.54774620994172,-6.200654799019659],[156.01996544822478,-6.540013929880388],[155.88002566957843,-6.81999684003776]]],[[[151.9827958518545,-5.478063246282346],[151.45910688700866,-5.56028045005874],[151.3013904156539,-5.840728448106702],[150.7544470562767,-6.083762709175389],[150.24119673075384,-6.317753594592986],[149.70996300679332,-6.316513360218053],[148.89006473205046,-6.026040134305433],[148.31893680236075,-5.74714242922613],[148.4018257997569,-5.437755629094724],[149.29841190002082,-5.583741550319217],[149.84556196512725,-5.505503431829339],[149.9962504416903,-5.026101169457675],[150.13975589416495,-5.001348158389789],[150.23690758687349,-5.532220147324281],[150.8074670758081,-5.455842380396888],[151.089672072554,-5.113692722192368],[151.64788089417087,-4.757073662946169],[151.53786176982155,-4.16780730552189],[152.13679162008438,-4.14879037843852],[152.33874311748102,-4.312966403829762],[152.31869266175178,-4.86766122805075],[151.9827958518545,-5.478063246282346]]],[[[147.19187381407494,-7.38802418378998],[148.0846358583494,-8.044108168167611],[148.7341052593936,-9.104663588093757],[149.30683515848446,-9.07143564213007],[149.26663089416135,-9.514406019736027],[150.03872846903434,-9.684318129111702],[149.73879845601226,-9.872937106977005],[150.80162763895916,-10.293686618697421],[150.69057498596388,-10.582712904505868],[150.02839318257585,-10.652476088099931],[149.782310012002,-10.393267103723943],[148.92313764871722,-10.280922539921363],[147.91301842670802,-10.130440769087471],[147.13544315001226,-9.492443536012019],[146.56788089415062,-8.942554619994155],[146.04848107318494,-8.06741423913131],[144.74416792213802,-7.630128269077473],[143.8970878440097,-7.915330498896281],[143.2863757671843,-8.245491224809056],[143.4139132020807,-8.983068942910947],[142.62843143124422,-9.326820570516503],[142.06825890520022,-9.159595635620036],[141.0338517600139,-9.117892754760419],[141.01705691951904,-5.859021905138022],[141.00021040259188,-2.600151055515624],[142.7352466167915,-3.289152927263217],[144.58397098203326,-3.861417738463401],[145.27317955951,-4.373737888205028],[145.82978641172568,-4.876497897972683],[145.98192182839298,-5.465609226100014],[147.6480733583476,-6.083659356310804],[147.8911076194162,-6.614014580922315],[146.9709053895949,-6.721656589386257],[147.19187381407494,-7.38802418378998]]],[[[153.14003787659877,-4.499983412294114],[152.8272921083683,-4.766427097190999],[152.638673130503,-4.176127211120928],[152.40602583232496,-3.789742526874562],[151.95323693258356,-3.462062269711822],[151.38427941305005,-3.035421644710112],[150.66204959533886,-2.741486097833956],[150.93996544820456,-2.500002129734028],[151.4799841656545,-2.779985039891386],[151.82001509013512,-2.999971612157907],[152.2399894553711,-3.240008640153661],[152.64001671774253,-3.659983005389648],[153.01999352438466,-3.980015150573294],[153.14003787659877,-4.499983412294114]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"S. Is.\",\"name\":\"Solomon Is.\",\"name_long\":\"Solomon Islands\",\"iso_a2\":\"SB\",\"iso_a3\":\"SLB\",\"iso_n3\":\"090\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[162.11902469304087,-10.482719008021135],[162.39864586817222,-10.82636728276212],[161.70003218001838,-10.820011081590224],[161.31979699121476,-10.204751478723125],[161.917383254238,-10.446700534713656],[162.11902469304087,-10.482719008021135]]],[[[160.85222863183796,-9.872937106977005],[160.46258833235729,-9.895209649294841],[159.8494474632142,-9.794027194867368],[159.64000288313517,-9.63997975020527],[159.70294477766666,-9.242949720906779],[160.36295617089846,-9.400304457235533],[160.6885176943372,-9.610162448772812],[160.85222863183796,-9.872937106977005]]],[[[161.67998172428915,-9.599982191611375],[161.52939660059053,-9.784312025596435],[160.78825320866056,-8.91754322676492],[160.57999718652437,-8.320008640173967],[160.92002811100494,-8.320008640173967],[161.28000613835,-9.120011488484451],[161.67998172428915,-9.599982191611375]]],[[[159.8750272971986,-8.337320244991716],[159.917401971678,-8.538289890174866],[159.1336771995394,-8.114181410355398],[158.58611372297472,-7.754823500197715],[158.21114953026486,-7.421872246941149],[158.35997765526545,-7.320017998893917],[158.82000125552773,-7.560003350457392],[159.64000288313517,-8.020026950719569],[159.8750272971986,-8.337320244991716]]],[[[157.5384257346893,-7.34781991946693],[157.33941979393327,-7.404767347852555],[156.9020304710148,-7.176874281445392],[156.49135786359133,-6.765943291860395],[156.54282759015396,-6.59933847415148],[157.1400004417189,-7.021638278840655],[157.5384257346893,-7.34781991946693]]]]}},{\"type\":\"Feature\",\"properties\":{\"abbrev\":\"Van.\",\"name\":\"Vanuatu\",\"name_long\":\"Vanuatu\",\"iso_a2\":\"VU\",\"iso_a3\":\"VUT\",\"iso_n3\":\"548\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[167.8448767438451,-16.466333103097156],[167.5151811058229,-16.597849623279966],[167.18000776597782,-16.15999521247096],[167.21680138576963,-15.891846205308452],[167.8448767438451,-16.466333103097156]]],[[[167.10771243720149,-14.933920179913954],[167.27002811103026,-15.740020847234874],[167.00120731024796,-15.614602146062495],[166.79315799384088,-15.668810723536723],[166.64985924709558,-15.392703545801195],[166.62913699774649,-14.626497084209603],[167.10771243720149,-14.933920179913954]]]]}}]}\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/maps/japan.prefectures.geo.json",
    "content": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-\",\"name\":null,\"name_alt\":null,\"name_local\":null,\"type\":null,\"type_en\":null,\"region\":null,\"postal\":null,\"region_code\":null},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[128.35124759200008,31.99673086100013],[128.3546655610002,31.98928457200016],[128.34896894600004,31.990383205000015],[128.34603925899998,31.986151434000178],[128.3438419930001,31.991319078000103],[128.34620201900023,31.99534739800005],[128.34766686300011,32.00063711100013],[128.34473717500006,32.00340403900019],[128.34310957100016,32.007228908000016],[128.35002688900025,32.00796133000013],[128.35629316500004,32.00934479400003],[128.35124759200008,31.99673086100013]]],[[[128.39405358200005,32.0548363300001],[128.40276126400025,32.05292389500012],[128.41391035200016,32.054388739],[128.40487714900004,32.03864166900006],[128.39747155000006,32.04157135600015],[128.3887638680001,32.039903062000135],[128.38371829499997,32.043768622000144],[128.38982181100008,32.04645416900003],[128.39405358200005,32.0548363300001]]],[[[132.49187259200002,32.73611074400013],[132.48796634200002,32.733181057000095],[132.48633873800011,32.73375071800017],[132.48715254000004,32.74001699400013],[132.49227949300015,32.74249909100014],[132.49415123800011,32.74074941600007],[132.49187259200002,32.73611074400013]]],[[[132.4976505870002,32.79930247600011],[132.49236087300014,32.7966576190001],[132.48951256600017,32.79726797100007],[132.48226972700016,32.7954369160001],[132.48609459700018,32.80540599200019],[132.499766472,32.80353424700003],[132.4976505870002,32.79930247600011]]],[[[133.54265384200008,34.130316473000065],[133.53183027400004,34.12352122600005],[133.52369225400005,34.13389720300013],[133.53174889400006,34.13703034100003],[133.53842207100016,34.13218821800014],[133.54265384200008,34.130316473000065]]],[[[133.21192467500012,34.24811432500012],[133.20915774800002,34.24233633000016],[133.20256595099997,34.24278392100014],[133.2006942070001,34.260199286000116],[133.19857832099999,34.263902085000055],[133.2002873060001,34.27000560100005],[133.21371503999998,34.28554922100015],[133.21916751400013,34.28819407800013],[133.224457227,34.29087962400003],[133.23129316500004,34.28432851800012],[133.23170006600006,34.27224355700004],[133.2115991550002,34.258937893],[133.20964603000002,34.25377024900011],[133.21192467500012,34.24811432500012]]],[[[135.01848392000014,34.28290436400006],[135.01596113400004,34.278591213000155],[135.01197350400005,34.27480703300013],[135.0104272800002,34.27480703300013],[135.00228925900015,34.277004299000126],[135.00139407599997,34.28213125200013],[135.00359134200008,34.28477610900005],[135.00757897200006,34.282294012000094],[135.01783287900017,34.28978099200016],[135.0240177740002,34.29234446800008],[135.0245874360002,34.29035065300003],[135.02182050900015,34.288560289000046],[135.02125084700018,34.28449127800009],[135.01848392000014,34.28290436400006]]],[[[135.05681399800008,34.29539622600008],[135.04167728000007,34.29104238500018],[135.03850345100003,34.29344310100011],[135.03459720100003,34.29323965100015],[135.04590905000006,34.298488674000154],[135.0586857430002,34.29783763200011],[135.05681399800008,34.29539622600008]]],[[[133.4517521490001,34.34275950700011],[133.45460045700023,34.337144273000106],[133.44597415500002,34.33588288],[133.43987063900025,34.337144273000106],[133.43604576900023,34.340236721000096],[133.43262780000012,34.338975328000075],[133.42709394600004,34.34662506700012],[133.43156985800024,34.348944403000175],[133.44157962300008,34.35020579600008],[133.4517521490001,34.34275950700011]]],[[[133.58041425900004,34.34988027600015],[133.57422936300023,34.347113348],[133.5674748060002,34.35521067900014],[133.58008873800011,34.361558335000055],[133.58847089900004,34.36233144700016],[133.5900171230002,34.35936107000008],[133.58383222700004,34.35374583500008],[133.58041425900004,34.34988027600015]]],[[[133.55258222700016,34.39069245000012],[133.54737389400006,34.38442617400007],[133.54680423300007,34.37681712400003],[133.55176842500023,34.37697988500018],[133.55412845100003,34.37555573100012],[133.5567326180002,34.37669505400005],[133.55901126400025,34.37754954600014],[133.55949954499997,34.370957749000084],[133.5567326180002,34.36635976800015],[133.54493248800023,34.36517975500011],[133.53939863400004,34.36827220300002],[133.529144727,34.37592194200012],[133.51661217500006,34.38727448100012],[133.52295983200023,34.39256419499999],[133.52499433700004,34.39061107000013],[133.52719160200004,34.39118073100009],[133.5279240240001,34.39284902600012],[133.53565514400006,34.39439524900014],[133.5377710300002,34.39866771000008],[133.54704837300002,34.39899323100009],[133.5558374360002,34.395860093000024],[133.55738366000006,34.39219798400016],[133.55518639400017,34.39097728100013],[133.55258222700016,34.39069245000012]]],[[[139.54997806100002,39.18345774900014],[139.5489201180001,39.18195221600011],[139.53899173300024,39.189113674000154],[139.53996829500014,39.19318268400009],[139.53956139400023,39.19590892100017],[139.546153191,39.20282623900009],[139.55665123800006,39.204331773000135],[139.56275475400017,39.197577216000084],[139.55510501400013,39.19472890800013],[139.55062910200016,39.19070058800018],[139.5489201180001,39.18553294499999],[139.54997806100002,39.18345774900014]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-34\",\"name\":\"Hiroshima\",\"name_alt\":\"Hirosima\",\"name_local\":\"広島県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chugoku\",\"postal\":\"HS\",\"region_code\":\"JPN-CGK\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[132.4972436860002,33.96796295800014],[132.49463951900012,33.96466705900012],[132.4930119150001,33.96727122600011],[132.49447675900015,33.97064850500011],[132.49789472700016,33.97178782800016],[132.4960229830002,33.98021067900011],[132.5001733730002,33.984849351000136],[132.50733483200017,33.98834870000003],[132.51164798300024,33.989528713000155],[132.51295006600017,33.99026113500018],[132.51441491000006,33.9911156270001],[132.51848391999997,33.989528713000155],[132.52377363400015,33.98834870000003],[132.5240177740001,33.98826732000005],[132.52287845100008,33.9872093770001],[132.51815839900004,33.982855536],[132.51351972700004,33.9759789080001],[132.50652103000007,33.97365957200013],[132.50473066500015,33.96946849200019],[132.4972436860002,33.96796295800014]]],[[[132.42693118600008,34.01504140800016],[132.42562910200016,34.01129791900003],[132.40796959700006,34.015244859000106],[132.40178470100003,34.02228424700017],[132.40902754000004,34.029893296000026],[132.41871178500017,34.02566152600009],[132.42416425900004,34.020209052000055],[132.42628014400012,34.016302802000055],[132.42693118600008,34.01504140800016]]],[[[132.53744550899998,34.05943431200008],[132.53207441500004,34.04747142100014],[132.52523847700004,34.04913971600014],[132.5235294930002,34.05719635600009],[132.52588951900017,34.06334056200008],[132.5297143890002,34.068793036000116],[132.533457879,34.07335032800016],[132.53744550899998,34.05943431200008]]],[[[132.39031009200002,34.17649974200019],[132.40455162900005,34.16982656500012],[132.4104923840001,34.171128648000135],[132.41529381600017,34.159287828000075],[132.39641360800013,34.145941473000065],[132.38754316499998,34.15025462400014],[132.3839624360002,34.156317450000145],[132.38217207100004,34.17239004100004],[132.39031009200002,34.17649974200019]]],[[[132.5494897800002,34.12148672100004],[132.55152428500006,34.108587958],[132.5776473320001,34.11888255400011],[132.58057701900012,34.09983958500011],[132.5698348320001,34.07587311400009],[132.55518639400006,34.07135651200012],[132.53223717500012,34.08209870000003],[132.47803795700023,34.08047109600001],[132.4553328790001,34.08812083500014],[132.4739689460001,34.105210679],[132.49732506600017,34.11713288000011],[132.51002037900005,34.13182200700011],[132.49683678500017,34.15704987200009],[132.50660241000006,34.16779205900009],[132.51929772200018,34.174383856000176],[132.53248131600006,34.177313544000086],[132.54330488400015,34.17670319200015],[132.55152428500006,34.17064036700005],[132.5490014980002,34.16632721600014],[132.54786217500006,34.161688544000114],[132.54509524800014,34.15814850500014],[132.5378524100001,34.15704987200009],[132.54623457100016,34.14496491100009],[132.5490014980002,34.13336823100015],[132.5494897800002,34.12148672100004]]],[[[132.79989668100004,34.17332591400013],[132.78256269600016,34.16148509300017],[132.77051842500023,34.1677513690001],[132.7803654310001,34.187933661000145],[132.79989668100004,34.17332591400013]]],[[[132.842051629,34.15900299700003],[132.81438235800007,34.15208567900014],[132.80648847700004,34.15456777600015],[132.80290774800008,34.15973541900017],[132.80331464900004,34.16636790600013],[132.80713951900006,34.17572663000006],[132.81861412900005,34.185614325000174],[132.83253014400012,34.188910223000036],[132.83668053500006,34.19179922100015],[132.84066816500004,34.19261302300005],[132.84465579500002,34.195095119000186],[132.8499455090001,34.19627513200011],[132.85189863400004,34.193304755],[132.8536889980002,34.188625393000095],[132.86361738400004,34.182806708000115],[132.85873457100004,34.170152085000055],[132.842051629,34.15900299700003]]],[[[132.3184513680001,34.19074127800009],[132.3048608730002,34.18390534100017],[132.30014082100004,34.188544012000094],[132.29395592500023,34.18748607000005],[132.29420006600017,34.19647858300006],[132.30543053500017,34.19822825700005],[132.31430097700004,34.1958275410001],[132.3184513680001,34.19074127800009]]],[[[132.76140384200008,34.17715078300013],[132.75611412900017,34.16746653900016],[132.74740644599999,34.162543036000116],[132.740000847,34.15989817900014],[132.73755944100017,34.16160716400013],[132.73731530000023,34.16840241100006],[132.73161868600008,34.17133209800009],[132.72364342500012,34.17080312700001],[132.69792728000007,34.18134186400006],[132.68897545700023,34.189886786],[132.695160352,34.20014069200012],[132.70899498800011,34.20331452000009],[132.72885175899998,34.19879791900003],[132.73853600400017,34.196966864000146],[132.75505618600002,34.18911367400007],[132.76140384200008,34.17715078300013]]],[[[132.67554772200006,34.17792389500006],[132.67473392000014,34.17332591400013],[132.6705835300002,34.17283763200014],[132.6641544930001,34.1695824240001],[132.6622827480002,34.16946035400012],[132.65992272200006,34.173692124000056],[132.65520267000014,34.174261786],[132.64861087300008,34.17035553600003],[132.64649498800023,34.17951080900009],[132.64787845100014,34.18789297100015],[132.6617130870002,34.19879791900003],[132.67164147200006,34.203517971000124],[132.67750084700018,34.19712962400011],[132.68262780000012,34.186997789000074],[132.67554772200006,34.17792389500006]]],[[[132.93230228000013,34.27484772300012],[132.9284774100001,34.25193919500013],[132.91911868600008,34.232896226000136],[132.91675866,34.221869208],[132.90577233200023,34.21308014500012],[132.883067254,34.208644924000154],[132.86312910200004,34.20848216400019],[132.8499455090001,34.21515534100014],[132.84302819100017,34.228420315000065],[132.84644616000017,34.24022044500005],[132.85401451900023,34.24502187700013],[132.85718834700018,34.24583567900014],[132.88599694100006,34.25702545800006],[132.89714603000007,34.25674062700001],[132.90821373800011,34.262884833],[132.92115319100017,34.27732982000005],[132.9284774100001,34.284002997000115],[132.93230228000013,34.27484772300012]]],[[[132.29590905000006,34.24323151200004],[132.27930748800023,34.238348700000174],[132.26791425900015,34.24445221600014],[132.26726321700014,34.253607489000146],[132.28663170700023,34.279120184000035],[132.29883873800011,34.292222398000106],[132.31446373800011,34.30044179900001],[132.32862389400012,34.30621979400017],[132.33838951900023,34.304429429],[132.34424889400012,34.295640367000104],[132.345469597,34.286566473000036],[132.34498131600006,34.28196849200016],[132.32553144600004,34.26471588700015],[132.29590905000006,34.24323151200004]]],[[[132.44174238400015,34.30272044499999],[132.44239342500006,34.29783763200011],[132.43555748800006,34.29975006700006],[132.43230228000002,34.29783763200011],[132.42562910200016,34.30105215100015],[132.41936282600003,34.301174221000124],[132.417246941,34.30426666900003],[132.42269941500015,34.306341864000146],[132.43010501400013,34.31350332200016],[132.43197675900004,34.31724681200002],[132.43254641999997,34.322821356000034],[132.43881269600004,34.32623932500003],[132.4454044930001,34.32208893400009],[132.44564863400015,34.31191640800013],[132.44174238400015,34.30272044499999]]],[[[132.993980748113,35.08054637281687],[133.02286787335376,35.06654205976828],[133.03759565591488,35.06302806229529],[133.05439049692384,35.06336395907944],[133.09702355369407,35.07145132122146],[133.12792605964003,35.06731720612409],[133.1659599142188,35.0615294464268],[133.22941857340848,35.059953315293214],[133.26430016482075,35.050625718801925],[133.30414269363044,34.99739899276936],[133.30962039586464,34.96721995723529],[133.30651981044085,34.93337189369795],[133.30667483917236,34.90869639702116],[133.3100854838581,34.87688955351014],[133.3159765972422,34.85802765585173],[133.3229529162442,34.843635769175464],[133.36222700427285,34.79270864468826],[133.36563764895837,34.7800479187765],[133.36610273695172,34.7669221048712],[133.36419070903403,34.750127264761474],[133.36377729698464,34.72893992803533],[133.36548261932754,34.70780426725317],[133.3715287623426,34.68943329511053],[133.39312951111802,34.64899648709827],[133.39266442312467,34.60822378230178],[133.39809044761617,34.59318594047873],[133.4056352073989,34.58104197850447],[133.42785607289957,34.55313670519489],[133.44149865074226,34.53008901649493],[133.44697635297618,34.517583320214],[133.44914676331248,34.508798326081845],[133.44676964740134,34.4878176949306],[133.44304894435336,34.47277985310781],[133.44302287120527,34.47267264568025],[133.43034915500007,34.47296784100017],[133.41911868600008,34.46792226800015],[133.41480553500006,34.45123932500012],[133.42221113400015,34.445013739000146],[133.41244550899998,34.4244652360001],[133.341075066,34.348049221000096],[133.32553144600004,34.3368187520001],[133.29542076900006,34.32489655200014],[133.28174889400023,34.321519273000135],[133.269786004,34.322414455000015],[133.26465905000012,34.33173248900006],[133.26986738399998,34.33917877800003],[133.28223717500023,34.34271881700012],[133.29590905000018,34.345038153000175],[133.30551191500015,34.34882233300003],[133.31495201900023,34.36603424700003],[133.30469811300023,34.37759023600013],[133.28321373800011,34.38312409100017],[133.25847415500002,34.382310289000074],[133.25847415500002,34.38979726800012],[133.27662194100006,34.40176015800016],[133.26661217500023,34.420599677],[133.247325066,34.42649974200013],[133.2296655610002,34.376166083000086],[133.21322675900004,34.36310455900009],[133.19499759200014,34.35370514500009],[133.18213951900012,34.34137604400003],[133.20191491,34.32831452000015],[133.20866946700008,34.307806708],[133.20289147200018,34.28888580900018],[133.18555748800011,34.280503648000106],[133.17823326900012,34.282782294000086],[133.16578209700018,34.29230377800009],[133.16098066499998,34.29417552300005],[133.15560957100004,34.29181549700009],[133.1440535820001,34.28261953300013],[133.12517337300008,34.2762718770001],[133.10271243600008,34.25714752800003],[133.09327233200017,34.252630927000084],[133.07642662900017,34.25958893400015],[133.07064863399998,34.277248440000065],[133.07227623800011,34.31753164300015],[133.07691491000006,34.316717841000056],[133.0874129570001,34.32648346600017],[133.10328209700006,34.34511953300016],[133.11402428500017,34.35175202000012],[133.11793053500006,34.34227122600011],[133.11703535200016,34.32656484600015],[133.11329186300023,34.314113674000126],[133.13314863400004,34.31818268400009],[133.14747155000012,34.35138580900012],[133.16846764400023,34.35504791900017],[133.16846764400023,34.36249420800014],[133.15259850400005,34.373846747000115],[133.13428795700005,34.382310289000074],[133.12322024800002,34.384344794000086],[133.11329186300023,34.383978583000086],[133.10352623800011,34.384588934000035],[133.09327233200017,34.38979726800012],[133.07911217500023,34.382310289000074],[133.085785352,34.351019598],[133.05909264400006,34.33234284100003],[133.01832116,34.32330963700012],[132.91187584700006,34.314764716000084],[132.883067254,34.30459219000015],[132.85303795700017,34.28733958500014],[132.83619225400017,34.311102606000034],[132.81820722700004,34.303371486000046],[132.79908287900005,34.284002997000115],[132.778086785,34.273098049000126],[132.76221764400006,34.27033112200017],[132.76400800900015,34.26316966400013],[132.77279707100004,34.25340403900019],[132.778086785,34.24274323100015],[132.77149498800023,34.23529694200009],[132.75660241000017,34.232326565000065],[132.72657311300023,34.23212311400012],[132.7029728520001,34.225490627000156],[132.65626061300011,34.19928620000012],[132.62720787900005,34.197984117000104],[132.62224368600008,34.201890367000104],[132.61841881600006,34.20888906500009],[132.61557050900004,34.21548086100016],[132.61353600400017,34.21845123900009],[132.60425866000006,34.21662018400009],[132.59945722700004,34.2123070330001],[132.59652754000004,34.207424221000124],[132.59302819100006,34.204250393000066],[132.56617272200006,34.19196198100012],[132.55445397200006,34.19236888200011],[132.544688347,34.204250393000066],[132.559825066,34.2184105490001],[132.55062910200004,34.23086172100001],[132.533457879,34.244777736000074],[132.5210067070001,34.269761460000026],[132.50733483200017,34.283026434000035],[132.503672722,34.28733958500014],[132.50342858200005,34.30036041900003],[132.507090691,34.30841705900015],[132.50904381600017,34.31622955900015],[132.503672722,34.32831452000015],[132.51075280000018,34.32982005400011],[132.5231225920002,34.33429596600017],[132.53101647200018,34.335150458000086],[132.52613366,34.34560781500015],[132.51758873800011,34.353013414000046],[132.49683678500017,34.36249420800014],[132.48129316499998,34.35455963700018],[132.46168053500006,34.34955475500014],[132.44157962300008,34.350002346000124],[132.405528191,34.368068752000156],[132.38347415500002,34.36566803600003],[132.34603925899998,34.34882233300003],[132.3123478520001,34.326971747000144],[132.22250410200016,34.23895905200014],[132.23170006600006,34.22524648600002],[132.23829186300023,34.204250393000066],[132.24040774780988,34.18980540594744],[132.2327352238515,34.19202179586917],[132.19981733630098,34.201530260413236],[132.1714469749978,34.215534573461824],[132.16059492421644,34.21801504216053],[132.14400678878238,34.22835032810521],[132.12643680231653,34.24876251937481],[132.0708846381156,34.35945343752606],[132.06111779205256,34.43505605689158],[132.05042077000243,34.45784536407233],[132.047785271673,34.473632513829486],[132.0434444518999,34.482443346383334],[132.0412740415638,34.495207424183306],[132.04856041892808,34.50869497329445],[132.06390831911372,34.52652334217841],[132.0949141787464,34.553420925135626],[132.10979699093835,34.5775538194532],[132.11635989789093,34.594581204458976],[132.11930545458316,34.61181529414047],[132.12333621599348,34.624811916836464],[132.131191034139,34.63871287709766],[132.1474174343671,34.659176744311324],[132.15082807815335,34.66842682553765],[132.14927778544146,34.68067414029936],[132.1420947608644,34.69299896942697],[132.14380008410663,34.705013740192],[132.15330854685206,34.71653758454188],[132.20746544797206,34.748370266474595],[132.22854943281007,34.76330475550999],[132.24126183466606,34.77482859986007],[132.2479280953053,34.78294179952444],[132.2595552924429,34.781443182756774],[132.2668933448521,34.77410513034759],[132.27650516218338,34.76847239848247],[132.289372592771,34.76769725212658],[132.30761437370447,34.77640473099349],[132.35221113523576,34.78686920814745],[132.36776574099625,34.784492092236306],[132.37934126218957,34.779686184469895],[132.39040001764673,34.778911038114],[132.40218224531444,34.78340688751787],[132.4145845888078,34.7937163359402],[132.42409305155323,34.79973664053354],[132.43969933505613,34.80009837484015],[132.4541687360983,34.7954991717494],[132.46920657792126,34.79198517517578],[132.509100782675,34.789504706477075],[132.52227827432293,34.79214020390732],[132.54553266769838,34.79914236133099],[132.58697716696273,34.817616686261104],[132.66785078298724,34.838235582206394],[132.67792768831217,34.844178372433944],[132.6799430690173,34.85624481914316],[132.6726566916531,34.866244208303655],[132.66056440562275,34.87404735050477],[132.6479553565546,34.879447537473894],[132.64144412734467,34.88391754845607],[132.64010053930846,34.888671780278415],[132.6425293320631,34.89469208397257],[132.6498157094275,34.90334788689542],[132.6694010756992,34.92195140213546],[132.68087324410496,34.930426337005784],[132.69374067559184,34.937661038426214],[132.71410119001794,34.94381053332948],[132.7292940605724,34.950218411550495],[132.74712243125524,34.962207342994546],[132.80841067920946,35.02755219257965],[132.8286161640049,35.055276597836794],[132.8391581564239,35.06649038292484],[132.8540409686158,35.07325999635184],[132.87574507107806,35.0777558457557],[132.93879031821862,35.06881582289192],[132.95398318877315,35.06922923404194],[132.9812683453579,35.08008128482341],[132.993980748113,35.08054637281687]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-33\",\"name\":\"Okayama\",\"name_alt\":null,\"name_local\":\"岡山県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chugoku\",\"postal\":\"OY\",\"region_code\":\"JPN-CGK\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[133.7771916020001,34.423081773000106],[133.77035566500015,34.416856187000164],[133.76311282600014,34.41901276200015],[133.75879967500023,34.427191473000065],[133.75847415500013,34.4305687520001],[133.76295006600017,34.42959219000004],[133.7671818370002,34.426540432000124],[133.77540123800011,34.425848700000174],[133.7771916020001,34.423081773000106]]],[[[134.13158572819881,35.242784532180465],[134.13964725191911,35.226790675948976],[134.1437813661171,35.21495677323655],[134.15204959541248,35.201727607443146],[134.15577029756113,35.19258087810512],[134.15856082642094,35.18023021145511],[134.16445193890578,35.167440294334156],[134.17458052017477,35.161626695315775],[134.1888432147426,35.16524404647565],[134.24734093563566,35.185759588734],[134.2688383316237,35.188240058332056],[134.28423790865244,35.18808502870105],[134.29607181226427,35.18568207526759],[134.30816409739523,35.18663808877683],[134.3253723486549,35.19247752531764],[134.34774824378636,35.206171780003686],[134.3956522972159,35.22921946870353],[134.3956522972159,35.216791286788364],[134.38821089111966,35.18751658792023],[134.39399865081705,35.17134186543474],[134.39709923714022,35.15206655482764],[134.39410200360456,35.14069774010868],[134.38557539279017,35.131757717244895],[134.37394819565267,35.12286937122455],[134.3626310568778,35.11103546851204],[134.31328006442348,35.04101390236973],[134.26532433505,35.00269582785022],[134.26361901270715,35.00096466708571],[134.26361901270715,35.000189520729876],[134.26501427578833,34.99352326098976],[134.2656343934125,34.98189606385229],[134.26454918779487,34.96742666281027],[134.25865807531002,34.936549994386795],[134.26361901270715,34.8971983910928],[134.26392907106973,34.872858792099535],[134.2577278993232,34.850973823383285],[134.25679772423567,34.83585846629525],[134.2597432800283,34.821337389309164],[134.28863040616832,34.799529934059294],[134.3061487157905,34.778135890858735],[134.31064456609377,34.77033275045609],[134.31400353393585,34.761237697961334],[134.3163806498471,34.74408112174642],[134.3143652691418,34.73175629351819],[134.30470177586622,34.70925120717739],[134.30464055720617,34.709108550374495],[134.27637780000023,34.71523672100001],[134.24781334700018,34.71816640800013],[134.22071373800023,34.72846100500006],[134.208750847,34.728013414000046],[134.21680748800023,34.711900132],[134.22689863400004,34.70791250200001],[134.2788192070001,34.69822825700005],[134.2710067070001,34.696275132],[134.2516382170002,34.693752346000096],[134.24480228000007,34.69147370000012],[134.22445722700016,34.66766998900012],[134.22022545700017,34.66351959800009],[134.21225019599999,34.66136302299999],[134.19629967500012,34.652004299000126],[134.17823326900006,34.64883047100007],[134.17066491000006,34.645982164000046],[134.15528405000006,34.63686758000013],[134.16211998800011,34.632228908000016],[134.17025800900004,34.630764065000065],[134.17969811300023,34.6323102890001],[134.18946373800006,34.63686758000013],[134.1772567070001,34.61444733300014],[134.14795983200005,34.596991278000175],[134.11304772200006,34.585638739],[134.08366946700008,34.581529039000046],[134.06934655000006,34.58584219000015],[134.04737389400023,34.604641018],[134.02881920700023,34.60887278900013],[133.99439537900005,34.60871002800015],[133.97925866000006,34.605902411000116],[133.9538680350001,34.59300364800002],[133.93669681100008,34.58722565300003],[133.92514082100016,34.577866929000024],[133.92945397200006,34.561102606000176],[133.94255618600013,34.55512116100006],[133.95736738399998,34.564439195000105],[133.9840600920002,34.589056708000115],[134.00074303500006,34.594468492000075],[134.01929772200006,34.596828518],[134.03565514400006,34.59341054900001],[134.04615319100006,34.581529039000046],[134.04566491000006,34.56391022300012],[134.03345787900017,34.55247630400008],[134.01758873800023,34.54364655200011],[134.00513756600006,34.5338402360001],[134.00766035200016,34.53050364800008],[134.00863691500004,34.528550523000106],[134.01197350400005,34.520086981000176],[133.98316491,34.52863190300009],[133.96656334700006,34.506415106000034],[133.9538680350001,34.473822333000086],[133.93628991000017,34.45123932500012],[133.85906009200002,34.462876695000105],[133.82178795700005,34.46214427299999],[133.8264266290001,34.437567450000174],[133.7944442070001,34.44293854400003],[133.780528191,34.46336497600002],[133.76889082100004,34.488023179],[133.7438257170002,34.50584544500008],[133.73682701900023,34.50238678600006],[133.73047936300011,34.506984768],[133.7257593110002,34.51642487200003],[133.72339928500006,34.52757396000014],[133.716563347,34.520086981000176],[133.69996178500017,34.52594635600012],[133.6850692070001,34.520086981000176],[133.66944420700023,34.51080963700012],[133.64063561300011,34.50169505400014],[133.59620201900006,34.482326565],[133.58619225400017,34.47573476800012],[133.58334394600016,34.46869538000006],[133.57667076900012,34.46332428600003],[133.56885826900012,34.459906317],[133.55046634200008,34.45701732000008],[133.54330488400004,34.453070380000085],[133.53809655000012,34.44855377800003],[133.53207441500004,34.445013739000146],[133.47006269600016,34.423976955000015],[133.5092879570001,34.462103583],[133.52247155000023,34.48305898600013],[133.50114993600008,34.49217357000013],[133.49024498800011,34.48956940300003],[133.468516472,34.476996161000145],[133.4565535820001,34.47235748900012],[133.44302287120527,34.47267264568025],[133.44304894435336,34.47277985310781],[133.44676964740134,34.4878176949306],[133.44914676331248,34.508798326081845],[133.44697635297618,34.517583320214],[133.44149865074226,34.53008901649493],[133.42785607289957,34.55313670519489],[133.4056352073989,34.58104197850447],[133.39809044761617,34.59318594047873],[133.39266442312467,34.60822378230178],[133.39312951111802,34.64899648709827],[133.3715287623426,34.68943329511053],[133.36548261932754,34.70780426725317],[133.36377729698464,34.72893992803533],[133.36419070903403,34.750127264761474],[133.36610273695172,34.7669221048712],[133.36563764895837,34.7800479187765],[133.36222700427285,34.79270864468826],[133.3229529162442,34.843635769175464],[133.3159765972422,34.85802765585173],[133.3100854838581,34.87688955351014],[133.30667483917236,34.90869639702116],[133.30651981044085,34.93337189369795],[133.30962039586464,34.96721995723529],[133.30414269363044,34.99739899276936],[133.26430016482075,35.050625718801925],[133.2796997418496,35.06597361898726],[133.28946658701344,35.07248484999583],[133.3069848984343,35.075895493782085],[133.36005659393678,35.0952741562772],[133.37793663966423,35.10576447185288],[133.3832076363233,35.11506622992262],[133.38444786977337,35.124264635204796],[133.38300092984903,35.13276540849684],[133.38289757706164,35.141757107304684],[133.38734174962198,35.15036123338429],[133.39716027162936,35.156614081075034],[133.4470797057638,35.15868113952298],[133.4962756685873,35.17475250922091],[133.50144331335824,35.17697459640057],[133.50423384041935,35.18116038744192],[133.50516401640616,35.18782664628266],[133.50526736919377,35.197335109927295],[133.50702436658113,35.211442775763445],[133.51291548086468,35.22095124030754],[133.52449100205817,35.22839264640358],[133.53554975841476,35.233663642163336],[133.5446964868535,35.24092418110614],[133.55001915855726,35.25038096790742],[133.56087121023828,35.28164520905911],[133.57358361209415,35.30616567610498],[133.57993981257218,35.31515737581229],[133.59203209860257,35.32099681235299],[133.6078967627253,35.32228872354578],[133.73522749225495,35.295339463745236],[133.75579471225618,35.28849233685186],[133.77016076140998,35.28022410755649],[133.78432010319014,35.27001801192175],[133.81108849493822,35.24224192982116],[133.8234908393308,35.2390379907108],[133.83423953732475,35.24312042896473],[133.84374800007018,35.25394664222385],[133.84932905509189,35.26407522169417],[133.856615431557,35.272963569513095],[133.86808759996282,35.28009491634738],[133.90069542915072,35.29239390705327],[133.91046227431443,35.300352077986005],[133.9177486507796,35.31102326071483],[133.92906578955458,35.31939484279776],[133.94999474476177,35.3261902937471],[133.9705102888187,35.32727549936472],[133.98694339372267,35.32502757376359],[133.99970747332125,35.31665599258004],[134.0013611188207,35.3099638944182],[134.0009477067713,35.30531301538342],[133.99826053339703,35.29841421254598],[133.99970747332125,35.29360830298073],[134.0013611188207,35.290843614341284],[134.0066321145804,35.287872219227594],[134.08114953012685,35.27916474125986],[134.11360232968386,35.2607420922737],[134.13158572819881,35.242784532180465]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-32\",\"name\":\"Shimane\",\"name_alt\":\"Simane\",\"name_local\":\"島根県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chugoku\",\"postal\":\"SM\",\"region_code\":\"JPN-CGK\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[133.11973193300017,35.58490697300003],[133.14142764200008,35.57016529100012],[133.15646656900017,35.56262168500017],[133.18966112899997,35.571907532000026],[133.20706772900022,35.572828130000076],[133.22473074000018,35.58294498500014],[133.23016293200013,35.578197844],[133.22315514400012,35.56850820500013],[133.22803795700005,35.56468333500011],[133.2393363700002,35.56361480200009],[133.24625319200007,35.567319817000126],[133.26637679800004,35.57423650500009],[133.2867788330001,35.576192753000115],[133.3087723730001,35.577188578000076],[133.326019727,35.56850820500013],[133.31290421499997,35.561976121000114],[133.2988742620001,35.55863831700019],[133.28692853600015,35.55372692200014],[133.27013504800001,35.54961013700013],[133.25749759207022,35.53620026206629],[133.2536031427709,35.53695262326495],[133.2096781757073,35.52418854456563],[133.20487226794077,35.52116547350779],[133.20084150563108,35.51648875605129],[133.2039420919539,35.512277127487536],[133.20781782373368,35.508737290693446],[133.2194966977144,35.50150259017248],[133.2232174007623,35.49809194638614],[133.22616295835397,35.494164536863835],[133.2286434270525,35.4900304235653],[133.2312789253821,35.48145213500804],[133.2332943060873,35.476775418450714],[133.24202762337606,35.46383047079958],[133.24988243972294,35.45613068318444],[133.27530724523322,35.43915497502216],[133.2872961766772,35.43328970005963],[133.3169584491732,35.42362620678402],[133.32000735865282,35.4215333108137],[133.3225395041948,35.4195954453735],[133.32512332568106,35.416133123844574],[133.32574344330524,35.41148224480979],[133.32326297370727,35.40597870505324],[133.31561486203626,35.400578518084174],[133.3112740422633,35.39902822537228],[133.30636478081013,35.3963410510987],[133.30388431211125,35.38926138020854],[133.2973730820021,35.33378672947403],[133.30016360906328,35.30081716597944],[133.29980187385726,35.282446193836805],[133.29318729096067,35.26916535120007],[133.27158654218525,35.25583283082052],[133.18750898705,35.22244985617591],[133.1746415546639,35.21444000929894],[133.16440962060747,35.20588756006286],[133.1656498558563,35.19498383243787],[133.17169599797197,35.186224676727434],[133.17495161302648,35.17402903880914],[133.17231611469683,35.15782847790196],[133.1476147813971,35.12186168087196],[133.12792605964003,35.06731720612409],[133.09702355369407,35.07145132122146],[133.05439049692384,35.06336395907944],[133.03759565591488,35.06302806229529],[133.02286787335376,35.06654205976828],[132.993980748113,35.08054637281687],[132.9812683453579,35.08008128482341],[132.95398318877315,35.06922923404194],[132.93879031821862,35.06881582289192],[132.87574507107806,35.0777558457557],[132.8540409686158,35.07325999635184],[132.8391581564239,35.06649038292484],[132.8286161640049,35.055276597836794],[132.80841067920946,35.02755219257965],[132.74712243125524,34.962207342994546],[132.7292940605724,34.950218411550495],[132.71410119001794,34.94381053332948],[132.69374067559184,34.937661038426214],[132.68087324410496,34.930426337005784],[132.6694010756992,34.92195140213546],[132.6498157094275,34.90334788689542],[132.6425293320631,34.89469208397257],[132.64010053930846,34.888671780278415],[132.64144412734467,34.88391754845607],[132.6479553565546,34.879447537473894],[132.66056440562275,34.87404735050477],[132.6726566916531,34.866244208303655],[132.6799430690173,34.85624481914316],[132.67792768831217,34.844178372433944],[132.66785078298724,34.838235582206394],[132.58697716696273,34.817616686261104],[132.54553266769838,34.79914236133099],[132.52227827432293,34.79214020390732],[132.509100782675,34.789504706477075],[132.46920657792126,34.79198517517578],[132.4541687360983,34.7954991717494],[132.43969933505613,34.80009837484015],[132.42409305155323,34.79973664053354],[132.4145845888078,34.7937163359402],[132.40218224531444,34.78340688751787],[132.39040001764673,34.778911038114],[132.37934126218957,34.779686184469895],[132.36776574099625,34.784492092236306],[132.35221113523576,34.78686920814745],[132.30761437370447,34.77640473099349],[132.289372592771,34.76769725212658],[132.27650516218338,34.76847239848247],[132.2668933448521,34.77410513034759],[132.2595552924429,34.781443182756774],[132.2479280953053,34.78294179952444],[132.24126183466606,34.77482859986007],[132.22854943281007,34.76330475550999],[132.20746544797206,34.748370266474595],[132.15330854685206,34.71653758454188],[132.14380008410663,34.705013740192],[132.1420947608644,34.69299896942697],[132.14927778544146,34.68067414029936],[132.15082807815335,34.66842682553765],[132.1474174343671,34.659176744311324],[132.131191034139,34.63871287709766],[132.12333621599348,34.624811916836464],[132.11930545458316,34.61181529414047],[132.11635989789093,34.594581204458976],[132.10979699093835,34.5775538194532],[132.0949141787464,34.553420925135626],[132.06390831911372,34.52652334217841],[132.04856041892808,34.50869497329445],[132.0412740415638,34.495207424183306],[132.0434444518999,34.482443346383334],[132.047785271673,34.473632513829486],[132.05042077000243,34.45784536407233],[131.9916129898476,34.41335195622786],[131.98773725806805,34.40177643503445],[131.98990766840424,34.38617015243055],[131.99688398740616,34.37800527592266],[132.00029463119225,34.37242422090107],[132.00199995263577,34.36394928603083],[131.9985893088494,34.3583423943859],[131.99362837055284,34.35345897135453],[131.96623986208002,34.34343374287293],[131.96226077661348,34.33966136298143],[131.9586951031964,34.33397695607174],[131.95218387308725,34.31671702706909],[131.9467578467971,34.30604584434032],[131.939471470332,34.303177802013934],[131.93089318267428,34.3075702995297],[131.92055789583017,34.3157351751383],[131.90805219865004,34.31955923097375],[131.89632164872503,34.31769887989931],[131.8454203626595,34.30307444922646],[131.8126575047401,34.299637966119306],[131.79508751827436,34.30173086208963],[131.78196170526851,34.30653676985604],[131.77141971195013,34.31426239679227],[131.7647534531093,34.324055081276995],[131.75839725173168,34.33746511512301],[131.75322960786005,34.351753648112336],[131.7512659039983,34.36345836051494],[131.7513692567857,34.37350942651888],[131.7554000181963,34.38177765581416],[131.77250491756783,34.40376597821735],[131.77265994719895,34.40973460506798],[131.7684741561575,34.41614248328899],[131.69648888615322,34.4289840763541],[131.68325971856123,34.437743232064534],[131.67922895715074,34.44709666607834],[131.68155439711782,34.45660512972306],[131.68108930912436,34.4678447541322],[131.6720976103164,34.479394436004426],[131.66713667201984,34.49608592422605],[131.6688419943625,34.50978017801272],[131.6784538107947,34.52368113917329],[131.69349165261772,34.55504873311257],[131.69938276600195,34.5622834345328],[131.7034135283118,34.57104258934406],[131.70331017462476,34.58104197850447],[131.68558515762905,34.61576854118529],[131.6818644545809,34.625225327986485],[131.67922895715074,34.64062490501534],[131.6789188987881,34.64894481115404],[131.68046919239944,34.66227732973503],[131.6796245203553,34.66648457050654],[131.67562866336792,34.67214318498067],[131.68033219599997,34.67464904300006],[131.71159915500013,34.67157623900012],[131.74608180700014,34.675353457000185],[131.7903751960001,34.68610260600015],[131.8229558310002,34.698283032000134],[131.8407843850001,34.705328510000086],[131.85840905000023,34.71450429900007],[131.86312910200016,34.73749420800006],[131.86687259200008,34.746039130000085],[131.87375767000017,34.75922287700014],[131.89633222700016,34.76292552300008],[131.9013219030002,34.77045129700015],[131.92378849800005,34.78785980400009],[131.95700657400002,34.813030243000085],[131.99732506600017,34.837225653000175],[132.02116946700008,34.86587148600013],[132.0345512000001,34.876579005000124],[132.0538843110002,34.872259833000086],[132.06055748800023,34.88568756700006],[132.06330411800005,34.907489343000165],[132.11117597700004,34.94310130400008],[132.13184655000023,34.95864492400007],[132.15794037100002,34.97613237400016],[132.2163192070001,35.013576565000065],[132.23649497000022,35.034818088000165],[132.2708418870001,35.040329058000154],[132.29361034800016,35.04840696100017],[132.3168499870001,35.062346339000115],[132.32659426800015,35.09206984399999],[132.35704074200024,35.11120724300018],[132.37994884300022,35.12498077800008],[132.3892122780001,35.13482940100006],[132.38657425800014,35.14553759200011],[132.40235436300023,35.16046784100017],[132.408084915,35.17505067800006],[132.46767755800008,35.21314807900002],[132.4958267010002,35.232455210000026],[132.52971849900004,35.24491322199999],[132.53193742200008,35.25767159600012],[132.55026525600024,35.26243229300012],[132.57561724500025,35.27327009700004],[132.6211415370001,35.28444409600017],[132.63867918900004,35.29464467300012],[132.6513954430001,35.31292353300013],[132.66334675600004,35.32953554900017],[132.6714464620001,35.35512813600012],[132.67413653100007,35.372037254000034],[132.67402321100022,35.38542475600015],[132.67079114300012,35.3994745880001],[132.653742456,35.406869889000134],[132.6311420550002,35.41981396300018],[132.62380835800005,35.43106990400007],[132.63535916900017,35.44095469500009],[132.65587637100012,35.44299398600016],[132.6820987360002,35.449016416000106],[132.70144369900007,35.44921971800012],[132.7344381420002,35.44863223200012],[132.7485510070002,35.451549379000156],[132.75473933400022,35.4562450750001],[132.74447675899998,35.46356842700011],[132.72549563500016,35.47077964400002],[132.73627944100022,35.47274245600006],[132.75271983700006,35.477077635000015],[132.7754265140001,35.48100749600012],[132.8048266220002,35.49518180100013],[132.8376793790001,35.50612318400012],[132.86490913700007,35.5109826810001],[132.92223136400006,35.513175013000065],[132.94636538000012,35.51867749700013],[132.96973717500023,35.51699453300016],[132.97320551100003,35.54188385],[132.99148534400015,35.54455131400009],[133.01439948700025,35.54078011700007],[133.03348937900014,35.545992142],[133.04249238800006,35.561527969000096],[133.051772182,35.57373032500011],[133.0646006050001,35.580271302000156],[133.0814422240002,35.57532590200016],[133.09134539000016,35.58753877200017],[133.09126101299998,35.60148491100007],[133.10535285100016,35.59751658900008],[133.11973193300017,35.58490697300003]]],[[[133.06666100400005,36.006537177000084],[133.07911217500023,35.993638414000074],[133.06495201900012,35.99506256700012],[133.02784264400012,35.993638414000074],[133.02133222700004,35.99575429900007],[133.01612389400012,36.00092194200006],[133.01295006600006,36.00779857],[133.01246178500017,36.014797268000066],[133.02418053500006,36.02558014500006],[133.0451766290001,36.02000560100005],[133.06666100400005,36.006537177000084]]],[[[133.09945722700004,36.03750234600001],[133.07911217500023,36.02094147300012],[133.07862389400023,36.049994208000115],[133.08130944100006,36.08014557500003],[133.09351647200012,36.102769273000106],[133.12134850400017,36.109767971000096],[133.1354272800002,36.08954498900006],[133.12322024800002,36.062933661000116],[133.09945722700004,36.03750234600001]]],[[[133.11361738400015,36.11351146000011],[133.09327233200017,36.109767971000096],[133.09058678500017,36.11786530200011],[133.09327233200017,36.123358466000056],[133.11361738400015,36.11351146000011]]],[[[133.03874759200008,36.13080475500011],[133.05665123800023,36.124945380000085],[133.07422936300006,36.127915757],[133.08757571700008,36.12702057500009],[133.08814537900005,36.11827220300002],[133.08716881600017,36.11078522300015],[133.07276451900012,36.107855536000116],[133.05836022200006,36.10317617400001],[133.05176842500018,36.085516669000086],[133.05396569100006,36.074896552000055],[133.05697675900015,36.06769440300012],[133.05583743600002,36.06175364799999],[133.04493248800023,36.055121161000116],[133.03598066500004,36.053900458000115],[133.0270288420002,36.056301174000154],[133.01978600400017,36.060614325000145],[133.0171004570001,36.065008856000034],[133.01677493600008,36.074774481000176],[133.01490319100012,36.07977936400012],[133.01002037900005,36.0815290390001],[133.00025475400017,36.08177317900005],[132.99040774800008,36.077053127000156],[132.99317467500012,36.06635163000011],[133.0040796230002,36.048285223],[132.99496504000004,36.03823476800012],[132.98145592500023,36.039862372000144],[132.96550540500002,36.04913971600011],[132.94939212300008,36.06195709800015],[132.969493035,36.082017320000105],[133.01449628999998,36.11098867400001],[133.03874759200008,36.13080475500011]]],[[[133.37924238399998,36.20823802299999],[133.37338300900015,36.197699286000116],[133.36019941499998,36.19163646000014],[133.35254967500023,36.203517971000096],[133.34351647200006,36.20433177300002],[133.3343205090001,36.198879299000154],[133.326019727,36.19163646000014],[133.33765709700012,36.184393622000115],[133.34131920700017,36.17503489799999],[133.33741295700017,36.16559479400014],[133.326019727,36.158148505],[133.31836998800006,36.15843333500011],[133.27833092500023,36.164984442],[133.25635826900012,36.164984442],[133.24976647200006,36.16828034100014],[133.20264733200023,36.202297268000066],[133.19263756600017,36.20595937700004],[133.1877547540001,36.21613190300015],[133.19629967500012,36.27798086100016],[133.20948326900017,36.29718659100011],[133.29200280000018,36.34247467700011],[133.2928166020001,36.33055247599999],[133.30079186300023,36.32587311400006],[133.31120853000002,36.32428620000003],[133.32016035200016,36.321844794000114],[133.32349694100017,36.318345445000105],[133.32650800899998,36.30939362200009],[133.32943769600004,36.30491771000011],[133.33570397200006,36.30093008000013],[133.34864342500023,36.297593492000104],[133.35401451900012,36.294663804],[133.35938561300006,36.2885602890001],[133.36361738399998,36.27928294500005],[133.367035352,36.27423737200003],[133.38184655000006,36.25771719000015],[133.37924238399998,36.20823802299999]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-31\",\"name\":\"Tottori\",\"name_alt\":null,\"name_local\":\"鳥取県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chugoku\",\"postal\":\"TT\",\"region_code\":\"JPN-CGK\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[134.43647667885563,35.21748891877854],[134.42329918810702,35.220847887519994],[134.41585778201107,35.22617055922393],[134.3956522972158,35.22921946870349],[134.34774824378633,35.20617178000364],[134.32537234865492,35.19247752531757],[134.30816409739515,35.18663808877679],[134.29607181226424,35.18568207526755],[134.28423790865247,35.188085028701096],[134.26883833162367,35.18824005833201],[134.24734093563563,35.185759588734015],[134.18884321474258,35.16524404647558],[134.1745805201748,35.16162669531573],[134.1644519389058,35.167440294334114],[134.15856082642097,35.18023021145504],[134.15577029756116,35.192580878105076],[134.15204959541245,35.201727607443104],[134.14378136611708,35.21495677323651],[134.1396472519191,35.22679067594893],[134.1315857281988,35.24278453218042],[134.11360232968383,35.260742092273745],[134.08114953012677,35.27916474125982],[134.00663211458038,35.28787221922755],[134.00136111882063,35.29084361434133],[133.99970747332122,35.29360830298077],[133.99826053339694,35.29841421254591],[134.00094770677123,35.30531301538335],[134.00136111882063,35.30996389441813],[133.99970747332122,35.316655992579996],[133.98694339372264,35.32502757376355],[133.97051028881867,35.32727549936476],[133.94999474476168,35.32619029374703],[133.9290657895545,35.31939484279772],[133.91774865077957,35.311023260714876],[133.91046227431445,35.300352077985934],[133.9006954291507,35.292393907053224],[133.86808759996273,35.28009491634734],[133.85661543155692,35.27296356951314],[133.8493290550918,35.26407522169413],[133.84374800007015,35.25394664222378],[133.83423953732472,35.24312042896469],[133.82349083933076,35.23903799071073],[133.81108849493813,35.242241929821205],[133.7843201031901,35.27001801192171],[133.77016076141,35.28022410755645],[133.75579471225615,35.2884923368519],[133.73522749225492,35.295339463745194],[133.60789676272532,35.32228872354574],[133.5920320986025,35.320996812352945],[133.5799398125722,35.31515737581225],[133.57358361209407,35.306165676105024],[133.5608712102382,35.28164520905915],[133.55001915855723,35.250380967907375],[133.5446964868534,35.24092418110607],[133.53554975841467,35.23366364216329],[133.52449100205814,35.228392646403535],[133.51291548086465,35.2209512403075],[133.50702436658116,35.21144277576349],[133.50526736919375,35.19733510992734],[133.50516401640618,35.1878266462827],[133.50423384041937,35.18116038744185],[133.5014433133582,35.17697459640053],[133.4962756685872,35.17475250922092],[133.44707970576377,35.15868113952294],[133.39716027162928,35.15661408107499],[133.387341749622,35.150361233384245],[133.38289757706156,35.14175710730464],[133.383000929849,35.132765408496795],[133.38444786977334,35.12426463520475],[133.38320763632328,35.115066229922576],[133.37793663966414,35.10576447185284],[133.3600565939367,35.09527415627724],[133.30698489843428,35.07589549378204],[133.28946658701335,35.07248484999579],[133.27969974184958,35.0659736189873],[133.26430016482072,35.05062571880188],[133.2294185734084,35.05995331529323],[133.16595991421872,35.06152944642676],[133.12792605963995,35.06731720612413],[133.14761478139707,35.121861680871916],[133.17231611469686,35.157828477901916],[133.17495161302645,35.174029038809095],[133.17169599797188,35.18622467672739],[133.1656498558562,35.19498383243791],[133.16440962060744,35.20588756006279],[133.17464155466394,35.21444000929898],[133.18750898705002,35.22244985617587],[133.27158654218516,35.25583283082048],[133.29318729096065,35.26916535120003],[133.2998018738573,35.28244619383676],[133.30016360906325,35.300817165979396],[133.29737308200208,35.333786729473985],[133.30388431211122,35.389261380208495],[133.30636478081004,35.39634105109863],[133.31127404226328,35.39902822537232],[133.31561486203623,35.40057851808413],[133.32326297370724,35.40597870505317],[133.3257434433052,35.41148224480972],[133.32512332568098,35.41613312384459],[133.32253950419476,35.41959544537346],[133.32000735865273,35.42153331081366],[133.31695844917311,35.42362620678398],[133.28729617667716,35.43328970005959],[133.27530724523314,35.43915497502212],[133.24988243972285,35.45613068318448],[133.2420276233761,35.463830470799536],[133.23329430608726,35.47677541845076],[133.23127892538207,35.48145213500797],[133.22864342705248,35.49003042356526],[133.22616295835388,35.49416453686388],[133.22321740076234,35.49809194638618],[133.21949669771436,35.501502590172436],[133.2078178237336,35.508737290693404],[133.20394209195388,35.51227712748749],[133.200841505631,35.516488756051245],[133.2048722679408,35.52116547350775],[133.2096781757072,35.524188544565675],[133.25360314277083,35.53695262326491],[133.25749759207014,35.536200262066245],[133.254730665,35.53384023600012],[133.25416100400003,35.5217959660001],[133.26050866000008,35.50995514500005],[133.2646590500001,35.49616120000013],[133.29454316300013,35.4804164630001],[133.3296256210001,35.465504387000124],[133.3608574540001,35.46041669200008],[133.38068988100014,35.45641486000008],[133.40015709700003,35.454291083000015],[133.41741190900007,35.45654296500001],[133.44124508900006,35.491037665000135],[133.49275500400012,35.51036183600013],[133.5255946270001,35.52242699200008],[133.58018171100008,35.533602742000085],[133.63256824800015,35.52132835600014],[133.73114464200006,35.50290661400001],[133.91154673700015,35.514594572000135],[134.0083833370001,35.53704892300004],[134.03478971200008,35.519355662000066],[134.17366172700008,35.536271360000086],[134.23776516100008,35.546651206000064],[134.26206575800006,35.55358778700014],[134.28158613400007,35.564886786000045],[134.29387121000002,35.58589395600009],[134.34262129,35.59381745000013],[134.35206139400015,35.594183661000045],[134.36150149800005,35.595770575000074],[134.36558094755821,35.59996541641965],[134.36883222862446,35.59756907855018],[134.39844282427697,35.57860382720489],[134.41678795799788,35.54418732378596],[134.44443484888916,35.43871572545038],[134.46799930242582,35.40011343009067],[134.47404544544088,35.38316356124929],[134.47854129394545,35.36187286993703],[134.50210574838155,35.33239146639309],[134.50613650889278,35.32226288512402],[134.50758344881712,35.313917141462895],[134.50789350807884,35.30601064737348],[134.51068403514,35.28880239521436],[134.51006391841509,35.27187836389541],[134.50479292175595,35.25820994673171],[134.49218387268752,35.244334824892235],[134.4627283057667,35.22531789850217],[134.44846561119897,35.21867747718383],[134.43647667885563,35.21748891877854]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-35\",\"name\":\"Yamaguchi\",\"name_alt\":\"Yamaguti\",\"name_local\":\"山口県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chugoku\",\"postal\":\"YC\",\"region_code\":\"JPN-CGK\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[132.15390058700004,33.72728099200005],[132.1321720710001,33.712347723],[132.1285099620002,33.72109609600007],[132.13851972700004,33.72984446800014],[132.14267011800004,33.738511460000055],[132.13900800900004,33.74062734600015],[132.13949628999998,33.747788804000024],[132.14201907600003,33.75112539300012],[132.1442163420002,33.75413646000014],[132.14682050899998,33.75844961100013],[132.14893639400012,33.76296621300018],[132.1534123060001,33.762722072000045],[132.14771569100017,33.75413646000014],[132.15137780000023,33.75035228100002],[132.15186608200023,33.746161200000174],[132.14714603000013,33.73895905200014],[132.15390058700004,33.72728099200005]]],[[[131.97120201900023,33.7711449240001],[131.95818118600008,33.76935455900012],[131.95036868600008,33.77313873900012],[131.94516035200016,33.77912018400015],[131.96322675899998,33.79165273600013],[131.97510826900023,33.79657623900003],[131.98829186300006,33.786932684000035],[131.98585045700023,33.77753327000012],[131.97120201900023,33.7711449240001]]],[[[132.25245201900023,33.78534577],[132.27409915500002,33.77220286700005],[132.2729598320001,33.768540757],[132.2573348320001,33.766017971000096],[132.23633873800006,33.768540757],[132.20443769600016,33.782131252000156],[132.18832441499998,33.77968984600004],[132.19532311300006,33.79352448100012],[132.20875084700006,33.80011627800009],[132.22575931100002,33.799872137000065],[132.24366295700005,33.79328034100017],[132.25245201900023,33.78534577]]],[[[132.0198673840001,33.86041901200004],[132.02165774800008,33.85350169500013],[132.02182050900004,33.84800853100002],[132.00196373800011,33.853013414000046],[131.99952233200005,33.85769277600015],[132.00586998800011,33.85736725500014],[132.01124108200005,33.85630931200008],[132.01262454500014,33.86680735900016],[132.01750735800013,33.86672597900018],[132.01807701900006,33.861761786000116],[132.0198673840001,33.86041901200004]]],[[[132.07016035200004,33.86741771000011],[132.0647892590001,33.862982489000146],[132.05925540500007,33.86908600500011],[132.0565698580002,33.87730540600005],[132.06267337300008,33.88231028900016],[132.06584720100014,33.87824127800012],[132.06682376400008,33.87274811400009],[132.07016035200004,33.86741771000011]]],[[[132.29818769600016,33.93048737200003],[132.32618248800006,33.896307684000035],[132.34009850400017,33.89720286700013],[132.35320071700002,33.903387762000094],[132.37403405000012,33.91681549700009],[132.3802189460001,33.917547919000114],[132.3952742850001,33.915472723],[132.40121504000004,33.91681549700009],[132.40707441500015,33.92304108300014],[132.40967858200017,33.93390534100003],[132.4155379570001,33.937933661],[132.42758222700004,33.939886786000145],[132.45394941500004,33.937567450000174],[132.469493035,33.937933661],[132.45785566500004,33.927679755000085],[132.43653405000023,33.90355052300005],[132.42847741,33.90253327000009],[132.41895592500012,33.90566640800016],[132.39454186300017,33.89931875200013],[132.38477623800023,33.89419179900004],[132.38070722700016,33.88641998900012],[132.37940514400017,33.86188385600009],[132.37745201900012,33.85203685100008],[132.37403405000012,33.841701565000065],[132.36255944100017,33.845445054000024],[132.34058678500006,33.844956773000106],[132.33236738400015,33.84796784100003],[132.32837975400017,33.85651276200004],[132.3296004570001,33.86579010600009],[132.32862389400012,33.875067450000145],[132.31861412900005,33.88328685100005],[132.311778191,33.875881252000156],[132.29810631600017,33.88401927300008],[132.29184004000004,33.87872955900009],[132.28882897200006,33.868353583],[132.28451582100004,33.86155833500008],[132.2721460300002,33.86041901200004],[132.25464928500006,33.865668036000116],[132.24366295700005,33.86155833500008],[132.23617597700004,33.86904531500012],[132.23121178500017,33.863918361000046],[132.22641035200016,33.860663153000175],[132.2163192070001,33.85537344],[132.20826256600017,33.87518952000012],[132.19336998800011,33.897650458000115],[132.18628991000017,33.92121002800015],[132.20191491000006,33.944769598000036],[132.22608483200023,33.95270416900017],[132.2535913420002,33.95010000200007],[132.2794702480002,33.941229559000035],[132.29818769600016,33.93048737200003]]],[[[132.35531660200004,33.93939850500006],[132.353363477,33.93789297100001],[132.34050540500007,33.947170315000065],[132.33798261800004,33.94818756700012],[132.33871503999998,33.95262278900019],[132.34327233200023,33.95856354400003],[132.34970136800015,33.96027252800003],[132.35482832100016,33.951198635000125],[132.35531660200004,33.93939850500006]]],[[[130.86426842500012,33.97044505400008],[130.86101321700008,33.96898021000011],[130.86467532600003,33.982692776000036],[130.86793053500017,33.97801341400013],[130.86426842500012,33.97044505400008]]],[[[131.86068769600004,33.9892845720001],[131.86329186300011,33.98049551000004],[131.868907097,33.9819196640001],[131.87590579500002,33.9720726580001],[131.87745201900023,33.968369859000134],[131.87053470100014,33.96898021000011],[131.8612573580002,33.959947007000025],[131.8585718110002,33.94969310100008],[131.84799238400015,33.94529857],[131.8360294930002,33.93349844000012],[131.83033287900005,33.931870835],[131.82219485800013,33.92959219000012],[131.819590691,33.92788320500004],[131.81918379000004,33.93394603100002],[131.81609134200002,33.93537018400009],[131.8062443370002,33.94065989800008],[131.80600019600016,33.949571031000104],[131.8130802740002,33.955877997000144],[131.820078972,33.95701732],[131.81836998800011,33.953884182000095],[131.8214624360002,33.94985586100013],[131.82715905000018,33.94782135600012],[131.8335067070001,33.948757229000094],[131.8330998060001,33.955471096000124],[131.84148196700008,33.95713939000014],[131.8478296230002,33.953884182000095],[131.84937584700006,33.95876699400016],[131.8467716810002,33.96617259300017],[131.84253991,33.96902090100009],[131.84961998800006,33.9720726580001],[131.85320071700002,33.97475820500007],[131.85450280000023,33.979030666000185],[131.85808353000013,33.983994859000134],[131.86068769600004,33.9892845720001]]],[[[132.26986738400004,34.00010814000011],[132.27125084700018,33.99591705900018],[132.2639266290001,33.99770742400007],[132.2609155610002,34.00153229400017],[132.26368248800023,34.01129791900003],[132.26986738400004,34.00010814000011]]],[[[131.73349043100004,33.98781972900015],[131.7233179050002,33.9798851580001],[131.71965579500014,33.98737213700015],[131.71216881600017,33.992336330000015],[131.70801842500023,33.99827708500014],[131.70248457100004,34.00010814000011],[131.69556725400005,34.006374416000156],[131.69141686300006,34.00792064000011],[131.69548587300002,34.01166413000014],[131.70248457100004,34.01943594000012],[131.70248457100004,34.02940501500002],[131.7125757170002,34.034369208000086],[131.7086694670002,34.027573960000055],[131.71111087300014,34.01618073100009],[131.71045983200005,34.00482819200012],[131.72046959700006,33.993597723000036],[131.73349043100004,33.98781972900015]]],[[[131.76148522200018,34.03843821800014],[131.75530032600003,34.0312767600001],[131.7496037120002,34.03062571800014],[131.74480228000002,34.025702216000084],[131.73666425900004,34.02313873900009],[131.72494550900004,34.03359609600015],[131.72730553500017,34.03770579600011],[131.73340905000018,34.03904857000002],[131.73609459700006,34.03969961100013],[131.74040774800002,34.04063548400005],[131.7422794930002,34.03876373900006],[131.75066165500007,34.036891994000186],[131.75676517000008,34.04653554900007],[131.7609155610002,34.04783763200005],[131.76596113400004,34.04474518400018],[131.7657169930001,34.04059479400006],[131.76148522200018,34.03843821800014]]],[[[130.79729251400008,34.10541413000014],[130.7960718110002,34.09357330900018],[130.79314212300008,34.098456122000144],[130.7842716810002,34.10057200700005],[130.77808678500006,34.10415273600013],[130.7749129570001,34.11660390800013],[130.78239993600008,34.11314524900014],[130.79265384200008,34.10984935100011],[130.7960718110002,34.109116929],[130.79729251400008,34.10541413000014]]],[[[130.85092207099999,34.347113348],[130.84725996200015,34.34210846600014],[130.83725019600016,34.35362376500011],[130.85279381600017,34.356472072000045],[130.86133873800006,34.36265696800011],[130.86841881600006,34.36701080900009],[130.86532636800004,34.37636953300013],[130.87183678500006,34.37295156500012],[130.87305748800006,34.37063222900015],[130.87712649800002,34.37014394700016],[130.88021894600004,34.36635976800015],[130.8702905610002,34.356472072000045],[130.86508222700004,34.35455963700018],[130.8565373060001,34.352687893],[130.85092207099999,34.347113348]]],[[[131.41846764400023,34.500026760000125],[131.40650475400005,34.490952867000104],[131.40113366000017,34.50560130400011],[131.41301517000002,34.51512278900013],[131.413910352,34.50967031500009],[131.41846764400023,34.500026760000125]]],[[[131.2759708990002,34.52151113500004],[131.2838647800002,34.51463450700014],[131.2885848320001,34.51573314000008],[131.29127037900017,34.513861395000035],[131.29346764400006,34.50967031500009],[131.29013105600004,34.50653717700011],[131.27686608200023,34.49909088700004],[131.2759708990002,34.52151113500004]]],[[[131.68046919239944,34.66227732973503],[131.6789188987881,34.64894481115404],[131.67922895715074,34.64062490501534],[131.6818644545809,34.625225327986485],[131.68558515762905,34.61576854118529],[131.70331017462476,34.58104197850447],[131.7034135283118,34.57104258934406],[131.69938276600195,34.5622834345328],[131.69349165261772,34.55504873311257],[131.6784538107947,34.52368113917329],[131.6688419943625,34.50978017801272],[131.66713667201984,34.49608592422605],[131.6720976103164,34.479394436004426],[131.68108930912436,34.4678447541322],[131.68155439711782,34.45660512972306],[131.67922895715074,34.44709666607834],[131.68325971856123,34.437743232064534],[131.69648888615322,34.4289840763541],[131.7684741561575,34.41614248328899],[131.77265994719895,34.40973460506798],[131.77250491756783,34.40376597821735],[131.7554000181963,34.38177765581416],[131.7513692567857,34.37350942651888],[131.7512659039983,34.36345836051494],[131.75322960786005,34.351753648112336],[131.75839725173168,34.33746511512301],[131.7647534531093,34.324055081276995],[131.77141971195013,34.31426239679227],[131.78196170526851,34.30653676985604],[131.79508751827436,34.30173086208963],[131.8126575047401,34.299637966119306],[131.8454203626595,34.30307444922646],[131.89632164872503,34.31769887989931],[131.90805219865004,34.31955923097375],[131.92055789583017,34.3157351751383],[131.93089318267428,34.3075702995297],[131.939471470332,34.303177802013934],[131.9467578467971,34.30604584434032],[131.95218387308725,34.31671702706909],[131.9586951031964,34.33397695607174],[131.96226077661348,34.33966136298143],[131.96623986208002,34.34343374287293],[131.99362837055284,34.35345897135453],[131.9985893088494,34.3583423943859],[132.00199995263577,34.36394928603083],[132.00029463119225,34.37242422090107],[131.99688398740616,34.37800527592266],[131.98990766840424,34.38617015243055],[131.98773725806805,34.40177643503445],[131.9916129898476,34.41335195622786],[132.05042077000243,34.45784536407233],[132.06111779205256,34.43505605689158],[132.0708846381156,34.35945343752606],[132.12643680231653,34.24876251937481],[132.14400678878238,34.22835032810521],[132.16059492421644,34.21801504216053],[132.1714469749978,34.215534573461824],[132.19981733630098,34.201530260413236],[132.2327352238515,34.19202179586917],[132.24040774780988,34.18980540594744],[132.24366295700005,34.166937567],[132.23894290500007,34.14264557500003],[132.22754967500012,34.132025458000086],[132.21371504000004,34.12433502800015],[132.20191491000006,34.108587958],[132.199066602,34.06476471600014],[132.21094811300023,34.02521393400009],[132.21322675900004,33.98965078300013],[132.18148847700016,33.95783112200009],[132.15723717500006,33.94891998900006],[132.14258873800006,33.94643789300012],[132.13559003999998,33.93842194200009],[132.13379967500012,33.913397528000175],[132.13892662900005,33.894964911000145],[132.15992272200006,33.85732656500015],[132.16163170700005,33.841701565000065],[132.15007571700002,33.83014557500006],[132.10580488399998,33.809027411000116],[132.07593834700018,33.78705475500011],[132.05591881600006,33.77716705900009],[132.04135175900004,33.77627187700001],[132.0444442070001,33.79328034100017],[132.06544030000012,33.80695221600011],[132.06885826900023,33.81167226800004],[132.0763452480002,33.826727606000034],[132.07846113400004,33.83490631700015],[132.08716881600012,33.82510000200001],[132.09888756600017,33.82363515800013],[132.10889733200005,33.830267645000035],[132.11329186300023,33.84487539300012],[132.109222852,33.86261627800003],[132.09945722700016,33.872259833],[132.08708743600013,33.878566799000126],[132.075450066,33.88641998900012],[132.0577905610002,33.89524974200016],[131.99480228000007,33.91347890800016],[131.97234134200008,33.91681549700009],[131.966563347,33.9215355490001],[131.94190514400012,33.950913804],[131.93091881600006,33.95697663],[131.90333092500023,33.96670156500012],[131.89356530000012,33.97142161700005],[131.87086022200006,33.98851146],[131.85206139400012,33.99835846600011],[131.83366946700008,33.99469635600015],[131.812266472,33.97142161700005],[131.82650800900004,33.97142161700005],[131.82650800900004,33.96523672100007],[131.79135175900015,33.96405670800006],[131.77955162900017,33.96967194200015],[131.77751712300008,33.98574453300013],[131.80730228000007,34.005804755],[131.82390384200014,34.02016836100016],[131.82276451900006,34.02667877800015],[131.8136499360002,34.02875397300009],[131.79957116000006,34.0377464860001],[131.74146569100012,34.05621979400003],[131.703379754,34.044175523000106],[131.688731316,34.04718659100011],[131.67611738400004,34.04018789300012],[131.64486738400015,34.02887604400014],[131.6334741550002,34.02667877800015],[131.606211785,34.02667877800015],[131.60173587300002,34.03001536700019],[131.59815514400017,34.0360375020001],[131.5932723320001,34.037298895],[131.5851343110002,34.02667877800015],[131.59400475400005,34.01532623900009],[131.59742272200006,34.00263092700011],[131.59302819100006,33.991685289000074],[131.57886803500006,33.98574453300013],[131.57178795700005,33.985174872000144],[131.56763756600006,33.98627350500011],[131.56568444100017,33.98965078300013],[131.5651961600001,33.99628327],[131.56226647200018,34.00462474200016],[131.5561629570001,34.00519440300003],[131.55111738400015,34.00214264500012],[131.55103600400017,33.99941640800007],[131.519297722,34.008490302000084],[131.51050866000006,34.012396552000055],[131.49301191499998,34.02814362200003],[131.48292076900023,34.032049872000115],[131.46900475400017,34.02667877800015],[131.47380618600008,34.01862213700012],[131.47429446700002,34.013983466000084],[131.46900475400017,34.00617096600011],[131.46631920700005,34.00678131700006],[131.4567163420002,33.99933502800009],[131.45533287900005,33.99315013200011],[131.45199628999998,33.98956940300015],[131.45183353000002,33.985907294000086],[131.44955488400015,33.982245184000035],[131.44117272200018,33.97890859600001],[131.43433678500017,33.98021067900011],[131.42823326900012,33.985907294000086],[131.4235132170002,33.99310944200012],[131.42058353000007,33.99941640800007],[131.41553795700023,33.989406643000095],[131.4096785820001,33.98305898600016],[131.40251712300002,33.98139069200015],[131.39332116000017,33.98574453300013],[131.40219160200004,33.99933502800009],[131.40479576900012,34.01727936400012],[131.40064537900017,34.03294505400011],[131.38990319100017,34.03974030200014],[131.38314863400004,34.03302643400009],[131.35572350400017,33.989447333000086],[131.33253014400023,33.962103583],[131.3219507170002,33.95307038],[131.2722274100001,33.92682526200015],[131.2579858730002,33.92479075700014],[131.24984785200016,33.937933661],[131.24252363400015,33.9344750020001],[131.23357181100002,33.93305084800012],[131.22584069100006,33.935003973000065],[131.22266686300017,33.94135163000003],[131.22071373800011,33.950384833000115],[131.21583092500023,33.95075104400003],[131.20923912900017,33.94725169500005],[131.20215905000012,33.944769598000036],[131.20036868600002,33.94253164300012],[131.19467207100016,33.93764883000016],[131.1870223320001,33.932766018000095],[131.17823326900023,33.93048737200003],[131.17318769600004,33.93398672100001],[131.1706649100001,33.94188060099999],[131.16026595300016,33.95279605600014],[131.16153708300018,33.964374254000134],[131.16684003999998,33.97239817900011],[131.16602623800006,33.977118231000034],[131.15739993600008,33.97890859600001],[131.1239987910001,33.999931396000036],[131.10316468400006,34.03021689000015],[131.07242398400004,34.03699727700011],[131.035594808,34.047975095000155],[131.01396225400018,34.01476201500013],[130.96859785200016,33.97142161700005],[130.93208630100006,33.945365284],[130.92629066500004,33.92269067000011],[130.9157534550002,33.91398982200015],[130.89918053500006,33.92426178600006],[130.8937280610002,33.932318427],[130.87940514400012,33.937933661],[130.88022440100022,33.95043178000019],[130.89942467500018,33.94985586100013],[130.90965033600017,33.973651604000096],[130.90772545700023,33.98493073100012],[130.91342207100004,33.99941640800007],[130.91713452200005,34.01091306300016],[130.91281170100015,34.021920678000114],[130.90528405000012,34.029608466000084],[130.91257739700004,34.053341562000085],[130.90391367800024,34.06582577800019],[130.87842858200023,34.06785716400013],[130.87322024800002,34.07819245000003],[130.86931399800002,34.091742255],[130.86280358200023,34.10179271000008],[130.86158287900005,34.11273834800015],[130.87322024800002,34.12909577000006],[130.88648522200018,34.13898346600017],[130.89926191500004,34.14565664300012],[130.91114342500023,34.15448639500009],[130.92090905000012,34.17064036700005],[130.92335045700023,34.18378327],[130.92025800900015,34.19257233300006],[130.91578209700018,34.19969310100011],[130.91041100400005,34.224920966000084],[130.8967391290001,34.244818427000084],[130.8937280610002,34.25604889500009],[130.89161217500006,34.25983307500009],[130.87623131600006,34.27000560100005],[130.87159264400012,34.27924225500011],[130.87533613400015,34.286688544000086],[130.8820093110002,34.293402411000116],[130.88624108200023,34.30044179900001],[130.88965905000023,34.331203518000095],[130.89307701900023,34.34349192900014],[130.89991295700017,34.35504791900017],[130.91114342500023,34.35089752800003],[130.9274194670002,34.348944403000175],[130.94190514400023,34.350978908000016],[130.94825280000018,34.3587914080001],[130.95476321700008,34.361721096000124],[130.99610436300011,34.36933014500006],[131.01172936300006,34.37596263200011],[131.03360681,34.38178924000012],[131.01788186800005,34.39329681000014],[131.00359134200008,34.39288971600011],[130.96509850400017,34.39996979400017],[130.95508873800011,34.403469143000066],[130.94890384200014,34.39500560100011],[130.94629967500012,34.39240143400012],[130.93392988399998,34.39598216400019],[130.93921959700006,34.40770091400016],[130.94776451900006,34.418931382],[130.95875084700006,34.42743561400012],[130.97639607600004,34.43896729200016],[130.98973169800004,34.435038253000144],[131.00284535400024,34.42594628900015],[131.00114993600002,34.41486237200003],[131.00977623800023,34.41030508000007],[131.02263431100008,34.409369208],[131.03728274800008,34.411118882],[131.0509546230002,34.41518789300015],[131.06120853000002,34.42084381700015],[131.07471764400012,34.42475006700012],[131.09937584700018,34.413275458],[131.11571755700007,34.40979405500018],[131.13392298800002,34.411561804000044],[131.1407983730002,34.37848541900014],[131.15365644600004,34.36933014500006],[131.17269941500004,34.367743231000034],[131.178957564,34.378775997000034],[131.1917016570002,34.39364779300003],[131.173594597,34.41022370000009],[131.17058353000013,34.41681549700017],[131.17131454900013,34.42953460500014],[131.19271894599999,34.42475006700012],[131.20736738400004,34.42894114800008],[131.23487389400023,34.43349844],[131.25263176100012,34.43738699200016],[131.26453684000015,34.428087194000014],[131.28121391700003,34.40795780000012],[131.2662558520001,34.4094488410001],[131.2510269640002,34.41913055400012],[131.24101992700017,34.421183598000184],[131.22950280000006,34.41771067900008],[131.22201582100004,34.40839264500012],[131.21607506600017,34.39691803600009],[131.21176191499998,34.38377513200011],[131.20899498800023,34.36933014500006],[131.21607506600017,34.37230052300008],[131.22877037900005,34.38019440300015],[131.23617597700004,34.382310289000074],[131.28448215100005,34.384211288000145],[131.32309029500007,34.393011809000186],[131.33473090300006,34.416114291000056],[131.36505001200013,34.41565087500008],[131.414317254,34.423976955000015],[131.4133146280001,34.45387373300012],[131.42640717300017,34.46564545000014],[131.4562280610002,34.49038320500013],[131.46192467500012,34.49925364799999],[131.45476321700008,34.519110419000086],[131.46176191500004,34.523504950000174],[131.4826766290001,34.52757396000014],[131.52076256600017,34.551906643],[131.53646894600016,34.56561920800014],[131.55103600400017,34.581529039000046],[131.55543053500006,34.592189846000096],[131.55729919400002,34.614856999],[131.5647924990001,34.61949431700019],[131.58518438800016,34.61842166400008],[131.5958764980002,34.61684804900007],[131.59424889400006,34.62567780200014],[131.59410358500023,34.64205125400004],[131.5968715730002,34.65958937200004],[131.61524189000014,34.66488063600015],[131.62940514400023,34.65924713700015],[131.65641471600006,34.65358629000012],[131.67085855700023,34.66281777400009],[131.67335498800017,34.670931860000096],[131.67562866336792,34.67214318498067],[131.6796245203553,34.66648457050654],[131.68046919239944,34.66227732973503]]],[[[131.1486108730002,34.760321356000176],[131.14014733200017,34.75910065300015],[131.13607832099999,34.762030341000084],[131.12810306100008,34.78432851800012],[131.13575280000006,34.79100169499999],[131.147715691,34.78961823100009],[131.1499129570001,34.78742096600011],[131.14820397200018,34.78375885600015],[131.14828535200016,34.78066640800007],[131.15219160200016,34.779079494000044],[131.15748131600006,34.77891673400008],[131.16114342500012,34.775824286],[131.16049238400015,34.76850006700009],[131.1563419930002,34.76341380400005],[131.1486108730002,34.760321356000176]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-41\",\"name\":\"Saga\",\"name_alt\":null,\"name_local\":\"佐賀県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":null,\"postal\":\"SG\",\"region_code\":null},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[129.9767358730002,33.53139883000007],[129.96827233200017,33.526109117000104],[129.96452884200002,33.53188711100016],[129.96656334700006,33.53876373900009],[129.96322675900004,33.53921133000007],[129.97706139400023,33.5442162130001],[129.9767358730002,33.53139883000007]]],[[[129.86878891900002,33.53939995000006],[129.88583138300007,33.53584022100013],[129.910059486,33.547204999000044],[129.942149285,33.531683661000116],[129.96127363399998,33.512762762000094],[129.9569968010001,33.502782411000155],[129.94554470200003,33.49085512700013],[129.94120890100012,33.47953658700011],[129.95333126000017,33.47365559600017],[129.96264789400018,33.48260727800009],[129.96690162200017,33.472528834000016],[129.966868876,33.46302701300014],[129.97538907600003,33.45414314300014],[129.98894602800024,33.44885137800016],[130.016856316,33.44578685099999],[130.02881920700023,33.45066966400016],[130.036274927564,33.45759283361518],[130.03643517494348,33.457596340199004],[130.09074710479499,33.458784897704916],[130.1211328468033,33.462195543289724],[130.16350752205452,33.46173045529635],[130.21203169220897,33.468706773398836],[130.23084191302397,33.46948191975484],[130.25895389190853,33.46798330298709],[130.27523196808093,33.46446930641328],[130.28995975244075,33.45883657544762],[130.350834589245,33.42700389351491],[130.3658724310678,33.42085439681291],[130.37967003764217,33.41315460919786],[130.40106408084264,33.39406016754266],[130.4142932484347,33.38654124618158],[130.42623050303533,33.38695465823098],[130.44664269430493,33.40178579447881],[130.45863162664833,33.40852956768542],[130.48689863516384,33.41961416336302],[130.50669070880903,33.42354157198595],[130.51929975787758,33.42085439681291],[130.52663781118602,33.41423981391621],[130.52891157430977,33.40933055336241],[130.5364046572491,33.37698110569353],[130.53666303876824,33.36969472922833],[130.53190880694578,33.33788788481813],[130.5261210472486,33.33085988987203],[130.51743940590384,33.327681790082565],[130.5076725607402,33.32651907009905],[130.49583865802768,33.32199738227338],[130.48395307757247,33.31481435769648],[130.47790693635616,33.30427236617682],[130.470930617354,33.28729665801437],[130.46503950397008,33.28132803026445],[130.45336062998902,33.26471405730787],[130.4442139024496,33.259727281488935],[130.42359500470567,33.259107163864556],[130.41408654196024,33.25448212325141],[130.4041646671657,33.24815176029553],[130.39083214678627,33.243242498842264],[130.38122033125333,33.23644704789292],[130.37470910024493,33.227248644409414],[130.37021325084098,33.2074565698649],[130.36664757742372,33.19978261977208],[130.36230675675156,33.19520925510308],[130.35372846909368,33.18877554025899],[130.34675215099108,33.17991303176099],[130.3453568870108,33.16712311553941],[130.34628706299756,33.15686534306114],[130.3493359715778,33.14216339802242],[130.34936061834557,33.14204455304544],[130.33741295700023,33.14337799700017],[130.31478925900015,33.14850495000009],[130.30054772200018,33.149644273000135],[130.29044274400022,33.14958862900015],[130.2581486340001,33.17865631700003],[130.2413964770001,33.188234066000135],[130.21930161999998,33.159465952000076],[130.17823326900006,33.13491445500013],[130.15088951900012,33.11188385600015],[130.12756487200014,33.12093662000011],[130.13194608500007,33.10197172699999],[130.14512925400018,33.08483626000016],[130.1604923840001,33.05410390800013],[130.20390147900017,32.99178161500005],[130.22231019900008,32.975205048000035],[130.21914692699997,32.955059859000144],[130.20425778500024,32.95087110800013],[130.20410127959255,32.94357262887702],[130.10340783070686,32.959952296960935],[130.0595345395873,32.97323314139642],[130.03219770705854,32.98754751190809],[130.01576460215475,32.99976898914748],[129.99716108601527,33.01718394598241],[129.92517581601103,33.06878286403803],[129.9171142931901,33.078317166104355],[129.915408969948,33.086740424131264],[129.93091189976442,33.09896190047128],[129.93540775006775,33.10741099691997],[129.93587283716184,33.11846975417576],[129.93013675340845,33.132086492697496],[129.91933637857093,33.14428213151517],[129.90517703679083,33.14986318653693],[129.86218224481482,33.159862575697346],[129.82921268132023,33.17508128557286],[129.8150016617973,33.18595917387684],[129.80647505098275,33.196682034348484],[129.80321943592824,33.20629384988122],[129.7981034689,33.216396592728486],[129.7666841981173,33.25610993122835],[129.7594494975963,33.268202216359256],[129.75820926324687,33.27910594308479],[129.7641003766311,33.288226834001264],[129.79479617700204,33.31724315045112],[129.8101957540309,33.32509796769709],[129.81454436463432,33.331246004800775],[129.8145451180002,33.331244208],[129.814707879,33.33087799700009],[129.8198348320001,33.30475495000003],[129.82715905000012,33.29389069200015],[129.83790123800023,33.28538646000011],[129.85157311300006,33.28001536700005],[129.84799238399998,33.29340241100006],[129.84132811800018,33.31597731400008],[129.846295401,33.34796460600005],[129.8590081340001,33.367514084],[129.87102535699998,33.378177884000124],[129.86186331800016,33.390046186000134],[129.8686629570001,33.39744700700008],[129.816392187,33.4261020310001],[129.79688561300023,33.444484768000095],[129.78725722300024,33.45451903900006],[129.79717159900005,33.46758179600015],[129.80424474499998,33.48182305800013],[129.81844004700005,33.48421177600015],[129.8354598320001,33.46637604400003],[129.84131920700023,33.45441315300009],[129.84888756600017,33.44245026200015],[129.86207116000006,33.43699778900013],[129.8713485040001,33.44293854400014],[129.86687259200002,33.45673248900006],[129.84395138900007,33.48121705400017],[129.83683473900007,33.51325412100006],[129.85457924400012,33.53285385400012],[129.85318715600013,33.55250171100006],[129.86878891900002,33.53939995000006]]],[[[129.88990319100012,33.55349355700015],[129.87663821700008,33.5442162130001],[129.87273196700008,33.545355536000145],[129.8722436860002,33.553778387000094],[129.87232506600017,33.559759833000115],[129.87712649800008,33.56427643400015],[129.8834741550002,33.565863348],[129.89063561300023,33.558050848],[129.88990319100012,33.55349355700015]]],[[[129.76303144600016,33.58279043200018],[129.7701929050002,33.57221100500014],[129.7662866550002,33.568996486000074],[129.76246178500017,33.56622955900012],[129.76075280000006,33.56382884300017],[129.75782311300017,33.56346263200005],[129.75098717500023,33.56720612200009],[129.7442326180002,33.57013580900012],[129.74236087300002,33.57237376500011],[129.73975670700023,33.57562897300015],[129.74097741000017,33.58116282800016],[129.7452091810001,33.58279043200018],[129.750254754,33.58091868700002],[129.75033613399998,33.58091868700002],[129.76303144600016,33.58279043200018]]],[[[129.86548912900005,33.59121328300013],[129.85808353000007,33.58779531500012],[129.84815514400023,33.59979889500015],[129.84961998800011,33.6025658220001],[129.84815514400023,33.60460032800013],[129.8492944670002,33.60635000200013],[129.85206139400023,33.60891347900004],[129.85434004000004,33.61212799700009],[129.85499108200005,33.61766185100011],[129.86272220100003,33.618272203000075],[129.86150149800008,33.60728587400003],[129.85987389400023,33.60460032800013],[129.86117597700016,33.60024648600013],[129.86426842500023,33.59837474200016],[129.86614017000002,33.597316799000126],[129.86614017000002,33.593207098000065],[129.86548912900005,33.59121328300013]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-40\",\"name\":\"Fukuoka\",\"name_alt\":\"Hukuoka\",\"name_local\":\"福岡県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kyushu\",\"postal\":\"FO\",\"region_code\":\"JPN-KYS\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[130.31324303500006,33.62051015800016],[130.30022220100014,33.60643138200011],[130.29330488400015,33.613023179],[130.29639733200023,33.62018463700004],[130.30144290500007,33.63821035400015],[130.31324303500006,33.62051015800016]]],[[[130.37940514400012,33.76093170800006],[130.37973066500015,33.75800202000012],[130.37110436300023,33.75938548400002],[130.36605879000004,33.75877513200005],[130.36182701900006,33.75413646000014],[130.3599552740002,33.76780833500008],[130.36972089900004,33.76683177299999],[130.37940514400012,33.76093170800006]]],[[[130.50407962300008,33.90306224200016],[130.50709069100017,33.89492422100015],[130.49496504000015,33.899562893000066],[130.49211673300013,33.90517812700007],[130.48650149800002,33.9142113300001],[130.49805748800023,33.912827867000104],[130.49903405000023,33.907375393000066],[130.50407962300008,33.90306224200016]]],[[[130.43742923300007,33.89866771],[130.42432701900012,33.88967519700016],[130.4119572270001,33.89826080900018],[130.407481316,33.90884023600013],[130.41407311300011,33.91299062700007],[130.42969811300006,33.91555410400018],[130.44206790500013,33.913397528000175],[130.43742923300007,33.89866771]]],[[[130.99292883500007,33.89766466700014],[131.00177079800008,33.88722868400005],[130.99982932100008,33.8738635400001],[130.98460705500023,33.87396048700013],[130.98808132899998,33.864284161000015],[130.9906843900001,33.85682718300005],[130.98433485600017,33.84869791800013],[130.97974694100017,33.84271881700012],[130.96357418600005,33.83323528200016],[130.96192467500012,33.82005442900008],[130.96168053500017,33.81622955900015],[130.96599368600008,33.81305573100009],[130.98551079600023,33.808664865000154],[131.00694470900024,33.813087590000166],[131.00359134200008,33.77968984600004],[131.0060327480002,33.77228424700003],[131.00977623800023,33.746161200000174],[131.019786004,33.726548570000105],[131.05079186300023,33.681870835000055],[131.06446373800006,33.65326569200009],[131.08130944100017,33.634955145000035],[131.10297618100017,33.62425323800015],[131.11997169700024,33.62633852100011],[131.14587465700012,33.62163367200013],[131.18869910100003,33.61602829300013],[131.20089214658574,33.61270976599219],[131.1951241398672,33.607302964859954],[131.18220503063768,33.5970968692251],[131.17951785726328,33.59273021103043],[131.17517703659084,33.58332510017321],[131.17517703659084,33.56844228798123],[131.18313520662448,33.545446275225444],[131.18111982591913,33.535937812479986],[131.1750736838034,33.52549917194928],[131.16298139867266,33.51304515251188],[131.15709028528843,33.50361420323283],[131.14949384956165,33.495656033199296],[131.13972700439766,33.491547756523815],[131.1244824569998,33.49015249344275],[131.06427941286475,33.49811066347628],[131.0042313992598,33.500126044181556],[130.99508466992168,33.498937485776324],[130.9806669457229,33.49578522440858],[130.95053958523377,33.48519155514613],[130.93798221210935,33.47733673790019],[130.92557986861598,33.46612295191274],[130.9108004092116,33.44875967102199],[130.88584069169448,33.425401923060306],[130.8748336121814,33.4078319356952],[130.86491173738685,33.38468089420796],[130.84129560790595,33.3520213890758],[130.83850507994546,33.34551015896669],[130.83710981686457,33.33974823679175],[130.83633466960944,33.331454169074576],[130.83695478723374,33.32251414621079],[130.8430009302488,33.294402167326226],[130.84114057827506,33.27721975448809],[130.8262577660831,33.23298472816272],[130.83075361458756,33.21471710880748],[130.85214765778815,33.159733385387526],[130.8560233913665,33.14299022122184],[130.83473270095342,33.0809268251128],[130.78682864842344,33.10051219318315],[130.72864098499346,33.13446360950799],[130.71592858313764,33.13914032606512],[130.70089074131468,33.140587266888886],[130.68518110592336,33.14004466452958],[130.67308882079234,33.13531627022975],[130.64983442561837,33.11312124315084],[130.635881789413,33.10428457307465],[130.6177950381106,33.10113231080753],[130.57986453721858,33.09836762126862],[130.56368981293454,33.09167552310696],[130.54648156077533,33.07746450538259],[130.52178022657628,33.0615998403603],[130.50824100242042,33.04945587838604],[130.4989909202948,33.033978786991426],[130.50048953706246,33.01620209405134],[130.5019364769868,33.00984589357323],[130.49945600828826,33.00248200094369],[130.49821577393882,33.000234076241654],[130.48395307757247,32.99467886054096],[130.44514407843647,32.989511217568676],[130.4293827662017,32.98064870907065],[130.4291583888049,32.980522543166174],[130.42644290500002,33.00714752800009],[130.42847741000017,33.05052317900008],[130.42164147200006,33.069769598],[130.42221113400004,33.08258698100006],[130.42066491000017,33.08759186400009],[130.41504967500018,33.09162018400015],[130.4096785820001,33.09267812700001],[130.4044702480002,33.093003648000135],[130.400157097,33.09503815300015],[130.37940514400012,33.107855536],[130.37224368600008,33.11489492400007],[130.36752363400015,33.126369533000016],[130.36801191500015,33.13495514500012],[130.3647567070001,33.14032623900009],[130.34936061834557,33.14204455304544],[130.3493359715778,33.14216339802242],[130.34628706299756,33.15686534306114],[130.3453568870108,33.16712311553941],[130.34675215099108,33.17991303176099],[130.35372846909368,33.18877554025899],[130.36230675675156,33.19520925510308],[130.36664757742372,33.19978261977208],[130.37021325084098,33.2074565698649],[130.37470910024493,33.227248644409414],[130.38122033125333,33.23644704789292],[130.39083214678627,33.243242498842264],[130.4041646671657,33.24815176029553],[130.41408654196024,33.25448212325141],[130.42359500470567,33.259107163864556],[130.4442139024496,33.259727281488935],[130.45336062998902,33.26471405730787],[130.46503950397008,33.28132803026445],[130.470930617354,33.28729665801437],[130.47790693635616,33.30427236617682],[130.48395307757247,33.31481435769648],[130.49583865802768,33.32199738227338],[130.5076725607402,33.32651907009905],[130.51743940590384,33.327681790082565],[130.5261210472486,33.33085988987203],[130.53190880694578,33.33788788481813],[130.53666303876824,33.36969472922833],[130.5364046572491,33.37698110569353],[130.52891157430977,33.40933055336241],[130.52663781118602,33.41423981391621],[130.51929975787758,33.42085439681291],[130.50669070880903,33.42354157198595],[130.48689863516384,33.41961416336302],[130.45863162664833,33.40852956768542],[130.44664269430493,33.40178579447881],[130.42623050303533,33.38695465823098],[130.4142932484347,33.38654124618158],[130.40106408084264,33.39406016754266],[130.37967003764217,33.41315460919786],[130.3658724310678,33.42085439681291],[130.350834589245,33.42700389351491],[130.28995975244075,33.45883657544762],[130.27523196808093,33.46446930641328],[130.25895389190853,33.46798330298709],[130.23084191302397,33.46948191975484],[130.21203169220897,33.468706773398836],[130.16350752205452,33.46173045529635],[130.1211328468033,33.462195543289724],[130.09074710479499,33.458784897704916],[130.03643517494348,33.457596340199004],[130.036274927564,33.45759283361518],[130.0402124360002,33.46124909100011],[130.04462561600022,33.48574090400011],[130.0552943830002,33.495225676000175],[130.08597211100007,33.50653186400014],[130.10384486200022,33.51190524500011],[130.11526180200022,33.50895078600017],[130.12598717500023,33.51618073100009],[130.12989342500023,33.51898834800015],[130.14275149800008,33.520493882000025],[130.1564233730002,33.52537669500008],[130.16797936300023,33.534084377000156],[130.17416425900004,33.546861070000105],[130.16187584700006,33.54661692900005],[130.12989342500023,33.540025132],[130.12037194100017,33.54315827000006],[130.11793053500006,33.556830145],[130.10914147200006,33.55988190300009],[130.09693444100017,33.56171295800006],[130.092051629,33.566473700000145],[130.09302819100017,33.57343170800006],[130.09848066500004,33.581691799000126],[130.10914147200006,33.589748440000065],[130.11939537900017,33.59125397300012],[130.12916100400005,33.59125397300012],[130.13941491000006,33.59467194200012],[130.15666362200002,33.60650551500011],[130.15383933900003,33.61894298100016],[130.15954752700023,33.62959605400009],[130.17074629000004,33.62885163000011],[130.18189537900005,33.62905508000007],[130.19068444100017,33.63031647300009],[130.19776451900023,33.63361237200003],[130.20476321700002,33.63971588700012],[130.20801124200003,33.66507643700014],[130.22055097700016,33.65973541900017],[130.23170006600012,33.65330638200008],[130.2365014980002,33.64423248900006],[130.2271562330001,33.6348435390001],[130.23138438300006,33.61884615200013],[130.24537194100006,33.61176178600009],[130.25147545700023,33.60797760600015],[130.2719213730002,33.61166814100015],[130.27544953700007,33.60336765000001],[130.26543167800006,33.58857951500009],[130.27324374700018,33.58264295600004],[130.28541100400017,33.57697174700003],[130.29029381600006,33.57420482000008],[130.29529753400007,33.57904258200013],[130.30152428500006,33.579657294000114],[130.30543053500006,33.584947007],[130.315307038,33.59736182000002],[130.33026873200018,33.59791994000001],[130.3580496240002,33.5972586000001],[130.37303995000016,33.603144833000144],[130.39086562400016,33.60487402200012],[130.3994783520001,33.61611050099999],[130.4024076850001,33.63092078800018],[130.40750100800008,33.650468484],[130.40326038100002,33.65818688100002],[130.39869225400005,33.669134833],[130.37924238400015,33.659247137000094],[130.36158287900005,33.64622630400011],[130.34897188800002,33.64113020500015],[130.3361683330002,33.647677552000104],[130.32478674000012,33.65303453300014],[130.31839397600018,33.65838070800008],[130.30698072000015,33.65544060500018],[130.29490003900005,33.66435087500004],[130.28926262000007,33.681544502000136],[130.29428057700014,33.6892394070001],[130.30496604600015,33.68744291200012],[130.31061090100005,33.67261755300014],[130.32412225900012,33.664887418000134],[130.34549789700012,33.66247159000007],[130.38648522200018,33.67723216400013],[130.40202884200008,33.68939850500011],[130.415293816,33.702826239],[130.42872155000012,33.71369049700009],[130.44459069100006,33.71820709800015],[130.4521130860002,33.72797385299999],[130.4676738440002,33.743563601000105],[130.46998655100006,33.762869178000145],[130.46675959500013,33.780352986000096],[130.45570365100016,33.78223217800014],[130.44800866000006,33.78994375200013],[130.44955488399998,33.8022321640001],[130.45435631600017,33.80768463700004],[130.4620874360002,33.81118398600013],[130.47217858200023,33.817531643000066],[130.47779381600006,33.825140692],[130.48894290500007,33.85537344],[130.49708092500006,33.84813060099999],[130.50757897200006,33.849066473000065],[130.53052819100006,33.86155833500008],[130.53907311300017,33.86945221600014],[130.54232832100004,33.87596263200014],[130.54818769600016,33.88076406500012],[130.56470787900005,33.88328685100005],[130.61207116000017,33.879787502000156],[130.633067254,33.882473049000126],[130.65406334700006,33.896307684000035],[130.68441816500004,33.93423086100016],[130.69239342500006,33.937323309000035],[130.7052514980002,33.938706773000106],[130.71705162900017,33.93789297100001],[130.73150483800012,33.93184873400004],[130.75604891900016,33.943681113000125],[130.79049451200004,33.94441130800014],[130.82822790400004,33.93958513800014],[130.8213388820001,33.9202871370001],[130.83586868700016,33.925729009000136],[130.8614234190001,33.925567718000124],[130.87119899500024,33.91351609900012],[130.88085281300008,33.89593952100013],[130.89698326900023,33.88930898600013],[130.9135848320001,33.89118073100012],[130.926036004,33.8990746110001],[130.94141686300023,33.91681549700009],[130.94947136400017,33.93819283900014],[130.9765672460001,33.95736693000005],[131.00714759400014,33.96605493200009],[131.01868565800012,33.956262097000135],[131.01531009200008,33.93512604400014],[131.00635826900012,33.92291901200015],[131.00359134200008,33.91681549700009],[131.00416100400005,33.91107819200003],[130.99292883500007,33.89766466700014]]],[[[130.7164819670002,34.00010814000011],[130.71713300900015,33.99778880400005],[130.71070397200006,33.99892812700007],[130.71404056100008,34.00682200700005],[130.7164819670002,34.00010814000011]]],[[[130.11361738400004,34.24603913],[130.10474694100017,34.238714911],[130.10775800899998,34.24957916900017],[130.11361738400004,34.24603913]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-43\",\"name\":\"Kumamoto\",\"name_alt\":null,\"name_local\":\"熊本県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kyushu\",\"postal\":\"KM\",\"region_code\":\"JPN-KYS\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[130.04224694100006,32.17430247600005],[130.04574629000004,32.164048570000105],[130.04468834700018,32.1615257830001],[130.04428144600016,32.1563988300001],[130.0325626960001,32.17177969000012],[130.02247155000012,32.160589911000116],[130.0177514980002,32.161078192],[130.01701907599997,32.1676292990001],[130.01701907599997,32.17177969000012],[130.01718183700015,32.178208726000136],[130.02230879000015,32.18740469],[130.0289819670002,32.19114817900005],[130.03744550900015,32.18252187700013],[130.03956139400017,32.18040599200013],[130.03614342500023,32.179754950000174],[130.04224694100006,32.17430247600005]]],[[[130.16732832099999,32.11432526200012],[130.1492619150001,32.110500393],[130.13461347700016,32.11798737200017],[130.12777754000015,32.13231028900016],[130.13331139400017,32.149115302000084],[130.1232202480002,32.158921617000104],[130.1150822270001,32.17544179900007],[130.111094597,32.19330475500014],[130.11329186300011,32.207586981000034],[130.12110436300011,32.21263255400008],[130.13200931100008,32.21133047100007],[130.15308678500017,32.203762111000046],[130.17603600400017,32.20335521000011],[130.18726647200018,32.19635651200004],[130.1941024100001,32.17641836100016],[130.19076582100016,32.164618231000176],[130.17359459700006,32.12433502800012],[130.16732832099999,32.11432526200012]]],[[[130.21184329500002,32.21832916900014],[130.20378665500007,32.20311107],[130.20215905000023,32.20840078300016],[130.20199629000015,32.21417877800012],[130.19906660200016,32.20892975500011],[130.18848717500012,32.22101471600003],[130.193207227,32.228054104000094],[130.19385826900023,32.23533763200011],[130.1919865240001,32.2431908220001],[130.19776451900023,32.242987372000144],[130.20126386800015,32.236110744000044],[130.2101343110002,32.227403062000135],[130.20842532599997,32.22337474200019],[130.20899498800023,32.219916083],[130.21184329500002,32.21832916900014]]],[[[130.17383873800011,32.23301829600014],[130.16757246200015,32.22675202],[130.16342207099999,32.230088609000134],[130.16016686300023,32.23086172100015],[130.15748131600017,32.238714911000145],[130.16203860800013,32.24249909100014],[130.1639103520001,32.24518463700004],[130.16667728000007,32.25075918200007],[130.16985110800013,32.25633372600005],[130.1797794930002,32.26076894700013],[130.1871037120002,32.26809316600004],[130.18775475400017,32.26003652600009],[130.18344160200016,32.25633372600005],[130.17839603000002,32.25104401200018],[130.17562910200016,32.2389183610001],[130.17383873800011,32.23301829600014]]],[[[130.22315514400006,32.25706614799999],[130.21509850400005,32.252671617000104],[130.21070397200006,32.25535716400019],[130.21094811300011,32.26577383000007],[130.21290123800023,32.27667877800015],[130.21705162900017,32.28693268400018],[130.22291100400005,32.292059637000094],[130.22852623800023,32.293036200000145],[130.231211785,32.29511139500009],[130.23226972700016,32.29865143400015],[130.24390709700018,32.30182526200004],[130.26026451900006,32.2950707050001],[130.26644941500015,32.28705475500006],[130.26303144600016,32.281317450000174],[130.25359134200002,32.271633205000015],[130.24284915500013,32.2630882830001],[130.23267662900017,32.258775132],[130.22315514400006,32.25706614799999]]],[[[130.33130944100006,32.30548737200009],[130.30266360800024,32.30426666900017],[130.30119876400013,32.305853583],[130.30534915500007,32.30963776200012],[130.3103947270001,32.31061432500009],[130.32935631600012,32.31842682500009],[130.33041425900015,32.32713450700008],[130.33497155000012,32.340521552000084],[130.3462833990001,32.34979889500012],[130.3536076180001,32.350897528000175],[130.3600366550002,32.348781643000066],[130.36947675900004,32.34813060100011],[130.37045332099999,32.341742255],[130.35515384200002,32.32217031500012],[130.33130944100006,32.30548737200009]]],[[[130.3159285820001,32.36912669500005],[130.31820722700016,32.36782461100016],[130.32073001400008,32.368150132],[130.32252037900017,32.36550527600009],[130.32178795700023,32.36196523600002],[130.32276451900012,32.3594017600001],[130.3255314460001,32.36107005400011],[130.33212324300004,32.358547268],[130.3340763680002,32.35476308800018],[130.3404240240002,32.35228099200005],[130.3267521490001,32.34760163000014],[130.31877688900025,32.33950429900001],[130.31706790500007,32.341213283000016],[130.3125919930001,32.34076569200012],[130.3121037120001,32.34686920800003],[130.3198348320001,32.34792715100015],[130.31836998800023,32.34935130400014],[130.31625410200004,32.350897528000175],[130.3112085300002,32.349636135000154],[130.29900149800014,32.35602448100009],[130.3042098320001,32.362575588000155],[130.3107202480002,32.36107005400011],[130.31324303500006,32.3672142600001],[130.31495201900023,32.36831289300015],[130.3159285820001,32.36912669500005]]],[[[130.42969811300006,32.382350979000094],[130.4317326180002,32.37864817900005],[130.4273380870002,32.373195705000015],[130.42188561300006,32.370388088000155],[130.4205021490001,32.36692942900008],[130.4156193370002,32.36831289300015],[130.41602623800023,32.37653229400014],[130.41586347700016,32.380601304000024],[130.4122827480002,32.380601304000024],[130.40780683700015,32.3930931660001],[130.41285241000017,32.399888414000046],[130.4161889980002,32.396185614],[130.4244083990001,32.38898346600014],[130.4235132170002,32.38340892100014],[130.42969811300006,32.382350979000094]]],[[[130.38331139400012,32.5094261740001],[130.39177493600008,32.50873444200015],[130.41000410200004,32.51162344000009],[130.44019616000006,32.52399323100012],[130.45704186300023,32.52558014500015],[130.46851647200018,32.51162344000009],[130.43482506600017,32.49274323100015],[130.392832879,32.397162177000084],[130.36231530000018,32.374986070000105],[130.35873457099999,32.37995026200015],[130.35792076900006,32.39118073100015],[130.358653191,32.41290924700003],[130.35385175900004,32.42413971600003],[130.34310957100004,32.42182038000006],[130.32447350400005,32.409816799000126],[130.28443444100017,32.40753815300015],[130.26482181100002,32.403631903000175],[130.241953972,32.39549388200005],[130.22169030000012,32.42186107000005],[130.2130639980002,32.43650950700005],[130.21363366000017,32.45042552300005],[130.21859785200004,32.455959377000156],[130.22510826900012,32.45819733300014],[130.23910566499998,32.46027252800015],[130.24789472700016,32.46458567900008],[130.26783287900005,32.484198309000035],[130.29029381600006,32.49795156500015],[130.30518639400012,32.50405508000013],[130.34156334700018,32.514349677000055],[130.36304772200012,32.527085679000024],[130.37094160200016,32.52704498900003],[130.37720787900005,32.522894598000065],[130.37964928500017,32.51532623900012],[130.38331139400012,32.5094261740001]]],[[[130.18946373800006,32.52179596600003],[130.19898522200018,32.48016998900006],[130.20150800899998,32.39549388200005],[130.20411217500006,32.38764069200006],[130.20679772200006,32.38125234600015],[130.20834394599999,32.373277085],[130.20557701900023,32.34662506700006],[130.20337975400005,32.33885325700005],[130.19922936300011,32.33274974200016],[130.19068444100017,32.32355377800009],[130.18140709700006,32.31826406500012],[130.1577254570001,32.31024811400009],[130.15308678500017,32.303371486000074],[130.13331139400017,32.26512278900013],[130.12777754000015,32.26141998900009],[130.10531660200016,32.25214264500012],[130.09693444100017,32.24502187700007],[130.07496178500017,32.22077057500009],[130.06104576900012,32.21185944200015],[130.03687584700018,32.19977448100015],[130.01197350400005,32.19061920800006],[129.99545332100016,32.19065989800005],[129.994395379,32.19989655200011],[130.00912519600004,32.25214264500012],[129.99732506600006,32.244045315],[129.98633873800006,32.24437083500011],[129.975840691,32.24591705900015],[129.95834394600016,32.237534898000106],[129.95801842500023,32.24237702000009],[129.96127363399998,32.25214264500012],[129.96534264400023,32.258205471000124],[129.99626712300008,32.28217194200009],[130.01661217500006,32.29311758000013],[130.03012129000015,32.29523346600014],[130.04216556100008,32.29450104400003],[130.05323326900012,32.296535549000154],[130.06430097700004,32.30678945500007],[130.05420983200005,32.30695221600014],[130.04615319100006,32.308254299000126],[130.03028405000012,32.313625393],[130.00879967500006,32.30589427299999],[129.99146569100017,32.314439195000105],[129.979746941,32.33299388200011],[129.97527103000007,32.35541413000011],[129.97657311300023,32.36644114799999],[129.98047936300023,32.37563711100013],[129.98698978000002,32.38296133000016],[129.99545332100016,32.38865794500013],[129.98902428500017,32.40448639500006],[129.994395379,32.42413971600003],[130.0065210300002,32.44403717700011],[130.02003014400006,32.46067942900008],[130.03028405000012,32.48240794500005],[130.02540123800023,32.50214264500006],[130.00294030000023,32.53335195500016],[130.01547285200004,32.532782294000086],[130.02475019599999,32.52773672100015],[130.03296959700018,32.521918036],[130.043223504,32.51902903900016],[130.05648847700016,32.52118561400006],[130.07691491,32.530991929],[130.10279381600006,32.536525783000016],[130.1202905610002,32.54287344000015],[130.13982181100008,32.54767487200003],[130.1604923840001,32.54633209800015],[130.18946373800006,32.52179596600003]]],[[[130.46745853000002,32.544623114000146],[130.46257571700008,32.54165273600013],[130.46184329500014,32.562323309000064],[130.46037845099997,32.57575104400006],[130.46233157600014,32.57920970300013],[130.46729576900006,32.58360423400002],[130.47494550900004,32.585109768000066],[130.48137454500002,32.57444896000014],[130.48170006600006,32.561428127000156],[130.46745853000002,32.544623114000146]]],[[[130.5108341810002,32.59304433800004],[130.4971623060001,32.579982815000065],[130.4871525400001,32.57558828300007],[130.48682701900023,32.58405182500012],[130.4878849620002,32.590236721000096],[130.4819442070001,32.58864980700015],[130.47885175900004,32.59048086100013],[130.47990970100014,32.59300364800005],[130.47852623800006,32.59495677299999],[130.4805607430002,32.59780508000013],[130.47689863400015,32.59951406500012],[130.4796655610002,32.60675690300015],[130.48845462300008,32.609727281000076],[130.49708092500006,32.60781484600004],[130.51010175900015,32.60691966400013],[130.5108341810002,32.59304433800004]]],[[[130.452891472,32.6109886740001],[130.45004316500015,32.60325755400008],[130.44678795700023,32.596828518000066],[130.45264733200005,32.59064362200009],[130.45386803500017,32.58006419500005],[130.45093834700006,32.563421942],[130.44581139400023,32.55255768400009],[130.43189537900017,32.54987213700004],[130.42457116000017,32.54682038000014],[130.42758222700016,32.53729889500012],[130.4213973320001,32.54075755400014],[130.40919030000012,32.54926178600006],[130.40674889400006,32.56098053600006],[130.411387566,32.57387929900007],[130.40772545700017,32.58173248900006],[130.39966881600006,32.58641185100008],[130.39877363400004,32.593166408000016],[130.4135848320001,32.613592841000084],[130.41651451900023,32.613959052],[130.41684004000015,32.610052802],[130.4200952480002,32.611558335000055],[130.42847741000017,32.617661851000136],[130.43653405000012,32.62067291900014],[130.4464624360002,32.61660390800007],[130.452891472,32.6109886740001]]],[[[131.20907677607224,33.030206407099925],[131.22039391484728,33.000492459559425],[131.23465661031426,32.97519684525817],[131.2382222837314,32.96387970648327],[131.24008263570502,32.95450043404783],[131.24380333875305,32.90651886715206],[131.2461804546642,32.89385814124033],[131.26214847157465,32.86628876381532],[131.32390180932114,32.822286282386116],[131.27052005365746,32.81050405471838],[131.25806603422004,32.80381195745592],[131.24225304604138,32.793269965036885],[131.23605187429453,32.78365814950406],[131.22519982261386,32.757070624010126],[131.21042036320912,32.73469472887861],[131.181378209237,32.70864980754264],[131.17517703659084,32.70144094544328],[131.15957075398703,32.67565440472704],[131.14654829286943,32.66296784129277],[131.12541263208735,32.64604380997393],[131.11285525896315,32.631109320938336],[131.10458702966778,32.61408193593256],[131.09998782837565,32.586383368197815],[131.09502689007903,32.57578969983474],[131.0856734560651,32.57134552727432],[131.07327111167243,32.569226792882105],[131.05921512267977,32.56873586736633],[131.04660607271208,32.564498399481565],[131.03513390520553,32.55287120234409],[131.0225248561371,32.53408681905144],[131.00412804557274,32.48742300087072],[130.99989057768798,32.47055064549592],[131.0000456082183,32.4601120058643],[131.00206098802417,32.4509136014815],[131.00578169197152,32.439079697869786],[131.01306806933601,32.42228485776006],[131.0215946801504,32.410580146256834],[131.04366051602008,32.39210582132661],[131.05420250933847,32.38078868345109],[131.0631425313027,32.36370962250113],[131.06893029189953,32.34815501584127],[131.08350304572886,32.325184841507294],[131.0878438664013,32.31366099715733],[131.08712039598956,32.299630845686934],[131.08091922424288,32.28740936934692],[131.06179894416587,32.264335842225265],[131.05032677576006,32.24619741497868],[131.05032677576006,32.233769233063626],[131.0549776547949,32.222581285497895],[131.0717208189607,32.19782827445523],[131.07843875464468,32.18364309335412],[131.08164269375536,32.16369599097716],[131.07590661000202,32.155634467256945],[131.06505455922044,32.15206879294051],[131.05296227408965,32.153231512923995],[131.0056266623407,32.165788886048276],[130.99725508025764,32.16268829972522],[130.98645470631973,32.15447174727326],[130.96743777903018,32.13188914656688],[130.9522449075764,32.12013275822032],[130.93689700739108,32.11496511524793],[130.90925011560049,32.121915594928694],[130.89684777210707,32.122432358866305],[130.88367028225778,32.11915090628925],[130.85183760032496,32.10163259486838],[130.83741987522708,32.0956639671184],[130.82346723812245,32.09214997054467],[130.81075483626674,32.09083222093027],[130.7418184748427,32.09351939520384],[130.71236290882112,32.0909097361954],[130.67448408387324,32.112226264130825],[130.61087039595205,32.15702973123702],[130.5897864120134,32.16346344698057],[130.57438683498438,32.160672919019945],[130.5635347842028,32.15400665928],[130.55268273342145,32.14997589786931],[130.5387300972162,32.146151842033944],[130.52550092962431,32.139847317499786],[130.47186079334082,32.12723826843127],[130.44979495657194,32.11884084702753],[130.42437015196097,32.11341482253603],[130.40788537021368,32.11372487999928],[130.39331261638407,32.118375759034066],[130.3826155952336,32.12646312117609],[130.35589887942976,32.15302480734904],[130.3453568870108,32.161215522278454],[130.3448401230731,32.161706447794344],[130.34465862896903,32.161778425075354],[130.34498131600017,32.16274648600002],[130.34848066500015,32.166571356000034],[130.3569442070001,32.16937897300009],[130.358653191,32.17267487200003],[130.35775800900004,32.18724192900005],[130.358653191,32.19065989800005],[130.43100019600016,32.257147528000175],[130.441172722,32.28261953300007],[130.44402103000007,32.28782786700016],[130.45704186300023,32.29486725500014],[130.46168053500017,32.299953518000066],[130.46371504000004,32.30939362200017],[130.46045983200017,32.318182684000064],[130.46168053500017,32.32721588700015],[130.46957441500015,32.34711334800015],[130.47706139400012,32.349025783000016],[130.50318444100017,32.32721588700015],[130.51246178500006,32.33820221600011],[130.51392662900017,32.350327867000104],[130.51002037900017,32.378485419000086],[130.51490319100006,32.385891018000066],[130.54053795700023,32.416001695000105],[130.56202233200023,32.42706940300015],[130.57129967500012,32.451890367000104],[130.56763756600006,32.47768789300012],[130.55046634200002,32.49168528900019],[130.55884850400017,32.50177643400015],[130.57007897200018,32.50560130400008],[130.59880618600002,32.50535716400013],[130.59058678500006,32.5184593770001],[130.56771894600004,32.53416575700005],[130.56470787900005,32.54633209800015],[130.57007897200018,32.55459219000012],[130.6412866550002,32.60748932500009],[130.6564233730002,32.62372467700014],[130.66830488400015,32.64874909100014],[130.64144941500015,32.64834219000012],[130.4780379570001,32.62201569200012],[130.46168053500017,32.615220445000105],[130.44800866000006,32.62140534100017],[130.4572046230002,32.63519928600009],[130.47527103000002,32.647040106000034],[130.54420006600017,32.67670319200009],[130.56544030000023,32.69342682500012],[130.57650800900004,32.69839101800015],[130.59197024800014,32.69721100500011],[130.60816491000017,32.70221588700015],[130.61646569100006,32.70376211100002],[130.62598717500023,32.704006252000156],[130.62598717500023,32.71141185100005],[130.61231530000012,32.71625397300012],[130.60141035200004,32.72614166900003],[130.59449303500006,32.737982489],[130.59197024800014,32.74872467700011],[130.59376061300011,32.75560130400011],[130.59815514400012,32.76308828300007],[130.60938561300011,32.77570221600017],[130.61304772200006,32.78123607],[130.61133873800023,32.78546784100011],[130.60767662900017,32.78986237200009],[130.60564212300008,32.79645416900014],[130.59302819100017,32.815863348000065],[130.51612389400006,32.86847565300009],[130.4702254570001,32.90973541900014],[130.441254102,32.923651434000035],[130.4331160820001,32.94171784100014],[130.4291583888049,32.980522543166174],[130.4293827662017,32.98064870907065],[130.44514407843647,32.989511217568676],[130.48395307757247,32.99467886054096],[130.49821577393882,33.000234076241654],[130.49945600828826,33.00248200094369],[130.5019364769868,33.00984589357323],[130.50048953706246,33.01620209405134],[130.4989909202948,33.033978786991426],[130.50824100242042,33.04945587838604],[130.52178022657628,33.0615998403603],[130.54648156077533,33.07746450538259],[130.56368981293454,33.09167552310696],[130.57986453721858,33.09836762126862],[130.6177950381106,33.10113231080753],[130.635881789413,33.10428457307465],[130.64983442561837,33.11312124315084],[130.67308882079234,33.13531627022975],[130.68518110592336,33.14004466452958],[130.70089074131468,33.140587266888886],[130.71592858313764,33.13914032606512],[130.72864098499346,33.13446360950799],[130.78682864842344,33.10051219318315],[130.83473270095342,33.0809268251128],[130.8708028498717,33.07082408316499],[130.95121137880213,33.03227346374943],[130.9769462417756,33.028862819963095],[130.99089887888024,33.03310028694868],[130.99523969865314,33.044985867403895],[131.0085722190327,33.07121165589321],[131.009657423751,33.0795315620319],[131.00361128073595,33.08911754004238],[130.9859379423822,33.10955556883452],[130.9797367697361,33.119942532521904],[130.97301883405206,33.14461802829932],[130.97291548126438,33.1542040063098],[130.97756636029916,33.1607152373184],[131.00795210230777,33.16668386506838],[131.02893273255958,33.177019151013056],[131.0449007512686,33.18115326611034],[131.05942182825473,33.18061066285175],[131.0906343925631,33.1650043811472],[131.12494754409389,33.14244761796324],[131.14076053317214,33.12596283621595],[131.17228315584296,33.07413137506292],[131.1812748564495,33.06289175155315],[131.20142866440145,33.04201447229009],[131.20907677607224,33.030206407099925]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-45\",\"name\":\"Miyazaki\",\"name_alt\":null,\"name_local\":\"宮崎県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kyushu\",\"postal\":\"MZ\",\"region_code\":\"JPN-KYS\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[131.41496829500008,31.527858791000043],[131.417735222,31.511053778000033],[131.4053654310001,31.523504950000145],[131.40984134200008,31.542588609000134],[131.413828972,31.539862372000172],[131.41496829500008,31.527858791000043]]],[[[131.82471764400023,32.669541734000134],[131.82886803500017,32.668402411],[131.83757571700008,32.66868724200013],[131.8296818370001,32.66046784100003],[131.8245548840001,32.65159739799999],[131.82268313900008,32.65184153900013],[131.82292728000007,32.65525950700014],[131.82642662900005,32.65660228100005],[131.820078972,32.65525950700014],[131.8130802740002,32.65558502800015],[131.81421959700018,32.65802643400009],[131.8101505870002,32.66205475500006],[131.812266472,32.66583893400009],[131.81137129000004,32.66766998900006],[131.8091740240002,32.669175523000106],[131.81421959700018,32.67133209800012],[131.81820722700004,32.67422109600015],[131.82471764400023,32.669541734000134]]],[[[131.83022749210502,32.75239390745283],[131.83456831277752,32.740017402381184],[131.8392191918123,32.73410045057541],[131.87233051605838,32.731657540330914],[131.872325066,32.73163483300006],[131.87110436300011,32.72679271],[131.86687259200008,32.72272370000003],[131.86215254000015,32.72036367400007],[131.85938561300011,32.7176781270001],[131.85699406300003,32.70086428700016],[131.85592484900022,32.686084221],[131.84791100400017,32.68183014500009],[131.83395173900024,32.69866452900014],[131.82193770100017,32.70268559100013],[131.8165644120002,32.689146346000044],[131.80063958399998,32.68083304800011],[131.78842207100004,32.662990627000156],[131.7756453790001,32.6537132830001],[131.770762566,32.64874909100014],[131.767832879,32.64411041900003],[131.7682397800002,32.64240143400012],[131.76978600400005,32.63955312700007],[131.770762566,32.63174062700007],[131.76685631600006,32.62079498900012],[131.74903405000023,32.59406159100011],[131.74333743600008,32.58795807500012],[131.72950280000018,32.58405182500012],[131.70879259100016,32.585903477000116],[131.7047128720001,32.58157515700019],[131.69906660200004,32.56452057500003],[131.68213951900023,32.53506094000015],[131.68506920700023,32.52244700700008],[131.69564863400004,32.51146067900011],[131.71265709700018,32.51162344000009],[131.7189233730002,32.50873444200015],[131.73650149800008,32.49168528900019],[131.71729576900012,32.47296784100003],[131.69548587300002,32.4682477890001],[131.6789456940002,32.473907004000026],[131.65979027600022,32.476819574000146],[131.65662940000018,32.45735773000003],[131.67388918300006,32.44180489400016],[131.68913158600017,32.42173733500012],[131.66358483200023,32.40643952000012],[131.65308678500006,32.40127187700001],[131.64136803500017,32.39923737200009],[131.6334741550002,32.39549388200005],[131.62940514400023,32.386786200000174],[131.62183678500017,32.360296942],[131.61947675900004,32.33710358300006],[131.61646569100006,32.33234284100017],[131.6110132170002,32.32998281500004],[131.60279381600017,32.32355377800009],[131.59253991000017,32.303656317],[131.5804142590001,32.25397370000012],[131.5624169000001,32.2179163710001],[131.55032724200024,32.169466089000096],[131.53052845600016,32.10739128200008],[131.49648639400007,32.012744267000116],[131.4585703370001,31.90545186200002],[131.44727623800006,31.876898505000085],[131.44996178500017,31.858710028000175],[131.44762186299997,31.847434831000115],[131.45590347800018,31.82197397700004],[131.46804227599998,31.804899433000045],[131.49634850400005,31.795965887000094],[131.49301191499998,31.786932684000092],[131.47852623800023,31.77033112200003],[131.4752710300002,31.762152411000116],[131.45053144600004,31.651109117000132],[131.45533287900005,31.62091705900012],[131.42335045700023,31.59833405200014],[131.41146894600016,31.586127020000063],[131.40691165500002,31.569728908000158],[131.40406334700018,31.568060614000146],[131.39079837300008,31.54881419500002],[131.38648522200006,31.539007880000057],[131.38526451900023,31.526922919000143],[131.387950066,31.520493882000054],[131.39144941500004,31.516750393000038],[131.39332116000017,31.512274481000148],[131.39372806100008,31.491034247000172],[131.38892662900017,31.485581773000135],[131.37623131600006,31.47443268400012],[131.37354576900023,31.467352606000148],[131.36378014400012,31.419663804],[131.35865319100017,31.410467841000113],[131.34546959700006,31.394964911],[131.34180748800011,31.386053778000147],[131.34058678500017,31.37872955900015],[131.33822675900004,31.37262604400017],[131.331309441,31.367661851000108],[131.32764733200005,31.371323960000083],[131.32496178500017,31.37287018400012],[131.31820722700016,31.3751488300001],[131.32260175900015,31.38129303600006],[131.32374108200005,31.38393789300015],[131.32504316500015,31.388128973000093],[131.28484134200008,31.381984768],[131.26587975400017,31.381170966000084],[131.24984785200016,31.388128973000093],[131.24512780000018,31.39557526200015],[131.2457788420002,31.412176825000117],[131.24301191500015,31.422267971000096],[131.2386173840001,31.428412177000055],[131.21924889400023,31.446478583000086],[131.21265709700018,31.45473867400007],[131.20780235400005,31.456049140000133],[131.20034249199998,31.457373282],[131.1786330130001,31.452428893],[131.16155688500012,31.463997931000122],[131.15064537914046,31.471340236031565],[131.152904494247,31.47389313436075],[131.1553849629456,31.478414822186252],[131.181378209237,31.51177195930852],[131.18664920499694,31.5250011260011],[131.18732099766592,31.540943305389263],[131.19150678870736,31.564120185298307],[131.19181684796925,31.57466217681805],[131.18623579294749,31.586831977214004],[131.1747636254411,31.60026784948174],[131.1560050796708,31.61274770824015],[131.12634280807416,31.616959336804015],[131.11580081565515,31.616726792807313],[131.10696414557893,31.617889512790825],[131.10076297383213,31.619413967081],[131.0711007022355,31.633624985704458],[131.04722619033632,31.637268175286025],[131.03585737471818,31.645355536528868],[131.01663374185378,31.675922145690507],[131.01260298134255,31.686309109377888],[131.0108976581004,31.69605011612002],[131.0102258654314,31.705946153392304],[131.00764204304588,31.71364594190665],[131.002371047286,31.723516139858063],[130.97601606668812,31.747209783704605],[130.9654740742691,31.76067149529335],[130.89700280173795,31.78426178635233],[130.88909630674928,31.790230414102425],[130.8810347839282,31.801340847302285],[130.8763839048934,31.82444021284546],[130.8811898126597,31.870225531882895],[130.8734383491005,31.88756297525113],[130.84703169165928,31.909525458333277],[130.8237772973844,31.91960236275891],[130.8027966662331,31.935053615731746],[130.783883090832,31.954845689377166],[130.7211995788974,32.04106781732584],[130.71096764484096,32.04964610408446],[130.70011559495865,32.06703522429622],[130.71236290882112,32.0909097361954],[130.7418184748427,32.09351939520384],[130.81075483626674,32.09083222093027],[130.82346723812245,32.09214997054467],[130.83741987522708,32.0956639671184],[130.85183760032496,32.10163259486838],[130.88367028225778,32.11915090628925],[130.89684777210707,32.122432358866305],[130.90925011560049,32.121915594928694],[130.93689700739108,32.11496511524793],[130.9522449075764,32.12013275822032],[130.96743777903018,32.13188914656688],[130.98645470631973,32.15447174727326],[130.99725508025764,32.16268829972522],[131.0056266623407,32.165788886048276],[131.05296227408965,32.153231512923995],[131.06505455922044,32.15206879294051],[131.07590661000202,32.155634467256945],[131.08164269375536,32.16369599097716],[131.07843875464468,32.18364309335412],[131.0717208189607,32.19782827445523],[131.0549776547949,32.222581285497895],[131.05032677576006,32.233769233063626],[131.05032677576006,32.24619741497868],[131.06179894416587,32.264335842225265],[131.08091922424288,32.28740936934692],[131.08712039598956,32.299630845686934],[131.0878438664013,32.31366099715733],[131.08350304572886,32.325184841507294],[131.06893029189953,32.34815501584127],[131.0631425313027,32.36370962250113],[131.05420250933847,32.38078868345109],[131.04366051602008,32.39210582132661],[131.0215946801504,32.410580146256834],[131.01306806933601,32.42228485776006],[131.00578169197152,32.439079697869786],[131.00206098802417,32.4509136014815],[131.0000456082183,32.4601120058643],[130.99989057768798,32.47055064549592],[131.00412804557274,32.48742300087072],[131.0225248561371,32.53408681905144],[131.03513390520553,32.55287120234409],[131.04660607271208,32.564498399481565],[131.05921512267977,32.56873586736633],[131.07327111167243,32.569226792882105],[131.0856734560651,32.57134552727432],[131.09502689007903,32.57578969983474],[131.09998782837565,32.586383368197815],[131.10458702966778,32.61408193593256],[131.11285525896315,32.631109320938336],[131.12541263208735,32.64604380997393],[131.14654829286943,32.66296784129277],[131.15957075398703,32.67565440472704],[131.17517703659084,32.70144094544328],[131.181378209237,32.70864980754264],[131.21042036320912,32.73469472887861],[131.22519982261386,32.757070624010126],[131.23605187429453,32.78365814950406],[131.24225304604138,32.793269965036885],[131.25806603422004,32.80381195745592],[131.27052005365746,32.81050405471838],[131.32390180932114,32.822286282386116],[131.34281538292353,32.81073659871505],[131.35661299039717,32.80463877975596],[131.3823995302142,32.80192576795976],[131.4016231630786,32.802158311057255],[131.42146691266797,32.80479381028614],[131.45004398044537,32.81649852088999],[131.46415164628152,32.817893784870265],[131.47831098806174,32.81499990502154],[131.4900932148299,32.80848867401305],[131.49768965055696,32.800401312770376],[131.50311567594773,32.7927532010993],[131.50342573520945,32.783218899032974],[131.5012553239741,32.77515737621202],[131.4998600608932,32.767018338125766],[131.5018754415983,32.75916351998053],[131.50776655498262,32.75164459951863],[131.51562137312786,32.74593435328775],[131.5268868350595,32.74298879749496],[131.5397025897029,32.743040473439024],[131.55897789941062,32.747562161264696],[131.57076012617884,32.75409922889635],[131.5903454942492,32.75978363670551],[131.68325971856123,32.76332347349951],[131.69566206295397,32.76582977972056],[131.7002095892012,32.770971585170614],[131.70651411283595,32.779808255246806],[131.72082848514648,32.805749823795296],[131.7268746281616,32.81368215630651],[131.73493615098252,32.8204259304123],[131.7468734073818,32.823035590320316],[131.76118777879296,32.82218292869926],[131.7802047060823,32.816421007423614],[131.7952942238493,32.81432811145321],[131.80924686095395,32.80897960132749],[131.8191687357485,32.79836009454279],[131.82785037709326,32.76833608684099],[131.83022749210502,32.75239390745283]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-38\",\"name\":\"Ehime\",\"name_alt\":null,\"name_local\":\"愛媛県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Shikoku\",\"postal\":\"EH\",\"region_code\":\"JPN-SHK\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[132.45980879000015,32.94464752800015],[132.44621829500002,32.93996816600004],[132.43702233200023,32.94090403900013],[132.4385685560001,32.94293854400014],[132.45590254000015,32.95022207200016],[132.45980879000015,32.94464752800015]]],[[[132.3326929050002,33.1100121110001],[132.32032311300023,33.10130442900011],[132.31723066500015,33.10814036700013],[132.3326929050002,33.1100121110001]]],[[[132.41618899800008,33.11033763200011],[132.41179446700008,33.10130442900011],[132.40902754000004,33.10460032800013],[132.41537519600016,33.11334870000003],[132.42139733200023,33.11155833500014],[132.41618899800008,33.11033763200011]]],[[[132.27051842500018,33.17633698100015],[132.27800540500002,33.17536041900017],[132.28134199300004,33.17666250200007],[132.28744550900015,33.17588939000014],[132.29053795700023,33.17808665600013],[132.29395592500023,33.175441799000154],[132.29509524800008,33.17080312700013],[132.29363040500013,33.17080312700013],[132.292816602,33.170721747000144],[132.28956139400023,33.16502513200008],[132.29395592500023,33.16075267100014],[132.30111738399998,33.16030508000016],[132.30713951900012,33.163600979000094],[132.30648847700004,33.157985744000186],[132.30746504000004,33.15249258000007],[132.31332441500015,33.14948151200015],[132.31161543100004,33.148016669000114],[132.296722852,33.15330638200008],[132.28874759200008,33.15566640800013],[132.28663170700023,33.15802643400018],[132.28459720100003,33.16498444200009],[132.28060957100004,33.16889069200006],[132.26490319100017,33.16856517100014],[132.26189212300008,33.17597077000012],[132.26596113400004,33.18073151200004],[132.27051842500018,33.17633698100015]]],[[[132.37240644599999,33.20563385600015],[132.3667098320001,33.202785549000126],[132.36133873800023,33.200954494000044],[132.3615828790001,33.19843170800014],[132.36052493600002,33.19513580900009],[132.36361738400004,33.189683335000055],[132.35954837300008,33.18724192900011],[132.35515384200008,33.190130927000055],[132.35474694100017,33.19460683800013],[132.353363477,33.19932689000002],[132.35246829500002,33.20123932500009],[132.34782962300008,33.204657294000086],[132.34172610800007,33.2045759140001],[132.34758548300013,33.2076683610001],[132.3536076180002,33.20563385600015],[132.36353600400005,33.20966217700014],[132.36963951900023,33.21470774900017],[132.37151126400008,33.215643622000144],[132.37435957100004,33.218247789000046],[132.37566165500002,33.22138092700011],[132.37859134200008,33.219183661000116],[132.38086998800023,33.2081159530001],[132.37240644599999,33.20563385600015]]],[[[132.47453860800024,33.26268138200008],[132.4703882170002,33.255764065000065],[132.4629826180002,33.257595119000044],[132.4649357430001,33.2612165390001],[132.46916751400008,33.26589590100012],[132.46973717500023,33.26854075700011],[132.46794681100008,33.275091864],[132.47307376400008,33.27265045800006],[132.47754967500023,33.26788971600014],[132.47453860800024,33.26268138200008]]],[[[132.3619897800002,33.37120189000014],[132.3577580090001,33.367254950000145],[132.3546655610002,33.371486721000096],[132.34392337300008,33.37653229400003],[132.35108483200023,33.37848541900017],[132.3575138680002,33.37771230700004],[132.3619897800002,33.37120189000014]]],[[[132.34424889400012,33.39590078300013],[132.341644727,33.38035716400013],[132.33822675899998,33.38800690300015],[132.34424889400012,33.39590078300013]]],[[[132.48194420700023,33.73346588700012],[132.47543379000015,33.73232656500009],[132.4791772800002,33.738511460000055],[132.48226972700016,33.739691473000065],[132.48918704500008,33.739691473000065],[132.4933374360002,33.738511460000055],[132.49236087300014,33.736517645],[132.48861738399998,33.73541901200015],[132.48194420700023,33.73346588700012]]],[[[132.53459720100014,33.84849681200011],[132.53207441500004,33.84190501500002],[132.5267033210001,33.85016510600012],[132.52116946700008,33.85154857],[132.5240991550002,33.85476308800004],[132.53459720100014,33.84849681200011]]],[[[132.69336998800011,33.91559479400017],[132.69320722700016,33.90330638200011],[132.6813257170002,33.90167877800009],[132.6761987640002,33.88886139500015],[132.68156985800013,33.88308340100009],[132.68311608200005,33.879787502000156],[132.68921959700018,33.877468166000185],[132.6882430350001,33.874009507],[132.68368574300004,33.87274811400009],[132.67701256600017,33.87453847900015],[132.6709904310001,33.86989980700004],[132.66732832100004,33.869370835000055],[132.66260826900023,33.87433502800012],[132.6635848320001,33.87933991100006],[132.66456139400006,33.8878034530001],[132.665293816,33.89964427300005],[132.66049238400004,33.902370510000125],[132.65300540500007,33.89679596600003],[132.65862063900025,33.9067650410001],[132.6641544930001,33.911118882000025],[132.66684004000015,33.91063060100011],[132.67286217500023,33.910549221000124],[132.6769311860002,33.91046784100014],[132.67969811300006,33.916367906000076],[132.68238366000006,33.922267971000124],[132.6885685560002,33.922267971000124],[132.68978925900015,33.926011460000055],[132.69288170700023,33.926011460000055],[132.69882246200004,33.92690664300015],[132.69662519600016,33.920762437000164],[132.69336998800011,33.91559479400017]]],[[[132.55526777400004,33.935695705000015],[132.54704837300008,33.93162669500005],[132.5327254570001,33.9271507830001],[132.52898196700008,33.926011460000055],[132.5231225920002,33.92727285400015],[132.5162052740002,33.930365302000055],[132.5171004570001,33.93349844000012],[132.52173912900005,33.93581777600018],[132.52588951900017,33.9353294940001],[132.53174889400012,33.93492259300008],[132.53956139400012,33.934393622000115],[132.54346764400012,33.936346747000144],[132.55144290500007,33.938299872000115],[132.55526777400004,33.935695705000015]]],[[[132.67359459700018,33.95701732],[132.66993248800023,33.9555931660001],[132.66537519599999,33.955918687000135],[132.65739993600002,33.95034414300012],[132.65170332100016,33.95530833500008],[132.6563419930001,33.96898021000011],[132.66195722700016,33.974595445000105],[132.66740970100003,33.9720726580001],[132.67253665500002,33.96657949400016],[132.67221113400004,33.96043528900019],[132.67359459700018,33.95701732]]],[[[132.54420006600006,33.96698639500006],[132.53956139400012,33.96088288],[132.5347599620002,33.96670156500012],[132.53549238400015,33.97085195500007],[132.53956139400012,33.976426499000084],[132.53581790500007,33.98021067900011],[132.53459720100014,33.98826732000005],[132.5396427740001,33.99079010600015],[132.5542098320001,33.993353583000086],[132.56495201900023,33.9897321640001],[132.55656985800024,33.98375071800008],[132.55404707100016,33.978338934000064],[132.55103600400005,33.975124416000185],[132.54420006600006,33.96698639500006]]],[[[133.367035352,33.993923244000044],[133.36369876400025,33.99079010600015],[133.35995527400016,33.99095286700013],[133.35181725400005,33.99522532800013],[133.34945722700016,34.00010814000011],[133.35743248800023,34.005764065],[133.36304772200006,34.007310289000046],[133.36760501400025,34.00763580900015],[133.36784915500013,34.00250885600015],[133.3702091810001,33.995306708000115],[133.367035352,33.993923244000044]]],[[[132.62029056100013,33.95380280200011],[132.60043379000004,33.94989655200011],[132.58912194100006,33.956447658000016],[132.58692467500023,33.96967194200015],[132.59636478000007,33.98094310100005],[132.60645592500012,33.98920319200012],[132.61378014400023,33.994045315],[132.61793053500017,33.99567291900003],[132.62452233200023,33.99909088700004],[132.6364038420002,34.00828685100011],[132.64136803500006,34.01406484600007],[132.64389082100016,34.011419989],[132.64429772200018,34.00088125200013],[132.64275149800014,33.98993561400006],[132.64096113400015,33.98395416900014],[132.63697350400017,33.97923411700005],[132.635020379,33.974839585000055],[132.63445071700002,33.96482982000008],[132.62029056100013,33.95380280200011]]],[[[132.9613228950001,34.12113403200006],[132.96631920700023,34.11432526200018],[132.97657311300006,34.108587958],[133.03184883800012,34.05273441000004],[133.06031334700018,33.99135976800012],[133.06919679500007,33.97422315400003],[133.07729439200003,33.95973272000005],[133.0866003050002,33.949313315],[133.1077633450001,33.94117284200014],[133.12701088800023,33.93329063900016],[133.14670395600004,33.931541102],[133.17329223100003,33.94058406500001],[133.20886842399997,33.947403314000056],[133.2436802240001,33.95625729500007],[133.24887447000006,33.97410853100014],[133.27904556000013,33.98327675900005],[133.2998234300002,33.98102486000006],[133.32260754300003,33.99055281000015],[133.34096681500014,33.98993913600019],[133.35994768400022,33.98270960000009],[133.41480553500006,33.98574453300013],[133.43824303500006,33.98187897300012],[133.48633873800011,33.96674225500011],[133.51099694100017,33.96523672100007],[133.53207441500004,33.970933335000055],[133.54859459700018,33.98094310100005],[133.56169681100008,33.99339427300008],[133.57252037900005,34.00617096600011],[133.58725019600016,34.02033112200003],[133.59188392169082,34.02380922087188],[133.59213545138996,34.0237633328115],[133.62510501488455,34.0179238962708],[133.64665408681665,34.01213613477485],[133.66060672392106,33.99787344020707],[133.66887495321643,33.92780019812069],[133.66680789566786,33.90129018789183],[133.65673099214158,33.873462428947974],[133.6478943220653,33.85565989848557],[133.5602510926137,33.85312775294342],[133.51539594866418,33.83700470640211],[133.49410525915036,33.82457652448694],[133.4685254249085,33.81927969030555],[133.42708092744286,33.81837535274026],[133.38393110583596,33.81323354729041],[133.33504520137492,33.81302684171534],[133.30228234255608,33.8051720244694],[133.28574588396546,33.80610220045621],[133.26838260307468,33.804810289263415],[133.2568587587248,33.79819570636671],[133.2363948906117,33.77943716149586],[133.22290734329934,33.77364939999974],[133.20905805898238,33.77282257680052],[133.1895243668561,33.775819811235365],[133.17882734570546,33.77155650492871],[133.1712309099785,33.763443305264374],[133.16533979659428,33.753288886472845],[133.14792483886018,33.729724432936294],[133.11676395139526,33.67228607833981],[133.10756554611308,33.659186102856395],[133.09692020090657,33.64709381772549],[133.07531945213114,33.63071238876576],[133.0661727236923,33.61797414848816],[133.0595064639521,33.602419541828496],[133.05578576090412,33.588802802407415],[133.0450370629102,33.56474742335497],[133.043693474874,33.55371450542016],[133.04524376758573,33.534594225343156],[133.0425565942114,33.5246723505485],[133.03542524557864,33.51167572695333],[133.00255903487155,33.475295518773194],[132.98581587070586,33.46131704414633],[132.97026126494555,33.45441824040951],[132.95026248572506,33.44968984610951],[132.82024458192197,33.45015493410288],[132.80903079593443,33.444005439199614],[132.80391482980556,33.435427151541816],[132.8104260599147,33.412792873991904],[132.81228641188832,33.400442206442776],[132.81585208620484,33.38904755330201],[132.8243786970194,33.37940989844809],[132.83388715976454,33.37077993394685],[132.84132856675987,33.362640895860594],[132.84799482560075,33.35284821227522],[132.85714155493886,33.3443732774049],[132.8692338400696,33.33540741611942],[132.8781221860901,33.32636404136743],[132.88225630118748,33.31631297536349],[132.87667524616577,33.30272207346492],[132.85962202363757,33.290423081859686],[132.8018994491002,33.261380926988224],[132.78438113767933,33.24970205300734],[132.77363243968537,33.239005031856706],[132.76825809113794,33.22758454029439],[132.76464074087747,33.21523387274509],[132.76340050742735,33.20420095481019],[132.76123009709139,33.195441799099754],[132.75859459876168,33.18823293700051],[132.75037804630975,33.18229014677293],[132.72433312407438,33.16766571699925],[132.71296430935544,33.156710313430224],[132.6957560571963,33.13069123051595],[132.6855241231399,33.12363739714816],[132.67327680927738,33.12288808921387],[132.66097781767215,33.129373480901464],[132.63260745726828,33.151981920029684],[132.61865482016393,33.153377184009756],[132.61384891239734,33.145005601927],[132.6141589698606,33.13585887348826],[132.61772464417712,33.125291043546824],[132.63565636584863,33.090822862385195],[132.6434078312066,33.08022919402204],[132.6482654158162,33.06904124645642],[132.64749026856114,33.05702647569127],[132.6484721213914,33.045812689703936],[132.65312300042618,33.03395294946908],[132.67172651566628,33.001112576284314],[132.67157148693454,32.98064870907065],[132.67534386592658,32.96279450086563],[132.67327680927738,32.95227834686814],[132.6694010756992,32.94233063455114],[132.64314944878802,32.909076850216465],[132.6430443746983,32.908943764019185],[132.63990319100017,32.90892161700013],[132.62720787900005,32.90631745000003],[132.61841881600006,32.90688711100002],[132.57545006600012,32.93183014500012],[132.55892988400004,32.93675364799999],[132.57203209700018,32.92308177300005],[132.5529077480002,32.92487213700004],[132.53638756600006,32.93121979400017],[132.52418053500017,32.93024323100009],[132.51734459700006,32.909409898000106],[132.52344811300023,32.90688711100002],[132.53223717500012,32.89940013200014],[132.5378524100001,32.89581940300015],[132.52076256600006,32.889308986000074],[132.5041610040001,32.89166901200004],[132.49048912900017,32.90119049700003],[132.47227232600002,32.93367231200013],[132.49403946600003,32.94893138500014],[132.4829185580002,32.97050124400012],[132.49612784400003,32.98533214000004],[132.5014754570001,32.99323151200012],[132.481408436,33.023527538000124],[132.47149541200017,33.04068590400006],[132.4411372870001,33.04365934800013],[132.41970413100003,33.05192155800013],[132.39670036500016,33.016593804000095],[132.38240215600015,33.019779079000116],[132.4004464870002,33.04391394800017],[132.39688478500014,33.05800530900008],[132.40792239700025,33.0693293260001],[132.4518335300002,33.052883205000015],[132.47152754000015,33.05560944200009],[132.48316491000006,33.06769440300009],[132.4627384770001,33.06769440300009],[132.46876061300011,33.08234284100011],[132.46900475400005,33.09577057500009],[132.4627384770001,33.10545482000005],[132.44906660200016,33.10871002800009],[132.44906660200016,33.11489492400007],[132.47144616000017,33.113959052],[132.47584069100006,33.11489492400007],[132.48015384200008,33.118312893000066],[132.48755944100006,33.12547435100011],[132.49008222700016,33.13275788000014],[132.47950280000012,33.13597239799999],[132.45020592500006,33.13666413000011],[132.44068444100017,33.14227936400012],[132.43539472700004,33.155829169000086],[132.43804361300025,33.16821834500006],[132.45860651700016,33.173705878000035],[132.4492466170001,33.18507532299999],[132.4381082460001,33.191397889000186],[132.42366068900012,33.199176094000094],[132.41538655700012,33.18927756600006],[132.39864748200014,33.187183802],[132.39467584800005,33.20389482100016],[132.420123834,33.20982423900013],[132.445493724,33.204965422],[132.4596588650002,33.204012318000096],[132.46120103900014,33.19140330900008],[132.47782146500018,33.181408297000175],[132.48570176400008,33.169153220000126],[132.50507728100015,33.18123508500004],[132.5026698830001,33.19873474700016],[132.51376684500016,33.21334291800012],[132.5361722930002,33.21756625000016],[132.54836358700018,33.22690294400006],[132.53682887000005,33.24258428900008],[132.5244922420002,33.24998236300014],[132.54139370700003,33.26255771700015],[132.52421284500005,33.263023330000024],[132.50770168700015,33.25790311900012],[132.502579812,33.26660913300016],[132.49182335100014,33.2707164940001],[132.4845705700001,33.276525918000075],[132.48473546100016,33.29084085900003],[132.50478834200024,33.30381085099999],[132.52228044300014,33.30762090800012],[132.52560976800012,33.31466423100015],[132.486803525,33.316068149000145],[132.42012139200008,33.30435563900012],[132.40122137500012,33.30938686700013],[132.3902743800002,33.31618808100002],[132.37867272200006,33.31948476800012],[132.38412519600016,33.33087799700009],[132.3913680350001,33.334865627000156],[132.40788821700008,33.33624909100014],[132.4155379570001,33.341457424000126],[132.41732832099999,33.34739817900005],[132.421153191,33.37555573100006],[132.40235436300023,33.36261627800003],[132.39063561300006,33.37498607],[132.39112389400023,33.397894598000065],[132.40805097700004,33.41657135600015],[132.40805097700004,33.42340729400017],[132.394379102,33.42340729400017],[132.398203972,33.43134186400012],[132.40414472700004,33.43764883000007],[132.41187584700006,33.441961981000176],[132.421153191,33.444484768000095],[132.38111412900017,33.466050523000106],[132.36296634200008,33.46808502800003],[132.34459623000023,33.46715465400014],[132.3337515950001,33.46244430800006],[132.32223273500003,33.45755532700012],[132.3078038900001,33.46428988300009],[132.27858602400013,33.44730071900004],[132.25360695600014,33.44070882600012],[132.2144021830002,33.42294673200017],[132.17973878400008,33.40698609900004],[132.14998490600018,33.389167102000144],[132.13943055100017,33.37397803600008],[132.13066048300007,33.36356636600014],[132.11413651800015,33.36415118300012],[132.10156774800024,33.37013383500006],[132.11029556700012,33.386727643000185],[132.07350789600017,33.36763167000005],[132.0254873910002,33.34490369600017],[132.01053892000007,33.348988639000126],[132.02963653000003,33.36524659100017],[132.05155469999997,33.37604022700016],[132.09431999299997,33.40848712800015],[132.13548759700004,33.42002259800016],[132.15324827599997,33.41542941400017],[132.16657605400005,33.42020372400013],[132.15708555000006,33.42884152800012],[132.15528002600016,33.43704614700012],[132.17133126100023,33.4456344340001],[132.199766627,33.4550101440001],[132.2218807500001,33.4540257770001],[132.24068462600022,33.46457605500014],[132.2646316510001,33.46407350200003],[132.29374587800018,33.48538023100009],[132.41236412900005,33.53497955900015],[132.42847741,33.54759349200013],[132.43539472700004,33.563666083000115],[132.44166100400017,33.572943427000055],[132.47046959700018,33.60211823100012],[132.47950280000012,33.60834381700006],[132.5006123410001,33.62499820900014],[132.6017358730002,33.66038646000011],[132.61848949800006,33.67428922700016],[132.645474593,33.68958766600004],[132.67660566499998,33.71564362200006],[132.69711347700004,33.75674062700013],[132.695567254,33.78644440300015],[132.69008267200016,33.81390538000015],[132.69898165800006,33.850384146000025],[132.70628330500008,33.877051785],[132.71237037200004,33.90454878000004],[132.75493687700012,33.910320249],[132.75748129400003,33.93494834400009],[132.77247155000012,33.955877997000144],[132.76986156700016,33.996270044000156],[132.79873386700004,34.01310423299999],[132.8471860450002,34.03996357900003],[132.86235909600006,34.055893542000135],[132.9191104300002,34.06840607300016],[132.92252062900016,34.08483910800011],[132.93033802800002,34.09437597000006],[132.92615829000025,34.11166809600006],[132.90620625300008,34.11316541800012],[132.8953637230002,34.11321435000018],[132.89749994100012,34.12271401800014],[132.91551131900022,34.120258787000026],[132.928774824,34.119874497000026],[132.94238361200004,34.14126203700012],[132.95403348500022,34.12539330900013],[132.9613228950001,34.12113403200006]]],[[[133.32715905000023,34.17377350500011],[133.31511478000002,34.17141347900015],[133.30640709700006,34.18231842700014],[133.3125919930002,34.183254299000126],[133.32097415500007,34.18138255400005],[133.32715905000023,34.17377350500011]]],[[[133.08301842500023,34.175034898000135],[133.09351647200012,34.17430247599999],[133.0991317070001,34.1751976580001],[133.1017358730002,34.17096588700015],[133.0944116550002,34.16038646000011],[133.0451766290001,34.118394273000135],[133.02784264400012,34.11155833500011],[133.0171004570001,34.11884186400012],[133.01685631600006,34.136297919000086],[133.02003014400012,34.149644273000106],[133.025645379,34.1544457050001],[133.03174889400012,34.15412018400015],[133.03679446700002,34.15326569200006],[133.03394616000017,34.16136302299999],[133.0262150400001,34.17617422100007],[133.033050977,34.189398505],[133.055430535,34.19354889500015],[133.07154381600017,34.18805573100012],[133.07667076900012,34.18065013200011],[133.08301842500023,34.175034898000135]]],[[[133.27385501400025,34.184719143000095],[133.27149498800011,34.183254299000126],[133.26343834700018,34.1920433610001],[133.26343834700018,34.19586823100009],[133.26653079499997,34.19879791900003],[133.26970462300002,34.19981517100008],[133.27263431100008,34.19725169499999],[133.2733667330002,34.19261302300005],[133.2764591810001,34.18870677300008],[133.27385501400025,34.184719143000095]]],[[[133.10767662900005,34.236558335],[133.10930423300024,34.22825755400011],[133.11329186300023,34.22219472900012],[133.1187443370001,34.21954987200003],[133.12338300899998,34.21861399900014],[133.13038170700005,34.218369859000106],[133.13738040500007,34.21950918200004],[133.13428795700005,34.21600983300014],[133.12663821700002,34.2105980490001],[133.1246037120002,34.20917389500012],[133.11532636800004,34.193711656000104],[133.10645592500012,34.19379303600009],[133.10108483200023,34.20209381700006],[133.08082116000006,34.19818756700006],[133.06560306100008,34.208929755000085],[133.05901126400008,34.209011135000154],[133.05884850400005,34.20909251500014],[133.05388431100008,34.211330471000124],[133.06185957100004,34.22097402600009],[133.06096438900013,34.22825755400011],[133.06316165500002,34.234890041000156],[133.06836998800023,34.23570384300008],[133.077972852,34.23371002800015],[133.10238691500015,34.23936595300013],[133.10767662900005,34.236558335]]],[[[133.1674910820001,34.235581773000106],[133.15129642000014,34.23371002800015],[133.1500757170002,34.24367910400004],[133.15202884200008,34.244981187000135],[133.16049238400004,34.24770742400001],[133.16667728000007,34.24664948100015],[133.1674910820001,34.235581773000106]]],[[[133.05176842500018,34.2223167990001],[133.0348413420002,34.20734284100014],[132.99947548700018,34.20837728800002],[132.96387780000012,34.191880601000136],[132.95484459700018,34.19818756700006],[132.96290123800023,34.21845123900009],[132.969493035,34.22304922100001],[132.98585045700023,34.22695547100001],[132.99089603000007,34.23212311400012],[132.99170983200005,34.242254950000174],[132.98731530000006,34.248846747000144],[132.97934004000004,34.25218333500008],[132.96973717500023,34.252630927000084],[132.98878014400023,34.27301666900014],[133.01002037900005,34.29124583500014],[133.0300399100001,34.295314846000096],[133.04493248800023,34.273098049000126],[133.05567467500023,34.24237702000015],[133.05176842500018,34.2223167990001]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-37\",\"name\":\"Kagawa\",\"name_alt\":null,\"name_local\":\"香川県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Shikoku\",\"postal\":\"KG\",\"region_code\":\"JPN-SHK\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[133.63005618600002,34.28571198100012],[133.63575280000012,34.273586330000015],[133.6440535820001,34.27761465100018],[133.65674889400023,34.28107330900018],[133.65674889400023,34.27871328300013],[133.65430748800023,34.2762718770001],[133.65333092500023,34.2754173850001],[133.65471438900013,34.27045319200015],[133.64079837300008,34.271104234000106],[133.63892662900017,34.26862213700015],[133.63005618600002,34.26394277600015],[133.62403405000023,34.25849030200011],[133.62151126400013,34.25551992400001],[133.61963951900023,34.257839260000154],[133.61841881600006,34.265488999000084],[133.62151126400013,34.271104234000106],[133.62614993600002,34.27480703300013],[133.623301629,34.27891673400008],[133.6237085300002,34.28636302300008],[133.63005618600002,34.28571198100012]]],[[[133.68246504000004,34.314764716000084],[133.675629102,34.30414459800015],[133.67147871200004,34.30646393400012],[133.6657007170002,34.31464264500012],[133.66187584700018,34.32530345300013],[133.67009524800008,34.323431708000086],[133.68246504000004,34.314764716000084]]],[[[134.1616317070001,34.36249420800014],[134.1616317070001,34.33490631700012],[134.16627037900005,34.32485586100016],[134.18262780000023,34.32094961100016],[134.19044030000023,34.325384833000115],[134.212168816,34.34723541900017],[134.21680748800023,34.35504791900017],[134.2378035820001,34.34080638200005],[134.26042728000007,34.334418036000145],[134.27125084700006,34.32306549700017],[134.25782311300023,34.29417552300005],[134.29688561300011,34.27529531500012],[134.30958092500006,34.273098049000126],[134.32178795700023,34.2689476580001],[134.343516472,34.25063711100013],[134.35767662900005,34.24640534100011],[134.38412519600004,34.250067450000145],[134.39665774800014,34.249457098],[134.40170332100016,34.24274323100015],[134.4057723320001,34.234442450000174],[134.41570071700014,34.22418854400014],[134.4397670110001,34.20834943300012],[134.4424456734757,34.208266882619554],[134.44205773297804,34.205380153771145],[134.41973351468997,34.16148102512922],[134.4082613471834,34.157036852568766],[134.3958590027908,34.154194647764726],[134.3601505881791,34.16401316977199],[134.21974572158788,34.16261790579172],[134.17210005057692,34.15765696929368],[134.14941409708283,34.15181753185361],[134.13670169522712,34.143704332189245],[134.12336917484768,34.1228270529261],[134.1080212746621,34.1122333854624],[134.09122643365313,34.106445623966366],[134.04776655458286,34.09946930586371],[134.03412397674018,34.095593574084106],[133.99970747332125,34.07905711549357],[133.98399783703067,34.074406236458586],[133.96539432179046,34.071977443704114],[133.95221683194117,34.07368276604684],[133.94131310431632,34.07823029229415],[133.93046105443412,34.08763540315137],[133.91836876840395,34.09536103008752],[133.89992028189542,34.10065786426908],[133.82824507115313,34.09288056138881],[133.81827151861566,34.087221992001346],[133.80369876478605,34.072003282125834],[133.7911413916619,34.06520783117644],[133.75713830029238,34.05885162979884],[133.74514936704978,34.05239207653314],[133.73496910983656,34.04417552318192],[133.71559044824053,34.032031562106866],[133.70391157425982,34.02148956968786],[133.66060672392106,33.99787344020707],[133.64665408681665,34.01213613477485],[133.62510501488455,34.0179238962708],[133.59213545138996,34.0237633328115],[133.59188392169082,34.02380922087188],[133.62086022200018,34.0455589860001],[133.63396243600002,34.06077708500008],[133.64128665500002,34.07916901200012],[133.64812259200008,34.1246605490001],[133.64600670700023,34.1673851580001],[133.64112389400006,34.184637762000094],[133.63298587300008,34.19830963700004],[133.62037194100017,34.21165599200005],[133.60401451900017,34.22337474200005],[133.58765709700006,34.23191966400016],[133.57227623800011,34.24217357],[133.55836022200006,34.25946686400009],[133.57935631600017,34.249416408000016],[133.60922285200016,34.23895905200014],[133.63754316500004,34.233587958000086],[133.6544702480002,34.23895905200014],[133.67261803500017,34.22313060099999],[133.70142662900005,34.231268622000115],[133.73194420700017,34.25096263200005],[133.754649285,34.27000560100005],[133.78199303500017,34.288275458000115],[133.84359785200004,34.31224192900008],[133.87419681100002,34.32831452000015],[133.88282311300023,34.33844635600012],[133.89063561300023,34.352199611000046],[133.89966881600006,34.364162502000156],[133.91211998800017,34.36933014500006],[133.92090905000012,34.37042877800012],[133.9370223320001,34.37506745000003],[133.94654381600017,34.376166083000086],[133.95622806100008,34.37396881700009],[133.97087649800008,34.36465078300013],[133.98902428500017,34.36078522300012],[134.00123131600006,34.35224030200011],[134.01197350400005,34.34882233300003],[134.02125084700012,34.34918854400003],[134.03687584700018,34.35468170800006],[134.04615319100006,34.35504791900017],[134.081309441,34.347723700000145],[134.08838951900017,34.35480377800003],[134.1007593110002,34.376166083000086],[134.11687259200008,34.360500393],[134.12281334700012,34.3666039080001],[134.1245223320001,34.38084544500002],[134.12745201900023,34.38979726800012],[134.14356530000012,34.39179108300006],[134.15300540500013,34.38572825700008],[134.15821373800011,34.37494538000006],[134.1616317070001,34.36249420800014]]],[[[133.70630944100017,34.354641018000066],[133.70313561300023,34.35175202000012],[133.69581139400012,34.35480377800003],[133.68848717500012,34.366156317],[133.69190514400012,34.37840403900016],[133.705332879,34.39179108300006],[133.70826256600006,34.39728424700009],[133.71566816500004,34.396144924000154],[133.724457227,34.38446686400006],[133.72584069100017,34.371405341000084],[133.720469597,34.363836981000034],[133.71290123800017,34.359442450000145],[133.70630944100017,34.354641018000066]]],[[[133.78907311300023,34.395656643000066],[133.79371178500017,34.38947174700009],[133.79281660200016,34.38633860900002],[133.78557376400025,34.383856512000094],[133.78378339900004,34.38149648600013],[133.77784264400023,34.38011302300008],[133.77076256600017,34.37323639500006],[133.76351972700016,34.36827220300002],[133.75847415500013,34.36855703300013],[133.75717207100004,34.37474192900011],[133.75798587300002,34.38011302300008],[133.75530032600014,34.38743724200016],[133.7530216810001,34.398179429],[133.757578972,34.39472077000009],[133.75993899800008,34.39191315300003],[133.76482181100008,34.39191315300003],[133.76693769600016,34.39305247600008],[133.77255293100004,34.39622630400014],[133.77572675900004,34.39972565300003],[133.77654056100002,34.40066152600012],[133.78158613400015,34.39805735900002],[133.784434441,34.39659251500014],[133.78907311300023,34.395656643000066]]],[[[134.0489201180001,34.38947174700009],[134.0394800140002,34.3782005880001],[134.043711785,34.395656643000066],[134.04826907600014,34.40269603100013],[134.05168704500008,34.409084377000156],[134.05705813900013,34.40656159100014],[134.05567467500023,34.39752838700004],[134.05420983200005,34.39069245000012],[134.0489201180001,34.38947174700009]]],[[[133.66887454500014,34.41030508000007],[133.67546634200002,34.407456773000135],[133.67432701900012,34.399766343000024],[133.67790774800008,34.400336005],[133.67839603000007,34.39708079600014],[133.67538496200004,34.39256419499999],[133.67278079500014,34.39004140800007],[133.66700280000006,34.38194407800013],[133.66325931100008,34.38853587400003],[133.6608992850001,34.390366929],[133.65951582100004,34.39826080900015],[133.65324954499997,34.40940989799999],[133.66032962300002,34.40749746300013],[133.66887454500014,34.41030508000007]]],[[[134.066254102,34.4252790390001],[134.05705813900013,34.416205145000035],[134.0554305350001,34.41628652600009],[134.05518639400023,34.42711009300008],[134.06169681100008,34.43414948100015],[134.066254102,34.4252790390001]]],[[[133.96729576900012,34.477728583000086],[133.97592207099999,34.474432684000035],[133.98088626400025,34.47589752800009],[134.00131269600004,34.4555931660001],[134.00733483200023,34.45266347900018],[134.01238040500007,34.451808986000074],[134.0122990240001,34.44977448100006],[134.002207879,34.44318268400015],[133.99838300899998,34.44395579600008],[133.98821048300024,34.44395579600008],[133.97934004000004,34.44717031500015],[133.96566816500015,34.472113348000065],[133.96729576900012,34.477728583000086]]],[[[134.10287519600004,34.480943101000136],[134.08399498800006,34.45982493700002],[134.07618248800011,34.46076080900009],[134.0678817070001,34.46670156500012],[134.0670679050002,34.467027085000055],[134.04900149800008,34.47435130400005],[134.0394800140002,34.478298244000044],[134.03663170700023,34.48078034100017],[134.03834069100006,34.4833031270001],[134.0459904310001,34.48765696800008],[134.05486087300014,34.48883698100009],[134.07203209700006,34.49848053600009],[134.08342532600003,34.50071849200005],[134.0874129570001,34.49782949400013],[134.09538821700008,34.49197011900016],[134.10181725400017,34.491441148000106],[134.10450280000023,34.490261135000154],[134.10287519600004,34.480943101000136]]],[[[134.02149498800023,34.491441148000106],[134.0190535820001,34.47728099200016],[134.01026451900023,34.49237702000009],[134.00871829500002,34.500026760000125],[134.01954186300011,34.507798570000105],[134.0230412120001,34.50185781500009],[134.02149498800023,34.491441148000106]]],[[[134.374278191,34.513332424000126],[134.36890709700006,34.48436107000005],[134.34693444100006,34.43081289300012],[134.34213300900015,34.43968333500008],[134.332774285,34.444769598],[134.32016035200004,34.44647858300003],[134.30591881600006,34.445013739000146],[134.30591881600006,34.45123932500012],[134.324961785,34.453680731000034],[134.32960045700023,34.46238841400013],[134.32064863400004,34.46893952],[134.299082879,34.46491120000006],[134.28492272200018,34.452297268000066],[134.27418053500017,34.435003973000065],[134.2623804050002,34.42088450700014],[134.24439537900017,34.41771067900008],[134.24870853000007,34.453192450000145],[134.24317467500023,34.463853257],[134.22339928500006,34.47235748900012],[134.204844597,34.4715843770001],[134.18588300900004,34.46605052299999],[134.17017662900005,34.46548086100002],[134.16138756600017,34.47919342700014],[134.1717228520001,34.485907294000086],[134.1725366550002,34.49664948100009],[134.17107181100002,34.50893789300015],[134.175059441,34.520086981000176],[134.20378665500007,34.51243724200005],[134.22828209700018,34.51837799700017],[134.27125084700006,34.54067617400001],[134.28321373800006,34.54364655200011],[134.3190210300002,34.54682038],[134.35368899800008,34.55438873900012],[134.3673608730002,34.55426666900014],[134.36329186300023,34.545355536000116],[134.36573326900006,34.53628164300012],[134.37061608200023,34.525864976000136],[134.374278191,34.513332424000126]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-39\",\"name\":\"Kochi\",\"name_alt\":\"Koti\",\"name_local\":\"高知県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Shikoku\",\"postal\":\"KC\",\"region_code\":\"JPN-SHK\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[132.56324303500006,32.709947007],[132.54965254000015,32.704291083],[132.54322350400017,32.70844147300012],[132.54053795700005,32.721502997000115],[132.53988691500004,32.73379140800016],[132.54379316500004,32.74559153900013],[132.548594597,32.75079987200003],[132.55494225400005,32.74750397300009],[132.55795332100016,32.745306708],[132.5620223320001,32.74213288000014],[132.56861412900017,32.73436107000013],[132.57154381600012,32.7254906270001],[132.57081139400006,32.71727122599999],[132.56324303500006,32.709947007]]],[[[133.74334069191949,33.82661774271473],[133.7841650735593,33.823103746141],[133.79501712524004,33.82382721655277],[133.81315555158736,33.823362128559396],[133.83206912698856,33.81651500076664],[133.88498579286014,33.790056668280656],[133.9063798360605,33.78496653877475],[133.92208947235136,33.786361802755025],[133.9335616398579,33.7953276640405],[133.9427600442407,33.805301214779305],[133.95469729974062,33.81240672499045],[133.97345584461166,33.81604991277318],[134.01753584220538,33.81077891701351],[134.02776777626187,33.80325999655152],[134.03691450380134,33.78248607007613],[134.04342573480974,33.77101390256958],[134.04838667220716,33.76018769020966],[134.05231408083026,33.74843130096373],[134.0601688989756,33.679339910808224],[134.06735192265316,33.675309150297],[134.10130333897806,33.67639435501546],[134.1323091977113,33.67220856397411],[134.14863895072713,33.66549062828993],[134.15871585515268,33.65624054616423],[134.16166141184468,33.645026760176805],[134.16310835176895,33.63427806218293],[134.16104129422038,33.62479543695984],[134.15484012337296,33.60921499277772],[134.18031660392822,33.563894761733835],[134.19008344909187,33.551518255762815],[134.21721357604562,33.54327586488917],[134.2350936217732,33.54038198504061],[134.24904625887783,33.54356008572928],[134.29566479708004,33.5303229879283],[134.27320508500011,33.50800352400013],[134.24463360500025,33.46075022600006],[134.21452884200008,33.37661367400001],[134.20777428500006,33.33909739799999],[134.19849694100017,33.31028880400005],[134.19629967500012,33.296779690000065],[134.1911996260001,33.2712576250001],[134.1902551310001,33.24832929400016],[134.17652712200007,33.236127821000096],[134.15796959700018,33.26972077000012],[134.1417641420002,33.29179326500015],[134.12750724600008,33.289525482],[134.10622434500013,33.294332361],[134.10796042300015,33.314923482000026],[134.07997292100018,33.339048073000086],[134.04004967500023,33.38410065300015],[134.02553646100014,33.41540034500015],[133.99662190200004,33.42456538000006],[133.96876061300023,33.43569570500013],[133.94214849600021,33.45887910400013],[133.92520516600015,33.49369326100013],[133.85490166900016,33.50009453000014],[133.7630521980001,33.5197844070001],[133.73939679000003,33.53750687899999],[133.6721478200001,33.528902337000105],[133.60773318400018,33.51582468000014],[133.58930516600017,33.50193355100008],[133.54686167900016,33.48611134800002],[133.46590938600016,33.4543240980001],[133.45036868600013,33.43085358300014],[133.45508873800023,33.414699611000074],[133.43970787900017,33.40167877800009],[133.41480553500006,33.39305247599999],[133.39161217500012,33.38983795800014],[133.37549889400023,33.38939036700005],[133.36500084700018,33.387600002000156],[133.35661868600013,33.38328685100005],[133.34669030000012,33.37555573100006],[133.32813561300011,33.35346100500006],[133.3230900400001,33.35130442900005],[133.326019727,33.36880117400001],[133.3191024100001,33.37152741100009],[133.31234785200016,33.37555573100006],[133.292246941,33.364691473000065],[133.269379102,33.346869208000086],[133.25456790500002,33.326239325000145],[133.25847415500002,33.306708075000174],[133.25196373800023,33.294745184000064],[133.254649285,33.28652578300013],[133.26075280000012,33.27977122600011],[133.26465905000012,33.27252838700018],[133.265879754,33.26764557500012],[133.26970462300002,33.26422760600012],[133.27084394600016,33.259507554000024],[133.269379102,33.25234609600015],[133.2651473320001,33.24843984600007],[133.26050866000017,33.2457542990001],[133.25847415500002,33.24213288000011],[133.25326582100004,33.21906159100014],[133.24097741000017,33.20579661700013],[133.22689863400015,33.19440338700015],[133.2153396250002,33.18750351900009],[133.2179468110002,33.16583893400018],[133.2254337900001,33.15908437700013],[133.20976104100023,33.15104182500012],[133.20383583600008,33.16262210700002],[133.19014923300008,33.163316435000084],[133.16503341100022,33.137199555],[133.13238695400017,33.089029456],[133.10743612900004,33.06106870400005],[133.09711613400023,33.048111006000155],[133.09886699200015,33.032648240000086],[133.0977641770002,33.02346692000016],[133.08985436300023,33.019273179],[133.0746170880001,33.02873789200011],[133.05284768100003,33.03949864300013],[133.01825880900012,33.02402326700009],[133.00170220600003,32.98695675099999],[132.99441272100015,32.94899317700002],[133.0092879570001,32.903469143],[133.00245201900006,32.869330145],[132.9899053080001,32.86257609500011],[132.9610590350002,32.86410052600003],[132.9550992650002,32.85262854200012],[132.95789947100016,32.835425557000136],[132.95547713300002,32.81461873300013],[132.96973717500023,32.807074286],[132.97673587300002,32.795152085000055],[132.98487389400023,32.78709544500013],[133.0040796230002,32.772284247000144],[133.01734459700006,32.75787995000009],[133.02759850400017,32.739650783000016],[133.02800540500007,32.72064850500003],[133.00220787900005,32.71165599200019],[132.98438561300017,32.71963125200013],[132.97657311300006,32.724514065],[132.97136478000007,32.73167552300005],[132.96176191499998,32.75112539300015],[132.95679772200006,32.75861237200003],[132.93913821700002,32.77045319200009],[132.91504967500012,32.77798086100013],[132.89283287900005,32.774237372000115],[132.88111412900017,32.75238678600006],[132.87208092500023,32.768744208000086],[132.85840905000012,32.769598700000174],[132.8432723320001,32.760931708000086],[132.82960045700005,32.74872467700011],[132.8121850920002,32.740301825000145],[132.79151451900012,32.74066803600009],[132.77198326900012,32.746079820000105],[132.75757897200006,32.75238678600006],[132.72934003999998,32.77415599200013],[132.711400796,32.789525389000104],[132.6646431760001,32.77886830400011],[132.6276607680002,32.752093098],[132.61990336400007,32.772990306000125],[132.62337688200014,32.799495693000026],[132.64600159500017,32.825692004000175],[132.66300726000011,32.85680765600013],[132.69278518700006,32.88433009100005],[132.70516686400018,32.89716469000008],[132.70915774800008,32.909409898000106],[132.6430443746983,32.908943764019185],[132.64314944878802,32.909076850216465],[132.6694010756992,32.94233063455114],[132.67327680927738,32.95227834686814],[132.67534386592658,32.96279450086563],[132.67157148693454,32.98064870907065],[132.67172651566628,33.001112576284314],[132.65312300042618,33.03395294946908],[132.6484721213914,33.045812689703936],[132.64749026856114,33.05702647569127],[132.6482654158162,33.06904124645642],[132.6434078312066,33.08022919402204],[132.63565636584863,33.090822862385195],[132.61772464417712,33.125291043546824],[132.6141589698606,33.13585887348826],[132.61384891239734,33.145005601927],[132.61865482016393,33.153377184009756],[132.63260745726828,33.151981920029684],[132.66097781767215,33.129373480901464],[132.67327680927738,33.12288808921387],[132.6855241231399,33.12363739714816],[132.6957560571963,33.13069123051595],[132.71296430935544,33.156710313430224],[132.72433312407438,33.16766571699925],[132.75037804630975,33.18229014677293],[132.75859459876168,33.18823293700051],[132.76123009709139,33.195441799099754],[132.76340050742735,33.20420095481019],[132.76464074087747,33.21523387274509],[132.76825809113794,33.22758454029439],[132.77363243968537,33.239005031856706],[132.78438113767933,33.24970205300734],[132.8018994491002,33.261380926988224],[132.85962202363757,33.290423081859686],[132.87667524616577,33.30272207346492],[132.88225630118748,33.31631297536349],[132.8781221860901,33.32636404136743],[132.8692338400696,33.33540741611942],[132.85714155493886,33.3443732774049],[132.84799482560075,33.35284821227522],[132.84132856675987,33.362640895860594],[132.83388715976454,33.37077993394685],[132.8243786970194,33.37940989844809],[132.81585208620484,33.38904755330201],[132.81228641188832,33.400442206442776],[132.8104260599147,33.412792873991904],[132.80391482980556,33.435427151541816],[132.80903079593443,33.444005439199614],[132.82024458192197,33.45015493410288],[132.95026248572506,33.44968984610951],[132.97026126494555,33.45441824040951],[132.98581587070586,33.46131704414633],[133.00255903487155,33.475295518773194],[133.03542524557864,33.51167572695333],[133.0425565942114,33.5246723505485],[133.04524376758573,33.534594225343156],[133.043693474874,33.55371450542016],[133.0450370629102,33.56474742335497],[133.05578576090412,33.588802802407415],[133.0595064639521,33.602419541828496],[133.0661727236923,33.61797414848816],[133.07531945213114,33.63071238876576],[133.09692020090657,33.64709381772549],[133.10756554611308,33.659186102856395],[133.11676395139526,33.67228607833981],[133.14792483886018,33.729724432936294],[133.16533979659428,33.753288886472845],[133.1712309099785,33.763443305264374],[133.17882734570546,33.77155650492871],[133.1895243668561,33.775819811235365],[133.20905805898238,33.77282257680052],[133.22290734329934,33.77364939999974],[133.2363948906117,33.77943716149586],[133.2568587587248,33.79819570636671],[133.26838260307468,33.804810289263415],[133.28574588396546,33.80610220045621],[133.30228234255608,33.8051720244694],[133.33504520137492,33.81302684171534],[133.38393110583596,33.81323354729041],[133.42708092744286,33.81837535274026],[133.4685254249085,33.81927969030555],[133.49410525915036,33.82457652448694],[133.51539594866418,33.83700470640211],[133.5602510926137,33.85312775294342],[133.6478943220653,33.85565989848557],[133.74334069191949,33.82661774271473]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-44\",\"name\":\"Oita\",\"name_alt\":null,\"name_local\":\"大分県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kyushu\",\"postal\":\"OT\",\"region_code\":\"JPN-SHK\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[131.9298608730002,32.71560293200015],[131.92839603000007,32.71385325700008],[131.9222111340001,32.71735260600015],[131.92286217500012,32.72398509300005],[131.92318769600016,32.73073965100015],[131.9256291020001,32.72850169499999],[131.92953535200016,32.72667064000002],[131.93213951900006,32.720282294000086],[131.9298608730002,32.71560293200015]]],[[[131.92530358200017,32.77155182500003],[131.9191186860002,32.768052476000136],[131.91114342500023,32.76988353100002],[131.91195722700016,32.77794017100014],[131.91879316500015,32.77720774900003],[131.92530358200017,32.77155182500003]]],[[[132.07805423300013,32.95742422100001],[132.07186933700015,32.95115794500008],[132.06674238400004,32.95929596600017],[132.06332441500004,32.960923570000105],[132.06959069100017,32.966294664000074],[132.07919355600004,32.973171291000156],[132.08008873800023,32.96714915600016],[132.08000735800024,32.96324290600016],[132.08057701900012,32.96116771000014],[132.07805423300013,32.95742422100001]]],[[[131.93148847700004,32.988592841000084],[131.9234318370001,32.980495510000154],[131.91976972700004,32.98143138200005],[131.91537519600016,32.98110586100013],[131.91301517000002,32.98419830900012],[131.9141544930002,32.99347565300015],[131.9180607430002,32.9955101580001],[131.9220483730002,32.997341213000155],[131.921153191,33.00096263200014],[131.92318769600016,33.00600820500007],[131.9256291020001,33.010972398000106],[131.92807050900004,33.01426829600014],[131.9317326180001,33.01691315300006],[131.93881269600004,33.01683177300008],[131.94410241,33.01548899900017],[131.94166100400017,33.01178620000003],[131.93596438900008,33.00576406500012],[131.93336022200018,33.00039297100007],[131.93197675900004,32.99876536700005],[131.93954511800004,32.99848053600003],[131.94719485800007,32.99420807500009],[131.93148847700004,32.988592841000084]]],[[[132.01474043100004,33.10504791900014],[132.0038354830002,33.09756094000009],[132.00294030000006,33.098496812000164],[132.00277754000004,33.1065534530001],[132.00831139400023,33.11127350500003],[132.01474043100004,33.10504791900014]]],[[[131.99024498800023,33.16982656500015],[131.98316491000017,33.16299062700013],[131.98194420700023,33.16856517100014],[131.98943118600013,33.173081773000106],[131.99024498800023,33.16982656500015]]],[[[131.95744876400013,33.276027736000074],[131.94906660200016,33.27195872600011],[131.94174238400015,33.278347072000045],[131.93832441500015,33.280707098],[131.9410913420002,33.28213125200007],[131.9483341810002,33.28204987200009],[131.95834394600004,33.28078847900018],[131.95744876400013,33.276027736000074]]],[[[131.60718834700018,33.67719147300012],[131.62745201900006,33.667669989000146],[131.64722741,33.66982656500015],[131.6673662740001,33.667192003000125],[131.68955031300018,33.641342245000104],[131.71311515500022,33.60543866400003],[131.72953798100005,33.58023616300012],[131.73707116,33.56037018400018],[131.73991946700008,33.53969961100016],[131.729109,33.51254769600014],[131.73729484100008,33.48460779900013],[131.72734758300007,33.46353913000014],[131.7092823690002,33.432637870000164],[131.7062434980002,33.409582667],[131.69743643400014,33.406392597000135],[131.67457116000017,33.41616445500013],[131.65870201900023,33.422837632000025],[131.6403100920002,33.41657135600015],[131.63542728000007,33.40232982000005],[131.63843834700006,33.386664130000085],[131.63461347700004,33.37396881700003],[131.609629754,33.36880117400001],[131.6010848320001,33.364935614],[131.59623445500003,33.349400219000145],[131.57628894100017,33.346355202],[131.5561629570001,33.346380927],[131.54737389400012,33.35228099200013],[131.53298094200002,33.366113306000116],[131.50602854600018,33.36307383900008],[131.49838511400006,33.340666269000124],[131.51037964000014,33.273403693000134],[131.5284936860002,33.2612165390001],[131.54786217500012,33.25495026200015],[131.61221917500015,33.252936484000074],[131.65453516400007,33.27162523500006],[131.70004316499998,33.25409577000015],[131.7108623820001,33.27017583800013],[131.76209241900003,33.253543499],[131.8221740970001,33.24403243800016],[131.85085719000014,33.25221895900002],[131.8844485440002,33.26830565600001],[131.90262453300014,33.2660418330001],[131.88219531500008,33.23653248400011],[131.87155536400022,33.21541862600019],[131.87224368600002,33.200344143000095],[131.86466184000002,33.19306970900011],[131.84874354000013,33.187999178000084],[131.83097601300003,33.16971306700016],[131.82666062900014,33.152561727000105],[131.80957368900025,33.12764888700009],[131.84262128999998,33.11326732000005],[131.86883062000018,33.12876699900015],[131.90040123800011,33.12913646000008],[131.91145721600017,33.124815134000116],[131.88435011500022,33.10935456000014],[131.86893776100024,33.094340211000045],[131.86321733100024,33.08253329200015],[131.88051074700016,33.07504043000013],[131.89085922300004,33.07951064400005],[131.90579752700003,33.077295736000124],[131.91583134900023,33.06916163000007],[131.92921694500004,33.068270865],[131.93261830800006,33.079469604000124],[131.93446936200021,33.092017862000105],[131.94238354500007,33.092556686000094],[131.950938347,33.07615794500013],[131.95801842500006,33.067206122000115],[131.9671330090001,33.061672268000095],[131.9760848320001,33.06024811400012],[131.98519941499998,33.06317780200014],[131.99146569100006,33.0689964860001],[131.99611291500017,33.091711249000056],[132.01343834700006,33.06386953300007],[132.0094507170002,33.05117422100001],[131.9840088880001,33.04554450900015],[131.94535031300003,33.04749881500011],[131.92955026400008,33.0470896020001],[131.92282875300012,33.02865454100014],[131.90677350700005,33.01184695400009],[131.9031681650001,32.983058986000074],[131.91459563600014,32.97649247000005],[131.93470534100018,32.959506862000026],[131.94970144800018,32.95927494200011],[131.959239129,32.944525458],[131.96989993600002,32.94830963700012],[131.9791772800002,32.95038483300014],[131.98560631600012,32.947333075000145],[131.99097741000017,32.94171784100014],[131.99822024800008,32.938910223],[132.01026451900012,32.94415924700017],[132.00977623800011,32.93903229400017],[132.01387909400003,32.93439292900017],[132.02183065700015,32.93691682100008],[132.03478980000003,32.95129061700011],[132.06074383000012,32.946884996],[132.08118890900002,32.943229703],[132.08411475400007,32.933233985000115],[132.06444569699997,32.936215320000045],[132.0437144790001,32.92727560900015],[132.0254749860001,32.923595910000145],[131.99480228000007,32.90355052299999],[131.98218834700018,32.88890208500008],[131.98666425900015,32.882269598],[131.99586022200006,32.878363348],[132.00017337300002,32.87128327000015],[131.98975670700023,32.85480377800015],[131.98096764400006,32.848334052000055],[131.97234134200008,32.84446849200005],[131.96371504000015,32.839016018],[131.95557701900023,32.82754140800007],[131.96851647200006,32.826117255],[131.99781334700018,32.82965729400017],[132.00342858200005,32.824123440000065],[131.99659264400023,32.810532945000105],[131.98072350400005,32.799261786000116],[131.96241295700005,32.792669989000146],[131.94809003999998,32.79340241100006],[131.92920983200023,32.77826569200006],[131.88249759200008,32.78343333500008],[131.87305748800023,32.76235586100016],[131.87305748800023,32.73468659100014],[131.87233051605838,32.731657540330914],[131.8392191918123,32.73410045057541],[131.83456831277752,32.740017402381184],[131.83022749210502,32.75239390745283],[131.82785037709326,32.76833608684099],[131.8191687357485,32.79836009454279],[131.80924686095395,32.80897960132749],[131.7952942238493,32.81432811145321],[131.7802047060823,32.816421007423614],[131.76118777879296,32.82218292869926],[131.7468734073818,32.823035590320316],[131.73493615098252,32.8204259304123],[131.7268746281616,32.81368215630651],[131.72082848514648,32.805749823795296],[131.70651411283595,32.779808255246806],[131.7002095892012,32.770971585170614],[131.69566206295397,32.76582977972056],[131.68325971856123,32.76332347349951],[131.5903454942492,32.75978363670551],[131.57076012617884,32.75409922889635],[131.55897789941062,32.747562161264696],[131.5397025897029,32.743040473439024],[131.5268868350595,32.74298879749496],[131.51562137312786,32.74593435328775],[131.50776655498262,32.75164459951863],[131.5018754415983,32.75916351998053],[131.4998600608932,32.767018338125766],[131.5012553239741,32.77515737621202],[131.50342573520945,32.783218899032974],[131.50311567594773,32.7927532010993],[131.49768965055696,32.800401312770376],[131.4900932148299,32.80848867401305],[131.47831098806174,32.81499990502154],[131.46415164628152,32.817893784870265],[131.45004398044537,32.81649852088999],[131.42146691266797,32.80479381028614],[131.4016231630786,32.802158311057255],[131.3823995302142,32.80192576795976],[131.35661299039717,32.80463877975596],[131.34281538292353,32.81073659871505],[131.32390180932114,32.822286282386116],[131.26214847157465,32.86628876381532],[131.2461804546642,32.89385814124033],[131.24380333875305,32.90651886715206],[131.24008263570502,32.95450043404783],[131.2382222837314,32.96387970648327],[131.23465661031426,32.97519684525817],[131.22039391484728,33.000492459559425],[131.20907677607224,33.030206407099925],[131.20142866440145,33.04201447229009],[131.1812748564495,33.06289175155315],[131.17228315584296,33.07413137506292],[131.14076053317214,33.12596283621595],[131.12494754409389,33.14244761796324],[131.0906343925631,33.1650043811472],[131.05942182825473,33.18061066285175],[131.0449007512686,33.18115326611034],[131.02893273255958,33.177019151013056],[131.00795210230777,33.16668386506838],[130.97756636029916,33.1607152373184],[130.97291548126438,33.1542040063098],[130.97301883405206,33.14461802829932],[130.9797367697361,33.119942532521904],[130.9859379423822,33.10955556883452],[131.00361128073595,33.08911754004238],[131.009657423751,33.0795315620319],[131.0085722190327,33.07121165589321],[130.99523969865314,33.044985867403895],[130.99089887888024,33.03310028694868],[130.9769462417756,33.028862819963095],[130.95121137880213,33.03227346374943],[130.8708028498717,33.07082408316499],[130.83473270095342,33.0809268251128],[130.8560233913665,33.14299022122184],[130.85214765778815,33.159733385387526],[130.83075361458756,33.21471710880748],[130.8262577660831,33.23298472816272],[130.84114057827506,33.27721975448809],[130.8430009302488,33.294402167326226],[130.83695478723374,33.32251414621079],[130.83633466960944,33.331454169074576],[130.83710981686457,33.33974823679175],[130.83850507994546,33.34551015896669],[130.84129560790595,33.3520213890758],[130.86491173738685,33.38468089420796],[130.8748336121814,33.4078319356952],[130.88584069169448,33.425401923060306],[130.9108004092116,33.44875967102199],[130.92557986861598,33.46612295191274],[130.93798221210935,33.47733673790019],[130.95053958523377,33.48519155514613],[130.9806669457229,33.49578522440858],[130.99508466992168,33.498937485776324],[131.0042313992598,33.500126044181556],[131.06427941286475,33.49811066347628],[131.1244824569998,33.49015249344275],[131.13972700439766,33.491547756523815],[131.14949384956165,33.495656033199296],[131.15709028528843,33.50361420323283],[131.16298139867266,33.51304515251188],[131.1750736838034,33.52549917194928],[131.18111982591913,33.535937812479986],[131.18313520662448,33.545446275225444],[131.17517703659084,33.56844228798123],[131.17517703659084,33.58332510017321],[131.17951785726328,33.59273021103043],[131.18220503063768,33.5970968692251],[131.1951241398672,33.607302964859954],[131.20089214658574,33.61270976599219],[131.21986918300004,33.60754487000004],[131.25646381200008,33.60495535200003],[131.28383445500018,33.58236918000013],[131.32309335200014,33.58122055200015],[131.36762048200015,33.574801779],[131.4177031080002,33.581681679],[131.42317582400008,33.58905352200004],[131.45931405600018,33.616143772],[131.4756370470001,33.631565882000174],[131.4840322870002,33.65303836400001],[131.4985572140001,33.66698519000015],[131.5210451170001,33.6763707920001],[131.55147501700017,33.68044424600005],[131.57380154600006,33.68089552700009],[131.59001712300008,33.6830101580001],[131.60718834700018,33.67719147300012]]],[[[131.66928144600004,33.718491929],[131.65105228000002,33.71637604400017],[131.63314863400015,33.71898021000008],[131.63461347700004,33.723334052000055],[131.64429772200018,33.729763088000155],[131.65569095100014,33.73240794500008],[131.66529381600006,33.73973216400016],[131.66993248800023,33.73977285400015],[131.6748966810002,33.738267320000105],[131.68059329500002,33.736476955000015],[131.68474368600002,33.73151276200015],[131.6818139980002,33.72671133000007],[131.66928144600004,33.718491929]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-36\",\"name\":\"Tokushima\",\"name_alt\":\"Tokusima\",\"name_local\":\"徳島県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Shikoku\",\"postal\":\"TS\",\"region_code\":\"JPN-SHK\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[134.82016035200004,33.856675523000106],[134.81934655000012,33.84821198100015],[134.81682376400008,33.84406159100003],[134.81234785200004,33.84015534100003],[134.80689537900017,33.83816152600018],[134.80534915500007,33.84658437700013],[134.80624433700015,33.85187409100011],[134.80998782599997,33.850653387000094],[134.81495201900012,33.85626862200009],[134.82016035200004,33.856675523000106]]],[[[134.60048233600017,34.22819423200012],[134.59404424800002,34.21381249700015],[134.61034936899998,34.216027454000184],[134.6278955280002,34.23308190400003],[134.64687145600018,34.23925245900007],[134.64070070400012,34.225355862000086],[134.6271762800002,34.20967378800019],[134.62574786500008,34.2010141440001],[134.6254044140001,34.193466909],[134.62720787900017,34.187933661000145],[134.6077580090001,34.18431224200016],[134.61915123800023,34.182074286],[134.62826582100004,34.17792389500006],[134.64208874500005,34.179263295000126],[134.63430226299997,34.165932268000134],[134.62369552699997,34.15077192300005],[134.62046262100003,34.14025293000013],[134.6227319670002,34.13446686400012],[134.6051873790001,34.110776919000116],[134.60458482500005,34.084834391000086],[134.6077580090001,34.06761302299999],[134.60181725400005,34.06370677299999],[134.59522545700005,34.045965887000094],[134.59424889400012,34.03974030200014],[134.59457441500004,34.02977122600005],[134.5997827480002,34.009466864000146],[134.60496636400003,34.001024089000126],[134.61163713300007,33.997020270000135],[134.6242507530001,33.997581129000096],[134.62659146500008,34.008139616],[134.63701600000016,34.01066622400016],[134.65092896999997,33.985940781000025],[134.66291603100015,33.968868609000126],[134.6959625000001,33.95189325700012],[134.690231657,33.94239734900013],[134.70257054000015,33.93726674100016],[134.7022308100002,33.92096530500008],[134.70289147200006,33.90623607000013],[134.69071417600006,33.895733504000034],[134.68677819100017,33.88621653900016],[134.67351321700008,33.88165924700003],[134.66236412900005,33.875881252000156],[134.64625084700018,33.85309479400003],[134.6608179050002,33.846380927],[134.71705162900017,33.84796784100003],[134.71705162900017,33.841701565000065],[134.708262566,33.84056224200013],[134.69825280000023,33.83624909100003],[134.68978925899998,33.83490631700015],[134.68978925899998,33.82807038000011],[134.72203124899997,33.83407324800014],[134.74004555000013,33.840282578000156],[134.75123131600017,33.83490631700015],[134.73230385400015,33.825951916000164],[134.70204863100005,33.813676891000156],[134.66465403400016,33.79848707600003],[134.65186608200005,33.78644440300015],[134.61679986200002,33.7826019970001],[134.6077580090001,33.766017971000096],[134.5793773950002,33.76417006200013],[134.57066101500013,33.73644897800001],[134.54637791100006,33.732502102000026],[134.5384402570002,33.71685772500014],[134.45730039200023,33.67092600800008],[134.4040223980002,33.65447756000016],[134.37617453800024,33.63565700800011],[134.388621681,33.62363408100008],[134.36376953800007,33.60075538900007],[134.360524174,33.58870858700003],[134.35296691800008,33.581630858000054],[134.33945285300015,33.57706668000016],[134.3262604810001,33.58012784400013],[134.31234801500014,33.57250647400015],[134.315460495,33.55513788700013],[134.31370106699998,33.543079177],[134.29671179400012,33.53136344700009],[134.29566479708004,33.5303229879283],[134.24904625887783,33.54356008572928],[134.2350936217732,33.54038198504061],[134.21721357604562,33.54327586488917],[134.19008344909187,33.551518255762815],[134.18031660392822,33.563894761733835],[134.15484012337296,33.60921499277772],[134.16104129422038,33.62479543695984],[134.16310835176895,33.63427806218293],[134.16166141184468,33.645026760176805],[134.15871585515268,33.65624054616423],[134.14863895072713,33.66549062828993],[134.1323091977113,33.67220856397411],[134.10130333897806,33.67639435501546],[134.06735192265316,33.675309150297],[134.0601688989756,33.679339910808224],[134.05231408083026,33.74843130096373],[134.04838667220716,33.76018769020966],[134.04342573480974,33.77101390256958],[134.03691450380134,33.78248607007613],[134.02776777626187,33.80325999655152],[134.01753584220538,33.81077891701351],[133.97345584461166,33.81604991277318],[133.95469729974062,33.81240672499045],[133.9427600442407,33.805301214779305],[133.9335616398579,33.7953276640405],[133.92208947235136,33.786361802755025],[133.9063798360605,33.78496653877475],[133.88498579286014,33.790056668280656],[133.83206912698856,33.81651500076664],[133.81315555158736,33.823362128559396],[133.79501712524004,33.82382721655277],[133.7841650735593,33.823103746141],[133.74334069191949,33.82661774271473],[133.6478943220653,33.85565989848557],[133.65673099214158,33.873462428947974],[133.66680789566786,33.90129018789183],[133.66887495321643,33.92780019812069],[133.66060672392106,33.99787344020707],[133.70391157425982,34.02148956968786],[133.71559044824053,34.032031562106866],[133.73496910983656,34.04417552318192],[133.74514936704978,34.05239207653314],[133.75713830029238,34.05885162979884],[133.7911413916619,34.06520783117644],[133.80369876478605,34.072003282125834],[133.81827151861566,34.087221992001346],[133.82824507115313,34.09288056138881],[133.89992028189542,34.10065786426908],[133.91836876840395,34.09536103008752],[133.93046105443412,34.08763540315137],[133.94131310431632,34.07823029229415],[133.95221683194117,34.07368276604684],[133.96539432179046,34.071977443704114],[133.98399783703067,34.074406236458586],[133.99970747332125,34.07905711549357],[134.03412397674018,34.095593574084106],[134.04776655458286,34.09946930586371],[134.09122643365313,34.106445623966366],[134.1080212746621,34.1122333854624],[134.12336917484768,34.1228270529261],[134.13670169522712,34.143704332189245],[134.14941409708283,34.15181753185361],[134.17210005057692,34.15765696929368],[134.21974572158788,34.16261790579172],[134.3601505881791,34.16401316977199],[134.3958590027908,34.154194647764726],[134.4082613471834,34.157036852568766],[134.41973351468997,34.16148102512922],[134.44205773297804,34.205380153771145],[134.4424456734757,34.208266882619554],[134.46862330400003,34.20746014400008],[134.49524256800012,34.224235342000114],[134.56088300900004,34.22142161700019],[134.5817163420002,34.229641018000095],[134.59424889400012,34.23212311400012],[134.60714479300023,34.25084548800014],[134.6110936680001,34.23941424300013],[134.60048233600017,34.22819423200012]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-23\",\"name\":\"Aichi\",\"name_alt\":\"Aiti\",\"name_local\":\"愛知県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chubu\",\"postal\":\"AI\",\"region_code\":\"JPN-CHB\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[136.9868831370002,34.54277496700003],[136.97980000400017,34.53798551800007],[136.97545270000003,34.54414496800014],[136.98361067300007,34.55046243300016],[136.9893261010001,34.54977737100013],[136.9868831370002,34.54277496700003]]],[[[137.00705755200013,34.674538660000096],[137.0020163610001,34.66826491900004],[136.99800820300013,34.675788546000106],[137.01023977100024,34.681697509000074],[137.00705755200013,34.674538660000096]]],[[[136.8150024280001,34.83991708900011],[136.80773059200024,34.83919645500008],[136.79805969000014,34.876400640000114],[136.81096899900004,34.876305527000156],[136.82350283300005,34.86203744100008],[136.82595147000004,34.84907610000012],[136.8147008650001,34.84630678400005],[136.8150024280001,34.83991708900011]]],[[[137.02071170478135,35.34766185221271],[137.0261894061163,35.33903188681218],[137.03218387318728,35.32603526501548],[137.036111280911,35.32135854845835],[137.04122724793925,35.317327786148454],[137.05068403474033,35.31148834870838],[137.05890058809163,35.3041502962992],[137.07083784359153,35.2849266634348],[137.07889936731206,35.279862372350536],[137.08665083177053,35.27769196201446],[137.10251549679268,35.27614166930256],[137.11460778192364,35.27236929031052],[137.12747521430973,35.266684882501366],[137.15930789624238,35.24573008887255],[137.168609654312,35.24219025297772],[137.18039188018108,35.24038157784763],[137.19961551304542,35.24123423946857],[137.2639009954345,35.266219794507904],[137.27955895398244,35.26916535120007],[137.2932015318252,35.2677700872198],[137.33542117744534,35.24875315993039],[137.35557498629655,35.24301707617714],[137.36983768086415,35.23715180121464],[137.39174848890164,35.22066701946734],[137.40694136035543,35.215240994076666],[137.42203087812246,35.21599030201084],[137.46652428596698,35.232320055026506],[137.49029544507871,35.24596263286922],[137.50006229114175,35.252938950971796],[137.54786298998513,35.2703539087059],[137.53871626244566,35.23717763873698],[137.55561445534298,35.20193431211884],[137.5652262717749,35.19312348136354],[137.57514814656966,35.18816254306684],[137.5882222836316,35.18847260232876],[137.61674767456557,35.192658393370166],[137.62966678289573,35.19759349234586],[137.64341271442518,35.204802354445135],[137.66237796397192,35.20901398390818],[137.68206668482955,35.20870392554572],[137.75131310371668,35.19622406678731],[137.80557335762447,35.19451874444444],[137.79797692279672,35.16232432730591],[137.7961165708231,35.13646027312309],[137.78696984328363,35.113360908479095],[137.77022667911794,35.08116649044122],[137.75348351495225,35.06318309192609],[137.69777632112041,35.01809540487908],[137.68485721189072,35.00251495979758],[137.67617557234482,34.96148387348198],[137.67043948769222,34.947221178015],[137.63333580910026,34.911719468978376],[137.62511925664833,34.89657827436791],[137.61798790891484,34.879266669421284],[137.6091512388385,34.86908641220823],[137.57917890798026,34.845031033155706],[137.57189253061577,34.840974433323495],[137.5636759790631,34.83777049421293],[137.51194786979826,34.82622081144133],[137.4975818224432,34.819787095697976],[137.48843509310498,34.80976186811576],[137.47153690020778,34.770901191236945],[137.46667931469872,34.75245270382926],[137.4632686709124,34.72521922498727],[137.4646639339933,34.70682241532221],[137.4679195499472,34.69333486621112],[137.46874637314662,34.686926987990105],[137.46790343229296,34.67096924919885],[137.42755727900018,34.66774579100017],[137.33970287999998,34.647447513000046],[137.1407268090002,34.589289111000156],[137.0958211670002,34.59323397800016],[137.04482177000014,34.580630235000044],[137.03150475400017,34.57941315300006],[137.01609532100017,34.57878471000002],[137.02359643000014,34.590778815000036],[137.02964210200005,34.59801495600014],[137.04248611100016,34.61757853200014],[137.052219903,34.64680316900002],[137.07283016600005,34.664599003000134],[137.10108483200017,34.63914622600011],[137.10450280000018,34.63202545800006],[137.1108504570001,34.625433661],[137.11869901800006,34.63370833900014],[137.12102007100006,34.63766082400004],[137.1370310010001,34.645578300000025],[137.15316816500004,34.651312567],[137.16765384200008,34.65668366100006],[137.19743544000002,34.673934111000065],[137.24111128400014,34.696677994000126],[137.25971580000012,34.7041445950001],[137.28225457700015,34.731178516000014],[137.298225934,34.72821530000006],[137.29967102300023,34.706846644],[137.29567769400003,34.6912040990001],[137.30345201200024,34.689428676000105],[137.30996040900007,34.70659849200008],[137.30925623700014,34.725664278000025],[137.33259033900012,34.720338258000154],[137.34081945300002,34.72654295100007],[137.31233949200006,34.73667473100012],[137.32081458400003,34.75525177400003],[137.3243163120002,34.76684465600006],[137.32154381600006,34.77338288000006],[137.29792810200016,34.797191095000144],[137.27995853000007,34.80809153900013],[137.2665052230001,34.80313888900004],[137.24580957200013,34.809640624000124],[137.22282679300022,34.816263291000084],[137.2077152950001,34.802580234000104],[137.19443232100016,34.80699872100003],[137.19084265200016,34.788680684000056],[137.1887391160001,34.77011170000013],[137.1708392290001,34.763864997],[137.17147080700013,34.784049803000144],[137.157473835,34.781164918000016],[137.1405333820001,34.78781337000005],[137.12154888900014,34.788343361000116],[137.10144007600016,34.784596454000095],[137.08744671100024,34.776028248],[137.07270864900008,34.78496379100001],[137.05016559100014,34.77760612499999],[137.02881708599998,34.780157144000114],[137.0182478530002,34.783289035000095],[137.00228925900004,34.802394924000126],[136.99561608200005,34.80809153900013],[136.97662599200012,34.82550679500018],[136.95823137300007,34.83235218500009],[136.96240352800018,34.85799564600016],[136.98259524800008,34.89826080900015],[136.97885175900015,34.92422109600015],[136.969493035,34.91160716400016],[136.95679772200006,34.884833075000145],[136.93848717500006,34.86098867400007],[136.93100019600004,34.84642161700005],[136.9257463930002,34.828270956],[136.91556403600012,34.77596001200014],[136.92660566500004,34.76361725500011],[136.93726647200018,34.746039130000085],[136.96264998500013,34.73496420500008],[136.971856878,34.713403750000154],[136.97149659400023,34.696411325],[136.9453916490002,34.698470690000036],[136.91320428500003,34.710874247000035],[136.8781098200001,34.72852009700016],[136.85596764400006,34.73981354400003],[136.8445937930002,34.752548136000044],[136.84288610100023,34.76907728900015],[136.85301440900017,34.78759522100013],[136.85697530900003,34.804757693000155],[136.86473401200013,34.82154509000013],[136.86426376300014,34.83652562000016],[136.85628619900015,34.856495216000084],[136.84341791300008,34.871163119000116],[136.82772581000003,34.87194149700012],[136.82293819700024,34.895623239000045],[136.82176567300021,34.91949379300014],[136.82224729599997,34.938299802000174],[136.823464778,34.954855423000154],[136.83329350600005,34.97209207400009],[136.84381381500017,34.989480583000145],[136.85690642700004,35.008037181000034],[136.86588050100013,35.02569016400004],[136.8798593170001,35.043836857000045],[136.88279637300016,35.07104784800005],[136.88572989700006,35.08919548600012],[136.87182146899997,35.09339527300007],[136.85706008100024,35.061786607],[136.8488647400001,35.039469415000084],[136.84412592100014,35.06076675100017],[136.84315173399997,35.07504335100005],[136.83011829400002,35.078831393000044],[136.8393272550002,35.03251957900015],[136.81302073200007,35.027385537000086],[136.808959273,35.048830543000136],[136.79946622900016,35.04855935200008],[136.79790005000004,35.024069190000134],[136.77846910600013,35.020401545000155],[136.75180097706672,35.02614980693416],[136.7359745629757,35.04168569593814],[136.7245023945699,35.05915233051569],[136.71008467037117,35.0913984244978],[136.69825076675957,35.10692719183639],[136.68414310092342,35.12126740256859],[136.6734460797728,35.13881154971334],[136.67654666609573,35.21562856680488],[136.6800606626695,35.236376654858745],[136.72558760018754,35.29128286391311],[136.7530277846046,35.338670152505514],[136.76098595643666,35.34750682258171],[136.77059777196948,35.35210602477325],[136.77648888625302,35.34740346889494],[136.78982140483387,35.3408405619423],[136.7973661637175,35.34053050447922],[136.8059444522747,35.342054958769396],[136.82863040576856,35.35295868639419],[136.84873253777644,35.36009003412788],[136.9097624051109,35.370709540013266],[136.93244835770568,35.38001129808303],[136.93957970633838,35.38189748847837],[136.95291222491917,35.38414541318021],[136.96500451094934,35.38104482685732],[136.99249637310922,35.36383657469817],[137.0131152690545,35.35443146384104],[137.02071170478135,35.34766185221271]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-21\",\"name\":\"Gifu\",\"name_alt\":\"Gihu\",\"name_local\":\"岐阜県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chubu\",\"postal\":\"GF\",\"region_code\":\"JPN-CHB\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[137.29490685416795,36.42113638043955],[137.30761925692315,36.423513495451516],[137.31867801327968,36.42919790326059],[137.3285998880744,36.43625173572903],[137.34131229082948,36.43968821883618],[137.35759036700182,36.43679433898754],[137.38818281458526,36.422996731513905],[137.41422773682052,36.41472850221854],[137.47753136637937,36.40604686087397],[137.52626224210877,36.387805080839826],[137.56538130140592,36.377004706002396],[137.60822106285173,36.34705821446502],[137.61829796817665,36.33059927024007],[137.62449913992333,36.31499298853555],[137.62279381668122,36.300161852287715],[137.61194176679913,36.27106802057273],[137.60630903493393,36.26039683694444],[137.5979891296945,36.25086253487808],[137.57220258987758,36.226574611828795],[137.56290083180784,36.21055491807505],[137.56181562708946,36.19825592736916],[137.56243574381455,36.18957428602451],[137.56486453656913,36.176190089700896],[137.561350539096,36.16559642223703],[137.54646772690404,36.13851797122737],[137.53943973195805,36.11789907528208],[137.5447624045613,36.10012238234202],[137.5577848647796,36.08482615810064],[137.5723576186092,36.07257884423831],[137.58212446467223,36.056817532003464],[137.58212446467223,36.0438984245726],[137.57731855690588,36.03123769776158],[137.55840498330332,36.00118785343662],[137.5199060407312,35.95276703517047],[137.48455936132547,35.91667104783056],[137.47350060406967,35.90049632444581],[137.46280358291892,35.889385891245865],[137.4490059754454,35.88406321954196],[137.42802534429416,35.88638865771037],[137.41112715139684,35.8865953641847],[137.39278201677655,35.880575060490656],[137.37593549982347,35.86850861288218],[137.33836673413742,35.82453196897521],[137.3232255386277,35.8016393090069],[137.3259643897448,35.78988292155964],[137.33325076710918,35.78381094012278],[137.35712527900847,35.78143382421163],[137.36890750487737,35.77745473874518],[137.37764082216626,35.76980662707419],[137.3844621124365,35.76156423620054],[137.3957792503121,35.755595608450534],[137.42368452362183,35.74872264313544],[137.43474327997836,35.74223725054867],[137.444045038048,35.73337474205064],[137.47195031135777,35.68164663278583],[137.4805285990156,35.669270127714185],[137.50331790619634,35.6466875270078],[137.51075931229246,35.63456940435455],[137.5118445170108,35.62245127990266],[137.50998416593643,35.59002431876729],[137.52817427002657,35.53922638638855],[137.5315849138127,35.53354197857939],[137.53416873619818,35.53049306909983],[137.53974979121983,35.5270565859926],[137.56042036400856,35.517625636713746],[137.5852250509953,35.49509471285069],[137.59473351463998,35.46956655545213],[137.59473351463998,35.453288479279905],[137.59070275322938,35.43631277111743],[137.59349328029055,35.42409129477741],[137.6057405950524,35.39398977270986],[137.60444868385957,35.38450714838612],[137.59767907133198,35.38029551892308],[137.57623335218744,35.3818974884783],[137.5684818868296,35.3787193868902],[137.5644511254191,35.37112295206262],[137.5756132345631,35.34512970577134],[137.57840376162426,35.32843821934836],[137.57261600192697,35.3132970238386],[137.56367597906308,35.301411445182055],[137.54786298998505,35.27035390870586],[137.50006229114172,35.25293895097175],[137.4902954450787,35.24596263286918],[137.466524285967,35.23232005502646],[137.42203087812243,35.215990302010795],[137.4069413603554,35.215240994076595],[137.3917484889016,35.220667019467385],[137.36983768086418,35.23715180121468],[137.35557498629657,35.2430170761771],[137.33542117744537,35.24875315993032],[137.29320153182516,35.26777008721976],[137.27955895398247,35.26916535120003],[137.26390099543448,35.26621979450786],[137.1996155130454,35.241234239468525],[137.1803918801811,35.240381577847586],[137.16860965431204,35.242190252977764],[137.15930789624235,35.24573008887248],[137.12747521430964,35.26668488250132],[137.11460778192355,35.27236929031048],[137.10251549679265,35.2761416693026],[137.08665083177044,35.277691962014416],[137.07889936731198,35.27986237235058],[137.07083784359156,35.28492666343476],[137.05890058809166,35.304150296299156],[137.05068403474036,35.31148834870834],[137.04122724793916,35.31732778614841],[137.03611128091092,35.32135854845828],[137.0321838731872,35.326035265015406],[137.0261894061162,35.33903188681214],[137.02071170478132,35.34766185221275],[137.01311526905448,35.354431463840996],[136.9924963731092,35.36383657469813],[136.96500451094937,35.38104482685728],[136.95291222491915,35.38414541318025],[136.93957970633835,35.3818974884783],[136.9324483577056,35.38001129808296],[136.90976240511088,35.37070954001322],[136.84873253777636,35.36009003412781],[136.82863040576854,35.35295868639423],[136.80594445227467,35.34205495876935],[136.79736616371747,35.34053050447926],[136.7898214048339,35.34084056194234],[136.776488886253,35.347403468894896],[136.7705977719695,35.35210602477321],[136.76098595643668,35.34750682258175],[136.75302778460457,35.33867015250553],[136.7255876001875,35.29128286391307],[136.6800606626694,35.236376654858674],[136.67654666609576,35.215628566804924],[136.67344607977276,35.138811549713296],[136.61804894430333,35.158009345055234],[136.58466596875945,35.17439077401497],[136.55815595942988,35.19402781802938],[136.54900923099115,35.204208075242434],[136.5387772969347,35.22275991543779],[136.52833865820247,35.22942617427853],[136.5151611683532,35.23190664297715],[136.3987858423926,35.20260610568721],[136.37615156484276,35.231493231827216],[136.37537641758752,35.266323147295424],[136.39397993372688,35.31861969734118],[136.39816572476832,35.343579413059445],[136.40245486859732,35.35714447743567],[136.40762251156963,35.393395494406576],[136.3973905784125,35.45277171534238],[136.37909712063566,35.50106334150004],[136.3684001003843,35.52082957582422],[136.35935672383374,35.53385203694194],[136.34757449796467,35.53622915285305],[136.33806603431998,35.5320175242893],[136.3260254251333,35.53294770027611],[136.31238284729056,35.54168101756491],[136.29796512309196,35.56397939743121],[136.29264244958938,35.57989573839757],[136.28923180580324,35.60015290003628],[136.25729577018365,35.64562815981171],[136.28985192342753,35.6952633731062],[136.30426964672677,35.744226792832194],[136.31315799364648,35.75461375651969],[136.32416507405895,35.76169342740974],[136.3370325046464,35.76639598238867],[136.35026167223833,35.76792043757814],[136.36452436680605,35.76608592402613],[136.39909590075513,35.75590566681308],[136.41465050741493,35.75378693332027],[136.42999840670115,35.75456207877687],[136.44271080855697,35.754045314839374],[136.45537153536804,35.75190074292493],[136.4760937850001,35.73993764900331],[136.48415530782125,35.73911082580398],[136.48927127484944,35.74818003987696],[136.4951623882336,35.756060696443996],[136.50802981972043,35.761796780197216],[136.61200280218748,35.779263413875384],[136.634378697319,35.779289252297104],[136.6685884951629,35.77386322690643],[136.6870886576154,35.77448334453078],[136.7100846703712,35.780839545009],[136.72682783363757,35.78316498497608],[136.75773034138206,35.78401764569776],[136.76858239216364,35.79099396380042],[136.7826900579998,35.81305980146868],[136.79555748858724,35.82437693934423],[136.80305057242595,35.83605581332509],[136.80212039643914,35.85037018473622],[136.7924569031635,35.86938711202566],[136.77896935405244,35.88698293691303],[136.76594689293472,35.8982742381655],[136.75385460780387,35.90442373306885],[136.72791303925533,35.91021149276622],[136.71659590137978,35.915172431062814],[136.70775923130338,35.92266551400229],[136.70124800029498,35.931605535966696],[136.6985608260212,35.943542792365974],[136.6991809427463,35.95576426870596],[136.7027982930067,35.97271413844672],[136.7071391136791,36.013874416870806],[136.71582075412448,36.038808295066815],[136.722331985133,36.051236476981785],[136.72429568899474,36.05991811832655],[136.72088504520866,36.07480093051851],[136.74527632104537,36.10616852535716],[136.7542680198532,36.147664700565514],[136.76253624914858,36.16156565992725],[136.81188724160296,36.22006338082039],[136.81679650305614,36.23520457633012],[136.81312747595223,36.2470126415202],[136.7916817568077,36.26502187845688],[136.7842403498122,36.27300588601284],[136.7732332702991,36.291996974880476],[136.79726281093002,36.28770783105156],[136.80584109858773,36.28899974224433],[136.8145227399325,36.294477444478446],[136.8193286476989,36.303159084923834],[136.8227392932837,36.31499298853555],[136.82863040576854,36.32858388953491],[136.83746707584487,36.33757558924202],[136.85048953696258,36.34279490815845],[136.88139204290837,36.33783397076115],[136.89891035432936,36.33742055871184],[136.9219063661858,36.332821357419675],[136.93570397365949,36.32656850793029],[136.94485070209817,36.31889455783758],[136.94872643387782,36.31111725495728],[136.9485714051462,36.30305573213636],[136.94283532139286,36.284038804846915],[136.9462459660785,36.27608063391409],[136.95797651600333,36.273315945274646],[136.97709679608027,36.2833670112785],[136.99849083928075,36.303520820129705],[137.0052087749649,36.30819753668692],[137.01683597210246,36.31346853334617],[137.02680952374044,36.31948883704021],[137.03528445861087,36.330495917452595],[137.03900516165885,36.340391953825616],[137.04153730720088,36.35080475503534],[137.04649824369892,36.35700592678202],[137.05409468032525,36.36266449616957],[137.06680708218113,36.36914988875634],[137.08127648322306,36.37896841076352],[137.13259118133794,36.42521881779422],[137.14576867118723,36.43271189983432],[137.1611682482161,36.43684601493166],[137.17589603077715,36.43201426874356],[137.18767825664622,36.42966299125405],[137.2001322778823,36.42891368331985],[137.26421105379706,36.450023504780944],[137.27630333892785,36.45224559196052],[137.28482994974235,36.45079865203621],[137.28824059532712,36.444649156233595],[137.2860185081476,36.4373627788691],[137.2850883321608,36.431109931178355],[137.28669030171602,36.42503794974161],[137.29490685416795,36.42113638043955]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-17\",\"name\":\"Ishikawa\",\"name_alt\":\"Isikawa\",\"name_local\":\"石川県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chubu\",\"postal\":\"IS\",\"region_code\":\"JPN-CHB\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[137.0452580090001,37.145086981000176],[137.04444420700005,37.13670482000013],[137.04590905000012,37.128119208000115],[137.0387475920002,37.12010325700008],[137.02222741,37.114447333000086],[137.0037541020001,37.11347077],[136.98585045700023,37.1083031270001],[136.97266686300006,37.10162995000006],[136.96322675900015,37.10154857000008],[136.94727623800023,37.09699127800012],[136.94369550899998,37.09735748900012],[136.93482506600006,37.10814036700013],[136.9155379570001,37.12775299700009],[136.90528405000018,37.14362213700012],[136.91146894600004,37.150946356000034],[136.91960696700008,37.14744700700014],[136.92351321700008,37.14008209800015],[136.92839603000002,37.13776276200018],[136.93433678500006,37.14179108300014],[136.94125410200016,37.14443594000012],[136.95020592500006,37.144761460000055],[136.95932050900015,37.147365627000156],[136.96664472700016,37.15180084800012],[136.97364342500023,37.153265692],[136.9812117850001,37.15131256700015],[136.98902428500017,37.14533112200003],[136.99903405000006,37.13947174700009],[137.00855553500017,37.140611070000105],[137.0162866550002,37.14484284100014],[137.02719160200016,37.15521881700015],[137.04175866000017,37.159735419000086],[137.048106316,37.15228913000011],[137.0452580090001,37.145086981000176]]],[[[137.35120513000012,37.44567560400013],[137.32513608100007,37.439111553000046],[137.30783313499998,37.439808897000106],[137.28725280900008,37.442975305000076],[137.26812894400004,37.43649042400007],[137.25360123900006,37.42910590200013],[137.2459830050001,37.41355557100012],[137.23736360900014,37.37988162000012],[137.248878415,37.35726027800008],[137.26748553400003,37.355085776000024],[137.2653492120002,37.339807303000114],[137.26254497500017,37.32604948699999],[137.25582138300004,37.315260486000014],[137.23766775000016,37.30170764600014],[137.23023522200018,37.29214101800015],[137.20918093000003,37.293031904000046],[137.16560551600006,37.30083373900014],[137.10872138900007,37.28344965100008],[137.09287312299998,37.26136598400005],[137.07130008,37.238111731000046],[137.07066671599998,37.21864397800006],[137.0543695370001,37.21018374600011],[137.03289212600012,37.20466420300015],[137.02695161400018,37.19136096500013],[137.01417076900006,37.184881903000175],[136.980235222,37.196478583],[136.96022288300006,37.20733866900018],[136.94588300800015,37.21718561900009],[136.95634768100004,37.22806693100007],[136.95995418700002,37.234720022000076],[136.95106995500024,37.23448289400007],[136.94180612000017,37.224720176000105],[136.93043053500006,37.22162506700006],[136.92652428500006,37.21352773600013],[136.91656779300004,37.198207266000125],[136.89259776400016,37.17332110000011],[136.88021545300015,37.144152871000145],[136.89566763700023,37.13890407800001],[136.89323421900022,37.11753438400014],[136.88775294,37.11031842000013],[136.86867050100014,37.11091236500006],[136.86811660400022,37.08910758899999],[136.8609261840002,37.07677545000014],[136.87468509200008,37.06964752800006],[136.89332116000006,37.073065497000144],[136.9142358730002,37.082424221000096],[136.93360436300011,37.079820054],[136.96322675900015,37.051743882],[136.98012596200024,37.04685964],[137.00399162800002,37.055457543000145],[137.02429826600022,37.0954088860001],[137.0386867560002,37.10282104000002],[137.04712975400005,37.098822333],[137.05396569100017,37.079820054],[137.04884394200022,37.02292600100019],[137.054446199,36.966749864000164],[137.0444249916471,36.95640420176632],[137.04262251191952,36.95627167409846],[137.0086194196506,36.95288686783475],[136.99141116839067,36.95340363177225],[136.97926720641632,36.95149160385439],[136.95074181458298,36.93965770114205],[136.9381844423581,36.93759064449266],[136.92686730358312,36.938107408430156],[136.91487837123978,36.93730642275328],[136.90464643808278,36.933094794189415],[136.89410444566377,36.92570506403753],[136.87157352090125,36.902424832240186],[136.85979129503224,36.88702525521133],[136.83281619681,36.82948354782742],[136.8273384945758,36.81310211796843],[136.82444461472724,36.79054535658294],[136.811732211972,36.75264069321345],[136.80429080587604,36.742615464731855],[136.78408532108077,36.72742259417744],[136.7771090029781,36.718947659307034],[136.77834923732738,36.705873521345964],[136.7812947940196,36.69543488171435],[136.7861007017859,36.6848928901948],[136.78765099539712,36.67510020570997],[136.78703087777276,36.66466156697773],[136.76920250888858,36.62153758379243],[136.76997765524462,36.60339915654585],[136.77276818320505,36.588593857820385],[136.78284508673133,36.55665782310008],[136.7836202330873,36.54340281798558],[136.77907270684003,36.52479930274565],[136.77380171108004,36.51286204724575],[136.7684273616335,36.49663564701747],[136.75835045810723,36.440360012404724],[136.75757531085188,36.418991808525064],[136.7620711611552,36.402791245819415],[136.7690474792577,36.39100901995023],[136.77323327029913,36.376591294852446],[136.7736983582926,36.35979645384323],[136.7664119809281,36.33450084134081],[136.7643966002229,36.321555895488146],[136.77323327029913,36.29199697488052],[136.78424034981222,36.27300588601288],[136.79168175680772,36.26502187845692],[136.81312747595226,36.24701264152016],[136.81679650305617,36.23520457633016],[136.81188724160305,36.220063380820434],[136.76253624914867,36.16156565992729],[136.7542680198533,36.14766470056556],[136.7452763210454,36.106168525357205],[136.7208850452087,36.07480093051849],[136.65623782761364,36.06343211490024],[136.63716922438013,36.06617096601734],[136.62300988170082,36.07446503373423],[136.58683637909562,36.108028876431476],[136.56621748315033,36.1235318062479],[136.53298953723734,36.1399132352076],[136.49485232987118,36.14580434769256],[136.46270958867663,36.1467862005227],[136.448911981203,36.14464162950763],[136.43573449135366,36.14022329446952],[136.41692426963928,36.14045583846611],[136.37832197517903,36.15539032570304],[136.35873660710868,36.15949860327798],[136.33563724156548,36.16192739513316],[136.32292483881022,36.16631989174955],[136.31191775929713,36.17376129784567],[136.30519982271355,36.18414826153305],[136.30106570941493,36.196292223507285],[136.29403771356957,36.22517934784857],[136.29000695215908,36.23592804584264],[136.286596308373,36.24236176248537],[136.2800850773643,36.24838206617933],[136.27605431505472,36.251275946927436],[136.24318810434752,36.269776109380004],[136.2412243995864,36.27179149008525],[136.2267192623897,36.28560274627942],[136.278667388,36.322405775000064],[136.29841744100005,36.34727817000011],[136.34677884800007,36.36580091000003],[136.38229344500022,36.391071368000084],[136.43018639400012,36.4325218770001],[136.63996143100022,36.662600574000024],[136.6896847930001,36.730826931000095],[136.76055452600022,36.87059936100003],[136.76563379700022,36.88952789300008],[136.765448029,36.913397111000066],[136.75823829400005,36.91931265700005],[136.75038571000007,36.92572910200009],[136.75195861900025,36.935416431000036],[136.76093284200007,36.95239759500005],[136.76761331400022,36.98099874800006],[136.7599096760001,37.000087101000176],[136.74228837100011,37.01338806100016],[136.7315599890001,37.047648676000094],[136.72044825800006,37.05499833000006],[136.71841293800006,37.07626148900009],[136.7267358730002,37.126613674000154],[136.720960846,37.142047501000135],[136.70856414900007,37.14987732700014],[136.69842979700016,37.14759712700011],[136.69626613100004,37.13636152400015],[136.6764437830001,37.13740149600012],[136.67025890300025,37.149957088000164],[136.67447257100017,37.17547192100007],[136.68200941000006,37.19905012400015],[136.69042389300014,37.21500773200002],[136.70557701900006,37.22825755400014],[136.69783547500012,37.239542596000106],[136.71277116100006,37.25106551400016],[136.72614534199997,37.26963998000012],[136.73321719800018,37.285014846000095],[136.7244348510002,37.297491111000156],[136.72153094200021,37.325329539],[136.74909419400004,37.35791740000012],[136.78376714400005,37.37389233900008],[136.84217066699998,37.399532772],[136.88079850700015,37.407986994000154],[136.9034835250002,37.398323368000106],[136.93476613600015,37.399457018000035],[137.04487433600005,37.44791639099999],[137.07153452100013,37.45300312900004],[137.09133864200012,37.47263502],[137.11384056300002,37.487746918000184],[137.25549087500022,37.52459398900005],[137.295709888,37.53149016499999],[137.34096555900013,37.517094138000104],[137.35087377100015,37.50610344200014],[137.34369685500005,37.48666688600015],[137.34529929800013,37.475020106],[137.35897927200014,37.46663506600005],[137.35613040500002,37.453192450000174],[137.35120513000012,37.44567560400013]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-24\",\"name\":\"Mie\",\"name_alt\":\"Miye\",\"name_local\":\"三重県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kinki\",\"postal\":\"ME\",\"region_code\":\"JPN-CHB\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[136.89120225700006,34.47970794400008],[136.88320429000007,34.475366090000094],[136.87753635300024,34.48317624300016],[136.87259894600012,34.49113933299999],[136.89196844200004,34.49669208000016],[136.89899152000018,34.50405336600012],[136.9089525420002,34.50236246700011],[136.90690636200011,34.49414952200003],[136.90247110600004,34.48606033400013],[136.89120225700006,34.47970794400008]]],[[[136.87398509000005,34.51341308800015],[136.8562159960002,34.51013930400008],[136.84912737600004,34.51503705200004],[136.86591662000004,34.521326624000054],[136.89017871600018,34.531200943000115],[136.89647361500013,34.53841118900003],[136.91005235100025,34.54010947400012],[136.90248620500014,34.53197415700005],[136.90317474600025,34.525015299000174],[136.88906349200008,34.52255264700007],[136.87398509000005,34.51341308800015]]],[[[136.7359745629757,35.04168569593814],[136.75180097706672,35.02614980693416],[136.7448022800002,35.02342357],[136.72559655000023,35.02724844],[136.68116295700023,35.000921942],[136.65919030000012,34.98395416900003],[136.64991295700005,34.968898830000015],[136.64869225400005,34.96259186400006],[136.64356530000023,34.95319245000006],[136.642425977,34.94806549700003],[136.64478600400017,34.94236888200005],[136.66166683600014,34.94050457500008],[136.645550992,34.929787448000084],[136.64825961300008,34.911600243000024],[136.6393159400001,34.88904870099999],[136.6161145450001,34.85350057100011],[136.56501226500004,34.80011926500005],[136.5349399390001,34.76569509900018],[136.52735436300023,34.72125885600012],[136.52165774800008,34.701727606000034],[136.51954186300023,34.68085358300006],[136.52165774800008,34.67609284100017],[136.53223717500012,34.66014232000008],[136.53630618600008,34.65668366100006],[136.54639733200023,34.651597398000135],[136.5456649100001,34.63987864800005],[136.54118899800002,34.626206773000106],[136.54004967500012,34.61570872600005],[136.55103600400005,34.601996161000116],[136.56812584700018,34.59638092700011],[136.61207116000006,34.59589264500012],[136.6329858730002,34.59113190300003],[136.64877363400004,34.57973867400007],[136.66260826900023,34.56598541900014],[136.6770939460001,34.55426666900014],[136.7522892590001,34.513332424000126],[136.80355879000004,34.49526601800004],[136.81446373800011,34.48602936400006],[136.82496178500017,34.48944733300006],[136.83887780000023,34.48916250200013],[136.85230553500006,34.48554108300006],[136.86215254000015,34.47919342700014],[136.86890709700018,34.467352606000176],[136.87077884200008,34.45685455900009],[136.87354576900012,34.44708893400015],[136.88331139400023,34.437567450000174],[136.89633222700004,34.433417059000035],[136.90699303500017,34.434637762000065],[136.91602623800006,34.43398672100001],[136.92432701900017,34.423976955000015],[136.92579186300023,34.41290924700017],[136.91675866,34.376166083000086],[136.90463300900004,34.38117096600003],[136.89307701900012,34.38239166900006],[136.88314863399998,34.37885163000006],[136.87525475400005,34.36933014500006],[136.87989342500023,34.36273834800009],[136.88721764400023,34.3570824240001],[136.89722741000006,34.353949286000116],[136.90992272200018,34.35504791900017],[136.894379102,34.34271881700012],[136.88868248800011,34.33616771000014],[136.88331139400023,34.32831452000015],[136.8912866550002,34.31769440300012],[136.89779707099999,34.30097077000009],[136.90023847700004,34.28253815300015],[136.8956811860002,34.26691315300015],[136.88599694100006,34.260443427000055],[136.85084069100017,34.24925364800005],[136.83545983200023,34.24640534100011],[136.81853274800002,34.24762604400003],[136.79420006600017,34.25311920800006],[136.77515709700018,34.26186758000013],[136.77344811300006,34.273098049000126],[136.78793379000004,34.27741120000003],[136.8092554050002,34.27387116100006],[136.8296004570001,34.26654694200015],[136.841644727,34.25946686400009],[136.83961022200018,34.26605866100006],[136.83806399800014,34.27533600500011],[136.83545983200023,34.280503648000106],[136.84848066500004,34.273098049000126],[136.85621178500006,34.29417552300005],[136.84644616000017,34.30756256700006],[136.82740319100017,34.31240469000012],[136.80746504000004,34.30784739799999],[136.80982506600017,34.30109284100014],[136.8097436860002,34.29791901200018],[136.80836022200012,34.294623114000146],[136.80746504000004,34.28733958500014],[136.77344811300006,34.30784739799999],[136.75660241000017,34.296087958000115],[136.72584069100012,34.29100169499999],[136.70264733200005,34.29515208500011],[136.70826256600017,34.31098053600006],[136.72339928500017,34.33128489800008],[136.70679772200012,34.33429596600017],[136.67628014400017,34.326402085],[136.64991295700005,34.314113674000126],[136.66814212300008,34.309556382],[136.66700280000012,34.29791901200018],[136.65479576900023,34.28603750200013],[136.62289472700004,34.274603583000086],[136.60808353000007,34.264593817],[136.59253991000006,34.26264069200015],[136.57406660200004,34.280503648000106],[136.56820722700016,34.274318752000156],[136.56397545700017,34.271714585000055],[136.56153405000012,34.26837799700003],[136.56055748800023,34.25946686400009],[136.53288821700008,34.27167389500006],[136.52295983200023,34.273098049000126],[136.51775149800008,34.26121653900016],[136.51221764400023,34.25218333500008],[136.50586998800011,34.24640534100011],[136.51482181100002,34.22980377800015],[136.50294030000006,34.22907135600012],[136.47169030000012,34.23895905200014],[136.4272567070001,34.213771877000156],[136.41651451900006,34.204250393000066],[136.38428795700023,34.204250393000066],[136.33668053500017,34.184027411000145],[136.29900149800008,34.15428294500013],[136.29704837300002,34.125677802000055],[136.308360222,34.11782461100016],[136.31641686300023,34.11432526200018],[136.31902103000007,34.10883209800015],[136.31348717500012,34.09495677300005],[136.308441602,34.09003327],[136.30030358200005,34.08567942900011],[136.2911889980002,34.082505601000136],[136.28345787900017,34.081284898000106],[136.27312259200008,34.085842190000065],[136.26075280000023,34.10512929900001],[136.2526961600001,34.108587958],[136.24634850400017,34.103583075000145],[136.23023522200018,34.08209870000003],[136.21851647200012,34.07514069200015],[136.21851647200012,34.06761302299999],[136.23308353000002,34.06777578300013],[136.24382571700008,34.06415436400009],[136.25049889400012,34.056341864],[136.2526961600001,34.04344310099999],[136.270762566,34.02814362200003],[136.27979576900012,34.01732005400011],[136.27662194100006,34.012396552000055],[136.27458743600002,34.00674062700007],[136.27540123800023,33.97996653900016],[136.27320397200006,33.97142161700005],[136.25863691500004,33.96588776200004],[136.24732506600006,33.97150299700003],[136.23861738400015,33.98248932500009],[136.23218834700006,33.99315013200011],[136.2190047540001,33.981146552],[136.20484459700018,33.97142161700005],[136.20484459700018,33.96523672100007],[136.22754967500012,33.96173737200009],[136.23471113400015,33.94843170800006],[136.22852623800011,33.9383812520001],[136.2111108730002,33.944769598000036],[136.20630944100006,33.92694733300014],[136.19890384200002,33.92088450700014],[136.16480553500006,33.91616445500013],[136.16049238400004,33.913397528000175],[136.15267988400015,33.90623607000013],[136.14258873800006,33.89362213700015],[136.13697350400017,33.89057038000006],[136.12574303500017,33.8895531270001],[136.1047442250002,33.89141140000011],[136.0693171280001,33.84704388300004],[136.01707507400025,33.73389913600009],[135.9937443370002,33.68626536700005],[135.99373956985735,33.68625974353479],[135.96713260298728,33.698563544571996],[135.9508028499716,33.70393789311926],[135.93824547684724,33.71429901838492],[135.92878869004593,33.72357493713368],[135.91995201996977,33.735589707898626],[135.90243371034754,33.74750112587621],[135.86315962141933,33.78708527316685],[135.8516874530135,33.80266571734897],[135.84682986930304,33.81741933923111],[135.8465198100412,33.82496409811462],[135.84765669160296,33.83814158796399],[135.87540693618124,33.85080231387572],[135.88331343027053,33.86294627495069],[135.89137495309174,33.882686671752495],[135.90036665279874,33.88852610919268],[135.91514611220342,33.88873281476755],[135.92072716722512,33.89343536974657],[135.92553307499136,33.90108348231685],[135.95752078655508,33.91795583769182],[135.97410892108977,33.93697276498118],[135.98480594313978,33.95314748836584],[135.9906970556245,33.970329902103245],[136.0038228704291,33.99311920838481],[136.0175688010593,34.00484975830973],[136.03539716994342,34.010740871693784],[136.08722863199583,34.00944896140048],[136.09777062441484,34.01164520925907],[136.10149132656343,34.01846649773064],[136.09079430631223,34.03590729388647],[136.0857816920715,34.04996328377858],[136.10939782065284,34.126237698511076],[136.1101729679082,34.13858836516091],[136.1072274112162,34.18938629843892],[136.1180794619975,34.22840200494856],[136.11869957872258,34.24114024522608],[136.1128084662378,34.25496369112149],[136.10438520731142,34.26899384169242],[136.10087120983852,34.283437405212126],[136.10645226486022,34.29204153129156],[136.1076924992095,34.30069733331523],[136.08195763623584,34.35087514896905],[136.07389611161628,34.36353587488081],[136.0692452325815,34.37699758646944],[136.07120893824188,34.39381826410154],[136.0807174009873,34.408804429980464],[136.0956002140786,34.420018215068595],[136.11389367095617,34.42518585894017],[136.15730187408226,34.431154486690176],[136.17750735887753,34.43862173120793],[136.20267378107022,34.45882721600317],[136.21042524642806,34.472469793845974],[136.21120039278387,34.48639659342798],[136.2079447777293,34.495414129758345],[136.20251875233848,34.50151194871755],[136.18127973876895,34.50396657989374],[136.17146121586248,34.50936676686287],[136.16164269385516,34.52781525337129],[136.15363284697835,34.53383555796462],[136.14247073783443,34.53556671782978],[136.1289831887232,34.53494660110478],[136.1142037293187,34.5363935410292],[136.09699547715948,34.54249135998829],[136.07585981727667,34.552490750047994],[136.05989179856758,34.56285187441448],[136.05245039337095,34.5745824243394],[136.05353559808927,34.58900014943738],[136.0592716818426,34.60553660712874],[136.0583415058558,34.61912750902739],[136.05338056935776,34.62643972391423],[136.04097822496513,34.63375193880087],[136.0381876970046,34.63716258438576],[136.03989302024675,34.64124502174043],[136.06330244415258,34.64680023923968],[136.07234581890455,34.65630870198494],[136.0648527368643,34.677315172457284],[136.06252729689723,34.69178457349922],[136.05911665311115,34.702533271493294],[136.05508589080128,34.70845022239986],[136.0496598654104,34.71167999903277],[136.0364823746618,34.71534902613668],[136.00309940001736,34.773691718298195],[136.08195763623584,34.800925198039565],[136.10118126820086,34.81911530212956],[136.1128084662378,34.83311961517815],[136.10040612184517,34.85420360001616],[136.10211144418767,34.86505564989828],[136.11265343570741,34.87105011696927],[136.1245906921068,34.87234202726275],[136.18045291556953,34.862523505255595],[136.2405009300738,34.863117784458254],[136.31160770093456,34.87929250694363],[136.34261355966808,34.89270254168902],[136.3624573101567,34.90582835469486],[136.37010542182773,34.91616364243818],[136.38157759023366,34.92639557469609],[136.39165449375994,34.93735097916441],[136.39987104711125,34.94946910271683],[136.4072091004197,34.966884060450965],[136.41165327387952,34.98236115184575],[136.41837120956362,35.02189362229281],[136.4323238466683,35.062097887207656],[136.44317589744966,35.13183523251003],[136.4419356631005,35.14147288736386],[136.43573449135366,35.15098135010929],[136.40700239484457,35.183434150565645],[136.39878584239258,35.20260610568725],[136.51516116835322,35.23190664297711],[136.52833865820256,35.2294261742786],[136.5387772969348,35.222759915437834],[136.54900923099117,35.20420807524239],[136.5581559594299,35.19402781802934],[136.58466596875954,35.174390774014924],[136.6180489443034,35.15800934505522],[136.6734460797728,35.13881154971334],[136.68414310092342,35.12126740256859],[136.69825076675957,35.10692719183639],[136.71008467037117,35.0913984244978],[136.7245023945699,35.05915233051569],[136.7359745629757,35.04168569593814]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-20\",\"name\":\"Nagano\",\"name_alt\":null,\"name_local\":\"長野県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chubu\",\"postal\":\"NN\",\"region_code\":\"JPN-CHB\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[138.67952518074247,36.73065237081019],[138.6609216664017,36.72416697822342],[138.6295540706638,36.71822418889522],[138.61358605375335,36.71264313387354],[138.60345747338363,36.70654531491434],[138.57973799021602,36.701584378416385],[138.54185916616763,36.69812205688734],[138.51829471173153,36.69251516434332],[138.50744266184927,36.68887197476184],[138.4992261093974,36.68205068629007],[138.49674563979943,36.671069444299405],[138.4988643741915,36.6561091168422],[138.49390343589482,36.645773830897525],[138.4842916203619,36.63848745443241],[138.44692955935164,36.63091685622791],[138.43654259656353,36.62543915489309],[138.4306514831793,36.61812693910696],[138.4278609561182,36.60794668189382],[138.42408857622678,36.59929087987015],[138.41912763882942,36.59084178342155],[138.41132449842684,36.58076487899589],[138.40470991373155,36.57037791620789],[138.39897382997822,36.54552155237775],[138.38378095942375,36.502733465976604],[138.37897505075796,36.48314809790642],[138.37680464132114,36.46782603614264],[138.38062869715662,36.44847321206913],[138.38889692555267,36.43452057496461],[138.40858564730974,36.4186817492633],[138.43840294763788,36.400284938698945],[138.450650263299,36.397571926003565],[138.46413781061142,36.39811452926213],[138.48170779797653,36.403359687499574],[138.4992261093974,36.40230032030357],[138.5141089206901,36.40434153943053],[138.54051557813142,36.41410838549365],[138.55415815597405,36.41757070612323],[138.56904096906544,36.41839752932245],[138.5837687516265,36.416898912554814],[138.60097700378566,36.411679591839714],[138.61482628810273,36.400284938698945],[138.6236629581789,36.38395518748192],[138.6283138372138,36.33744639713345],[138.6295540706638,36.332252915739446],[138.63265465698686,36.32390717207832],[138.6298641299257,36.31065216696393],[138.62211266456785,36.30011017544419],[138.6115189962046,36.290446682168664],[138.59281212817717,36.27941376423385],[138.58702436668113,36.270215358951674],[138.58857466029224,36.254970812453024],[138.5950858913008,36.226988022978816],[138.60469770683375,36.20497386305334],[138.60469770683375,36.18637034781332],[138.60025353427318,36.17621592902191],[138.59260542170284,36.16978221327855],[138.57555219917458,36.169549669281764],[138.56811079307863,36.16549306944955],[138.57028120341482,36.15334910747529],[138.57539717044307,36.14347890952408],[138.58950483627922,36.13249766753333],[138.6021138862468,36.12534048137812],[138.61327599539084,36.116400458514335],[138.61389611211592,36.10497996695187],[138.61265587776654,36.084257717319815],[138.61560143355933,36.06255361575671],[138.61467125937116,36.051882433027856],[138.61188073141057,36.042089749442425],[138.61451622884078,36.031030992186516],[138.62784874922033,36.01702667913793],[138.700919223943,35.96953603775788],[138.6995239608621,35.94982147937779],[138.7156986842469,35.89646556213603],[138.66278201657684,35.8594393988092],[138.64552208937295,35.85419424147105],[138.63446333211704,35.85326406548424],[138.62299116371122,35.85398753499672],[138.612965936129,35.85817332603813],[138.60428429568373,35.866803290539366],[138.5999434750113,35.87796540058275],[138.59467247925153,35.88623362897883],[138.58526736929366,35.89196971273205],[138.57286502490092,35.894579373539244],[138.55679365520302,35.89563873983596],[138.53193729137286,35.89954031003728],[138.51891483025526,35.89954031003728],[138.466308221847,35.88819733284066],[138.45276899589237,35.890316067232675],[138.44429406102205,35.89775747332871],[138.4416585626925,35.907653509701646],[138.44150353396088,35.9181179877549],[138.44067671076152,35.92514598180162],[138.4368526549261,35.932742418427836],[138.4281710153801,35.939150295749585],[138.3986637716157,35.950906684096225],[138.3715336446619,35.95728872299617],[138.36026818273035,35.95780548783296],[138.34962283662458,35.95098419936127],[138.33825402190558,35.93884023738704],[138.28719770710848,35.86266917634137],[138.27887780186904,35.854891873461185],[138.2697310734302,35.849310818439506],[138.257122024362,35.84809642251187],[138.2354179218995,35.85853506124411],[138.22549604710485,35.85796662046317],[138.216504348297,35.85039602315794],[138.18017581606108,35.786730659292516],[138.17971072806762,35.777609768376095],[138.18637698780765,35.76556915918934],[138.19769412658263,35.75670665069134],[138.20265506398,35.74709483515849],[138.19753909695174,35.73668203394868],[138.17381961468345,35.713220934098814],[138.16854861892352,35.69880320900083],[138.17211429324004,35.67472199242596],[138.1820361680347,35.64782440946894],[138.19614383387085,35.627851466871704],[138.1868420758011,35.607387600557246],[138.18188113840387,35.58697540928773],[138.17707522973797,35.57855215126074],[138.16896203007354,35.57074900995883],[138.1543892762441,35.56682160133579],[138.1432271671,35.56095632637337],[138.13594078973563,35.547081204533896],[138.1387313167968,35.535040595347226],[138.1396614927836,35.52271576621965],[138.1380595241277,35.51002920188617],[138.13361534976855,35.492252508946194],[138.1367159360915,35.480418606233684],[138.14043664003884,35.46987661381468],[138.1387313167968,35.4600580918075],[138.13051476524413,35.447836616366686],[138.11325483624165,35.434013170471445],[138.10943078040611,35.423574530839915],[138.11067101475544,35.41401439125113],[138.11826744958313,35.40414419240054],[138.1236934749739,35.39442902318086],[138.12679406129695,35.38453298590855],[138.1230733582488,35.37086457054352],[138.1125313658298,35.35737702143243],[138.0855045916635,35.33696483016274],[138.06767622277937,35.32973013054104],[138.03946089110724,35.324355781094425],[138.02778201712636,35.3190331084912],[138.01460452727707,35.30763845535043],[138.00530276920733,35.297173977297206],[137.9961560407686,35.29128286391307],[137.98468387326204,35.28652863209069],[137.95936242143853,35.28195526832096],[137.9398287293124,35.27309275982296],[137.8959037622488,35.24332713543893],[137.87543989503513,35.23567902286857],[137.83802615808065,35.20526744243844],[137.80557335762438,35.19451874444445],[137.7513131037167,35.196224066787266],[137.68206668482958,35.20870392554568],[137.6623779639719,35.20901398390822],[137.64341271442515,35.20480235444509],[137.6296667828957,35.19759349234582],[137.6167476745655,35.19265839337021],[137.58822228363152,35.18847260232869],[137.57514814656963,35.18816254306688],[137.56522627177492,35.19312348136356],[137.5556144553429,35.201934312118794],[137.53871626244558,35.23717763873702],[137.54786298998505,35.27035390870586],[137.56367597906308,35.301411445182055],[137.57261600192697,35.3132970238386],[137.57840376162426,35.32843821934836],[137.5756132345631,35.34512970577134],[137.5644511254191,35.37112295206262],[137.5684818868296,35.3787193868902],[137.57623335218744,35.3818974884783],[137.59767907133198,35.38029551892308],[137.60444868385957,35.38450714838612],[137.6057405950524,35.39398977270986],[137.59349328029055,35.42409129477741],[137.59070275322938,35.43631277111743],[137.59473351463998,35.453288479279905],[137.59473351463998,35.46956655545213],[137.5852250509953,35.49509471285069],[137.56042036400856,35.517625636713746],[137.53974979121983,35.5270565859926],[137.53416873619818,35.53049306909983],[137.5315849138127,35.53354197857939],[137.52817427002657,35.53922638638855],[137.50998416593643,35.59002431876729],[137.5118445170108,35.62245127990266],[137.51075931229246,35.63456940435455],[137.50331790619634,35.6466875270078],[137.4805285990156,35.669270127714185],[137.47195031135777,35.68164663278583],[137.444045038048,35.73337474205064],[137.43474327997836,35.74223725054867],[137.42368452362183,35.74872264313544],[137.3957792503121,35.755595608450534],[137.3844621124365,35.76156423620054],[137.37764082216626,35.76980662707419],[137.36890750487737,35.77745473874518],[137.35712527900847,35.78143382421163],[137.33325076710918,35.78381094012278],[137.3259643897448,35.78988292155964],[137.3232255386277,35.8016393090069],[137.33836673413742,35.82453196897521],[137.37593549982347,35.86850861288218],[137.39278201677655,35.880575060490656],[137.41112715139684,35.8865953641847],[137.42802534429416,35.88638865771037],[137.4490059754454,35.88406321954196],[137.46280358291892,35.889385891245865],[137.47350060406967,35.90049632444581],[137.48455936132547,35.91667104783056],[137.5199060407312,35.95276703517047],[137.55840498330332,36.00118785343662],[137.57731855690588,36.03123769776158],[137.58212446467223,36.0438984245726],[137.58212446467223,36.056817532003464],[137.5723576186092,36.07257884423831],[137.5577848647796,36.08482615810064],[137.5447624045613,36.10012238234202],[137.53943973195805,36.11789907528208],[137.54646772690404,36.13851797122737],[137.561350539096,36.16559642223703],[137.56486453656913,36.176190089700896],[137.56243574381455,36.18957428602451],[137.56181562708946,36.19825592736916],[137.56290083180784,36.21055491807505],[137.57220258987758,36.226574611828795],[137.5979891296945,36.25086253487808],[137.60630903493393,36.26039683694444],[137.61194176679913,36.27106802057273],[137.62279381668122,36.300161852287715],[137.62449913992333,36.31499298853555],[137.61829796817665,36.33059927024007],[137.60822106285173,36.34705821446502],[137.56538130140592,36.377004706002396],[137.5805741719604,36.395143134148356],[137.6105465028188,36.41385000307517],[137.6207784359761,36.423642687559976],[137.62821984297136,36.43452057496461],[137.64310265516337,36.468446152867614],[137.6567969098495,36.4919589304603],[137.67090457568554,36.50671255144306],[137.67648562980787,36.518856513417404],[137.6749353370961,36.52836497706204],[137.67152469331,36.53911367415674],[137.67571048435127,36.54877716743235],[137.68578738787755,36.559344998273076],[137.70180708163156,36.57172150334472],[137.719480421784,36.58983409306896],[137.72537153516822,36.604794420526076],[137.72831709186028,36.62140839348257],[137.72971235584055,36.64463694933576],[137.7340531756136,36.66804637324148],[137.73265791163334,36.77480988187051],[137.7753426452469,36.78736725499485],[137.78790001837115,36.794240221209236],[137.80107750911975,36.80338694874871],[137.8371993348814,36.84803538712336],[137.8453125354452,36.86136790570424],[137.84996341358072,36.87371857415283],[137.84794803287537,36.89242544218044],[137.84887820886217,36.900952052994825],[137.85321902863524,36.90916860634613],[137.86050540599973,36.91534394057038],[137.86872195935086,36.91924550987241],[137.8752331885608,36.92092499379342],[137.8847416531047,36.92213939151969],[137.89900434767242,36.92110586184603],[137.9124918967836,36.9182119819973],[137.92959679615512,36.91087392778957],[137.94261925727295,36.906998196009965],[137.9587423038143,36.90599050655666],[137.9733150576438,36.90363922816786],[137.989334752297,36.89697296842775],[137.99848147983647,36.89041006147508],[138.00545779793907,36.88291697853572],[138.00835167868695,36.874338690877835],[138.0092818537745,36.863589992883846],[138.00561282756985,36.838785304997856],[138.00742150270014,36.831602281320215],[138.01196902894748,36.82576284388014],[138.04421512292947,36.80718516706182],[138.05806440634717,36.81044078211633],[138.0735673361636,36.82664134392289],[138.08534956293192,36.83317841245383],[138.100439080699,36.83547801399919],[138.14601769416117,36.83713165949848],[138.16405276862037,36.839741319406386],[138.17847049371824,36.84436636001945],[138.2081327662142,36.857698880399],[138.2154191426793,36.859765937048394],[138.22270552004363,36.8587065698523],[138.234281041237,36.856045234000334],[138.2521610860653,36.84865550384845],[138.26394331283365,36.85082591418461],[138.27066124941712,36.85837067306804],[138.2737618348408,36.884053860097595],[138.27763756662034,36.89531932202908],[138.28626753112167,36.90766999047767],[138.29717125874657,36.91495636694279],[138.3235779161878,36.92560171214927],[138.35303348220947,36.9660126817399],[138.36574588406535,36.97740733488058],[138.3820756370809,36.986166489691726],[138.42424360585767,37.00166942040735],[138.481397739614,37.01417511668828],[138.49969119739086,37.0153895135152],[138.5193799182486,37.01355499906401],[138.53586469909663,37.01004100249028],[138.55307295125567,37.002082831557374],[138.55524336159186,37.000635890733776],[138.55942915263324,36.98926707601473],[138.57446699355688,36.92296621381965],[138.58154666624557,36.90854848872178],[138.60459435404624,36.886482651952775],[138.64800255807154,36.85806061470558],[138.65761437360436,36.84997325346285],[138.66681277888654,36.835839749205064],[138.6696033059477,36.822584744090676],[138.66681277888654,36.763234361576565],[138.67952518074247,36.73065237081019]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-22\",\"name\":\"Shizuoka\",\"name_alt\":\"Sizuoka\",\"name_local\":\"静岡県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chubu\",\"postal\":\"SZ\",\"region_code\":\"JPN-CHB\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[138.2622896673343,35.29376333351104],[138.2839937679979,35.29895681400578],[138.30461266484258,35.30190237069783],[138.32822879522274,35.29988698999259],[138.3429565777838,35.29205801026896],[138.352568394216,35.27836375648221],[138.36745120730734,35.21867747718383],[138.37494428934752,35.2037946640925],[138.38347090106117,35.19030711498132],[138.39830203730918,35.181418768961066],[138.4189726100979,35.16511485436703],[138.43044477760435,35.15930125624794],[138.44336388593462,35.15697581628086],[138.4579366397641,35.15728587554267],[138.47235436396272,35.16167837215917],[138.48739220578562,35.17216868683536],[138.49783084541724,35.18152212174854],[138.50341190043883,35.188446763906995],[138.5071326025875,35.19428620044778],[138.50899295456117,35.19940216747602],[138.51054324817233,35.20746369119641],[138.51007816017886,35.21919424112136],[138.50728763221844,35.231260687830456],[138.5040320171638,35.25676300680729],[138.5056856626632,35.2942284215045],[138.51627933192566,35.32973013054104],[138.5232556500282,35.38135488521982],[138.52769982258877,35.39685781503616],[138.53395267117878,35.4096735705788],[138.5434094588794,35.41703746230897],[138.5564835959412,35.41747671188071],[138.5932255393272,35.39210358321378],[138.61265587776654,35.383628648343375],[138.634670037692,35.38112234122303],[138.6483126164341,35.373913479123786],[138.65730431524184,35.365025133103416],[138.67239383390825,35.3561367861837],[138.69719852089497,35.350762436737085],[138.82969689339694,35.36466339789746],[138.90033857626418,35.3809414740698],[138.96457238181,35.38840871768815],[138.97490766775456,35.38670339624474],[138.98658654173556,35.38114818054413],[138.98751671772237,35.37381012633631],[138.98679324820978,35.35618846302714],[138.98834354092162,35.34848867451271],[138.99020389199603,35.342390855553504],[138.9936145366815,35.33370921510823],[138.99867882686635,35.312961127054365],[139.00028079642152,35.301411445182055],[139.00028079642152,35.29055939440059],[138.99805871014138,35.284771632904466],[138.99340783110642,35.27686513881508],[138.9771297549342,35.261672268260554],[138.97061852392574,35.24976085118237],[138.96860314322043,35.23025299747778],[138.97356408061773,35.20495738407598],[138.98389936656253,35.18299490009457],[139.0026062363886,35.15780263948028],[139.01237308155243,35.146795559067726],[139.01934939965497,35.14116282810207],[139.0287545114116,35.13555593555796],[139.05190555199954,35.12870880776529],[139.0728861840501,35.124833075086315],[139.09443084024332,35.11747362336345],[139.0884726750001,35.110057770000054],[139.07673175500014,35.075094009000026],[139.07587403600013,35.05317728600002],[139.1059676440001,35.049383856000134],[139.10175644700007,35.03054164000014],[139.09709101300007,35.011495013000086],[139.0865480910001,34.99730546900014],[139.100602546,34.974755422000044],[139.13070722700007,34.96515534100013],[139.1425887380001,34.94228750200011],[139.14828535200007,34.9050967470001],[139.14087975400003,34.87055084800002],[139.119297985,34.8673894880001],[139.099072944,34.85611941800002],[139.08838951900015,34.842271226000065],[139.08399498800014,34.82835521000007],[139.07481770100003,34.80896906900011],[139.0590859360001,34.79257003200013],[139.05171284900013,34.77778765100004],[139.04298346700006,34.76756908400009],[139.0263272690001,34.76424660000009],[139.0189891720001,34.75428978500014],[139.0109716230001,34.746621126000036],[138.99875872200005,34.73328434200002],[139.0026147800001,34.721828518000024],[138.9970809250001,34.715033270000106],[138.9887801440001,34.70140208500007],[138.98560631600003,34.683294989000046],[138.9875594410001,34.66478099200012],[138.99366295700008,34.64984772300008],[138.9824324880001,34.64447663000004],[138.97266686300014,34.645819403000104],[138.96485436300014,34.65253327000008],[138.9408259810001,34.661107187000084],[138.89597911200008,34.62904577600003],[138.86094893900008,34.61349798100014],[138.835297071,34.59589264500008],[138.81999759200005,34.60236237200007],[138.806000196,34.61367422100005],[138.79468834700003,34.62628815300005],[138.7713901310001,34.647313953000065],[138.7778426440001,34.663316148000064],[138.7739363940001,34.67096588700011],[138.76685631600003,34.67621491100007],[138.7590438160001,34.67845286700006],[138.7519637380001,34.682074286000045],[138.74659264400015,34.691473700000046],[138.74594160200002,34.70917389500005],[138.74400124200014,34.72889189500003],[138.7562422850001,34.73747022500007],[138.76182943900005,34.74828171400014],[138.77426191500007,34.751288153000104],[138.75876118800005,34.78270405300012],[138.74812686400008,34.81320925300011],[138.75789301600008,34.829702497000085],[138.76221764400015,34.84642161700009],[138.7602645190001,34.84906647300008],[138.76050866000003,34.8693708350001],[138.753838793,34.87946727500014],[138.7629955810001,34.89398688000006],[138.7767874000001,34.904828377000115],[138.78980553500003,34.906805731000105],[138.77849368600005,34.922023830000086],[138.7739363940001,34.93162669500009],[138.76369264600015,34.95605640500008],[138.76189355900016,34.979902605000035],[138.77680217,35.003160568000084],[138.78608990100008,35.026438762000026],[138.8265951470001,35.023625219000024],[138.85541578700014,35.019026928000045],[138.8751275990001,35.02009448400011],[138.8938094410001,35.02000560100004],[138.907562696,35.03066640800006],[138.85343549600015,35.07822164400008],[138.83853700300006,35.099525698],[138.8033316850001,35.12190930800011],[138.74422004400003,35.13484289300007],[138.69618557600006,35.13897567500007],[138.66363181200012,35.12714398500006],[138.6372695660001,35.11598535200005],[138.58678602100008,35.11436020000005],[138.55408217500008,35.09606937900011],[138.52765127300006,35.050808017000094],[138.49788298200008,35.03256950200006],[138.49488309400016,35.00073149500011],[138.50701000400005,34.991072252000066],[138.50702289100008,35.01189308800008],[138.52098226200008,35.01937347200004],[138.53161744200014,35.016923751000036],[138.51429303400005,34.98017780400008],[138.4683627440001,34.959069567000085],[138.429755054,34.94473446500007],[138.40050460700002,34.93249572800005],[138.36710789500006,34.91730943900012],[138.35335261200015,34.90411501600008],[138.34686565000004,34.88818873000011],[138.33643639400015,34.87409088700008],[138.32829837300005,34.85529205900005],[138.33008873800014,34.845404364000046],[138.34083092500015,34.8264834660001],[138.32588911900012,34.807908231000084],[138.31487266100015,34.798368680000095],[138.29322798300007,34.765774415000124],[138.24950579000006,34.73902319000004],[138.21598919100012,34.712282661000074],[138.20167076900015,34.660101630000014],[138.19361412900003,34.64150625200003],[138.20093834700003,34.62156810100004],[138.22505168600009,34.61630119900008],[138.24032772700002,34.60256107900008],[138.2332897610001,34.594588770000115],[138.19017637400015,34.601227717000086],[138.04723701,34.65463981500011],[137.96758503500013,34.666540387000026],[137.8927975680001,34.667696567000036],[137.79216556100005,34.63939036700012],[137.68350648400013,34.670754599000105],[137.595062696,34.67719147300005],[137.54699814000008,34.67728852500011],[137.46790343229287,34.67096924919889],[137.4687463731466,34.68692698799015],[137.46791954994717,34.693334866211075],[137.46466393399328,34.706822415322165],[137.46326867091238,34.72521922498723],[137.46667931469864,34.75245270382922],[137.47153690020775,34.77090119123699],[137.48843509310495,34.809761868115714],[137.49758182244312,34.81978709569793],[137.5119478697983,34.82622081144129],[137.56367597906308,34.83777049421289],[137.57189253061574,34.84097443332345],[137.57917890798018,34.845031033155664],[137.60915123883854,34.86908641220819],[137.6179879089148,34.87926666942124],[137.6251192566483,34.89657827436787],[137.63333580910023,34.911719468978305],[137.6704394876922,34.947221178014956],[137.6761755723448,34.96148387348194],[137.68485721189074,35.00251495979754],[137.69777632112033,35.018095404879006],[137.75348351495222,35.063183091926135],[137.7702266791179,35.08116649044118],[137.7869698432836,35.11336090847914],[137.79611657082307,35.13646027312305],[137.7979769227967,35.16232432730587],[137.80557335762438,35.19451874444445],[137.83802615808065,35.20526744243844],[137.87543989503513,35.23567902286857],[137.8959037622488,35.24332713543893],[137.9398287293124,35.27309275982296],[137.95936242143853,35.28195526832096],[137.98468387326204,35.28652863209069],[137.9961560407686,35.29128286391307],[138.00530276920733,35.297173977297206],[138.01460452727707,35.30763845535043],[138.02778201712636,35.3190331084912],[138.03946089110724,35.324355781094425],[138.06767622277937,35.32973013054104],[138.0855045916635,35.33696483016274],[138.1125313658298,35.35737702143243],[138.1230733582488,35.37086457054352],[138.12679406129695,35.38453298590855],[138.1236934749739,35.39442902318086],[138.11826744958313,35.40414419240054],[138.11067101475544,35.41401439125113],[138.10943078040611,35.423574530839915],[138.11325483624165,35.434013170471445],[138.13051476524413,35.447836616366686],[138.1387313167968,35.4600580918075],[138.14043664003884,35.46987661381468],[138.1367159360915,35.480418606233684],[138.13361534976855,35.492252508946194],[138.1380595241277,35.51002920188617],[138.1396614927836,35.52271576621965],[138.1387313167968,35.535040595347226],[138.13594078973563,35.547081204533896],[138.1432271671,35.56095632637337],[138.1543892762441,35.56682160133579],[138.16896203007354,35.57074900995883],[138.17707522973797,35.57855215126074],[138.18188113840387,35.58697540928773],[138.1868420758011,35.607387600557246],[138.19614383387085,35.627851466871704],[138.21371382123584,35.61178009717372],[138.216504348297,35.602064927954046],[138.22177534405688,35.57276439156337],[138.2369682146114,35.53516978655634],[138.24130903528373,35.51778066724404],[138.24332441598892,35.47478587526794],[138.24130903528373,35.46408885321793],[138.23479780517454,35.444141750841055],[138.23417768844962,35.43411652325892],[138.23789839059822,35.40843333622945],[138.2354179218995,35.393137111988096],[138.2265812518233,35.3675572777461],[138.22642622309178,35.355981757452156],[138.23045698540147,35.344225369105516],[138.23200727811337,35.33350250953316],[138.23138715958964,35.32368398752598],[138.23185224758316,35.313917141462895],[138.23588300989297,35.303685208305694],[138.2465800310436,35.29712230135314],[138.2622896673343,35.29376333351104]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-16\",\"name\":\"Toyama\",\"name_alt\":null,\"name_local\":\"富山県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chubu\",\"postal\":\"TY\",\"region_code\":\"JPN-CHB\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[137.73265791163334,36.77480988187051],[137.7340531756136,36.66804637324148],[137.72971235584055,36.64463694933576],[137.72831709186028,36.62140839348257],[137.72537153516822,36.604794420526076],[137.719480421784,36.58983409306896],[137.70180708163156,36.57172150334472],[137.68578738787755,36.559344998273076],[137.67571048435127,36.54877716743235],[137.67152469331,36.53911367415674],[137.6749353370961,36.52836497706204],[137.67648562980787,36.518856513417404],[137.67090457568554,36.50671255144306],[137.6567969098495,36.4919589304603],[137.64310265516337,36.468446152867614],[137.62821984297136,36.43452057496461],[137.6207784359761,36.423642687559976],[137.6105465028188,36.41385000307517],[137.5805741719604,36.395143134148356],[137.56538130140592,36.377004706002396],[137.52626224210877,36.387805080839826],[137.47753136637937,36.40604686087397],[137.41422773682052,36.41472850221854],[137.38818281458526,36.422996731513905],[137.35759036700182,36.43679433898754],[137.34131229082948,36.43968821883618],[137.3285998880744,36.43625173572903],[137.31867801327968,36.42919790326059],[137.30761925692315,36.423513495451516],[137.29490685416795,36.42113638043955],[137.28669030171602,36.42503794974161],[137.2850883321608,36.431109931178355],[137.2860185081476,36.4373627788691],[137.28824059532712,36.444649156233595],[137.28482994974235,36.45079865203621],[137.27630333892785,36.45224559196052],[137.26421105379706,36.450023504780944],[137.2001322778823,36.42891368331985],[137.18767825664622,36.42966299125405],[137.17589603077715,36.43201426874356],[137.1611682482161,36.43684601493166],[137.14576867118723,36.43271189983432],[137.13259118133794,36.42521881779422],[137.08127648322306,36.37896841076352],[137.06680708218113,36.36914988875634],[137.05409468032525,36.36266449616957],[137.04649824369892,36.35700592678202],[137.04153730720088,36.35080475503534],[137.03900516165885,36.340391953825616],[137.03528445861087,36.330495917452595],[137.02680952374044,36.31948883704021],[137.01683597210246,36.31346853334617],[137.0052087749649,36.30819753668692],[136.99849083928075,36.303520820129705],[136.97709679608027,36.2833670112785],[136.95797651600333,36.273315945274646],[136.9462459660785,36.27608063391409],[136.94283532139286,36.284038804846915],[136.9485714051462,36.30305573213636],[136.94872643387782,36.31111725495728],[136.94485070209817,36.31889455783758],[136.93570397365949,36.32656850793029],[136.9219063661858,36.332821357419675],[136.89891035432936,36.33742055871184],[136.88139204290837,36.33783397076115],[136.85048953696258,36.34279490815845],[136.83746707584487,36.33757558924202],[136.82863040576854,36.32858388953491],[136.8227392932837,36.31499298853555],[136.8193286476989,36.303159084923834],[136.8145227399325,36.294477444478446],[136.80584109858773,36.28899974224433],[136.79726281093002,36.28770783105156],[136.7732332702991,36.291996974880476],[136.76439660022288,36.32155589548819],[136.76641198092807,36.33450084134077],[136.77369835829256,36.35979645384327],[136.7732332702991,36.376591294852375],[136.76904747925767,36.391009019950275],[136.76207116115512,36.40279124581937],[136.75757531085185,36.41899180852511],[136.75835045810715,36.44036001240468],[136.76842736163343,36.49663564701751],[136.77380171108007,36.512862047245704],[136.77907270683994,36.524799302745606],[136.78362023308728,36.54340281798562],[136.7828450867313,36.55665782310004],[136.77276818320502,36.588593857820314],[136.7699776552446,36.603399156545805],[136.7692025088886,36.62153758379239],[136.78703087777274,36.66466156697777],[136.7876509953971,36.675100205709924],[136.78610070178593,36.68489289019476],[136.78129479401952,36.69543488171439],[136.77834923732735,36.70587352134592],[136.77710900297808,36.71894765930696],[136.78408532108068,36.7274225941774],[136.80429080587595,36.7426154647319],[136.81173221197196,36.75264069321341],[136.82444461472716,36.79054535658298],[136.82733849457577,36.813102117968384],[136.83281619680997,36.829483547827465],[136.8597912950322,36.887025255211285],[136.87157352090122,36.90242483224023],[136.8941044456637,36.92570506403749],[136.9046464380827,36.93309479418946],[136.91487837123975,36.93730642275321],[136.9268673035831,36.93810740843011],[136.93818444235808,36.937590644492616],[136.950741814583,36.93965770114201],[136.97926720641635,36.951491603854436],[136.99141116839064,36.953403631772204],[137.00861941965053,36.95288686783471],[137.04262251191943,36.9562716740985],[137.04442499164713,36.95640420176636],[137.02741452300012,36.93884298800009],[137.025040116,36.91394722700005],[136.987741561,36.871021637000126],[137.00554446700005,36.83592357000009],[137.05291460800004,36.811043985000055],[137.0806963020001,36.79008655300004],[137.12802035700008,36.77683954300008],[137.15699303500003,36.76080963700005],[137.19728775100015,36.758124588000044],[137.24144195900016,36.76506775100003],[137.29867296700007,36.75700875800007],[137.33334435900014,36.7627260670001],[137.35053007200014,36.7823090980001],[137.38360579600015,36.80014545500009],[137.3949054680001,36.827222804000144],[137.40835627700005,36.8452037480001],[137.41297129300006,36.86201101800012],[137.4195314380001,36.8773354570001],[137.413764539,36.896892108000145],[137.42710766900007,36.92201115800006],[137.44760175900007,36.9335391300001],[137.4993469650001,36.955741617000115],[137.5867332050001,36.969893864000085],[137.6145638948774,36.9751081741656],[137.6167476745655,36.969216619951084],[137.63736657141015,36.950664781554394],[137.67167972204157,36.93495514616299],[137.68454715442766,36.92591177051182],[137.69477908668546,36.911959133407294],[137.73265791163334,36.77480988187051]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-01\",\"name\":\"Hokkaido\",\"name_alt\":\"Ezo|Yeso|Yezo\",\"name_local\":\"北海道\",\"type\":\"Do\",\"type_en\":\"Circuit\",\"region\":\"Hokkaido\",\"postal\":\"HK\",\"region_code\":\"JPN-HKK\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[139.8029077480002,41.366359768000095],[139.81153405000012,41.364488023000106],[139.81487063900013,41.36461009300008],[139.81373131600017,41.3585065780001],[139.80616295700023,41.353379624000084],[139.79932701900012,41.352118231000176],[139.79672285200004,41.353867906000076],[139.7930607430001,41.358669338000155],[139.79582767000014,41.36318594000004],[139.8029077480002,41.366359768000095]]],[[[139.34815514400023,41.524237372000144],[139.35572350400017,41.52216217700011],[139.372894727,41.52216217700011],[139.37826582100016,41.519232489],[139.38184655000023,41.513576565],[139.37427819100017,41.504339911000145],[139.34555097700004,41.493353583],[139.34083092500012,41.49750397300012],[139.33643639400023,41.51113515800007],[139.33643639400023,41.51850006700006],[139.34034264400023,41.523830471000124],[139.34815514400023,41.524237372000144]]],[[[139.49390709700012,42.07884349200002],[139.45533287900005,42.051581122000115],[139.44564863400015,42.05483633000016],[139.43539472700004,42.06293366100009],[139.429453972,42.07379791900014],[139.43238366000006,42.08510976800015],[139.42725670700023,42.09393952],[139.42505944100006,42.102687893000066],[139.42505944100006,42.122992255000085],[139.4226994150001,42.13149648600002],[139.41285241000017,42.14398834800012],[139.41081790500002,42.153998114],[139.41431725400005,42.17251211100002],[139.42383873800011,42.19159577],[139.43702233200005,42.20722077000009],[139.45215905000023,42.21531810100011],[139.46371503999998,42.216253973],[139.48552493600002,42.22113678600006],[139.497325066,42.222235419000114],[139.5091251960001,42.225775458],[139.538340691,42.240668036000145],[139.54851321700008,42.243353583000115],[139.56421959700006,42.23175690300012],[139.55396569100012,42.211574611000074],[139.5206811860002,42.174505927000055],[139.51368248800023,42.14817942900005],[139.51270592500023,42.12372467700011],[139.50896243600013,42.100816148000106],[139.49390709700012,42.07884349200002]]],[[[145.29737389400023,43.52265045800006],[145.28353925900015,43.51788971600017],[145.27320397200006,43.52236562700013],[145.2768660820001,43.53815338700018],[145.28785241000006,43.54751211100002],[145.31869550900004,43.55776601800012],[145.33204186300011,43.56541575700008],[145.202403191,43.61383698100015],[145.28695722700016,43.60101959800012],[145.31153405000018,43.593329169000114],[145.34441165500007,43.57591380400014],[145.36036217500018,43.56476471600003],[145.3628035820001,43.55487702],[145.35010826900023,43.54962799700003],[145.31202233200023,43.54218170800014],[145.30420983200017,43.53473541900017],[145.29737389400023,43.52265045800006]]],[[[144.45508873800006,43.94220612200017],[144.44898522200018,43.94204336100002],[144.44825280000023,43.94220612200017],[144.45508873800006,43.94220612200017]]],[[[143.97584069100017,44.14826080900018],[143.94044030000018,44.13914622599999],[143.89649498800023,44.14590078300013],[143.81373131600017,44.168402411000116],[143.81763756600017,44.17560455900015],[143.8362736340001,44.17593008000007],[143.8595483730002,44.17275625200001],[143.8815210300002,44.16669342700011],[143.89649498800023,44.158148505],[143.9117944670002,44.15228913000006],[143.9572046230002,44.15200429900001],[143.97584069100017,44.14826080900018]]],[[[141.3048608730002,44.41299062700001],[141.29395592500012,44.41299062700001],[141.29550214900004,44.42169830900009],[141.3048608730002,44.42670319200015],[141.3096623060001,44.42995840100009],[141.3121850920002,44.434190171000026],[141.3278100920002,44.44464752800009],[141.32805423300013,44.43976471600003],[141.33611087300008,44.43272532800013],[141.32349694100017,44.417629299000126],[141.3048608730002,44.41299062700001]]],[[[141.42912845100014,44.43882884300014],[141.41578209700006,44.42670319200015],[141.39747155000006,44.429348049000126],[141.39201907600014,44.439032294000086],[141.4039005870001,44.44102610900005],[141.40992272200018,44.444159247000115],[141.4195255870001,44.44537995000003],[141.42912845100014,44.43882884300014]]],[[[141.26840254000015,45.101019598],[141.251231316,45.09918854400003],[141.23536217500006,45.10248444200006],[141.18181399800008,45.12661367400007],[141.15007571700002,45.14712148600002],[141.131114129,45.170152085],[141.14551842500006,45.18976471600014],[141.14087975400005,45.196030992000104],[141.1355086600001,45.20612213700015],[141.13184655000023,45.21027252800012],[141.16716556100008,45.23997630400014],[141.17969811300006,45.24437083500011],[141.20264733200017,45.24909088700004],[141.23519941500004,45.24127838700004],[141.26832116000017,45.22553131700009],[141.29297936300023,45.20652903900016],[141.32715905000023,45.17234935099999],[141.3356225920002,45.15778229400014],[141.32846113400015,45.147650458],[141.31641686300023,45.13792552300005],[141.3100692070001,45.12457916900006],[141.30445397200012,45.12018463700015],[141.26840254000015,45.101019598]]],[[[141.00660241000006,45.44041575700005],[141.02003014400012,45.43984609600007],[141.03532962300008,45.446234442],[141.05274498800023,45.450425523000135],[141.0690210300002,45.44269440300003],[141.07357832100004,45.42470937700007],[141.05909264400006,45.32196686400006],[141.04957116000017,45.29291413],[141.03565514400012,45.26487864800008],[141.02906334700006,45.278509833000115],[141.02995853000002,45.283921617000075],[141.03565514400012,45.29218170800006],[141.01050866000006,45.32778554900001],[140.99887129000004,45.35000234600007],[140.99415123800011,45.37103913],[141.00098717500012,45.419134833000086],[140.996348504,45.43024323100009],[140.96680748800023,45.46344635600012],[140.97413170700023,45.46771881700012],[140.98064212300002,45.46893952000015],[140.98609459700018,45.4674339860001],[140.99048912900017,45.463364976000136],[140.99317467500023,45.45880768400009],[141.00660241000006,45.44041575700005]]],[[[142.01775149800014,45.45571523600002],[142.05665123800011,45.40204498900012],[142.09652753999998,45.372300523000106],[142.18311608200017,45.325995184000035],[142.22120201900023,45.29962799700003],[142.53931725400005,45.01845937700001],[142.61426842500006,44.90387604400003],[142.63070722700016,44.88373444200009],[142.66570071700008,44.86664459800012],[142.7140405610002,44.80621979400003],[142.907481316,44.64740631700006],[142.93978925900015,44.63471100500003],[142.98145592500012,44.58649323100015],[143.042735222,44.55491771000008],[143.118418816,44.49689362200009],[143.20443769599999,44.44725169499999],[143.22486412900017,44.442450262000094],[143.24073326900012,44.436346747000115],[143.27857506600006,44.40615469],[143.29688561300011,44.39468008000016],[143.340098504,44.386542059000035],[143.3514103520001,44.381048895],[143.36850019600004,44.35138580900018],[143.36915123800006,44.34625885600015],[143.39649498800023,44.322943427000055],[143.40528405000012,44.31745026200004],[143.55241946700008,44.25250885600015],[143.77662194100017,44.18927643400009],[143.77662194100017,44.18235911699999],[143.69532311300011,44.18984609600007],[143.68043053500006,44.18235911699999],[143.68506920700005,44.17593008000007],[143.70899498800023,44.15786367400007],[143.71461022200006,44.15135325700008],[143.72828209700018,44.11347077000012],[143.7384546230002,44.11798737200009],[143.7424422540001,44.12091705900012],[143.75709069100012,44.11017487200009],[143.76970462300008,44.103745835],[143.78321373800023,44.100653387000094],[143.92969811300023,44.09894440300009],[143.9377547540001,44.09642161699999],[143.96607506600006,44.120062567],[143.98324629000015,44.12995026200012],[143.99919681100008,44.133978583],[144.11931399800008,44.12905508000013],[144.15308678500006,44.117254950000145],[144.17172285200004,44.10602448100015],[144.17156009200008,44.102728583000115],[144.15870201900023,44.100653387000094],[144.14039147200018,44.09300364800008],[144.12680097700004,44.080145575000145],[144.10547936300006,44.031561591000084],[144.13835696700008,44.020697333],[144.15284264400012,44.01984284100011],[144.16700280000023,44.025376695000105],[144.177582227,44.03522370000006],[144.188731316,44.050279039000074],[144.19760175900015,44.06720612200006],[144.20118248800023,44.08246491100003],[144.215098504,44.1114769550001],[144.24293053500017,44.11920807500012],[144.263438347,44.107814846000124],[144.25586998800006,44.07941315300012],[144.26832116000017,44.04938385600015],[144.27833092500006,44.0367699240001],[144.293711785,44.031561591000084],[144.30225670700023,44.02411530200011],[144.31861412900005,43.99119700700014],[144.32781009200014,43.98379140800013],[144.3365991550002,43.98029205900015],[144.37940514400006,43.956488348000065],[144.39535566499998,43.95087311400006],[144.43043053500017,43.942572333],[144.57032311300011,43.924790757],[144.740489129,43.91461823100015],[144.78809655000012,43.92177969],[144.83122806100013,43.94220612200017],[144.94760175899998,44.03408437700001],[145.03728274800002,44.11041901200004],[145.04851321700002,44.12213776200004],[145.11931399800002,44.15448639500015],[145.1666772800002,44.190497137000094],[145.18490644600016,44.19546133000016],[145.20167076900006,44.205796617000075],[145.25025475400005,44.271795966000084],[145.28972415500013,44.29889557500003],[145.30974368600008,44.31635163],[145.31836998800006,44.33633047100007],[145.32276451900017,44.34178294500002],[145.33204186300011,44.346136786],[145.34148196700002,44.345526434000035],[145.349457227,44.32599518400015],[145.36719811300006,44.302883205000015],[145.37305748800023,44.29222239800008],[145.371836785,44.24876536700013],[145.3498641290001,44.21161530200011],[145.29053795700023,44.15448639500015],[145.27914472700016,44.133937893],[145.26921634200008,44.085150458],[145.2604272800002,44.06940338700004],[145.1696069670002,43.985296942],[145.14429772200018,43.95294830900018],[145.12232506600006,43.917059637000094],[145.1061304050002,43.88076406500009],[145.11597741,43.86245351800012],[145.10987389400023,43.84194570500007],[145.09848066500015,43.82282135600009],[145.08920332100004,43.80072663000011],[145.07471764400006,43.78603750200001],[145.07146243600002,43.77460358300006],[145.07227623800023,43.76154205900018],[145.07471764400006,43.75372955900009],[145.11597741,43.68683502800015],[145.13013756600006,43.671576239],[145.19483483200005,43.620672919000086],[145.20753014400023,43.606878973000065],[145.21851647200018,43.58966705900015],[145.2262475920002,43.569769598000065],[145.22901451900017,43.548041083],[145.232269727,43.53994375200007],[145.24683678500006,43.53046295800006],[145.2521264980002,43.5143089860001],[145.26441491000017,43.489691473],[145.29078209700018,43.40534088700012],[145.31169681100002,43.36676666900006],[145.34571373800023,43.33270905200011],[145.39356530000006,43.305365302000055],[145.36166425900015,43.301459052000055],[145.29916425900004,43.34125397300015],[145.27003014400006,43.346380927000055],[145.26050866000017,43.338568427000055],[145.25831139400006,43.32709381700012],[145.26449629000015,43.31671784100003],[145.28028405000012,43.31220123900006],[145.29810631600017,43.309271552000055],[145.31511478000002,43.302923895000035],[145.32081139400012,43.29596588700004],[145.30420983200017,43.29169342700011],[145.32162519600004,43.27627187700007],[145.3510848320001,43.26544830900009],[145.3836369150001,43.25901927299999],[145.46843509200008,43.25299713700015],[145.491953972,43.244574286000116],[145.5171004570001,43.22898997600011],[145.52198326900017,43.241197007],[145.51335696700008,43.24884674700003],[145.50131269600016,43.25698476800015],[145.49659264400023,43.270575262000094],[145.50310306100002,43.27753327],[145.540863477,43.30166250200001],[145.582286004,43.33576080900012],[145.5948999360002,43.34284088700018],[145.62086022200018,43.347967841000084],[145.63379967500006,43.3531761740001],[145.64031009200008,43.35976797100007],[145.6437280610002,43.36688873900003],[145.64918053500006,43.373765367000104],[145.66163170700023,43.37982819200012],[145.67595462300002,43.38198476800004],[145.72315514400012,43.37982819200012],[145.73161868600008,43.38202545800003],[145.7443953790001,43.39179108300006],[145.75033613400004,43.394110419000114],[145.75709069100006,43.393133856000034],[145.76148522200006,43.39081452000006],[145.76449629000015,43.388373114000146],[145.76742597700016,43.38727448100009],[145.78003991000006,43.386053778000175],[145.81324303500017,43.378607489],[145.82496178500006,43.37360260600015],[145.81853274800002,43.364447333000086],[145.81031334700006,43.36078522300012],[145.78785241000006,43.36058177300008],[145.77767988399998,43.35626862200017],[145.76010175900015,43.33698151200004],[145.74691816500004,43.33270905200011],[145.7085067070001,43.326320705000015],[145.69898522200012,43.32208893400018],[145.68409264400023,43.31024811400012],[145.67310631600017,43.30792877800015],[145.64389082100016,43.31220123900006],[145.62916100400017,43.309759833000115],[145.62134850400017,43.303412177],[145.61548912900005,43.29450104400014],[145.60645592500012,43.28424713700004],[145.57984459700018,43.26504140800007],[145.57227623800023,43.25698476800015],[145.56617272200006,43.24607982000008],[145.55738366000017,43.22142161699999],[145.55176842500018,43.20978424700017],[145.5455835300002,43.20197174700017],[145.5171004570001,43.174994208000115],[145.53060957100016,43.16885000200013],[145.5188908210001,43.166449286],[145.49878991000006,43.16791413000006],[145.47828209700018,43.17210521],[145.45240319100017,43.18390534100014],[145.435394727,43.18207428600009],[145.40406334700006,43.174994208000115],[145.30250084700006,43.17348867400007],[145.28028405000012,43.16502513200011],[145.25831139400006,43.14948151200004],[145.23113040500007,43.14109935100008],[145.154063347,43.132269598],[145.138438347,43.12815989800005],[145.12549889400012,43.12006256700012],[145.11931399800002,43.10610586100016],[145.12273196700008,43.08820221600011],[145.13542728000002,43.08429596600011],[145.15219160200004,43.08462148600013],[145.16822350400005,43.07941315300003],[145.15902754000015,43.07050202],[145.14918053500006,43.066310940000065],[145.11101321700008,43.063625393],[145.10792076900006,43.05939362200017],[145.10645592500012,43.05292389500006],[145.09929446700008,43.044623114],[145.07569420700023,43.034002997000144],[145.05128014400012,43.033107815000065],[145.00310306100002,43.044623114],[145.02222741,43.020331122000115],[145.0275171230002,43.00934479400014],[145.02369225400017,42.99628327000009],[145.01433353000007,42.98948802300005],[145.00017337300014,42.986151434000035],[144.9337671230002,42.97825755400005],[144.89975019600016,42.97947825700008],[144.86882571700014,42.98847077000009],[144.842051629,43.00714752800015],[144.84050540500007,43.01691315300009],[144.84310957100016,43.02936432500003],[144.84017988400015,43.04010651200004],[144.82154381600006,43.044623114],[144.8100692070001,43.045355536000116],[144.80372155000006,43.04645416900017],[144.79867597700016,43.04669830900012],[144.79086347700016,43.044623114],[144.7838647800002,43.03994375200007],[144.77312259200008,43.0268415390001],[144.76628665500007,43.02415599200013],[144.75757897200006,43.01878489800008],[144.74740644600004,43.006170966000084],[144.73910566500004,42.99135976800004],[144.73560631600006,42.97947825700008],[144.74146569100017,42.96523672100007],[144.75538170700005,42.95441315300015],[144.77125084700012,42.94489166900017],[144.78345787900017,42.934759833000115],[144.74195397200018,42.92422109600015],[144.59913170700023,42.94912344],[144.52173912900005,42.944077867000075],[144.49586022200006,42.93325429900007],[144.46485436300011,42.937079169000086],[144.39063561300006,42.9564476580001],[144.38217207100004,42.961493231000034],[144.37305748800023,42.969549872000144],[144.36850019599999,42.97817617400007],[144.36491946700002,42.98956940300003],[144.35808353000013,42.99945709800015],[144.3452254570001,43.00372955900015],[144.32300866000006,43.00263092700011],[144.1831160820001,42.97638580900018],[144.04200280000018,42.926581122000115],[143.88640384200008,42.84418366100009],[143.790293816,42.757269598],[143.63884524800008,42.66107819200006],[143.57520592500006,42.60333893400012],[143.44809004000004,42.45502350500014],[143.34896894600016,42.31915924700009],[143.33171634200002,42.27724844000009],[143.32488040500002,42.22972239799999],[143.33643639400023,42.173000393],[143.33790123800011,42.15021393400018],[143.33594811300017,42.12970612200003],[143.33106530000006,42.1069196640001],[143.30347741000017,42.02578359600007],[143.29232832100016,42.00519440300003],[143.26547285200016,41.98672109600001],[143.25294030000006,41.94188060099999],[143.24154707100004,41.928045966000084],[143.2260848320001,41.93227773600002],[143.20313561300011,41.94586823100015],[143.18238366,41.96157461100013],[143.1733504570001,41.972113348],[143.163340691,41.99038320500007],[143.140472852,42.010484117000104],[143.11524498800023,42.027980861000074],[142.95972741000006,42.105373440000065],[142.757660352,42.15839264500009],[142.54639733200005,42.25055573100015],[142.45997155000006,42.27130768400015],[142.28101647200012,42.36652252800003],[142.2221785820001,42.411769924000154],[142.16775167700004,42.45985850800015],[142.06258038500008,42.470728272000045],[141.98096764400023,42.51023997600005],[141.92595462300008,42.55857982],[141.9093530610002,42.56549713700015],[141.8881942070001,42.56976959800009],[141.81714928500006,42.59959544499999],[141.72608483200023,42.617173570000105],[141.63648522200006,42.615790106000034],[141.54957116000017,42.60114166900003],[141.42774498800006,42.56439850500011],[141.262461785,42.46824778900016],[141.23462975400017,42.458685614],[141.21509850400017,42.44452545800006],[141.2032983730002,42.44135163],[141.191172722,42.44033437700013],[141.17969811300006,42.43744538000003],[141.168711785,42.43329498900006],[141.09156334700018,42.391791083000086],[141.07325280000012,42.37714264500006],[141.01677493600008,42.316107489],[141.00098717500012,42.3048363300001],[140.98975670700023,42.301418361000074],[140.977305535,42.29962799700009],[140.96469160200004,42.300482489],[140.95240319100017,42.3048363300001],[140.94581139400012,42.310248114000146],[140.93702233200023,42.31976959800012],[140.93165123800006,42.32876211100016],[140.93604576900023,42.332709052000055],[140.97543379000015,42.32656484600007],[140.984141472,42.329046942],[140.98682701900012,42.33982982000002],[140.97461998800023,42.34662506700012],[140.94695071700002,42.353216864],[140.92652428500017,42.36517975500011],[140.91797936300023,42.37254466400013],[140.91211998800011,42.38056061400006],[140.90137780000006,42.42084381700015],[140.73365319100012,42.56415436400009],[140.68628991000006,42.579169012000094],[140.66293379000015,42.57982005400005],[140.597422722,42.57172272300012],[140.52564537900005,42.58063385600015],[140.50066165500013,42.579169012000094],[140.45753014400012,42.56427643400015],[140.41700280000006,42.538153387000094],[140.38086998800023,42.50507233300006],[140.32545006600017,42.432806708000086],[140.30396569100006,42.39297109600001],[140.2885848320001,42.35016510600009],[140.28150475400005,42.3048363300001],[140.29175866000017,42.25958893400015],[140.32162519600016,42.233954169000086],[140.42408287900005,42.19228750200013],[140.45639082100016,42.17308177299999],[140.53028405000023,42.11176178600009],[140.54883873800006,42.10553620000003],[140.57195071700014,42.10785553600009],[140.61011803500017,42.117621161000116],[140.67408287900017,42.122056382],[140.69320722700016,42.12616608300014],[140.71355228000007,42.133490302000055],[140.77361087300002,42.1021996110001],[140.789317254,42.074530341000084],[140.83708743600008,42.02362702000006],[140.8536889980002,42.011216539000046],[140.8917749360002,41.99152252800009],[140.90845787900005,41.978949286000116],[140.9742944670002,41.913763739],[140.99219811300017,41.90216705900018],[141.0115666020001,41.893744208000115],[141.03370201900023,41.88873932500009],[141.05958092500018,41.88711172100007],[141.08326256600017,41.88263580900009],[141.1069442070001,41.87177155200011],[141.18279056100008,41.818304755],[141.19996178500017,41.79946523600013],[141.18995201900012,41.79092031500012],[141.14356530000023,41.78579336100002],[141.12159264400012,41.77936432500009],[141.10401451900006,41.77041250200007],[141.04330488400004,41.72386302300005],[141.00635826900006,41.71214427300005],[140.95997155000012,41.721991278000175],[140.87427819100006,41.758246161],[140.82398522200018,41.77069733300003],[140.7750757170002,41.77041250200007],[140.72999108200023,41.749986070000105],[140.70801842500012,41.74555084800012],[140.69857832100004,41.75983307500003],[140.70671634200002,41.77411530200011],[140.72242272200018,41.78335195500007],[140.73340905000023,41.793646552],[140.7272241550002,41.81134674700009],[140.70899498800011,41.82440827000009],[140.68482506600012,41.82990143400009],[140.66041100400005,41.82713450700005],[140.64161217500012,41.81509023600013],[140.63135826900006,41.79730866100006],[140.61573326900006,41.754706122000115],[140.60425866000006,41.736273505000085],[140.544769727,41.71259186400006],[140.5348413420002,41.70526764500015],[140.52344811300023,41.698675848000065],[140.47006269599999,41.68720123900012],[140.45289147200018,41.68105703300016],[140.4332788420002,41.64468008000007],[140.44320722700004,41.563706773000106],[140.43238366,41.53082916900003],[140.40064537900017,41.51300690300006],[140.274668816,41.48236725500014],[140.259532097,41.472723700000145],[140.24122155000023,41.45722077000015],[140.225840691,41.437933661000116],[140.21949303500017,41.417181708000115],[140.20899498800011,41.40110911700005],[140.18506920700023,41.4037946640001],[140.13746178500017,41.42096588700004],[140.12680097700004,41.42047760600015],[140.10971113399998,41.414862372000144],[140.10035241000006,41.413519598000065],[140.08838951900012,41.416815497000115],[140.06299889400012,41.431341864000146],[140.05209394600004,41.43463776200018],[140.03825931100002,41.44257233300014],[140.02816816500015,41.46141185099999],[140.01400800900015,41.50287506700009],[139.98365319100017,41.56049225500006],[139.97982832100016,41.581732489000146],[139.98422285200004,41.60317617400007],[140.0034285820001,41.63467031500009],[140.01954186300017,41.69696686400006],[140.07113691500004,41.760199286000145],[140.08301842500018,41.80076732000005],[140.08741295700023,41.805121161000145],[140.10938561300006,41.80341217700014],[140.11768639400023,41.80451080900018],[140.12671959700006,41.814032294000086],[140.12973066500015,41.82322825700005],[140.13135826900006,41.84552643400009],[140.13062584700006,41.85150788000003],[140.12435957099999,41.86005280200011],[140.12387129000004,41.865952867000075],[140.12777754000004,41.870794989000146],[140.13493899800008,41.87514883000013],[140.14503014400012,41.87958405200011],[140.14714603000002,41.89134349199999],[140.14795983200023,41.90753815300003],[140.15170332100016,41.913763739],[140.1451929050002,41.92963288000011],[140.13965905000023,41.9487165390001],[140.13843834700006,41.967515367000075],[140.14503014400012,41.982652085000055],[140.11182701900012,42.01113515800007],[140.06104576900023,42.08002350500011],[140.0275985040001,42.10553620000003],[139.92750084700006,42.138902085],[139.90935306100008,42.15656159100011],[139.90821373800011,42.16083405200011],[139.903086785,42.16559479400003],[139.88363691500004,42.19497304900004],[139.87435957100016,42.199896552],[139.8434350920002,42.20856354400017],[139.79835045700023,42.23590729400014],[139.7874455090001,42.24677155200014],[139.77759850400005,42.2711449240001],[139.76758873800006,42.31098053600009],[139.7723087900001,42.33047109600007],[139.78443444100017,42.35081614800005],[139.82618248800011,42.402044989],[139.83676191500015,42.42389557500006],[139.84961998800006,42.469305731000034],[139.84929446700008,42.51414622600005],[139.83277428500017,42.58881256700009],[139.8434350920002,42.62693919500005],[139.87696373800023,42.66339752800012],[139.91724694100006,42.6772321640001],[140.01400800900015,42.68158600500003],[140.03785241,42.68602122599999],[140.0538843110002,42.69415924700009],[140.09546959700018,42.733791408000044],[140.10515384200002,42.73908112200009],[140.11548912900005,42.74209219],[140.12761478000007,42.74298737200009],[140.13591556100002,42.74713776200004],[140.14429772200018,42.756984768000066],[140.169769727,42.79584381700006],[140.18336022200006,42.810207424000126],[140.2003686860002,42.81427643400009],[140.2229110040001,42.80133698100009],[140.2457788420002,42.77293528900016],[140.26075280000023,42.760972398000135],[140.28150475400005,42.757269598],[140.30323326900012,42.76972077],[140.30990644600016,42.79319896000008],[140.31177819100006,42.815985419000114],[140.31910241000006,42.82623932500003],[140.3374129570001,42.834133205000015],[140.35206139400012,42.85297272300015],[140.3771264980002,42.89378489800002],[140.39307701900012,42.908514716000084],[140.46119225400005,42.95888906500012],[140.50318444100006,42.98004791900003],[140.52116946700008,42.99628327000009],[140.52711022200018,43.015448309000035],[140.51685631600017,43.029974677],[140.50196373800011,43.04572174700003],[140.49439537900017,43.06854889500006],[140.48731530000012,43.082709052000084],[140.43921959700006,43.134019273000106],[140.406097852,43.16022370000003],[140.36793053500006,43.183091539000046],[140.33806399800002,43.210516669000086],[140.32935631600006,43.25071849200002],[140.36011803500006,43.32518138200005],[140.40951582100004,43.32469310099999],[140.43392988400015,43.32843659100011],[140.45289147200018,43.338853257],[140.45818118600008,43.347967841000084],[140.46119225400005,43.358587958000115],[140.46509850400005,43.36806875200013],[140.47331790500007,43.37360260600015],[140.48715254000015,43.37238190300003],[140.49968509200002,43.36517975500003],[140.51026451900012,43.357001044000086],[140.51807701900006,43.3531761740001],[140.53516686300023,43.34788646000011],[140.6209416020001,43.30121491100003],[140.63884524800002,43.28888580900015],[140.66879316500004,43.260321356000176],[140.68921959700018,43.24835846600014],[140.76140384200008,43.223456122000115],[140.77125084700018,43.21552155200014],[140.7863061860002,43.20062897300009],[140.79615319100006,43.19550202000006],[140.80990644600016,43.19245026200018],[140.91260826900006,43.20416901200015],[140.98829186300006,43.226996161],[141.01124108200017,43.22898997600011],[141.02230878999998,43.22378164300012],[141.01221764400023,43.210516669000086],[141.00440514400023,43.193060614000146],[141.02198326900006,43.174994208000115],[141.03256269600004,43.172308661000145],[141.0698348320001,43.16885000200013],[141.1108504570001,43.156195380000085],[141.1355086600001,43.152411200000145],[141.15650475400005,43.143011786000116],[141.16911868600013,43.14085521000011],[141.187266472,43.14150625200007],[141.20085696700008,43.14362213700018],[141.22754967500012,43.15448639500006],[141.303965691,43.19725169500008],[141.37086022200006,43.25071849200002],[141.39747155000006,43.27973053600009],[141.42335045700005,43.317857164000074],[141.4401147800002,43.36172109600001],[141.439707879,43.40778229400014],[141.42701256600006,43.43455638200005],[141.37086022200006,43.49713776200015],[141.35987389400006,43.52358633000013],[141.36247806100008,43.54336172100007],[141.37452233200005,43.56053294500002],[141.39136803500017,43.579087632000025],[141.3727319670002,43.61823151200004],[141.364024285,43.62750885600009],[141.36695397200006,43.646307684000064],[141.34392337300008,43.69017161700002],[141.33741295700005,43.71283600500006],[141.34359785200004,43.73151276200004],[141.35816491000017,43.75580475500003],[141.38502037900005,43.791327216000084],[141.40211022200012,43.80361562700013],[141.42481530000023,43.814601955000015],[141.44800866000017,43.82249583500008],[141.49057050900004,43.82733795800006],[141.51002037900005,43.832586981000034],[141.52816816500004,43.84121328300013],[141.58472741000006,43.875433661000116],[141.61573326900017,43.902777411],[141.64063561300011,43.93793366100006],[141.65894616000017,43.98379140800013],[141.66716556100008,44.025091864],[141.67017662900017,44.06663646000008],[141.65544681100008,44.276922919000114],[141.6657007170002,44.31273021000011],[141.74724368600002,44.42422109600004],[141.79175866000006,44.59699127800003],[141.79607181100002,44.641750393000066],[141.78345787900005,44.76536692900008],[141.75359134200008,44.885443427],[141.7218530610002,44.955145575000145],[141.58643639400023,45.16437409100003],[141.57488040500013,45.20685455900009],[141.58375084700006,45.25116608300014],[141.589691602,45.25820547100001],[141.60987389400012,45.275864976000136],[141.61784915500007,45.28534577000012],[141.62256920700005,45.29596588700018],[141.6246850920002,45.30548737200017],[141.6276961600001,45.31439850500011],[141.649180535,45.34100983300006],[141.65015709700018,45.35736725500006],[141.6390080090001,45.38898346600014],[141.63705488400004,45.40444570500007],[141.63941491000017,45.421128648000106],[141.6479598320001,45.437201239],[141.6657007170002,45.450425523000135],[141.67725670700023,45.44432200700005],[141.68360436300023,45.436183986000046],[141.68628991000006,45.425523179],[141.686778191,45.41229889500006],[141.69214928500017,45.402289130000085],[141.70443769600004,45.40208567900011],[141.72771243600008,45.408880927000055],[141.77409915500013,45.412909247000115],[141.82325280000023,45.422552802],[141.8575952480002,45.44033437700007],[141.86841881600017,45.443019924000126],[141.87533613399998,45.44818756700015],[141.87720787900005,45.46019114800008],[141.87720787900005,45.473863023000106],[141.878672722,45.484035549000154],[141.89389082099999,45.500555731000034],[141.91944420700005,45.51585521],[141.94499759200008,45.52041250200013],[141.96045983200005,45.51023997599999],[141.97754967500012,45.48078034100011],[141.987559441,45.46849192900005],[142.00180097700004,45.46344635600012],[142.01775149800014,45.45571523600002]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-18\",\"name\":\"Fukui\",\"name_alt\":\"Hukui\",\"name_local\":\"福井県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chubu\",\"postal\":\"FI\",\"region_code\":\"JPN-KIN\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[136.43573449135363,36.14022329446948],[136.44891198120297,36.14464162950756],[136.46270958867655,36.14678620052274],[136.4948523298711,36.14580434769252],[136.5329895372373,36.13991323520764],[136.5662174831503,36.123531806247826],[136.5868363790956,36.10802887643149],[136.62300988170074,36.07446503373427],[136.63716922438016,36.06617096601727],[136.6562378276136,36.063432114900166],[136.72088504520866,36.07480093051851],[136.72429568899474,36.05991811832655],[136.722331985133,36.051236476981785],[136.71582075412448,36.038808295066815],[136.7071391136791,36.013874416870806],[136.7027982930067,35.97271413844672],[136.6991809427463,35.95576426870596],[136.6985608260212,35.943542792365974],[136.70124800029498,35.931605535966696],[136.70775923130338,35.92266551400229],[136.71659590137978,35.915172431062814],[136.72791303925533,35.91021149276622],[136.75385460780387,35.90442373306885],[136.76594689293472,35.8982742381655],[136.77896935405244,35.88698293691303],[136.7924569031635,35.86938711202566],[136.80212039643914,35.85037018473622],[136.80305057242595,35.83605581332509],[136.79555748858724,35.82437693934423],[136.7826900579998,35.81305980146868],[136.76858239216364,35.79099396380042],[136.75773034138206,35.78401764569776],[136.72682783363757,35.78316498497608],[136.7100846703712,35.780839545009],[136.6870886576154,35.77448334453078],[136.6685884951629,35.77386322690643],[136.634378697319,35.779289252297104],[136.61200280218748,35.779263413875384],[136.50802981972043,35.761796780197216],[136.4951623882336,35.756060696443996],[136.48927127484944,35.74818003987696],[136.48415530782125,35.73911082580398],[136.4760937850001,35.73993764900331],[136.45537153536804,35.75190074292493],[136.44271080855697,35.754045314839374],[136.42999840670115,35.75456207877687],[136.41465050741493,35.75378693332027],[136.39909590075513,35.75590566681308],[136.36452436680605,35.76608592402613],[136.35026167223833,35.76792043757814],[136.3370325046464,35.76639598238867],[136.32416507405895,35.76169342740974],[136.31315799364648,35.75461375651969],[136.30426964672677,35.744226792832194],[136.28985192342753,35.6952633731062],[136.25729577018365,35.64562815981171],[136.1672754248209,35.67500621236678],[136.14696658723813,35.671207994053674],[136.1277429552731,35.663534043960965],[136.1239705753817,35.65526581556489],[136.12443566337515,35.64934886285977],[136.13218712783362,35.63335500752754],[136.13332400939544,35.6270763214151],[136.1343575381698,35.610901598030324],[136.14324588419015,35.59250478836526],[136.14634646961383,35.582143663099515],[136.1452612648955,35.5709557155339],[136.1354944188323,35.55793325531545],[136.1222652530389,35.55596955055424],[136.10924279192125,35.55839834240953],[136.09575524281018,35.5584758567754],[136.09001915905685,35.5513445090418],[136.08893395433853,35.54274038296228],[136.08970909979507,35.53186249375901],[136.0886238950767,35.522638250954515],[136.08082075377473,35.51429250729336],[136.07089887898007,35.515145168914415],[136.05673953719992,35.51431834571508],[136.0439237807579,35.511992905748],[136.01570844908565,35.503311266201905],[135.99152387882384,35.4845785588534],[135.97953494737988,35.480418606233684],[135.9671326029872,35.481813870213955],[135.95514367064385,35.48749827712385],[135.94506676711757,35.496877550458635],[135.9380904481157,35.50093415029076],[135.9309591003821,35.5026394717342],[135.92568810372296,35.497497667183524],[135.9211922552184,35.48907440915664],[135.90098677042312,35.413781847254455],[135.89385542179025,35.40073354771502],[135.88625898696267,35.3907083201328],[135.8764921408996,35.381587429216495],[135.866880323568,35.379830430929644],[135.85540815606146,35.38181997321317],[135.8412488142814,35.386574205035544],[135.81752933201307,35.38468801553947],[135.79918419829215,35.37688487423756],[135.74285688683585,35.33758494688783],[135.6882865736664,35.330350247266125],[135.53930342031657,35.36427582516916],[135.51878787715873,35.373448391130324],[135.5104162941766,35.382026678788236],[135.50250980008724,35.392516995263094],[135.48499148956563,35.40613373558347],[135.45305545484536,35.43912913749966],[135.44742272387953,35.452074083352244],[135.44685428309867,35.46184092941536],[135.4533655132078,35.47519928641796],[135.45346886599538,35.480418606233684],[135.44959313421577,35.49015961387508],[135.44370201993218,35.49886709184281],[135.43930952421516,35.50889232032432],[135.44184166885793,35.517728990400514],[135.45243533722106,35.526901557261056],[135.46266727127755,35.53354197857939],[135.47641320190775,35.53953644475109],[135.4787607049713,35.543724789933606],[135.4804793630001,35.53388092700001],[135.4786889980001,35.52728913000004],[135.47901451900015,35.522528387000136],[135.4882918630001,35.520697333000044],[135.49561608200008,35.523911851],[135.50269616000008,35.531642971000025],[135.51221764400015,35.548041083000044],[135.51246178500003,35.529852606000134],[135.508636915,35.51190827000008],[135.51099694100003,35.49819570500006],[135.52955162900003,35.492743231000105],[135.5582788420001,35.49176666900004],[135.5673934250001,35.492743231000105],[135.5768335300001,35.49599844000008],[135.59644616000008,35.505113023000064],[135.60524856500007,35.51716843800011],[135.62479780900003,35.532343969000124],[135.64894517800013,35.54283352900009],[135.66976972700002,35.5343692080001],[135.6647662210001,35.52966431400009],[135.64192428600006,35.52171835000007],[135.63461347700002,35.49355703300003],[135.62208092500015,35.48655833500007],[135.64506572400006,35.48020001400005],[135.69668606000005,35.491842543000104],[135.72443120100002,35.490732086000065],[135.73803636400015,35.498738638000134],[135.74835148300008,35.51371915000007],[135.74819794300004,35.533525420000075],[135.72080139600007,35.52230481500001],[135.70860440900003,35.54055094000006],[135.69163589700008,35.54587719600005],[135.70609333200002,35.56209461800012],[135.73844643100006,35.57053137300005],[135.7480446890001,35.564204344000046],[135.75613707800005,35.5490724150001],[135.7772579810001,35.54985604900011],[135.79405025600016,35.5313818220001],[135.81049953600012,35.526862684],[135.834320509,35.5343692080001],[135.82349694100003,35.53839752800005],[135.81617272200003,35.54596588700011],[135.80150220300004,35.568003462000064],[135.804254904,35.575174764000025],[135.81543350100003,35.577591581000064],[135.825890457,35.56935555100003],[135.84012939600007,35.573414589000066],[135.8547469410001,35.57904694200005],[135.85157311300014,35.58641185100004],[135.84449303500008,35.591742255000014],[135.83757571700005,35.5991885440001],[135.834320509,35.61318594000005],[135.83025149800005,35.61713288000004],[135.81601831600005,35.63085602400005],[135.8116846050001,35.643405811000065],[135.82322255600008,35.644620961000044],[135.84086572400014,35.63365449200006],[135.85375711000006,35.62491969800004],[135.8678407810001,35.61775318300002],[135.90184741800016,35.62436015600011],[135.90976709600005,35.614962749000085],[135.91944111600003,35.60816038200008],[135.9395865890001,35.61843882000008],[135.95093848200014,35.628881685000124],[135.97105288200015,35.625981151000104],[135.98057361100007,35.630480591000065],[135.98166761900006,35.643637888000086],[135.9635403680001,35.65540930800006],[135.97315915800004,35.67621119000012],[135.97437115000008,35.69094901400007],[135.9700837540001,35.70299009100006],[135.95644806000007,35.70056518700008],[135.95600508100011,35.728324936],[135.98154353000012,35.73985437800005],[136.01970526600007,35.76534720100008],[136.03356317100014,35.74622550600006],[136.03132091400005,35.73807644700011],[136.03889018900009,35.722506116000034],[136.04684700500002,35.70372189800014],[136.041958142,35.69146931700004],[136.03195403100005,35.691626426000056],[136.02700644400005,35.68094741500005],[136.04329577700003,35.67016694600005],[136.04745939000009,35.65881225800007],[136.06911435400002,35.66187974300004],[136.07462120000002,35.679031535000064],[136.08138118700003,35.68419368000005],[136.083595424,35.69044927400009],[136.082647693,35.70439476100003],[136.09239818700007,35.71516472700003],[136.09263301800001,35.72463868000008],[136.0929581580001,35.7431151800001],[136.09986412900008,35.7598330750001],[136.09945722700007,35.77570221600004],[136.08821240900005,35.795294073000065],[136.07668286700005,35.81173807300004],[136.07049761300004,35.824749331000135],[136.0488078790001,35.83866838800009],[136.01226671000012,35.8709803050001],[135.9934540640001,35.88514211700007],[135.99535838000008,35.913108400000056],[135.99438018900008,35.93591278200003],[135.9707075560001,35.9600640730001],[135.95728600400008,35.973130601000065],[135.96321741100013,35.99992322200009],[135.9873431540001,36.011733285000076],[136.0001323890001,36.02418682700002],[136.017914259,36.057440497000044],[136.03109785200007,36.08071523600006],[136.04361081500014,36.110832538000096],[136.0928204120001,36.161363244000114],[136.1075493940001,36.18516511200002],[136.1201548570001,36.19975469800005],[136.13484480500006,36.22455239400006],[136.11942239700005,36.246444489000055],[136.12417327600014,36.25317933200006],[136.14491123500005,36.254279236000116],[136.16262712500009,36.252242211000066],[136.19673780600016,36.26436216400003],[136.22671926238968,36.28560274627938],[136.24122439958637,36.27179149008521],[136.2431881043475,36.26977610937993],[136.27605431505464,36.25127594692739],[136.28008507736433,36.248382066179374],[136.2865963083729,36.24236176248533],[136.29000695215905,36.2359280458426],[136.29403771356954,36.22517934784861],[136.30106570941496,36.19629222350724],[136.30519982271358,36.184148261533096],[136.3119177592971,36.17376129784563],[136.3229248388102,36.166319891749595],[136.3356372415654,36.161927395133205],[136.35873660710865,36.15949860327791],[136.378321975179,36.155390325702996],[136.41692426963925,36.14045583846615],[136.43573449135363,36.14022329446948]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-28\",\"name\":\"Hyogo\",\"name_alt\":\"Hiogo\",\"name_local\":\"兵庫県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kinki\",\"postal\":\"HG\",\"region_code\":\"JPN-KIN\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[134.81731204499997,34.15554433800004],[134.81682376400008,34.153998114],[134.8112085300002,34.159572658000016],[134.81185957100004,34.165187893],[134.81609134200008,34.16779205900009],[134.81967207100004,34.17145416900014],[134.8190210300002,34.176906643000095],[134.8219507170002,34.179551499000084],[134.8291935560002,34.17332591400013],[134.82601972700016,34.160101630000085],[134.8224389980002,34.156642971000096],[134.81731204499997,34.15554433800004]]],[[[134.91627037900017,34.33828359600015],[134.91861316500004,34.320394420000056],[134.92618311400022,34.308495078000036],[134.95045006600017,34.28733958500014],[134.95825406100008,34.27035550100014],[134.93798406600015,34.26275510800012],[134.9094703660002,34.25718023200007],[134.88490593700024,34.244481070000134],[134.85611812200014,34.23600730500006],[134.83385849900012,34.22661011400014],[134.80004952200014,34.20075373700003],[134.75187547300018,34.191955442],[134.7286771410002,34.19093934900006],[134.72686973700004,34.20997004200008],[134.70537874300024,34.217968398],[134.70216251400004,34.22119879000003],[134.71359584400014,34.23997005300011],[134.72388756600012,34.252630927000084],[134.68811432000004,34.243579891000095],[134.66651451900006,34.26552969000009],[134.65716913300017,34.28820431600009],[134.6696351570001,34.302065221000035],[134.6832595860001,34.328226407000145],[134.7051556380001,34.323168076000044],[134.720814407,34.329788749],[134.79895562900006,34.446793791000076],[134.82422936300006,34.46084219000009],[134.84253991000006,34.470282294000086],[134.85043379000004,34.47573476800012],[134.85645592500012,34.4833031270001],[134.86500084700006,34.500677802000055],[134.87134850400017,34.50958893400009],[134.88383089300012,34.52948869500001],[134.9224611860001,34.54608208600011],[134.9381101050001,34.550905797000084],[134.96306260100013,34.580816112000136],[134.98133299800023,34.60240240600017],[135.00268188700014,34.608423048000034],[135.0281681650001,34.59100983300006],[135.03012129000004,34.57884349200016],[135.01841325900014,34.56604837500011],[135.00327729100005,34.5515747810001],[134.99539969300017,34.53023319400016],[134.98414147200018,34.50576406500009],[134.95630086700012,34.484877254000125],[134.93254200200008,34.453482284000145],[134.90297317900016,34.419005547],[134.89722741000017,34.38149648600013],[134.895762566,34.37274811400006],[134.89893639400006,34.36082591400013],[134.8996748380001,34.3490357590001],[134.91627037900017,34.33828359600015]]],[[[134.493988477,34.665798244000044],[134.4940698580002,34.658718166000185],[134.49366295700005,34.65416087400014],[134.49773196700002,34.65208567900011],[134.49952233200023,34.64720286700005],[134.49683678500006,34.64313385600009],[134.48698978000002,34.641343492000104],[134.4798283210001,34.63922760600009],[134.48275800900004,34.64248281500015],[134.48682701900023,34.644924221000096],[134.48617597700004,34.648098049000154],[134.48316491,34.64667389500009],[134.47901451900017,34.649155992000104],[134.46762129000004,34.64077383000013],[134.4585880870001,34.64484284100011],[134.45964603000007,34.64854564000014],[134.45972741000006,34.653876044000114],[134.4620874360002,34.65725332200013],[134.46436608200017,34.65932851800015],[134.46420332099999,34.665187893000095],[134.46876061300023,34.66445547100007],[134.47169030000023,34.66242096600003],[134.47510826900006,34.65936920800014],[134.4761662120002,34.66327545800014],[134.47852623800006,34.664740302],[134.48039798300024,34.66185130400008],[134.48414147200018,34.661932684000064],[134.48528079500002,34.66575755400005],[134.48959394600004,34.67064036700013],[134.493988477,34.665798244000044]]],[[[134.57927493600008,34.65562571800011],[134.57642662900005,34.651068427000055],[134.569590691,34.659776109000134],[134.56584720099997,34.664740302],[134.56674238400015,34.671332098000065],[134.5699162120002,34.674099026000036],[134.57406660200016,34.67560455900018],[134.5782983730002,34.674099026000036],[134.58838951900023,34.6694196640001],[134.58920332100016,34.66120026200012],[134.57927493600008,34.65562571800011]]],[[[134.53134199300004,34.677801825000174],[134.5328068370002,34.674099026000036],[134.53402754000004,34.677801825000174],[134.53777103000007,34.678900458000115],[134.54444420700023,34.68093496300004],[134.54590905000006,34.67161692900011],[134.54428144600016,34.66925690300015],[134.54509524800008,34.665798244000044],[134.5420841810001,34.65892161700005],[134.53728274800008,34.658514716000056],[134.53174889400017,34.66242096600003],[134.52588951900012,34.66567617400007],[134.51978600400005,34.668646552],[134.51327558700015,34.67084381700009],[134.51238040500007,34.675523179000024],[134.50896243600002,34.67617422100015],[134.5147404310002,34.680487372000144],[134.5216577480002,34.68223704600014],[134.52711022200006,34.68516673400016],[134.53557376400025,34.687201239],[134.53785241000006,34.684393622000144],[134.53386478000007,34.681057033000016],[134.53134199300004,34.677801825000174]]],[[[134.53698730900024,35.6722030990001],[134.59020875200022,35.65942839100005],[134.647447132,35.664265416000134],[134.69147450199998,35.66344715900003],[134.76690713200017,35.66655919200012],[134.7982952040002,35.665642253],[134.81341933700006,35.66237377200012],[134.83413714400015,35.65714558500015],[134.84234804400015,35.64536533400015],[134.86324991282666,35.65827840121362],[134.86740644694984,35.62237376643621],[134.86678633022498,35.61136668602366],[134.86539106714397,35.601393134385674],[134.86554609587554,35.58384898544237],[134.8677165062118,35.570697333115376],[134.8732975612334,35.558785916037166],[134.8839945823842,35.543825589479425],[134.91086632691943,35.51281972894738],[134.93350060357014,35.50059825350657],[134.95355105963378,35.49687755045859],[134.9710176942111,35.50075328223819],[135.00223025941884,35.514912624917784],[135.01246219257624,35.518349107125644],[135.02383100819455,35.51886587196243],[135.03514814696936,35.51336233220589],[135.0438297874147,35.50269114857768],[135.04739546083184,35.481813870214],[135.0504960471549,35.41639150626297],[135.04941084243634,35.40551361705978],[135.04475996340156,35.39499746396184],[135.03824873329236,35.38530813226443],[135.03106570871543,35.376884874237604],[135.0241410665569,35.36988271681385],[135.01483930938664,35.36611033782184],[135.00336714098083,35.365696925772525],[134.99489220611056,35.36688548417773],[134.98533206562243,35.37006358486657],[134.96300784733435,35.38166494448167],[134.94952029822306,35.38652252909152],[134.93892662986,35.38561819152635],[134.9302449894146,35.37959788693301],[134.9260591983734,35.368022365739606],[134.92373375930552,35.357118639013905],[134.92094323134498,35.31820628529185],[134.92202843606358,35.31110077598005],[134.92678266788587,35.302367458691336],[134.9367045426806,35.29378917103334],[134.95510135324494,35.285805162578285],[134.99029300301956,35.27769196201446],[135.0019202010565,35.272059231048715],[135.01432254365048,35.263946031384265],[135.02755171124252,35.25216380551528],[135.06465538983448,35.24063996026602],[135.0777812028402,35.233560289375774],[135.09426598458745,35.23485220056857],[135.11664187971897,35.245910956025895],[135.12697716656308,35.24797801447376],[135.1470276226266,35.247202867218576],[135.152712030436,35.244774075363196],[135.15927493738846,35.23955475554759],[135.17183230961345,35.20870392554572],[135.18847212099163,35.184209296022274],[135.2012878774336,35.170075791764404],[135.22485233007095,35.15713084591182],[135.24371422683006,35.158061020999256],[135.259475539065,35.156588243552605],[135.27441002810033,35.15216990851441],[135.28603722523778,35.14369497274471],[135.2939953961707,35.13232615802583],[135.30453738769032,35.12865713092184],[135.33120242665066,35.132842921963245],[135.3439148303053,35.129173895758626],[135.37915815602415,35.10788320624489],[135.3890800308187,35.100183416831285],[135.39641808502657,35.087884426125314],[135.40086225758708,35.076825669768894],[135.3970382017517,35.064268297543904],[135.378538039299,35.048662014040715],[135.35993452405913,35.03876597856707],[135.34670535736646,35.03429596578626],[135.338643833646,35.03018769000998],[135.338643833646,35.021971137558026],[135.34195112464474,35.014813951402644],[135.3633451678452,34.997450670512066],[135.36675581343016,34.95342234976168],[135.39889855372533,34.92409597405002],[135.43207482279482,34.91135773377239],[135.43982628815266,34.9037096221014],[135.4387410834344,34.88975698499688],[135.44013634741438,34.86541738510424],[135.43889611216585,34.84960439692544],[135.43486534985598,34.833946438377396],[135.43253991078828,34.81616974543742],[135.43316002841252,34.793845527149315],[135.44370201993215,34.76061758123633],[135.44850792859805,34.729198310453725],[135.4463375182618,34.72258372755702],[135.4449422551809,34.71922475791628],[135.442461785583,34.716124172492485],[135.43284997005017,34.70609894491044],[135.41293378998122,34.69208405211863],[135.3975743230001,34.6862137830001],[135.38121442800005,34.681262829],[135.363244418,34.68907092900015],[135.3412673680002,34.70850960500003],[135.3174851810002,34.70695845700011],[135.28686267099997,34.70544269800003],[135.287300908,34.679969876000044],[135.25360478100006,34.676997771000075],[135.25489028200005,34.70091002200017],[135.22006657000023,34.69077377000009],[135.24234854000005,34.650751996000096],[135.21090196999998,34.65518024800009],[135.20440327700007,34.67726067300008],[135.18603043700003,34.67490879400016],[135.18397604700013,34.65115585800011],[135.11131213300004,34.64038240500018],[135.07954647400007,34.62777575900016],[135.045768788,34.6249668870001],[135.01684557000007,34.642155986000105],[134.9792586600001,34.641017971000096],[134.96029707099999,34.64362213700018],[134.93702485700013,34.66736886900016],[134.9076249340001,34.677606433000065],[134.88835696700002,34.69147370000012],[134.83334394600016,34.721380927],[134.80046634200008,34.73090241100009],[134.7623804050002,34.75698476800015],[134.74439537900005,34.76654694200012],[134.69629967500006,34.776190497000115],[134.56690514400023,34.76654694200012],[134.55990644600016,34.76829661700013],[134.554453972,34.77163320500016],[134.54908287900005,34.77326080900018],[134.54265384200002,34.769964911000145],[134.53785241000006,34.76654694200012],[134.53150475400005,34.76312897300012],[134.52491295700023,34.76064687700001],[134.5190535820001,34.75971100500011],[134.50586998800023,34.760972398000135],[134.49586022200018,34.7645531270001],[134.490489129,34.77260976800012],[134.49122155000023,34.787054755],[134.4845483730002,34.7853050800001],[134.47820071700008,34.781927802000084],[134.47315514400012,34.776841539000046],[134.47136478000002,34.769964911000145],[134.47006269600004,34.76178620000003],[134.46705162900005,34.760972398000135],[134.46257571700008,34.762030341000084],[134.45704186300023,34.75971100500011],[134.43140709700018,34.73045482],[134.42286217500023,34.72557200700011],[134.40870201900023,34.72626373900006],[134.39820397200018,34.73151276200004],[134.38965905000023,34.73737213700018],[134.38135826900017,34.73981354400003],[134.36841881600017,34.73598867400001],[134.35775800900004,34.727362372000115],[134.3522241550002,34.716253973000065],[134.35401451900012,34.705145575000145],[134.32667076900012,34.704331773000135],[134.30464055720617,34.709108550374495],[134.30470177586622,34.70925120717739],[134.3143652691418,34.73175629351819],[134.3163806498471,34.74408112174642],[134.31400353393585,34.761237697961334],[134.31064456609377,34.77033275045609],[134.3061487157905,34.778135890858735],[134.28863040616832,34.799529934059294],[134.2597432800283,34.821337389309164],[134.25679772423567,34.83585846629525],[134.2577278993232,34.850973823383285],[134.26392907106973,34.872858792099535],[134.26361901270715,34.8971983910928],[134.25865807531002,34.936549994386795],[134.26454918779487,34.96742666281027],[134.2656343934125,34.98189606385229],[134.26501427578833,34.99352326098976],[134.26361901270715,35.000189520729876],[134.26361901270715,35.00096466708571],[134.26532433505,35.00269582785022],[134.31328006442348,35.04101390236973],[134.3626310568778,35.11103546851204],[134.37394819565267,35.12286937122455],[134.38557539279017,35.131757717244895],[134.39410200360456,35.14069774010868],[134.39709923714022,35.15206655482764],[134.39399865081705,35.17134186543474],[134.38821089111966,35.18751658792023],[134.3956522972159,35.216791286788364],[134.3956522972159,35.22921946870353],[134.41585778201116,35.22617055922389],[134.4232991881071,35.22084788751998],[134.43647667885566,35.2174889187785],[134.44846561119905,35.2186774771839],[134.46272830576677,35.22531789850213],[134.49218387268755,35.24433482489228],[134.50479292175604,35.25820994673167],[134.5100639184151,35.27187836389545],[134.51068403513997,35.288802395214404],[134.50789350807887,35.306010647373554],[134.5075834488172,35.31391714146294],[134.5061365088928,35.322262885123976],[134.50210574838164,35.33239146639313],[134.47854129394548,35.36187286993707],[134.47404544544085,35.38316356124933],[134.4679993024258,35.40011343009071],[134.4444348488892,35.43871572545042],[134.41678795799797,35.54418732378603],[134.39844282427705,35.57860382720493],[134.36883222862448,35.59756907855022],[134.36558094755824,35.59996541641969],[134.37649117900017,35.611184255000026],[134.41382264900008,35.62113994],[134.4662148620001,35.63755465300012],[134.49809573600012,35.65387932700018],[134.53698730900024,35.6722030990001]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-26\",\"name\":\"Kyoto\",\"name_alt\":\"Kioto\",\"name_local\":\"京都府\",\"type\":\"Fu\",\"type_en\":\"Urban Prefecture\",\"region\":\"Kinki\",\"postal\":\"KY\",\"region_code\":\"JPN-KIN\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[135.4787607049713,35.543724789933606],[135.47641320190775,35.53953644475109],[135.46266727127755,35.53354197857939],[135.45243533722106,35.526901557261056],[135.44184166885793,35.517728990400514],[135.43930952421516,35.50889232032432],[135.44370201993218,35.49886709184281],[135.44959313421577,35.49015961387508],[135.45346886599538,35.480418606233684],[135.4533655132078,35.47519928641796],[135.44685428309867,35.46184092941536],[135.44742272387953,35.452074083352244],[135.45305545484536,35.43912913749966],[135.48499148956563,35.40613373558347],[135.50250980008724,35.392516995263094],[135.5104162941766,35.382026678788236],[135.51878787715873,35.373448391130324],[135.53930342031657,35.36427582516916],[135.6882865736664,35.330350247266125],[135.74285688683585,35.33758494688783],[135.77189904170734,35.31732778614841],[135.82698611881426,35.2633000762375],[135.82894982267618,35.2484947784113],[135.81876956636233,35.2123729517503],[135.82047488780574,35.19914378505763],[135.8338074081853,35.15361684843876],[135.83644290561557,35.13648611154477],[135.83659793524646,35.12075063683238],[135.81473880405252,35.00556387017647],[135.8145837753209,35.00109385829492],[135.8248157093774,34.98096588786544],[135.83938846230762,34.96052785907341],[135.8535478049871,34.93404368726641],[135.85602827368592,34.919470934336175],[135.85644168483594,34.905208237969816],[135.85540815606146,34.88970530905277],[135.8588188007471,34.880067654198854],[135.8631596214193,34.874150702392996],[135.87354658420747,34.86810456027723],[135.88625898696267,34.86528819389508],[135.90181359272316,34.86479726837919],[135.9157662289283,34.867019355558796],[135.92889204283352,34.860301418975325],[135.9359200377795,34.849268500141136],[135.94320641514395,34.839837550862285],[135.95219811395185,34.83373973190311],[135.97736453704374,34.823197740383364],[135.98852664618772,34.81361176237289],[135.99762169778313,34.802785549113764],[136.00196251755608,34.79291535116255],[136.00309940001733,34.77369171829815],[136.03648237466177,34.715349026136636],[136.02919599819666,34.70361847621169],[136.01973921139546,34.698192449921635],[136.00883548377055,34.695918686797995],[135.9993786969692,34.69757233319663],[135.98542605986472,34.70328257942754],[135.96537560380102,34.71400543899978],[135.93204430599985,34.72392731379452],[135.90987511644346,34.72418569621291],[135.89003136595477,34.71746776052865],[135.8727714378515,34.70697744405379],[135.84920698521415,34.698657537915096],[135.83225711457413,34.699122625908444],[135.81768436074458,34.704186916992626],[135.8090027202993,34.7090445007031],[135.76848839702194,34.71992238990637],[135.75267540884295,34.72625275286222],[135.73934289026215,34.73377167422342],[135.72192793162867,34.749197088774594],[135.7062699730808,34.75953237651794],[135.71479658389518,34.785318915435596],[135.70844038341698,34.81115713119661],[135.70099897732086,34.822732652390016],[135.68270552044345,34.844488429897154],[135.6463769882074,34.89918793427577],[135.6338196150831,34.90993663226973],[135.6207971539655,34.91430329046452],[135.61020348470305,34.91337311537697],[135.59795617084063,34.91972931585528],[135.5827633002861,34.94654938444654],[135.57439171820317,34.95052846901362],[135.56643354727032,34.947221178014956],[135.56069746261784,34.94034821180057],[135.5571317892007,34.93290680570445],[135.56147260987302,34.925413722765086],[135.56374637299666,34.915956935963806],[135.5565116715764,34.91071177772636],[135.53821821469876,34.90978160353811],[135.50406009279902,34.92469025325261],[135.48592166555244,34.93812612641963],[135.47165897008546,34.95466258411099],[135.4583264506051,34.97471304017469],[135.44401207919407,34.98370473898254],[135.43021447172043,34.98758047076214],[135.39326582186015,34.98969920515415],[135.36334516784518,34.99745067051202],[135.3419511246447,35.01481395140269],[135.33864383364596,35.02197113755798],[135.33864383364596,35.03018769000991],[135.34670535736637,35.034295965786214],[135.35993452405904,35.03876597856703],[135.37853803929897,35.04866201404067],[135.39703820175163,35.06426829754386],[135.40086225758705,35.07682566976885],[135.39641808502654,35.087884426125356],[135.38908003081872,35.10018341683124],[135.37915815602406,35.107883206244935],[135.3439148303052,35.12917389575864],[135.33120242665063,35.13284292196329],[135.3045373876903,35.12865713092185],[135.2939953961706,35.13232615802579],[135.28603722523775,35.143694972744754],[135.2744100281003,35.15216990851445],[135.2594755390649,35.15658824355256],[135.2437142268301,35.1580610209993],[135.22485233007092,35.157130845911865],[135.20128787743357,35.17007579176436],[135.18847212099155,35.18420929602223],[135.17183230961342,35.20870392554568],[135.15927493738843,35.239554755547516],[135.1527120304359,35.24477407536324],[135.14702762262664,35.24720286721853],[135.126977166563,35.2479780144738],[135.11664187971894,35.245910956025824],[135.09426598458742,35.23485220056858],[135.07778120284024,35.233560289375816],[135.0646553898344,35.24063996026598],[135.02755171124244,35.25216380551521],[135.01432254365045,35.26394603138422],[135.0019202010564,35.27205923104867],[134.9902930030196,35.277691962014416],[134.95510135324486,35.285805162578214],[134.9367045426805,35.29378917103338],[134.9267826678858,35.30236745869129],[134.9220284360635,35.31110077598001],[134.92094323134495,35.31820628529178],[134.9237337593055,35.35711863901395],[134.92605919837337,35.36802236573965],[134.93024498941463,35.37959788693294],[134.93892662986002,35.38561819152628],[134.9495202982231,35.38652252909148],[134.96300784733427,35.38166494448163],[134.98533206562234,35.37006358486653],[134.99489220611048,35.366885484177686],[135.00336714098074,35.36569692577257],[135.01483930938662,35.366110337821794],[135.02414106655692,35.36988271681389],[135.03106570871546,35.37688487423756],[135.03824873329233,35.38530813226447],[135.0447599634016,35.3949974639618],[135.04941084243637,35.40551361705974],[135.0504960471548,35.41639150626298],[135.0473954608318,35.481813870213955],[135.04382978741467,35.50269114857764],[135.0351481469694,35.51336233220593],[135.02383100819446,35.518865871962475],[135.01246219257615,35.5183491071256],[135.00223025941887,35.51491262491774],[134.97101769421113,35.500753282238236],[134.95355105963375,35.496877550458635],[134.93350060357005,35.50059825350661],[134.9108663269194,35.512819728947335],[134.8839945823841,35.54382558947938],[134.87329756123336,35.558785916037124],[134.86771650621176,35.57069733311539],[134.86554609587552,35.58384898544233],[134.86539106714395,35.60139313438563],[134.86678633022495,35.6113666860237],[134.86740644694987,35.62237376643617],[134.86324991282663,35.65827840121358],[134.88096824600012,35.6573224950001],[134.885047535,35.6459137520001],[134.897749492,35.64916184900008],[134.91570383300007,35.64436743400003],[134.9270125660001,35.64118073100008],[134.9472762380001,35.654852606000105],[134.95362389400015,35.6579043640001],[134.96371504,35.661037502000084],[134.95962217700003,35.67375324500006],[134.98794378000008,35.68977723600008],[135.010843852,35.69111295300013],[135.0290315420001,35.69286863100007],[135.04482240700014,35.70173922100014],[135.08578535200007,35.72597890800006],[135.084119836,35.73902244700005],[135.13430528700013,35.751901306],[135.18384850400003,35.76023997600004],[135.21013431100005,35.7633731140001],[135.22684561200003,35.772320652000104],[135.25066024000006,35.75781549700005],[135.26172936300014,35.73981354400004],[135.27670332100007,35.731390692000105],[135.29249108200008,35.71165599200006],[135.304047071,35.68891022300002],[135.30616295700014,35.671535549000055],[135.29932701900015,35.6579043640001],[135.27921371900004,35.6685327000001],[135.25882700300014,35.65682881300009],[135.246415178,35.63552024800006],[135.242393935,35.622640356],[135.2327554000001,35.61421404300003],[135.20490238300016,35.587352774000095],[135.18817537200005,35.56504457600002],[135.188324232,35.54525757600004],[135.19604082100005,35.53989476800004],[135.20667266200007,35.54146785600008],[135.208084182,35.55905389800006],[135.2216903000001,35.56793854400013],[135.23524646400006,35.584548485000056],[135.24822550200014,35.59169447900007],[135.25342858200014,35.57127513200004],[135.26543768000016,35.567075332000115],[135.27091067100002,35.56292289500007],[135.26204205600004,35.55754659600004],[135.25195709700006,35.557315818000035],[135.23981521900015,35.557963034000124],[135.23873893400003,35.54202559100014],[135.301521966,35.51324263100008],[135.31617752400007,35.51833756300002],[135.32443612400004,35.52444638300011],[135.33548989200006,35.50821008500009],[135.32956419600015,35.49475491900003],[135.32496178500008,35.485663153000075],[135.31527754,35.466782945000034],[135.31348717500015,35.45864492400011],[135.31902103000004,35.44696686400002],[135.33025149800005,35.45453522300008],[135.339757307,35.471122489000095],[135.35251785900005,35.486984186000115],[135.37050393500016,35.48612645800003],[135.394867384,35.47646719000008],[135.4023543630001,35.482163804000066],[135.4023543630001,35.507025458000015],[135.37378991000008,35.501695054000066],[135.35022402500016,35.51031344800003],[135.3377962190001,35.537477564000056],[135.3613387380001,35.55418528900009],[135.3737085300001,35.55768463700008],[135.4028426440001,35.55817291900007],[135.4160262380001,35.56167226800008],[135.4259546230001,35.56981028900009],[135.4355574880001,35.58002350500004],[135.43948099800002,35.59056974300009],[135.46322209200008,35.603327396000054],[135.45824425800004,35.586987262000065],[135.45238978900005,35.57178111900009],[135.4700304860001,35.56595953900006],[135.482937686,35.55919918800009],[135.4787607049713,35.543724789933606]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-29\",\"name\":\"Nara\",\"name_alt\":null,\"name_local\":\"奈良県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kinki\",\"postal\":\"NR\",\"region_code\":\"JPN-KIN\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[135.84920698521415,34.698657537915096],[135.8727714378515,34.70697744405379],[135.89003136595477,34.71746776052865],[135.90987511644346,34.72418569621291],[135.93204430599985,34.72392731379452],[135.96537560380102,34.71400543899978],[135.98542605986472,34.70328257942754],[135.9993786969692,34.69757233319663],[136.00883548377055,34.695918686797995],[136.01973921139546,34.698192449921635],[136.02919599819666,34.70361847621169],[136.03648237466177,34.715349026136636],[136.04965986541038,34.711679999032725],[136.0550858908012,34.70845022239982],[136.05911665311106,34.70253327149334],[136.0625272968972,34.69178457349926],[136.06485273686428,34.67731517245724],[136.07234581890447,34.65630870198498],[136.0633024441525,34.64680023923964],[136.03989302024667,34.64124502174039],[136.03818769700456,34.63716258438572],[136.0409782249651,34.63375193880091],[136.05338056935773,34.62643972391419],[136.05834150585576,34.61912750902735],[136.05927168184257,34.6055366071287],[136.0535355980893,34.58900014943734],[136.05245039337092,34.574582424339354],[136.0598917985676,34.562851874414406],[136.07585981727664,34.55249075004804],[136.09699547715945,34.54249135998833],[136.1142037293186,34.53639354102913],[136.12898318872317,34.53494660110482],[136.14247073783434,34.53556671782971],[136.15363284697827,34.533835557964665],[136.16164269385513,34.527815253371244],[136.1714612158624,34.509366766862826],[136.18127973876892,34.50396657989367],[136.2025187523385,34.50151194871751],[136.20794477772927,34.4954141297583],[136.2112003927839,34.486396593428026],[136.21042524642797,34.47246979384593],[136.20267378107013,34.458827216003215],[136.1775073588775,34.43862173120786],[136.15730187408218,34.43115448669022],[136.11389367095614,34.42518585894021],[136.09560021407856,34.42001821506855],[136.08071740098723,34.40880442998039],[136.07120893824185,34.39381826410158],[136.0692452325814,34.3769975864694],[136.07389611161625,34.36353587488074],[136.08195763623587,34.35087514896901],[136.10769249920946,34.30069733331527],[136.10645226486014,34.2920415312916],[136.10087120983843,34.283437405212084],[136.10438520731145,34.26899384169238],[136.1128084662377,34.254963691121446],[136.11869957872256,34.24114024522612],[136.11807946199747,34.22840200494849],[136.10722741121612,34.189386298438876],[136.1101729679081,34.138588365160956],[136.10939782065282,34.126237698511034],[136.0857816920714,34.04996328377851],[136.09079430631215,34.03590729388651],[136.1014913265634,34.018466497730685],[136.09777062441475,34.01164520925903],[136.08722863199574,34.00944896140041],[136.0353971699434,34.01074087169383],[136.01756880105927,34.00484975830969],[136.00382287042908,33.99311920838474],[135.99069705562454,33.97032990210329],[135.98480594313975,33.95314748836586],[135.97410892108974,33.93697276498111],[135.95752078655505,33.91795583769178],[135.92553307499134,33.90108348231689],[135.92072716722504,33.89343536974653],[135.91514611220333,33.88873281476759],[135.90036665279877,33.88852610919261],[135.89137495309166,33.88268667175245],[135.8833134302705,33.862946274950644],[135.87540693618115,33.850802313875676],[135.847656691603,33.83814158796392],[135.8172709486953,33.872222195497955],[135.80073449100394,33.87653717684921],[135.78523156118752,33.87904348396961],[135.77050377772707,33.87540029618671],[135.75825646386465,33.87113698988014],[135.74709435382135,33.869147446697326],[135.73603559836414,33.86958669716833],[135.69634809828608,33.87891429276041],[135.68218875470728,33.87989614469129],[135.66823611940129,33.87762238246694],[135.65397342303504,33.872558092282105],[135.63738528939956,33.85907054317104],[135.62823856006145,33.85596995684807],[135.61857506678592,33.857132676831554],[135.60896325125304,33.86382477499339],[135.60291710823793,33.875090236924876],[135.59826622920315,33.890128078747864],[135.6035372249629,33.91653473618908],[135.6106685726965,33.937256984921845],[135.61330407102605,33.954852809809296],[135.60648278165507,33.977202867418384],[135.5847270041479,33.998416043465625],[135.5795076843322,34.01565013314709],[135.5712911318803,34.028517563734624],[135.5654000175967,34.035623073945686],[135.5540312028777,34.04169505538252],[135.54643476805006,34.05073843013443],[135.5430241233646,34.06456187602976],[135.55108564708485,34.08246776017904],[135.57501183492826,34.111509915050505],[135.58710412005905,34.129105739937955],[135.61516442299967,34.15695933730359],[135.6328894390963,34.184580389773174],[135.6400207868299,34.193468735793516],[135.64994266162444,34.20052256916125],[135.66110477076853,34.204449977784265],[135.70110233010843,34.20592275613032],[135.70844038341698,34.21289907513227],[135.70906050014196,34.22535309546896],[135.69448774631235,34.24860748974385],[135.66885623612632,34.272637031273945],[135.66281009311126,34.28493602108054],[135.6590893900633,34.32322825807762],[135.64684207620076,34.37175242823196],[135.65955447805663,34.37495636734252],[135.66544559144086,34.381105862245676],[135.67071658720073,34.388547268341796],[135.66885623612632,34.40787425399358],[135.67521243660462,34.4333507354481],[135.6769177589474,34.44797516522166],[135.6740238790988,34.46414988950579],[135.66513553307834,34.49306285316817],[135.6505627792489,34.51660146738379],[135.6411059924476,34.527014269492895],[135.62710167939895,34.53794383374094],[135.62296756430158,34.54380910870336],[135.62203738831477,34.553420925135555],[135.64653201693903,34.601066596146524],[135.66761600177705,34.674886379702656],[135.6740238790988,34.690053411835464],[135.68456587061843,34.705685532861],[135.7062699730808,34.75953237651794],[135.72192793162867,34.749197088774594],[135.73934289026215,34.73377167422342],[135.75267540884295,34.72625275286222],[135.76848839702194,34.71992238990637],[135.8090027202993,34.7090445007031],[135.81768436074458,34.704186916992626],[135.83225711457413,34.699122625908444],[135.84920698521415,34.698657537915096]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-27\",\"name\":\"Osaka\",\"name_alt\":null,\"name_local\":\"大阪府\",\"type\":\"Fu\",\"type_en\":\"Urban Prefecture\",\"region\":\"Kinki\",\"postal\":\"OS\",\"region_code\":\"JPN-KIN\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[135.25705428500012,34.43577806100008],[135.22441622000022,34.41630428000015],[135.21054196500003,34.421905936000186],[135.20517916100007,34.43049087500013],[135.24097098900015,34.45464659500011],[135.25705428500012,34.43577806100008]]],[[[135.50406009279905,34.924690253252564],[135.53821821469873,34.90978160353815],[135.55651167157643,34.91071177772632],[135.5637463729967,34.91595693596385],[135.561472609873,34.92541372276504],[135.55713178920078,34.93290680570449],[135.56069746261792,34.94034821180061],[135.56643354727035,34.947221178015],[135.57439171820315,34.95052846901366],[135.5827633002862,34.946549384446584],[135.59795617084072,34.91972931585532],[135.61020348470308,34.91337311537701],[135.62079715396558,34.91430329046456],[135.63381961508313,34.909936632269776],[135.64637698820738,34.899187934275815],[135.68270552044353,34.844488429897226],[135.70099897732084,34.82273265239006],[135.70844038341707,34.81115713119665],[135.7147965838952,34.78531891543555],[135.70626997308082,34.75953237651798],[135.6845658706185,34.70568553286104],[135.67402387909888,34.69005341183551],[135.66761600177713,34.67488637970273],[135.64653201693912,34.601066596146566],[135.62203738831474,34.553420925135626],[135.62296756430155,34.54380910870343],[135.62710167939892,34.5379438337409],[135.64110599244762,34.52701426949285],[135.650562779249,34.51660146738378],[135.6651355330783,34.49306285316821],[135.67402387909888,34.46414988950575],[135.67691775894744,34.447975165221735],[135.6752124366046,34.433350735448144],[135.6688562361263,34.407874253993654],[135.67071658720076,34.38854726834184],[135.66544559144089,34.38110586224572],[135.65955447805666,34.37495636734248],[135.64684207620073,34.371752428231915],[135.6010567571635,34.368496812278025],[135.52519575358113,34.33464874874069],[135.508866002364,34.33087636974865],[135.4925879252925,34.332168280941445],[135.4794104354432,34.33501048394665],[135.4568795106808,34.33464874874069],[135.33321780825517,34.30493480120016],[135.31259891230988,34.29733836547324],[135.1884710903685,34.2743027079036],[135.1205814795517,34.27571120630721],[135.09973570803655,34.280500099799795],[135.0852713040758,34.299181985920725],[135.10503071500003,34.31811688100008],[135.20898538000003,34.342277645000124],[135.25656401300014,34.376754752000025],[135.28129845600014,34.397428263],[135.31968984900018,34.42875819700011],[135.35347650300005,34.46748311400013],[135.37256231200004,34.484513116000116],[135.3742794250002,34.50775608100015],[135.3732082730002,34.51893221900009],[135.39099683600014,34.52364861700006],[135.3991382210002,34.53293199299999],[135.40832373099997,34.540974933000044],[135.4143791510002,34.55812547000012],[135.4152341600002,34.5799677080001],[135.41578152200012,34.60961152200004],[135.40731751300015,34.63420060100019],[135.40359067700015,34.658912182000066],[135.41293378998122,34.69208405211863],[135.43284997005017,34.70609894491044],[135.442461785583,34.716124172492485],[135.4449422551809,34.71922475791628],[135.4463375182618,34.72258372755702],[135.44850792859805,34.729198310453725],[135.44370201993215,34.76061758123633],[135.43316002841252,34.793845527149315],[135.43253991078828,34.81616974543742],[135.43486534985598,34.833946438377396],[135.43889611216585,34.84960439692544],[135.44013634741438,34.86541738510424],[135.4387410834344,34.88975698499688],[135.43982628815266,34.9037096221014],[135.43207482279482,34.91135773377239],[135.39889855372533,34.92409597405002],[135.36675581343016,34.95342234976168],[135.3633451678452,34.997450670512066],[135.39326582186013,34.989699205154224],[135.43021447172046,34.98758047076218],[135.4440120791941,34.98370473898258],[135.45832645060514,34.97471304017476],[135.47165897008549,34.954662584111034],[135.48592166555247,34.938126126419675],[135.50406009279905,34.924690253252564]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-25\",\"name\":\"Shiga\",\"name_alt\":\"Siga\",\"name_local\":\"滋賀県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kinki\",\"postal\":\"SH\",\"region_code\":\"JPN-KIN\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[136.25729577018365,35.64562815981171],[136.28923180580324,35.60015290003628],[136.29264244958938,35.57989573839757],[136.29796512309196,35.56397939743121],[136.31238284729056,35.54168101756491],[136.3260254251333,35.53294770027611],[136.33806603431998,35.5320175242893],[136.34757449796467,35.53622915285305],[136.35935672383374,35.53385203694194],[136.3684001003843,35.52082957582422],[136.37909712063566,35.50106334150004],[136.3973905784125,35.45277171534238],[136.40762251156963,35.393395494406576],[136.40245486859732,35.35714447743567],[136.39816572476832,35.343579413059445],[136.39397993372688,35.31861969734118],[136.37537641758752,35.266323147295424],[136.37615156484276,35.231493231827216],[136.3987858423926,35.20260610568721],[136.40700239484454,35.1834341505656],[136.43573449135363,35.15098135010925],[136.44193566310042,35.14147288736382],[136.4431758974497,35.131835232509985],[136.43232384666823,35.0620978872077],[136.4183712095636,35.02189362229285],[136.4116532738795,34.98236115184571],[136.4072091004196,34.96688406045098],[136.39987104711116,34.949469102716876],[136.39165449375986,34.937350979164364],[136.38157759023358,34.926395574696045],[136.3701054218277,34.916163642438136],[136.36245731015674,34.90582835469482],[136.342613559668,34.892702541688976],[136.3116077009346,34.87929250694367],[136.24050093007384,34.86311778445818],[136.1804529155695,34.86252350525555],[136.12459069210678,34.872342027262704],[136.11265343570744,34.871050116969286],[136.1021114441877,34.865055649898295],[136.10040612184508,34.85420360001612],[136.1128084662377,34.833119615178106],[136.1011812682009,34.81911530212952],[136.08195763623587,34.800925198039494],[136.00309940001733,34.77369171829815],[136.00196251755608,34.79291535116255],[135.99762169778313,34.802785549113764],[135.98852664618772,34.81361176237289],[135.97736453704374,34.823197740383364],[135.95219811395185,34.83373973190311],[135.94320641514395,34.839837550862285],[135.9359200377795,34.849268500141136],[135.92889204283352,34.860301418975325],[135.9157662289283,34.867019355558796],[135.90181359272316,34.86479726837919],[135.88625898696267,34.86528819389508],[135.87354658420747,34.86810456027723],[135.8631596214193,34.874150702392996],[135.8588188007471,34.880067654198854],[135.85540815606146,34.88970530905277],[135.85644168483594,34.905208237969816],[135.85602827368592,34.919470934336175],[135.8535478049871,34.93404368726641],[135.83938846230762,34.96052785907341],[135.8248157093774,34.98096588786544],[135.8145837753209,35.00109385829492],[135.81473880405252,35.00556387017647],[135.83659793524646,35.12075063683238],[135.83644290561557,35.13648611154477],[135.8338074081853,35.15361684843876],[135.82047488780574,35.19914378505763],[135.81876956636233,35.2123729517503],[135.82894982267618,35.2484947784113],[135.82698611881426,35.2633000762375],[135.77189904170734,35.31732778614841],[135.74285688683585,35.33758494688783],[135.79918419829215,35.37688487423756],[135.81752933201307,35.38468801553947],[135.8412488142814,35.386574205035544],[135.85540815606146,35.38181997321317],[135.866880323568,35.379830430929644],[135.8764921408996,35.381587429216495],[135.88625898696267,35.3907083201328],[135.89385542179025,35.40073354771502],[135.90098677042312,35.413781847254455],[135.9211922552184,35.48907440915664],[135.92568810372296,35.497497667183524],[135.9309591003821,35.5026394717342],[135.9380904481157,35.50093415029076],[135.94506676711757,35.496877550458635],[135.95514367064385,35.48749827712385],[135.9671326029872,35.481813870213955],[135.97953494737988,35.480418606233684],[135.99152387882384,35.4845785588534],[136.01570844908565,35.503311266201905],[136.0439237807579,35.511992905748],[136.05673953719992,35.51431834571508],[136.07089887898007,35.515145168914415],[136.08082075377473,35.51429250729336],[136.0886238950767,35.522638250954515],[136.08970909979507,35.53186249375901],[136.08893395433853,35.54274038296228],[136.09001915905685,35.5513445090418],[136.09575524281018,35.5584758567754],[136.10924279192125,35.55839834240953],[136.1222652530389,35.55596955055424],[136.1354944188323,35.55793325531545],[136.1452612648955,35.5709557155339],[136.14634646961383,35.582143663099515],[136.14324588419015,35.59250478836526],[136.1343575381698,35.610901598030324],[136.13332400939544,35.6270763214151],[136.13218712783362,35.63335500752754],[136.12443566337515,35.64934886285977],[136.1239705753817,35.65526581556489],[136.1277429552731,35.663534043960965],[136.14696658723813,35.671207994053674],[136.1672754248209,35.67500621236678],[136.25729577018365,35.64562815981171]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-30\",\"name\":\"Wakayama\",\"name_alt\":null,\"name_local\":\"和歌山県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kinki\",\"postal\":\"WK\",\"region_code\":\"JPN-KIN\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[135.85621178500006,33.466498114],[135.85287519600004,33.462144273000106],[135.8296004570001,33.450832424000126],[135.82667076900006,33.45099518400018],[135.81853274800008,33.4604352890001],[135.80925540500013,33.463324286000116],[135.80404707100004,33.46287669500013],[135.7994083990001,33.46381256700012],[135.80144290500002,33.47195058800004],[135.80982506600017,33.47805410400012],[135.83676191500015,33.476629950000145],[135.8550724620002,33.47349681200008],[135.85873457100016,33.471665757000025],[135.85621178500006,33.466498114]]],[[[135.64684207620073,34.371752428231915],[135.65908939006331,34.32322825807758],[135.66281009311123,34.28493602108058],[135.6688562361263,34.27263703127399],[135.69448774631238,34.24860748974383],[135.709060500142,34.225353095469],[135.70844038341707,34.21289907513231],[135.7011023301085,34.20592275613036],[135.66110477076856,34.204449977784336],[135.64994266162446,34.20052256916121],[135.64002078682998,34.19346873579359],[135.63288943909632,34.18458038977322],[135.61516442299964,34.15695933730355],[135.58710412005908,34.129105739938],[135.57501183492835,34.11150991505055],[135.55108564708482,34.082467760179085],[135.54302412336463,34.064561876029714],[135.54643476805003,34.05073843013447],[135.55403120287772,34.04169505538259],[135.56540001759677,34.03562307394573],[135.57129113188037,34.02851756373458],[135.5795076843323,34.01565013314713],[135.58472700414794,33.99841604346567],[135.6064827816551,33.97720286741834],[135.61330407102602,33.95485280980937],[135.6106685726966,33.937256984921916],[135.60353722496293,33.916534736189035],[135.59826622920323,33.89012807874791],[135.60291710823802,33.87509023692492],[135.60896325125307,33.86382477499343],[135.618575066786,33.85713267683151],[135.62823856006153,33.855969956848114],[135.63738528939965,33.859070543171086],[135.65397342303507,33.872558092282176],[135.6682361194013,33.87762238246698],[135.68218875470737,33.87989614469136],[135.69634809828605,33.878914292760484],[135.73603559836423,33.86958669716837],[135.74709435382138,33.86914744669737],[135.75825646386463,33.8711369898801],[135.7705037777271,33.875400296186754],[135.78523156118754,33.879043483969596],[135.80073449100396,33.876537176849254],[135.81727094869532,33.87222219549791],[135.84765669160296,33.83814158796399],[135.8465198100412,33.82496409811462],[135.84682986930304,33.81741933923111],[135.8516874530135,33.80266571734897],[135.86315962141933,33.78708527316685],[135.90243371034754,33.74750112587621],[135.91995201996977,33.735589707898626],[135.92878869004593,33.72357493713368],[135.93824547684724,33.71429901838492],[135.9508028499716,33.70393789311926],[135.96713260298728,33.698563544571996],[135.99373956985735,33.68625974353479],[135.99057050900015,33.68252187700001],[135.98072350400005,33.65298086100013],[135.96159915500007,33.64252350500006],[135.95728600400017,33.62571849200005],[135.93706668399997,33.61747297900003],[135.9387364620002,33.60394802800006],[135.95987107500017,33.5930882250001],[135.94607134200012,33.577809933000125],[135.91253492700017,33.54768686700014],[135.89420900499996,33.54164240700014],[135.88477623800023,33.52899811400012],[135.8280632870001,33.51364825899999],[135.80918398599997,33.50503721300002],[135.79799322100004,33.49516148700009],[135.78296949000017,33.47289128700008],[135.79021590700003,33.45339146900004],[135.7892632630002,33.44106232400016],[135.77202431600008,33.44491902400013],[135.760020379,33.43333567900008],[135.753846014,33.45157716400011],[135.77075201000005,33.46822920000007],[135.76557060700023,33.481545488000066],[135.66822350400017,33.490464585000055],[135.63754316500015,33.49461497599999],[135.49076302200004,33.54122717300005],[135.44483483200023,33.550848700000174],[135.40919030000012,33.57420482000008],[135.39750451600017,33.58258693600011],[135.38732414300003,33.59406731700001],[135.38681334100013,33.60783872000012],[135.39234459700012,33.61888255400014],[135.380707227,33.63711172100001],[135.37501061300011,33.64313385600012],[135.369395379,33.644964911],[135.35866231200023,33.65420731400009],[135.34196725399997,33.65721093600011],[135.32769512600012,33.671192935000036],[135.3429445310002,33.6938983510001],[135.3792142130002,33.69811089400004],[135.40235436300006,33.701117255],[135.39161217500018,33.71356842700011],[135.34083092500012,33.745306708000086],[135.32138786300007,33.75292358100013],[135.31210223400004,33.7655287840001],[135.2940207730002,33.76371566600001],[135.264345035,33.78131573100008],[135.2325826480002,33.78199800700004],[135.22984796100005,33.798554135000145],[135.19646243600002,33.81476471600011],[135.17335045700005,33.838324286000145],[135.1527576010001,33.87585417900014],[135.11868351100023,33.89293870400009],[135.10012023200014,33.88984017900013],[135.0654538490002,33.885571819000106],[135.05885950400003,33.8800328870001],[135.06130745400006,33.88987988000004],[135.06649658500024,33.896407725000145],[135.05736156500004,33.901297796],[135.06423797000005,33.90602024800005],[135.07912676300023,33.90602541400001],[135.07799229000008,33.91855214600015],[135.069559977,33.93066660300012],[135.084717046,33.94221372400001],[135.10122240100011,33.954437547000154],[135.09497587900003,33.958691242000114],[135.07733462100012,33.95898546600013],[135.0757372510001,33.97748793800015],[135.09199839900023,33.99203611100013],[135.113277088,33.99338693300008],[135.1279350470002,33.998605234000095],[135.15316816500015,34.00975169499999],[135.1616317070001,34.02496979400014],[135.16277103000002,34.03974030200014],[135.15308678500017,34.048570054],[135.13331139400023,34.058294989000146],[135.11109459700018,34.06574127800012],[135.09449303500006,34.06761302299999],[135.1149194670002,34.10179271000008],[135.11736087300002,34.103420315],[135.12549889400023,34.106146552000084],[135.128672722,34.108587958],[135.1305444670002,34.11322663000011],[135.13062584700018,34.12238190300012],[135.12283938400012,34.13710111500002],[135.14877062399998,34.13807629800006],[135.1870223320001,34.135931708000086],[135.18995201900023,34.149603583000115],[135.17969811300011,34.16583893400015],[135.16585549400006,34.18672339300009],[135.14877363400015,34.185614325000174],[135.13542728000007,34.22768789300012],[135.11962891700003,34.24220217300014],[135.09266843600008,34.25988713800014],[135.06104364700016,34.26727491900006],[135.07129967500012,34.2857933610001],[135.0852713040758,34.299181985920725],[135.09973570803655,34.280500099799795],[135.1205814795517,34.27571120630721],[135.1884710903685,34.2743027079036],[135.31259891230988,34.29733836547324],[135.33321780825517,34.30493480120016],[135.4568795106808,34.33464874874069],[135.4794104354432,34.33501048394665],[135.4925879252925,34.332168280941445],[135.508866002364,34.33087636974865],[135.52519575358113,34.33464874874069],[135.6010567571635,34.368496812278025],[135.64684207620073,34.371752428231915]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-12\",\"name\":\"Chiba\",\"name_alt\":\"Tiba|Tsiba\",\"name_local\":\"千葉県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kanto\",\"postal\":\"CH\",\"region_code\":\"JPN-KNT\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[139.79170535711654,36.07480093051851],[139.80545128774673,36.07779816315474],[139.86596439024441,36.00508942273865],[139.93236860612626,35.93847850218114],[139.96962731344973,35.91393219671346],[140.12734378498766,35.85442678546772],[140.16977013618282,35.84419485141126],[140.1896655617162,35.842902940218494],[140.31994184793797,35.850680243098765],[140.345573358124,35.85543447492114],[140.36779422362451,35.878430488576214],[140.39280561708557,35.88489004184191],[140.461431920147,35.892434800725425],[140.5514522646103,35.88855906804653],[140.56964236870033,35.88383067554517],[140.62571129773818,35.85365163821254],[140.670824823207,35.8416368683468],[140.69273563034506,35.83267100706129],[140.71087405759158,35.81972606120871],[140.75541914317893,35.77401825653723],[140.7768131863794,35.75688751874395],[140.7989823750366,35.74378754326057],[140.82476891485356,35.735390122755916],[140.85717003756713,35.73244456606383],[140.85946791781248,35.73537802957986],[140.8650008470001,35.733628648000064],[140.87427819100003,35.72923411700009],[140.87940514400015,35.72052643400011],[140.86876177700003,35.69675396200003],[140.86066467200015,35.68779101800007],[140.8468530610001,35.69525788000007],[140.84021114000006,35.704379042000056],[140.82830145700007,35.71396943700009],[140.8031441980001,35.70937139000003],[140.770890109,35.697051422000044],[140.72078890300008,35.68251721100006],[140.71626055100006,35.69879072800006],[140.66105645600015,35.688634978000096],[140.5961624470001,35.652075298000085],[140.48346749700016,35.5659910550001],[140.4232195130001,35.490644380000106],[140.38591856900007,35.380998573000085],[140.40503991000003,35.32892487200013],[140.4130965500001,35.301214911000045],[140.4040837080001,35.25950925500007],[140.39883763000006,35.22209519200007],[140.39419527500002,35.19659166200012],[140.377629651,35.176356909000106],[140.34893519300016,35.18051589700005],[140.33047969900008,35.15778336400004],[140.33353722800013,35.14234798800004],[140.32121361200012,35.13039407800002],[140.303349485,35.13340288500012],[140.2943684120001,35.14663912100008],[140.27891015,35.13198816600004],[140.25332548800014,35.13462394500007],[140.23738075200004,35.112429525000096],[140.2000669510001,35.1149842640001],[140.1555132030001,35.11807535700004],[140.13460281400006,35.12264536400008],[140.10595281100007,35.0976030030001],[140.10080523400006,35.07575134500004],[140.07548783400006,35.05926940100005],[140.05553662300014,35.058868491000084],[140.0211555850001,35.03842885800009],[139.99096763300014,35.02050211200009],[139.98057300900012,35.00751446600009],[139.9748695020001,34.99019749100009],[139.9640643580001,34.97595964400003],[139.964648033,34.95928462900011],[139.96213888100016,34.940850503000036],[139.95238826500008,34.92797425400002],[139.94303968000008,34.918649772000094],[139.9263958460001,34.907356652000146],[139.90079996000009,34.903012715000074],[139.88293893700006,34.901305321000024],[139.87064389700004,34.904726639000046],[139.855791469,34.90243585200005],[139.84309961700006,34.89998186000011],[139.83814537900003,34.90468984600011],[139.821984262,34.912109830000134],[139.82496460300007,34.921371227000094],[139.82318224300002,34.934921744000036],[139.81083100900003,34.94353955300005],[139.77881155300014,34.95413884000004],[139.75699355500007,34.95628367400005],[139.75243898200006,34.96473781400002],[139.7527773650001,34.97580871200006],[139.76530374200001,34.976499991000054],[139.78362189500007,34.97425062900001],[139.79445380400006,34.9734485380001],[139.80851286700016,34.976165857000126],[139.82044658100006,34.98136827700009],[139.8271049220001,34.9901935180001],[139.84030331500009,34.991003171000045],[139.8586531910001,34.986802476],[139.86548912900003,34.99054596600004],[139.86996504000007,34.999335028000104],[139.8676863940001,35.01333242400008],[139.8559676440001,35.02081940300013],[139.83578535200007,35.02724844000005],[139.83464603000004,35.0374209660001],[139.8462020190001,35.046820380000014],[139.8496199880001,35.05801015800003],[139.8474227220001,35.06932200700001],[139.83806399800005,35.08832428600002],[139.83578535200007,35.09926992400008],[139.83643639400015,35.13519928600007],[139.81897429600008,35.15766287700012],[139.82211347700007,35.17059967700007],[139.8194346680001,35.18504826700007],[139.8246088240001,35.19906390000003],[139.85195684100006,35.21466754000002],[139.8651506070001,35.224098964000035],[139.86632662800002,35.24620537700011],[139.8483179050001,35.27171458500007],[139.84933413400006,35.29355360000005],[139.80813764700008,35.30548489100002],[139.78215300800002,35.31417710000008],[139.78100337900003,35.31806065600006],[139.8021897060001,35.31735035200009],[139.82415804200002,35.32842398900007],[139.824667997,35.34700932700014],[139.83874471700005,35.35929899400011],[139.84477673600009,35.37620508400005],[139.8927430760001,35.36418368900004],[139.90786505500006,35.3787185920001],[139.90731450700002,35.392304486000114],[139.900121044,35.42310445500007],[139.91282345400015,35.43443618000009],[139.94075591000006,35.442517644000134],[139.95867218600011,35.45620969700005],[139.98233663500002,35.458668431],[140.005859289,35.47342712000008],[140.0201180250001,35.49121049500013],[140.041455089,35.51377297500011],[140.07188712900012,35.542856802000074],[140.09015640100012,35.54084883200002],[140.09782201000013,35.55532235600012],[140.10204868400007,35.567087494000134],[140.086119779,35.56680512400004],[140.08301842500015,35.585598049000026],[140.07960045700014,35.603583075000074],[140.06382667700012,35.61534216400011],[140.04761498000005,35.630775009000104],[140.03287822000004,35.64099547700002],[140.00894986300005,35.659759819000044],[139.99577884200005,35.65668366100007],[139.98361433200006,35.66864466800004],[139.95041397400007,35.67259201500008],[139.92053189700005,35.662066673000055],[139.93957929100014,35.6368914980001],[139.91207079900005,35.624087461000016],[139.89716783000011,35.61437375800011],[139.8911238940001,35.623114325000046],[139.8794051440001,35.6250674500001],[139.8708602220001,35.63129303600007],[139.86424285686127,35.63206757687868],[139.8769714697575,35.660821031265584],[139.88115726079894,35.67343008123328],[139.885963170364,35.69774384180482],[139.8639490086398,35.778126533212884],[139.87480106032064,35.85037018473622],[139.8718555027292,35.869361274503234],[139.86653283102532,35.88881745046493],[139.85785118968062,35.900754705964914],[139.8386275577156,35.922898057998964],[139.83056603399518,35.93791006140023],[139.79645958893883,36.01891286953327],[139.78591759741914,36.03679291436154],[139.75284468023779,36.07955516234088],[139.7717582538403,36.08002025033423],[139.79170535711654,36.07480093051851]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-08\",\"name\":\"Ibaraki\",\"name_alt\":null,\"name_local\":\"茨城県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kanto\",\"postal\":\"IB\",\"region_code\":\"JPN-KNT\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[140.797286048648,36.84648905231134],[140.79314212300005,36.83755117400011],[140.75171959700003,36.78506094000005],[140.7418725920001,36.768459377000084],[140.6938582690001,36.6179873720001],[140.63892662900003,36.53974030200004],[140.6235457690001,36.50608958500001],[140.61011803500008,36.4339053410001],[140.6105249360001,36.41815827000005],[140.6235457690001,36.37962474200009],[140.6247664720001,36.3635928410001],[140.6168725920001,36.33779531500005],[140.5793819980001,36.30475890300008],[140.56410737100015,36.283095802000105],[140.56643664500012,36.24583748900011],[140.57837975400003,36.16095612200007],[140.6099023050001,36.09505676000006],[140.646034105,36.013320444000044],[140.78931725400003,35.8086612000001],[140.83082116000003,35.76105377800013],[140.84164472700002,35.74241771000004],[140.8525496750001,35.737534898000064],[140.85946699300007,35.73537832200006],[140.85946791781248,35.73537802957986],[140.85717003756713,35.73244456606383],[140.82476891485356,35.735390122755916],[140.7989823750366,35.74378754326057],[140.7768131863794,35.75688751874395],[140.75541914317893,35.77401825653723],[140.71087405759158,35.81972606120871],[140.69273563034506,35.83267100706129],[140.670824823207,35.8416368683468],[140.62571129773818,35.85365163821254],[140.56964236870033,35.88383067554517],[140.5514522646103,35.88855906804653],[140.461431920147,35.892434800725425],[140.39280561708557,35.88489004184191],[140.36779422362451,35.878430488576214],[140.345573358124,35.85543447492114],[140.31994184793797,35.850680243098765],[140.1896655617162,35.842902940218494],[140.16977013618282,35.84419485141126],[140.12734378498766,35.85442678546772],[139.96962731344973,35.91393219671346],[139.93236860612626,35.93847850218114],[139.86596439024441,36.00508942273865],[139.80545128774673,36.07779816315474],[139.79170535711654,36.07480093051851],[139.7717582538403,36.08002025033423],[139.75284468023779,36.07955516234088],[139.7334660177425,36.084903673365886],[139.7240092309412,36.089244493138835],[139.7109350929801,36.098287868790095],[139.6855102892684,36.12549551010966],[139.67899905825993,36.140688381563564],[139.67403812086252,36.15221222591353],[139.6537292832798,36.20313935040073],[139.66778527227245,36.2011498072178],[139.67667361919217,36.20089142479941],[139.70364871561577,36.204689643112516],[139.74617841959832,36.218203029746036],[139.78199018789678,36.23432607718664],[139.79180870990405,36.24158661612941],[139.80638146373354,36.265952053544396],[139.8172335154144,36.28000804343651],[139.84038455600245,36.299955145813385],[139.85867801288003,36.305200304050715],[139.87449100195818,36.30439931927319],[139.88337934797852,36.30512278878567],[139.89485151638428,36.309308579827075],[139.90735721176588,36.32656850793029],[139.95350426690834,36.362121893810354],[139.9652864927774,36.366540228848436],[139.97608686671555,36.36821971276953],[139.99438032359302,36.364860744927455],[140.00285525936272,36.364369819411564],[140.01417239813765,36.364860744927455],[140.03928714438626,36.37338735574184],[140.05484175014658,36.38333506895819],[140.0864677274036,36.39160329825356],[140.1142696470265,36.39602163329175],[140.14419030194085,36.39095734310695],[140.15721276305857,36.392533474240366],[140.17028690012035,36.40160268831343],[140.1811389509018,36.41488353095016],[140.2003625837662,36.459273586906534],[140.2104394881918,36.47622345574791],[140.2300248562621,36.49963288055301],[140.23539920480937,36.5168669702345],[140.23762129108968,36.54133576133631],[140.22981814978777,36.62355296539701],[140.22165327417918,36.66047577593625],[140.2210331556555,36.677193102579594],[140.22795779781404,36.68941457802035],[140.24227217012447,36.700318304745934],[140.25932539265278,36.708999946090614],[140.26676679874873,36.718637600045156],[140.25482954324883,36.74385569908125],[140.2506437522074,36.761296495237076],[140.2513155457758,36.80943309176374],[140.2464579611659,36.84191172884317],[140.2467680204278,36.85986928983587],[140.24537275644752,36.87635407248246],[140.23994673105676,36.89624949801589],[140.23731123272722,36.924826564894005],[140.2860421075572,36.90586131444809],[140.3055757996833,36.89384654458232],[140.32821007723334,36.87426117651205],[140.3544617050436,36.85666535072531],[140.37864627530536,36.83594310199254],[140.40691328292172,36.815763454719615],[140.44985639895356,36.794576117094124],[140.46928673739296,36.79222484050399],[140.48122399289286,36.797108263535534],[140.49672692270929,36.81274038276251],[140.5064937678731,36.819509996189396],[140.51476199806774,36.82953522377153],[140.53243533732086,36.8456065943688],[140.56468143130292,36.86317658083453],[140.5698490742754,36.87139313418575],[140.5668518407398,36.8829944929015],[140.56147749219252,36.8973088652119],[140.55972049390567,36.90823843035923],[140.56147749219252,36.91790192273558],[140.57000410390617,36.92606679924336],[140.5757401876595,36.92673859281177],[140.583026565024,36.92247528740458],[140.5879358255778,36.914723822946],[140.59868452357188,36.90513784403623],[140.61439415896325,36.896146145228414],[140.68777469294764,36.87351186857785],[140.69816165573587,36.868602607124586],[140.797286048648,36.84648905231134]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-14\",\"name\":\"Kanagawa\",\"name_alt\":null,\"name_local\":\"神奈川県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kanto\",\"postal\":\"KN\",\"region_code\":\"JPN-KNT\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[139.46748742080774,35.488919379525726],[139.4705880071308,35.49003042356526],[139.47255171099272,35.493544420138875],[139.47399865091705,35.49873790153299],[139.47477379817224,35.50809133644597],[139.4718282405808,35.541810207874846],[139.47596235567812,35.54997508438262],[139.48547081932287,35.549639186699096],[139.4892948751583,35.55467763936156],[139.48981163999508,35.558785916037124],[139.48485070169858,35.56581391008385],[139.45958092671833,35.58570933741595],[139.45322472624002,35.59227224436859],[139.45446495969009,35.59731069613167],[139.46206139541704,35.59875763605609],[139.47988976520054,35.59069611323508],[139.48779625928995,35.59178131795352],[139.49167199106944,35.59658722571993],[139.49559939969254,35.60289175115338],[139.5028341011129,35.60751679086715],[139.51373782783844,35.61139252444542],[139.52738040568113,35.614028021875754],[139.5394210148679,35.612606920373054],[139.6548661639423,35.56413442616284],[139.66251427561332,35.55925100403067],[139.66799197784752,35.555091051410955],[139.69775760313092,35.538089504826786],[139.78452233343893,35.51178620017305],[139.78662138357868,35.51152965048249],[139.77857506600003,35.50185781500002],[139.74455011300006,35.48430496600014],[139.71407684000013,35.46473072500007],[139.68822173300015,35.46110650400004],[139.66689706,35.46793427800009],[139.6516219410001,35.46458567900004],[139.64230183700016,35.45426118200004],[139.66221936600004,35.446480587000025],[139.68054456100003,35.43838609900007],[139.68739709600004,35.43217553000008],[139.68204337700007,35.41881092200009],[139.68685957100007,35.40070221600013],[139.67457116000008,35.4008242860001],[139.6499959250001,35.410550808000096],[139.6357919080001,35.40163242700007],[139.64214195600005,35.388480564000076],[139.6572384790001,35.37702195400004],[139.654633009,35.3394229190001],[139.64966881600003,35.326646226000065],[139.65023847700002,35.310370184000135],[139.64835814200006,35.29789050700006],[139.6575045670001,35.286647050000056],[139.66438993500003,35.298780810000096],[139.67689930500006,35.29153718100008],[139.69418379000007,35.26874420800007],[139.73422804800015,35.26520292300006],[139.745453321,35.24896881700005],[139.725922071,35.233303127000056],[139.72934004,35.22931549700007],[139.73096764400015,35.225897528000075],[139.73226972700002,35.22256094000005],[139.72802465700016,35.20708410000006],[139.68879598000007,35.20913368600009],[139.666456229,35.193893224000135],[139.660589416,35.178232492000035],[139.6705835300001,35.159979559000135],[139.68034915500004,35.149603583000044],[139.68506920700014,35.14044830900005],[139.6750594410001,35.136460679000066],[139.66431725400008,35.134833075000046],[139.64151397500007,35.13963386500009],[139.625336134,35.13178131700005],[139.6162849360001,35.13400170800011],[139.6114520120001,35.141972689000056],[139.61606961400003,35.15262239800006],[139.6143019980001,35.1740426960001],[139.60179274300003,35.20183531900011],[139.62395895200007,35.211617096000055],[139.6199183290001,35.21974007600008],[139.604322561,35.224233507000065],[139.59759518300012,35.23868757400007],[139.56949795300005,35.268857317000084],[139.56625410200007,35.27606842700007],[139.56068062000014,35.28692774600009],[139.54851321700005,35.28790924700013],[139.5441777970001,35.30789279800004],[139.5266033730001,35.30203700900003],[139.50667053000012,35.30417269500009],[139.48666425900007,35.30052317900001],[139.46691429100014,35.313318584000086],[139.41485572000016,35.31881465900008],[139.3300220760001,35.30447567700011],[139.2820504240001,35.29619758400014],[139.23336693300007,35.285324419000034],[139.18353782100013,35.26326671600009],[139.14595743700005,35.23522698500008],[139.14242597700002,35.18402741100007],[139.15805097700002,35.136460679000066],[139.12077055300006,35.15025745000014],[139.09443084024332,35.11747362336345],[139.0728861840501,35.124833075086315],[139.05190555199954,35.12870880776529],[139.0287545114116,35.13555593555796],[139.01934939965497,35.14116282810207],[139.01237308155243,35.146795559067726],[139.0026062363886,35.15780263948028],[138.98389936656253,35.18299490009457],[138.97356408061773,35.20495738407598],[138.96860314322043,35.23025299747778],[138.97061852392574,35.24976085118237],[138.9771297549342,35.261672268260554],[138.99340783110642,35.27686513881508],[138.99805871014138,35.284771632904466],[139.00028079642152,35.29055939440059],[139.00028079642152,35.301411445182055],[138.99867882686635,35.312961127054365],[138.9936145366815,35.33370921510823],[138.99020389199603,35.342390855553504],[138.98834354092162,35.34848867451271],[138.98679324820978,35.35618846302714],[138.98751671772237,35.37381012633631],[138.98658654173556,35.38114818054413],[138.97490766775456,35.38670339624474],[138.96457238181,35.38840871768815],[138.90033857626418,35.3809414740698],[138.90607466001745,35.40083690050261],[138.9332564638147,35.437062079051714],[138.9519633318422,35.44969696744113],[138.97015343593233,35.457887682370625],[138.99092736240777,35.46478648520815],[139.08048261887768,35.50493907327956],[139.09102461219607,35.51400828735254],[139.09924116374862,35.5256613229117],[139.10626915869472,35.5392780623327],[139.1165010927512,35.58477916142914],[139.1175862983689,35.59787913691261],[139.11371056479072,35.63079702446316],[139.12177208941034,35.65399974189455],[139.14735192275293,35.650020657327445],[139.1827502790021,35.63144297871048],[139.20383426294092,35.623717353573],[139.24527876220515,35.59167796516593],[139.26636274614378,35.582143663099515],[139.2977303409824,35.575296536206224],[139.34196536730786,35.57322947775816],[139.37726036987016,35.56676992449238],[139.39906782422065,35.5563054473385],[139.4172062523666,35.53984650311364],[139.4260429224429,35.52790924671436],[139.45818566273806,35.49297597935794],[139.46314660103474,35.4898237170909],[139.46748742080774,35.488919379525726]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-11\",\"name\":\"Saitama\",\"name_alt\":null,\"name_local\":\"埼玉県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kanto\",\"postal\":\"ST\",\"region_code\":\"JPN-KNT\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[139.4730684758295,36.178076280096235],[139.51420291583196,36.1928299019784],[139.56308882119208,36.19096955000467],[139.61001102089187,36.17944570655408],[139.67403812086252,36.15221222591353],[139.67899905825993,36.140688381563564],[139.6855102892684,36.12549551010966],[139.7109350929801,36.098287868790095],[139.7240092309412,36.089244493138835],[139.7334660177425,36.084903673365886],[139.75284468023779,36.07955516234088],[139.78591759741914,36.03679291436154],[139.79645958893883,36.01891286953327],[139.83056603399518,35.93791006140023],[139.8386275577156,35.922898057998964],[139.85785118968062,35.900754705964914],[139.86653283102532,35.88881745046493],[139.8718555027292,35.869361274503234],[139.87480106032064,35.85037018473622],[139.8639490086398,35.778126533212884],[139.80452111175993,35.790348009552986],[139.75780561673588,35.795283108528594],[139.7401322783819,35.79140737584973],[139.73067548978207,35.78716990796488],[139.72132205576816,35.778126533212884],[139.71470747287157,35.77642120997089],[139.70612918521368,35.77564606451426],[139.64127526294294,35.77711884196094],[139.6257723331267,35.77301056618467],[139.61507531107668,35.76748118710711],[139.60360314267086,35.75903209155777],[139.53559695903243,35.75156484614067],[139.5287756696614,35.75926463555447],[139.5262952009628,35.76732615837548],[139.52288455627718,35.77479340289324],[139.51668338363123,35.77755809243203],[139.50738162736013,35.777661445219536],[139.49280887263131,35.77383738938397],[139.46717736244528,35.76138336814793],[139.45260460771647,35.75637075480654],[139.43431115083888,35.75223663970908],[139.41147016771401,35.751978258190064],[139.38449507129042,35.754923813982856],[139.2895137876311,35.80210439700028],[139.2715303900154,35.80897736321475],[139.0601737812949,35.85305735900991],[139.04560102836467,35.85881928208423],[139.01361331590186,35.877293606115046],[139.00394982262623,35.87886973724858],[138.99371789036832,35.876544298180875],[138.95289350782912,35.8578891060973],[138.92281782418326,35.835384019756674],[138.86897098232487,35.83776113476853],[138.8306270693837,35.84546092418233],[138.79760582904566,35.856002915701964],[138.7156986842469,35.89646556213603],[138.6995239608621,35.94982147937779],[138.700919223943,35.96953603775788],[138.71642215375937,35.98105988210784],[138.73182173078823,35.999560045459674],[138.73213178915074,36.000696926122174],[138.73228681878163,36.00188548452749],[138.73569746256788,36.01209158016232],[138.74143354722042,36.019868883042534],[138.75073530529005,36.02718109792934],[138.7642745303453,36.03216787374831],[138.7827230168537,36.0351651072838],[138.85827396117435,36.07273387296985],[138.95702762292638,36.1111294618552],[139.0182125180932,36.128906154795175],[139.0337671247529,36.13867300085829],[139.04074344285556,36.14975759473725],[139.04229373556734,36.16440786383191],[139.04229373556734,36.1757250035061],[139.04436079311614,36.18665456775415],[139.0496317888759,36.19649892818293],[139.0896293482158,36.249028022225446],[139.10823286435522,36.26874258150494],[139.11185021371642,36.2708613140984],[139.1380501646833,36.270602932579266],[139.17763431197386,36.258381456239164],[139.19277550658433,36.25760630988327],[139.1987182959126,36.254428209194515],[139.23241132981906,36.242180895331984],[139.30429324793505,36.228228258227546],[139.378603957007,36.222492174474326],[139.41250369738776,36.21174347648025],[139.44423302563365,36.19631806102967],[139.4730684758295,36.178076280096235]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-09\",\"name\":\"Tochigi\",\"name_alt\":\"Totigi\",\"name_local\":\"栃木県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kanto\",\"postal\":\"TC\",\"region_code\":\"JPN-KNT\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[140.23731123272722,36.924826564894005],[140.23994673105676,36.89624949801589],[140.24537275644752,36.87635407248246],[140.2467680204278,36.85986928983587],[140.2464579611659,36.84191172884317],[140.2513155457758,36.80943309176374],[140.2506437522074,36.761296495237076],[140.25482954324883,36.74385569908125],[140.26676679874873,36.718637600045156],[140.25932539265278,36.708999946090614],[140.24227217012447,36.700318304745934],[140.22795779781404,36.68941457802035],[140.2210331556555,36.677193102579594],[140.22165327417918,36.66047577593625],[140.22981814978777,36.62355296539701],[140.23762129108968,36.54133576133631],[140.23539920480937,36.5168669702345],[140.2300248562621,36.49963288055301],[140.2104394881918,36.47622345574791],[140.2003625837662,36.459273586906534],[140.1811389509018,36.41488353095016],[140.17028690012035,36.40160268831343],[140.15721276305857,36.392533474240366],[140.14419030194085,36.39095734310695],[140.1142696470265,36.39602163329175],[140.0864677274036,36.39160329825356],[140.05484175014658,36.38333506895819],[140.03928714438626,36.37338735574184],[140.01417239813765,36.364860744927455],[140.00285525936272,36.364369819411564],[139.99438032359302,36.364860744927455],[139.97608686671555,36.36821971276953],[139.9652864927774,36.366540228848436],[139.95350426690834,36.362121893810354],[139.90735721176588,36.32656850793029],[139.89485151638428,36.309308579827075],[139.88337934797852,36.30512278878567],[139.87449100195818,36.30439931927319],[139.85867801288003,36.305200304050715],[139.84038455600245,36.299955145813385],[139.8172335154144,36.28000804343651],[139.80638146373354,36.265952053544396],[139.79180870990405,36.24158661612941],[139.78199018789678,36.23432607718664],[139.74617841959832,36.218203029746036],[139.70364871561577,36.204689643112516],[139.67667361919217,36.20089142479941],[139.66778527227245,36.2011498072178],[139.6537292832798,36.20313935040073],[139.63848473498257,36.22574778952884],[139.60546349464448,36.25241282848927],[139.5886686554341,36.2601126170037],[139.5028341011129,36.27437531247068],[139.4823702329998,36.27515045792728],[139.46826256716372,36.27300588601284],[139.4548783708401,36.268845934292415],[139.44051232258568,36.268484199086544],[139.42356245284492,36.279103704972044],[139.40077314656344,36.30036855696332],[139.3913163597622,36.31098806374817],[139.3835648953036,36.32346792340596],[139.35374759317693,36.348065903918325],[139.34351566001965,36.35855622039318],[139.3383996929914,36.368555610453086],[139.33653934101773,36.37912344039444],[139.33777957626643,36.38883860871482],[139.34088016169005,36.39571157492921],[139.34506595273155,36.402016100362744],[139.356073033144,36.41514191336856],[139.36320437997813,36.42227326110205],[139.3783972523313,36.443848172355246],[139.3835648953036,36.45906688133147],[139.39079959492528,36.47508657508541],[139.39302168210486,36.48748891947804],[139.39705244441478,36.498444322147805],[139.40309858563114,36.50810781542333],[139.41178022697585,36.51813304300546],[139.4164311060107,36.52588450836333],[139.41891157381013,36.532705796835074],[139.42712812716127,36.54358368603826],[139.43865197241058,36.55151601854945],[139.44454308489537,36.56151540770986],[139.44764367121843,36.57270335527568],[139.4458866720322,36.58593252196826],[139.43586144355078,36.595105088828745],[139.4234074241133,36.60045359985375],[139.36366946797148,36.60662893317871],[139.35374759317693,36.60970368108005],[139.3413452496835,36.61536225046743],[139.31204471239346,36.63215709147664],[139.30594689343434,36.64474030212324],[139.30718712778372,36.658925483224465],[139.31276818280537,36.67634044005919],[139.32568729113552,36.70465912541829],[139.3304415220586,36.71930939451295],[139.3288912293467,36.75656810183642],[139.33261193329406,36.76907379811726],[139.34212039603938,36.78054596652311],[139.35204227083415,36.79026113484349],[139.35932864819853,36.800363877690785],[139.3666150246636,36.80827037178025],[139.36894046373146,36.81429067637359],[139.3682686710623,36.823049832084024],[139.3607239121788,36.82953522377153],[139.35064700685388,36.83656321871763],[139.33917483934732,36.84870717979251],[139.3363843122861,36.86007599541085],[139.34026004496508,36.87175486939171],[139.35958702971755,36.90203725771326],[139.40790449429687,36.925136624155925],[139.4458866720322,36.956142482889305],[139.46299157140385,36.96270538984186],[139.509552036797,36.97490102776024],[139.67853397026653,37.05321666072035],[139.74741865484702,37.07701365825366],[139.7644202014311,37.07995921494583],[139.79118859317896,37.08122528681753],[139.82234948064405,37.105539049187826],[139.86131351120963,37.130447088961915],[139.88585981667717,37.139180406250745],[139.9168656763099,37.14357290286722],[139.95815514504392,37.1393871118257],[140.09809492364178,37.105823269128564],[140.1134428238272,37.09802012782663],[140.16356896443614,37.0655156523255],[140.17628136629196,37.05892690695114],[140.1850146826814,37.05293244077953],[140.19090579606564,37.04342397713481],[140.19416141201953,37.03096995679812],[140.1988122910543,37.02019542128161],[140.20718387313724,37.01213389756131],[140.22547733001483,37.00414988910606],[140.2291980330628,37.00203115471405],[140.23043826741213,37.00104930278309],[140.2312650897121,36.999989936486344],[140.23452070476662,36.990248927945686],[140.2410319357752,36.94306834582753],[140.23731123272722,36.924826564894005]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-13\",\"name\":\"Tokyo\",\"name_alt\":\"Edo|Yedo|Tokio|T¢quio\",\"name_local\":\"東京都\",\"type\":\"To\",\"type_en\":\"Metropolis\",\"region\":\"Kanto\",\"postal\":\"TK\",\"region_code\":\"JPN-KNT\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[141.46794681100008,24.213771877000013],[141.46314537900017,24.212103583],[141.4557397800002,24.215887762000094],[141.45297285200004,24.222398179000024],[141.46021569100006,24.227362372000144],[141.46998131600017,24.226263739],[141.47071373800011,24.219183661000145],[141.46794681100008,24.213771877000013]]],[[[153.98365319100006,24.282945054],[153.97828209700018,24.282416083000115],[153.97201582100004,24.284816799000154],[153.97299238399998,24.289699611000046],[153.9812117850001,24.295355536000116],[153.98560631600017,24.291083075000117],[153.98365319100006,24.282945054]]],[[[153.96623782600008,24.30866120000003],[153.95346113400004,24.29250722900018],[153.94068444100017,24.311468817],[153.95370527400004,24.315822658000073],[153.96623782600008,24.30866120000003]]],[[[141.30258222700004,24.734361070000105],[141.28956139400023,24.726955471000124],[141.28980553500017,24.760239976000136],[141.29265384200002,24.773871161000088],[141.29948978000007,24.785874742000104],[141.31169681100002,24.79181549700003],[141.33025149800002,24.794094143],[141.34799238400004,24.793117580000043],[141.35775800900015,24.789618231000034],[141.36548912900017,24.777736721000096],[141.35726972700016,24.773423570000162],[141.34262129000015,24.772121486000074],[141.33057701900023,24.769110419000086],[141.32032311300023,24.758612372000115],[141.30258222700004,24.734361070000105]]],[[[141.29566491000006,25.424505927000027],[141.28744550900004,25.417710679],[141.27466881600017,25.427069403000118],[141.27051842500023,25.44037506700012],[141.27475019600016,25.451117255000057],[141.28825931100008,25.452541408000016],[141.2975366550002,25.44529857000019],[141.29948978000007,25.434800523000106],[141.29566491000006,25.424505927000027]]],[[[142.1432397800002,26.702215887000122],[142.1534936860002,26.70066966400016],[142.16586347700016,26.701320705000015],[142.16586347700016,26.694525458],[142.15430748800011,26.686468817000062],[142.15560957100004,26.67914459800015],[142.16578209700018,26.674383856000148],[142.18018639400023,26.67401764500012],[142.18018639400023,26.666571356000148],[142.175547722,26.663234768000123],[142.17481530000006,26.66152578300013],[142.175059441,26.6590843770001],[142.17269941500015,26.653509833],[142.18181399800008,26.65717194200012],[142.18620853000002,26.65790436400006],[142.18913821700008,26.654527085000055],[142.19320722700004,26.646063544000114],[142.18140709700006,26.644232489000117],[142.175547722,26.638657945000105],[142.175059441,26.63007233300003],[142.18018639400023,26.618801174000012],[142.159922722,26.632025458000058],[142.13900800900015,26.66250234600001],[142.11133873800006,26.721177476000108],[142.11768639400012,26.721747137000094],[142.12240644599999,26.720445054],[142.13184655000012,26.714992580000157],[142.13575280000012,26.70669179900007],[142.1432397800002,26.702215887000122]]],[[[142.24105879000015,27.084295966000084],[142.23462975400005,27.050034898000106],[142.21851647200006,27.040350653000147],[142.1985783210001,27.05109284100017],[142.18018639400023,27.078070380000142],[142.21436608200023,27.063788153000033],[142.21070397200018,27.072821356000148],[142.20557701900023,27.08095937700007],[142.19955488400015,27.087469794000143],[142.19320722700004,27.091742255000085],[142.20655358200023,27.09711334800012],[142.2208764980002,27.09833405200014],[142.23324629000015,27.094305731000176],[142.24105879000015,27.084295966000084]]],[[[142.22681725400005,27.10545482000019],[142.2213647800002,27.103786526000178],[142.21379642000008,27.10382721600017],[142.20476321700002,27.10866933800007],[142.2006942070001,27.113633531000104],[142.1946720710001,27.11513906500015],[142.18930097700004,27.11701080900012],[142.18832441500015,27.12067291900017],[142.1885685560001,27.126613674000012],[142.19052168100004,27.133693752000156],[142.19312584700006,27.13556549700003],[142.19621829500014,27.131577867000047],[142.220550977,27.120591539000188],[142.22698001400008,27.113836981000148],[142.2296655610002,27.108303127000127],[142.22681725400005,27.10545482000019]]],[[[142.1959741550002,27.15241120000003],[142.18921959700006,27.14549388200014],[142.1842554050002,27.158351955000157],[142.18531334700006,27.174139716000113],[142.19092858200023,27.185207424000126],[142.20045006600006,27.18329498900009],[142.20411217500012,27.173895575000145],[142.20183353000002,27.16274648600013],[142.1959741550002,27.15241120000003]]],[[[140.87818444100006,27.232611395000063],[140.86996504000015,27.218247789000188],[140.867930535,27.233954169000143],[140.87419681100008,27.238226630000085],[140.87818444100006,27.232611395000063]]],[[[142.21184329500002,27.494655666000128],[142.21306399800014,27.484605210000055],[142.208832227,27.497788804000024],[142.21184329500002,27.494655666000128]]],[[[142.1928817070001,27.616197007],[142.19532311300023,27.599107164000046],[142.17945397200012,27.612494208000058],[142.18311608200017,27.61945221600014],[142.1928817070001,27.616197007]]],[[[142.14291425900015,27.67194245000009],[142.1388452480002,27.667629299000097],[142.12517337300008,27.677435614000117],[142.12533613400004,27.680894273000135],[142.13331139400006,27.681911526000093],[142.141856316,27.679673570000105],[142.14291425900015,27.67194245000009]]],[[[142.11166425899998,27.725856838000126],[142.107188347,27.716742255000113],[142.1051538420002,27.72703685100005],[142.10881595100014,27.729681708000143],[142.11166425899998,27.725856838000126]]],[[[142.08513431100002,27.72606028900016],[142.08057701900006,27.71694570500007],[142.07862389400023,27.727240302],[142.0822860040001,27.7298851580001],[142.08513431100002,27.72606028900016]]],[[[140.31861412900005,30.47553131700009],[140.30648847700016,30.47361888200011],[140.30160566499998,30.48069896],[140.30144290500002,30.486273505],[140.30762780000006,30.491156317000062],[140.31714928500017,30.491156317000062],[140.32593834700006,30.485174872000144],[140.31861412900005,30.47553131700009]]],[[[140.04566491,31.443304755000113],[140.04346764400012,31.443101304000052],[140.0444442070001,31.44505442900011],[140.04566491,31.443304755000113]]],[[[140.0431421230002,31.444566148000106],[140.04151451900006,31.443101304000052],[140.04127037900005,31.444159247000115],[140.04135175900004,31.446478583000086],[140.0431421230002,31.444566148000106]]],[[[139.75709069100012,32.470363674000126],[139.77377363400004,32.44098541900003],[139.75635826900017,32.452297268],[139.75709069100012,32.470363674000126]]],[[[139.6905216810002,33.13275788000014],[139.6979272800002,33.116278387000065],[139.6858830090001,33.116888739],[139.67986087300008,33.123114325000145],[139.67562910200016,33.13556549700009],[139.68100019599999,33.137396552000055],[139.6905216810002,33.13275788000014]]],[[[139.8452254570001,33.04677969000012],[139.8357853520001,33.03978099200005],[139.82097415500013,33.040269273000135],[139.8029077480002,33.047430731000176],[139.78760826900023,33.058417059000035],[139.77637780000023,33.07998281500015],[139.74341881600006,33.118597723],[139.7397567070001,33.133246161000116],[139.75082441500015,33.14524974200005],[139.76775149800002,33.14988841400016],[139.79102623800023,33.135321356000034],[139.81316165500002,33.123765367000104],[139.83887780000018,33.116685289000046],[139.86231530000023,33.107001044000086],[139.87745201900023,33.08759186400009],[139.869395379,33.075506903000175],[139.8452254570001,33.04677969000012]]],[[[139.29908287900017,33.65070221600017],[139.29908287900017,33.65009186400003],[139.29615319100017,33.651068427],[139.29908287900017,33.65070221600017]]],[[[139.62289472700016,33.856756903000175],[139.61019941499998,33.85285065300009],[139.59424889400006,33.85472239800005],[139.58130944100017,33.86253489800005],[139.57976321700002,33.88031647300012],[139.5924585300002,33.89581940300006],[139.60962975400005,33.896389065],[139.61898847700016,33.89012278900016],[139.62281334700018,33.885565497000115],[139.6295679050002,33.87173086100002],[139.628754102,33.86570872600011],[139.62289472700016,33.856756903000175]]],[[[139.53451582099999,34.037543036000145],[139.50611412900017,34.03587474200013],[139.48707116000017,34.06077708500008],[139.49439537900017,34.09275950700008],[139.52344811300017,34.11237213700004],[139.55632571700008,34.11347077000006],[139.57504316500004,34.09003327],[139.56128991,34.05809153900019],[139.53451582099999,34.037543036000145]]],[[[139.14234459700006,34.1885033220001],[139.12956790500013,34.18569570500016],[139.12126712300002,34.1889509140001],[139.1222436860002,34.19542064000011],[139.12891686300006,34.204738674000154],[139.13078860800013,34.21181875200001],[139.13078860800013,34.216823635000154],[139.13021894600016,34.21922435100002],[139.13575280000006,34.236558335],[139.14682050900004,34.24095286700016],[139.16114342500012,34.237127997000144],[139.16911868600008,34.22890859600015],[139.17367597700004,34.211615302000055],[139.16716556100013,34.20661041900003],[139.15699303500017,34.20433177300005],[139.14234459700006,34.1885033220001]]],[[[139.2238061860002,34.32436758000007],[139.2124943370002,34.316636460000055],[139.2046818370002,34.31972890800013],[139.1976017590001,34.316636460000055],[139.1975203790001,34.32326894700013],[139.20541425900015,34.32908763200008],[139.20720462300008,34.332790432000124],[139.20753014400006,34.3354352890001],[139.21029707100004,34.336737372000115],[139.21623782600008,34.334824937000135],[139.2238061860002,34.32436758000007]]],[[[139.283457879,34.33685944200009],[139.26783287900005,34.32269928600006],[139.25367272200018,34.34137604400003],[139.25464928500017,34.35346100500014],[139.26539147200018,34.388617255],[139.27035566500004,34.39598216400019],[139.279551629,34.402777411000116],[139.29151451900023,34.41453685100002],[139.30095462300008,34.41791413000011],[139.30250084700018,34.39972565300003],[139.2963973320001,34.367865302],[139.283457879,34.33685944200009]]],[[[139.29721113400004,34.474432684000035],[139.2915958990002,34.46979401200012],[139.2886662120001,34.473822333000086],[139.2924910820001,34.4758568380001],[139.29721113400004,34.474432684000035]]],[[[139.29045657599997,34.52338288000011],[139.27678470100014,34.51276276200015],[139.26433353000002,34.525864976000136],[139.27938886800015,34.53396230700015],[139.29045657599997,34.52338288000011]]],[[[139.4537866550002,34.678941148000106],[139.44271894600016,34.67670319200012],[139.421641472,34.677191473000036],[139.35935284600006,34.70229865300003],[139.35119548300017,34.720033634000075],[139.35201437000015,34.76733961600017],[139.35646512200017,34.79722949200014],[139.40064537900017,34.78864166900003],[139.418223504,34.78082916900003],[139.4377547540001,34.771918036],[139.45004316500015,34.756781317],[139.4567163420002,34.737127997000144],[139.4585067070001,34.71503327000006],[139.45785566500015,34.68988678600009],[139.4537866550002,34.678941148000106]]],[[[139.1694442070001,35.04364655200011],[139.1714787120002,35.036607164000046],[139.1642358730002,35.038519598],[139.16277103000002,35.04092031500012],[139.1694442070001,35.04364655200011]]],[[[139.3844950712904,35.7549238139829],[139.411470167714,35.75197825819011],[139.4343111508389,35.752236639709125],[139.4526046077165,35.7563707548065],[139.46717736244537,35.761383368147975],[139.4928088726314,35.77383738938396],[139.5073816273601,35.77766144521958],[139.51668338363132,35.7775580924321],[139.5228845562772,35.77479340289328],[139.5262952009629,35.767326158375525],[139.52877566966143,35.759264635554516],[139.53559695903246,35.75156484614071],[139.60360314267083,35.75903209155784],[139.61507531107665,35.76748118710715],[139.62577233312678,35.77301056618471],[139.6412752629429,35.7771188419609],[139.70612918521377,35.77564606451422],[139.71470747287165,35.776421209970934],[139.72132205576818,35.77812653321293],[139.73067548978216,35.78716990796492],[139.74013227838194,35.79140737584977],[139.75780561673596,35.795283108528665],[139.80452111176,35.79034800955294],[139.86394900863982,35.77812653321293],[139.88596317036402,35.69774384180478],[139.88115726079903,35.673430081233235],[139.87697146975754,35.66082103126554],[139.8642428568613,35.63206757687867],[139.85027103000002,35.63751862200017],[139.84327233200023,35.62889232000008],[139.83727706700003,35.61454952400014],[139.8238875110002,35.62295089600018],[139.82035734500008,35.638049330000015],[139.80682518800015,35.638192534000055],[139.8003035820001,35.618638414000046],[139.78914674800015,35.603243412],[139.77485460200015,35.61763727099999],[139.77562521600012,35.64110350500012],[139.76527245200006,35.65351048500004],[139.75816223600012,35.63156424200018],[139.76880944100017,35.60097890800013],[139.784804085,35.58046609600011],[139.78500472800008,35.56816522000018],[139.7990563450002,35.540010128000134],[139.78687584700012,35.52716705900012],[139.78809655000012,35.51325104400003],[139.78663170700017,35.511542059000035],[139.78662138357865,35.511529650482444],[139.784522333439,35.51178620017309],[139.697757603131,35.53808950482683],[139.6679919778476,35.55509105141091],[139.6625142756134,35.55925100403071],[139.65486616394227,35.56413442616291],[139.53942101486788,35.612606920373096],[139.52738040568116,35.61402802187571],[139.5137378278384,35.61139252444546],[139.502834101113,35.60751679086722],[139.49559939969257,35.60289175115342],[139.49167199106947,35.59658722571989],[139.48779625929004,35.591781317953476],[139.47988976520062,35.590696113235126],[139.46206139541712,35.598757636056135],[139.4544649596901,35.59731069613166],[139.45322472624005,35.592272244368544],[139.45958092671836,35.585709337415906],[139.4848507016986,35.56581391008389],[139.4898116399951,35.558785916037166],[139.48929487515832,35.554677639361515],[139.48547081932296,35.54963918669905],[139.47596235567815,35.549975084382666],[139.4718282405809,35.5418102078748],[139.4747737981722,35.508091336446014],[139.47399865091714,35.49873790153303],[139.4725517109928,35.493544420138946],[139.47058800713077,35.4900304235653],[139.46748742080771,35.48891937952568],[139.46314660103477,35.48982371709097],[139.4581856627381,35.49297597935792],[139.42604292244292,35.52790924671443],[139.4172062523667,35.53984650311368],[139.39906782422068,35.55630544733846],[139.37726036987024,35.56676992449242],[139.34196536730795,35.57322947775812],[139.29773034098244,35.57529653620618],[139.26636274614387,35.58214366309956],[139.24527876220523,35.591677965166],[139.20383426294094,35.62371735357296],[139.18275027900208,35.63144297871044],[139.14735192275296,35.65002065732743],[139.1217720894103,35.653999741894594],[139.03578250455897,35.69634857782471],[138.99268436069477,35.732547918851466],[138.9805403987203,35.746913967105925],[138.9709285831875,35.76164175146563],[138.94658898329484,35.81662547488578],[138.92281782418334,35.835384019756745],[138.95289350782915,35.857889106097375],[138.99371789036834,35.87654429818092],[139.00394982262625,35.87886973724862],[139.01361331590195,35.87729360611509],[139.0456010283647,35.85881928208427],[139.06017378129494,35.85305735900995],[139.27153039001544,35.808977363214794],[139.28951378763114,35.802104397000235],[139.3844950712904,35.7549238139829]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-19\",\"name\":\"Yamanashi\",\"name_alt\":\"Yamanasi\",\"name_local\":\"山梨県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chubu\",\"postal\":\"YN\",\"region_code\":\"JPN-KNT\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[138.466308221847,35.88819733284066],[138.51891483025526,35.89954031003728],[138.53193729137286,35.89954031003728],[138.55679365520302,35.89563873983596],[138.57286502490092,35.894579373539244],[138.58526736929366,35.89196971273205],[138.59467247925153,35.88623362897883],[138.5999434750113,35.87796540058275],[138.60428429568373,35.866803290539366],[138.612965936129,35.85817332603813],[138.62299116371122,35.85398753499672],[138.63446333211704,35.85326406548424],[138.64552208937295,35.85419424147105],[138.66278201657684,35.8594393988092],[138.7156986842469,35.89646556213603],[138.79760582904566,35.856002915701964],[138.8306270693837,35.84546092418233],[138.86897098232487,35.83776113476853],[138.92281782418326,35.835384019756674],[138.94658898329487,35.81662547488574],[138.97092858318751,35.76164175146559],[138.98054039872034,35.74691396710588],[138.99268436069468,35.732547918851424],[139.03578250455894,35.69634857782464],[139.12177208941034,35.65399974189455],[139.11371056479072,35.63079702446316],[139.1175862983689,35.59787913691261],[139.1165010927512,35.58477916142914],[139.10626915869472,35.5392780623327],[139.09924116374862,35.5256613229117],[139.09102461219607,35.51400828735254],[139.08048261887768,35.50493907327956],[138.99092736240777,35.46478648520815],[138.97015343593233,35.457887682370625],[138.9519633318422,35.44969696744113],[138.9332564638147,35.437062079051714],[138.90607466001745,35.40083690050261],[138.90033857626418,35.3809414740698],[138.82969689339694,35.36466339789746],[138.69719852089497,35.350762436737085],[138.67239383390825,35.3561367861837],[138.65730431524184,35.365025133103416],[138.6483126164341,35.373913479123786],[138.634670037692,35.38112234122303],[138.61265587776654,35.383628648343375],[138.5932255393272,35.39210358321378],[138.5564835959412,35.41747671188071],[138.5434094588794,35.41703746230897],[138.53395267117878,35.4096735705788],[138.52769982258877,35.39685781503616],[138.5232556500282,35.38135488521982],[138.51627933192566,35.32973013054104],[138.5056856626632,35.2942284215045],[138.5040320171638,35.25676300680729],[138.50728763221844,35.231260687830456],[138.51007816017886,35.21919424112136],[138.51054324817233,35.20746369119641],[138.50899295456117,35.19940216747602],[138.5071326025875,35.19428620044778],[138.50341190043883,35.188446763906995],[138.49783084541724,35.18152212174854],[138.48739220578562,35.17216868683536],[138.47235436396272,35.16167837215917],[138.4579366397641,35.15728587554267],[138.44336388593462,35.15697581628086],[138.43044477760435,35.15930125624794],[138.4189726100979,35.16511485436703],[138.39830203730918,35.181418768961066],[138.38347090106117,35.19030711498132],[138.37494428934752,35.2037946640925],[138.36745120730734,35.21867747718383],[138.352568394216,35.27836375648221],[138.3429565777838,35.29205801026896],[138.32822879522274,35.29988698999259],[138.30461266484258,35.30190237069783],[138.2839937679979,35.29895681400578],[138.2622896673343,35.29376333351104],[138.2465800310436,35.29712230135314],[138.23588300989297,35.303685208305694],[138.23185224758316,35.313917141462895],[138.23138715958964,35.32368398752598],[138.23200727811337,35.33350250953316],[138.23045698540147,35.344225369105516],[138.22642622309178,35.355981757452156],[138.2265812518233,35.3675572777461],[138.2354179218995,35.393137111988096],[138.23789839059822,35.40843333622945],[138.23417768844962,35.43411652325892],[138.23479780517454,35.444141750841055],[138.24130903528373,35.46408885321793],[138.24332441598892,35.47478587526794],[138.24130903528373,35.51778066724404],[138.2369682146114,35.53516978655634],[138.22177534405688,35.57276439156337],[138.216504348297,35.602064927954046],[138.21371382123584,35.61178009717372],[138.19614383387085,35.627851466871704],[138.1820361680347,35.64782440946894],[138.17211429324004,35.67472199242596],[138.16854861892352,35.69880320900083],[138.17381961468345,35.713220934098814],[138.19753909695174,35.73668203394868],[138.20265506398,35.74709483515849],[138.19769412658263,35.75670665069134],[138.18637698780765,35.76556915918934],[138.17971072806762,35.777609768376095],[138.18017581606108,35.786730659292516],[138.216504348297,35.85039602315794],[138.22549604710485,35.85796662046317],[138.2354179218995,35.85853506124411],[138.257122024362,35.84809642251187],[138.2697310734302,35.849310818439506],[138.27887780186904,35.854891873461185],[138.28719770710848,35.86266917634137],[138.33825402190558,35.93884023738704],[138.34962283662458,35.95098419936127],[138.36026818273035,35.95780548783296],[138.3715336446619,35.95728872299617],[138.3986637716157,35.950906684096225],[138.4281710153801,35.939150295749585],[138.4368526549261,35.932742418427836],[138.44067671076152,35.92514598180162],[138.44150353396088,35.9181179877549],[138.4416585626925,35.907653509701646],[138.44429406102205,35.89775747332871],[138.45276899589237,35.890316067232675],[138.466308221847,35.88819733284066]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-05\",\"name\":\"Akita\",\"name_alt\":null,\"name_local\":\"秋田県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Tohoku\",\"postal\":\"AK\",\"region_code\":\"JPN-THO\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[140.9262614277227,40.24397797290513],[140.9070377948583,40.22948273434076],[140.88481692935778,40.21674449316379],[140.8705542338908,40.20162913697504],[140.85541303928034,40.180596828980526],[140.85644656805465,40.16829783827464],[140.8595471543777,40.1596161969299],[140.8618725934454,40.15015941012871],[140.86032229983425,40.14204621046426],[140.85691165604806,40.13256358524126],[140.85567142079935,40.11749990499655],[140.85598148006125,40.10408987115062],[140.85055545467048,40.0919975860198],[140.84156375496337,40.077166449771795],[140.83815311117712,40.066391913356114],[140.83784305191523,40.05383454023183],[140.84063357987577,40.03954600724242],[140.84249393095018,39.977456772711946],[140.8444576357113,39.96665639877372],[140.84528445891056,39.95815562638106],[140.84941857310872,39.938596095833134],[140.85505130407438,39.92133616772992],[140.8690039411789,39.89123464656163],[140.8705542338908,39.878315538231504],[140.8640430037815,39.87033152887696],[140.85396609935594,39.86666250357169],[140.84249393095018,39.866972561034856],[140.83034996987516,39.86854869216839],[140.81655236240158,39.868471177802604],[140.80477013563325,39.865551459532185],[140.79438317284507,39.86046133092566],[140.78585656113137,39.85033275055602],[140.7831693868577,39.84015249334291],[140.7827559757077,39.829145412930444],[140.7861666203932,39.81733734774036],[140.79371137927671,39.80904328092265],[140.81624230403906,39.794315497462236],[140.82647423719618,39.78434194582414],[140.824613886122,39.7750401877544],[140.81918785983186,39.76522166574733],[140.8041500180089,39.74594635693887],[140.7971736999063,39.734990953369845],[140.79019738180364,39.71977224439351],[140.80053266774837,39.69318471889966],[140.80864586831223,39.67713918672402],[140.81283165935355,39.66235972821892],[140.81376183534047,39.64393707923276],[140.81035118975558,39.63137970610859],[140.80616539871414,39.62161286094474],[140.79092085221555,39.599081936182415],[140.74813276581455,39.56014374403853],[140.73914106700659,39.546888739823515],[140.72932254499943,39.529267076514344],[140.7143363791206,39.49278351554676],[140.6759924661793,39.43550019058134],[140.65811242135112,39.39227285370919],[140.66043786041882,39.3807490102586],[140.66446862182943,39.36741648987905],[140.68451907789313,39.33434357449636],[140.69335574796935,39.30256256850768],[140.69940189008514,39.292072252032824],[140.70684329618115,39.28561269786766],[140.7173852886001,39.27364960394604],[140.72281131399086,39.266285712215875],[140.73169966001132,39.25918020290399],[140.75154340960057,39.24783722570737],[140.76053511020717,39.24086090850409],[140.7665812523229,39.2312490911726],[140.76999189700837,39.22003530518518],[140.76921674975318,39.2057984481398],[140.7741776880498,39.19579905897939],[140.78244591734517,39.18835765198398],[140.79061079295366,39.182053128349196],[140.79608849518786,39.174715074141375],[140.7940731135833,39.16779043198284],[140.7861666203932,39.16272614179795],[140.77495283440578,39.157300117306534],[140.7639457539933,39.14807587270329],[140.75293867358084,39.12756033044485],[140.7493730001638,39.11327179655615],[140.751336704925,39.09906077703323],[140.75851972860255,39.08640005112139],[140.77278242406953,39.075392970709004],[140.78585656113137,39.06784821182549],[140.7946932312076,39.0602001001545],[140.79841393425556,39.04867625580454],[140.79531334883188,39.03911611621575],[140.77464277604332,39.00392446554167],[140.75944990458936,38.95173126918269],[140.73428348239685,38.943463039887405],[140.7254984891639,38.936951808878916],[140.71040896959823,38.92811513880261],[140.695629511093,38.916798000926974],[140.6419893739104,38.889331977189016],[140.57760053873386,38.87202037224242],[140.55806684840627,38.871529445827235],[140.54406253535763,38.8736998561634],[140.53290042531432,38.881709703040286],[140.4793636418185,38.89220001951523],[140.44892622296675,38.91540273694673],[140.43326826441887,38.939742336839345],[140.4211759792879,38.963875230257656],[140.4050529318473,38.97565745702602],[140.34278283106283,39.0038469511758],[140.31968346641884,39.00718008149556],[140.2858354028815,39.00831696215806],[140.21069786971094,39.019995836138946],[140.1949365583754,39.026739610244846],[140.18454959468795,39.034336045971685],[140.1780383645788,39.04118317286506],[140.16511925714792,39.04526561111902],[140.15054650331842,39.04500722870063],[140.12620690342578,39.048469550229555],[140.10429609628773,39.05738373557091],[140.06858768077677,39.077149969895146],[140.03520470613233,39.105106920048286],[140.02414594887642,39.108362535102884],[140.01215701743246,39.109809475027305],[139.99489708932919,39.10603709603518],[139.9845101256417,39.10169627536277],[139.97365807486017,39.09937083629504],[139.96275434813464,39.0982339538339],[139.8856531111021,39.11510631010816],[139.88100223206732,39.11510631010816],[139.8808549493155,39.115106310035316],[139.891937696,39.133205471000025],[139.895843946,39.152289130000014],[139.89795983200008,39.19330475500004],[139.9035814390001,39.22493941100004],[139.91684255200005,39.26249903000007],[139.93181044000005,39.28711183400014],[139.96357962400015,39.29882013200006],[139.9953101900001,39.327328560000126],[140.02024245400003,39.40758142300007],[140.04857715000003,39.50466964600005],[140.05933387900006,39.69806625600009],[140.05929588100003,39.727492249000136],[140.03911708700002,39.76928182100005],[140.0390897680001,39.793282631000096],[140.02759850400008,39.82453034100004],[140.00456790500004,39.85089752800005],[139.96648196700005,39.8800723330001],[139.92139733200008,39.89850495000013],[139.87745201900015,39.89280833500004],[139.8642684250001,39.88222890800009],[139.85482832100007,39.87083567900004],[139.84376061300014,39.861761786000045],[139.82553144600007,39.85805898600009],[139.760101759,39.85805898600009],[139.75180097700002,39.86627838700008],[139.73812910200007,39.89191315300005],[139.73015384200005,39.90273672100005],[139.7086694670001,39.92426178600007],[139.704172093,39.945157201000114],[139.71943758300011,39.952141356000055],[139.70099004100013,39.96066114800004],[139.69773066900007,39.988566482000124],[139.700639313,40.005636112000076],[139.74345199600015,39.982463933000105],[139.80046634200005,39.9625918640001],[139.82211347700007,39.96108633000006],[139.86692463200012,39.9842972860001],[139.90419316100008,40.01769512100006],[139.97803795700008,40.125474351],[139.993500196,40.169623114000075],[139.98861094900013,40.19111380400008],[140.002841718,40.222439703000134],[140.01115209900001,40.23545842000004],[140.0198987270001,40.33321038300005],[140.02426296100015,40.35501606700009],[140.01173376100007,40.3678589080001],[139.99857651000008,40.37715986500007],[139.9634554810001,40.40392272000011],[139.94605553500008,40.41885000200011],[139.94376212601424,40.4292795527856],[139.94409915605115,40.429315496912636],[139.981409540218,40.43329458327838],[139.99613732277925,40.43189931929811],[140.0124670757948,40.439495755025064],[140.02455936092582,40.448745836251305],[140.04548831613283,40.46153575337232],[140.06135298115515,40.46409373643681],[140.07964643803274,40.46021800375783],[140.09530439658062,40.45422353848551],[140.12186608275357,40.44861664504219],[140.30061486228607,40.44709218985264],[140.32945031068323,40.45590302240649],[140.35797570161736,40.472878730568965],[140.3846924183204,40.48011343019064],[140.40903201731373,40.48083689970312],[140.4227262719998,40.476909491979455],[140.42939253084057,40.46897715946827],[140.43383670519972,40.45980459170852],[140.47409264515943,40.444766750784936],[140.5277844591854,40.41221059844037],[140.5442175640893,40.40797313145478],[140.5511422062478,40.41133209929697],[140.5596171411181,40.417662462252764],[140.57434492367918,40.42383779647719],[140.59232832309368,40.42518138271464],[140.60674604729218,40.42246837001926],[140.61759809807376,40.41903188691202],[140.63346276309596,40.41233978875019],[140.64788048729463,40.41179718729035],[140.66152306513737,40.414122626358136],[140.68885989766613,40.42370860436861],[140.70203738841474,40.425284736401494],[140.7169202006068,40.42554311792054],[140.74890791217035,40.446265367552684],[140.77428104083737,40.454378567217134],[140.78802697236694,40.46244009003814],[140.8052352236266,40.481663722902454],[140.81903283110023,40.4922573921649],[140.84993533704613,40.499517931107675],[140.86884891244733,40.496598211937936],[140.9150993185787,40.4825422229452],[140.93013716040167,40.47541087521171],[140.9403690935588,40.468202013112375],[140.94408979660682,40.46409373643681],[140.94688032366798,40.45967540139861],[140.94832726539096,40.45484365521048],[140.9486373228541,40.449701849760544],[140.9464152365739,40.44414663316056],[140.94207441590157,40.43980581338761],[140.93912886010878,40.43546499271517],[140.9366483914102,40.432907008751414],[140.94657026620473,40.426214911488955],[140.95308149631404,40.42011709252975],[140.9599027847857,40.410789496038376],[140.96398522393883,40.38061046050433],[140.9627449886903,40.36864736658271],[140.95742231698625,40.35717519817689],[140.95153120270277,40.34764089611045],[140.94801720612907,40.33867503482497],[140.9435730326693,40.31916718201984],[140.93571821542326,40.29759227076664],[140.93401289308053,40.28555166337853],[140.93370283381864,40.27149567348653],[140.9262614277227,40.24397797290513]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-02\",\"name\":\"Aomori\",\"name_alt\":null,\"name_local\":\"青森県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Tohoku\",\"postal\":\"AO\",\"region_code\":\"JPN-THO\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[141.68098803715316,40.45100587481204],[141.67448449086646,40.4481257195263],[141.58777143830122,40.40709463231141],[141.57846968023154,40.39644928620555],[141.57381880119675,40.3858814562642],[141.5707182148737,40.376295478253695],[141.56544721911382,40.36756216096498],[141.55314822840796,40.35919057888205],[141.53687015223574,40.35640005092162],[141.51108361241876,40.35898387330707],[141.4735665217775,40.36854401289585],[141.4543428898125,40.36740713223335],[141.43057173070076,40.36177440036823],[141.3829260596898,40.34554800103942],[141.35688113835374,40.340070298805216],[141.33543541920915,40.34262828186971],[141.32566857314612,40.34996633607753],[141.3149198751521,40.35508230220648],[141.30251753075942,40.35539236146832],[141.24153934116765,40.33417918632037],[141.08764692636453,40.26268484093265],[141.02542850242344,40.21863068355921],[141.01343956918072,40.213101305380945],[141.00150231278144,40.211318467773],[140.98847985166378,40.21434153883092],[140.97437218582763,40.21930247712763],[140.9262614277227,40.24397797290513],[140.93370283381864,40.27149567348653],[140.93401289308053,40.28555166337853],[140.93571821542326,40.29759227076664],[140.9435730326693,40.31916718201984],[140.94801720612907,40.33867503482497],[140.95153120270277,40.34764089611045],[140.95742231698625,40.35717519817689],[140.9627449886903,40.36864736658271],[140.96398522393883,40.38061046050433],[140.9599027847857,40.410789496038376],[140.95308149631404,40.42011709252975],[140.94657026620473,40.426214911488955],[140.9366483914102,40.432907008751414],[140.93912886010878,40.43546499271517],[140.94207441590157,40.43980581338761],[140.9464152365739,40.44414663316056],[140.9486373228541,40.449701849760544],[140.94832726539096,40.45484365521048],[140.94688032366798,40.45967540139861],[140.94408979660682,40.46409373643681],[140.9403690935588,40.468202013112375],[140.93013716040167,40.47541087521171],[140.9150993185787,40.4825422229452],[140.86884891244733,40.496598211937936],[140.84993533704613,40.499517931107675],[140.81903283110023,40.4922573921649],[140.8052352236266,40.481663722902454],[140.78802697236694,40.46244009003814],[140.77428104083737,40.454378567217134],[140.74890791217035,40.446265367552684],[140.7169202006068,40.42554311792054],[140.70203738841474,40.425284736401494],[140.68885989766613,40.42370860436861],[140.66152306513737,40.414122626358136],[140.64788048729463,40.41179718729035],[140.63346276309596,40.41233978875019],[140.61759809807376,40.41903188691202],[140.60674604729218,40.42246837001926],[140.59232832309368,40.42518138271464],[140.57434492367918,40.42383779647719],[140.5596171411181,40.417662462252764],[140.5511422062478,40.41133209929697],[140.5442175640893,40.40797313145478],[140.5277844591854,40.41221059844037],[140.47409264515943,40.444766750784936],[140.43383670519972,40.45980459170852],[140.42939253084057,40.46897715946827],[140.4227262719998,40.476909491979455],[140.40903201731373,40.48083689970312],[140.3846924183204,40.48011343019064],[140.35797570161736,40.472878730568965],[140.32945031068323,40.45590302240649],[140.30061486228607,40.44709218985264],[140.12186608275357,40.44861664504219],[140.09530439658062,40.45422353848551],[140.07964643803274,40.46021800375783],[140.06135298115515,40.46409373643681],[140.04548831613283,40.46153575337232],[140.02455936092582,40.448745836251305],[140.0124670757948,40.439495755025064],[139.99613732277925,40.43189931929811],[139.981409540218,40.43329458327838],[139.94409915605115,40.429315496912636],[139.94376212601424,40.4292795527856],[139.94263756600003,40.4343936220001],[139.94564863400007,40.4780947940001],[139.94353274800005,40.52142975500007],[139.94278633300013,40.54458555800011],[139.930978309,40.55443808700005],[139.92850275500007,40.57097044500006],[139.91515451300012,40.58617340700002],[139.87639612100008,40.583437846000066],[139.856781446,40.59015534100007],[139.8603621750001,40.60879140800006],[139.88038466000006,40.625232554000064],[139.90029743700006,40.64247505800003],[139.925841999,40.64682100100009],[139.938624076,40.66984990800009],[139.95954043000006,40.6776925150001],[139.97013709000015,40.693649883000035],[139.9868773930001,40.7178996830001],[139.99815616500013,40.74210409000011],[140.04597727600003,40.76777008700009],[140.06931012100006,40.765582512000094],[140.09513809700002,40.74927419200006],[140.1076180130001,40.74407301800002],[140.1223587180001,40.74476553400004],[140.14013941000007,40.74957629000011],[140.1547744620001,40.761448243000046],[140.1980380870001,40.78645254300008],[140.21908790400005,40.77833468000006],[140.240570509,40.7835147160001],[140.25806725400008,40.79564036700009],[140.27076256600003,40.81240469000002],[140.29664147200003,40.87376536700003],[140.3092554050001,40.93622467700007],[140.31625410200002,40.954413153000075],[140.31511478000004,40.97850169500009],[140.319238175,40.99972153500008],[140.32192155800016,41.027445197000134],[140.324833894,41.02746135900014],[140.34099368600005,41.005072333000044],[140.34982602400015,40.998256667000106],[140.36228565300007,40.99287923600005],[140.36959444000001,40.986054691000106],[140.38233483200008,40.988267320000034],[140.37587989300005,41.01236805000008],[140.36962853800003,41.02166163200005],[140.37686074100014,41.028008653000114],[140.38558219700013,41.02942508400008],[140.39651086100002,41.02701276800005],[140.40779258300006,41.02569846100002],[140.40446189300008,41.034181320000044],[140.39898677500003,41.03937203000001],[140.37897206000002,41.038166566000115],[140.3680536810001,41.03838470300013],[140.35058592000007,41.03884449200007],[140.34435617100007,41.04402182000007],[140.34066533100008,41.050034933000035],[140.3337851330001,41.045611403000066],[140.3269048200001,41.04118680800008],[140.32395648300013,41.045284584000115],[140.32264494100002,41.07050782800013],[140.32185208300007,41.0829208590001],[140.3001619810001,41.111277292000096],[140.281795428,41.11920156000011],[140.26472982300004,41.12099656200013],[140.24809544900012,41.12336100200011],[140.245477048,41.13387384600014],[140.25618438200007,41.138896678000066],[140.28150475400003,41.14036692900004],[140.30397109100005,41.136680501000114],[140.32043302,41.149779762],[140.32936704900015,41.17211254600008],[140.33277428500008,41.19131094000005],[140.32837975400008,41.20132070500003],[140.3300887380001,41.211859442000076],[140.33277902700002,41.237153409000086],[140.34100620700013,41.24646276100012],[140.33822086800006,41.25889596300007],[140.34228304300007,41.26573129800005],[140.35914755300013,41.25394959800008],[140.379063953,41.24411441700008],[140.39148121400007,41.24044348400008],[140.40943444100003,41.2246768250001],[140.43580162900003,41.198146877000056],[140.46224804000008,41.18388740700007],[140.4836739810001,41.18346812600004],[140.51106755500007,41.197719024],[140.52475613000001,41.21997640700005],[140.5485975940001,41.22624299300011],[140.5939069910001,41.22017794000001],[140.6327023830001,41.194257795000055],[140.64266573200013,41.17137914700004],[140.63701139400007,41.1447473960001],[140.63461347700002,41.1154645850001],[140.63103274800005,41.10565827000005],[140.63152103000004,41.093085028000075],[140.63697350400008,41.071926174000055],[140.64128665500004,41.041489976],[140.65455162900003,41.00849030200004],[140.67302493600005,40.89557526200008],[140.70312167000014,40.85329202300008],[140.752402511,40.83518657000013],[140.79922346800004,40.83908125100004],[140.8307489350001,40.8668380420001],[140.85206829100002,40.883848724000075],[140.8707263990001,40.915135476000046],[140.86570872100003,40.92226795400009],[140.86293283500004,40.9335641210001],[140.86688513900003,40.940992390000105],[140.87083680400008,40.94485248400011],[140.86280763700006,40.94726942200006],[140.85360123300006,40.9449188910001],[140.84724564000015,40.94760752700006],[140.8405199490001,40.952971527000074],[140.84921628000006,40.956248688000045],[140.861488388,40.95713361300008],[140.85948816000007,40.965184801000106],[140.8610516550001,40.9720427660001],[140.87096258300005,40.9779781500001],[140.86853472400003,40.98426696100006],[140.8721016980001,40.989024474000075],[140.88233210700002,41.007822022000084],[140.8886749560001,41.01077779500014],[140.90288540600005,41.008995810000044],[140.91440585900006,40.99609776500009],[140.93883612500008,40.99434078100012],[140.94870251300011,40.9907477350001],[140.957775072,40.97791522800006],[140.96643156800013,40.96960157300006],[140.98140227400006,40.95976859900006],[140.98178952400013,40.95381221000008],[140.97981597600005,40.95173234200013],[140.9794214110001,40.95143509800005],[140.97665426700013,40.94608651000007],[140.97506573300006,40.937755320000065],[140.97979578000002,40.935663849000036],[140.99097741000003,40.93431224200003],[141.01099694100003,40.92951080900011],[141.03493617600014,40.91833765900006],[141.04715557500003,40.914749897000036],[141.05822181400004,40.916522572000076],[141.0680756420001,40.91084813100005],[141.073919208,40.90012069800011],[141.083704774,40.886413220000065],[141.09276681100005,40.8816313830001],[141.11134557100002,40.87263328600005],[141.12626868300003,40.872919082000095],[141.1471450140001,40.87883994300006],[141.16377570600008,40.89130807700005],[141.18524863300001,40.91121452900009],[141.22640007400003,40.987442920000035],[141.23640716500006,41.01572156100008],[141.24190264900005,41.082742],[141.24789472700002,41.098130601],[141.246366158,41.106862387000035],[141.25234706100008,41.11875852600005],[141.263463214,41.12617311400007],[141.27223835200004,41.13657760500013],[141.277882083,41.155329909000045],[141.2704802500001,41.18520388400012],[141.2561012490001,41.21123666100007],[141.23546690100005,41.23797276500002],[141.20591119200003,41.2647197040001],[141.18337455300008,41.27706277200005],[141.16123245800014,41.27846726200008],[141.15246083700004,41.263980445000044],[141.14597765300005,41.25550840300011],[141.1369634360001,41.24669717700007],[141.137639411,41.24271426100003],[141.1438538590001,41.24166270200007],[141.14963557400011,41.248748404000054],[141.15611297200013,41.25479616600006],[141.1591148390001,41.25669431400007],[141.158871181,41.253231738000125],[141.1570088420001,41.24821429700009],[141.14683011300008,41.23628857000003],[141.11337199500008,41.21436328300004],[141.0947367270001,41.21026451300011],[141.07237646000016,41.19661705300004],[141.05807571800005,41.18306812500006],[141.0341351350001,41.185861325000076],[141.00563260700005,41.19576286500006],[140.9920538990001,41.19335164900005],[140.977561863,41.196477815000065],[140.96306758300005,41.18990252900005],[140.94626517500006,41.17328947700011],[140.907788697,41.17364541400008],[140.89078478800008,41.16377995100004],[140.87535602200006,41.16654929600011],[140.86273009800004,41.164123111000094],[140.82850222700006,41.14610662600012],[140.81452501500007,41.128461806000075],[140.79815518300012,41.128577901000085],[140.76579604300002,41.14501873900008],[140.76530067700008,41.184626508000036],[140.76555366700006,41.19595586000014],[140.778168165,41.206854559000135],[140.78469859000006,41.23514784300008],[140.780395753,41.25193437900006],[140.79309271300014,41.27842175200004],[140.79928382000008,41.30300457000004],[140.80321422500003,41.32118674800003],[140.80314793400015,41.32954397600005],[140.81517287700007,41.33756239600004],[140.82363136600014,41.364709589000086],[140.82730899100005,41.387559300000106],[140.83695137100005,41.418221886000055],[140.85333808400003,41.430339450000105],[140.88382800500005,41.467409939000106],[140.89795983200008,41.47614166900013],[140.90105228000004,41.48102448100008],[140.90710228800003,41.5007985150001],[140.9057158930001,41.51308858200011],[140.8994617900001,41.52417520800009],[140.90362850500003,41.528154139000065],[140.90709272100008,41.53854826300005],[140.913572359,41.54685719300008],[140.92699769800004,41.533324933000074],[140.95080024500015,41.521911388],[140.96441713000007,41.50907653600004],[140.97805707600003,41.497659041000105],[140.9930157440001,41.491067669000074],[141.00333950800007,41.48708488900007],[141.02126455500002,41.484118126000084],[141.0653072390001,41.48111619600013],[141.10775040700003,41.46386893600004],[141.14290933000012,41.43322531400014],[141.1596639490001,41.41712322800004],[141.17995380600007,41.40562133200004],[141.18402107200006,41.39741473700013],[141.19499759200005,41.38715241100002],[141.23566610900002,41.367585477],[141.276095682,41.35319552800007],[141.31791884200004,41.353046706000015],[141.3488110140001,41.360219794000045],[141.3821608780001,41.372599583000095],[141.41035306700007,41.390388346000066],[141.4313316260001,41.406088314000044],[141.44819724900003,41.418404235000054],[141.46057858100005,41.43022317800006],[141.463865746,41.42602266500006],[141.46692099400002,41.419327886000104],[141.4604310750001,41.413356293000106],[141.46344160600006,41.40381685400013],[141.461849335,41.400483716000025],[141.45381379800006,41.39635130100007],[141.45559512800003,41.39196423800007],[141.45988422000005,41.38796292700002],[141.45746549300014,41.3774729620001],[141.44528424100008,41.36618409700006],[141.43988362800008,41.3511756610001],[141.43191811600002,41.33002057500008],[141.42379443000004,41.31270692100006],[141.40891673300007,41.2566773930001],[141.40428358000008,41.23065244500009],[141.40376745800006,41.22063293800011],[141.39081676700005,41.16101119700008],[141.39460702200012,41.138200175000065],[141.39828535200007,41.12811920800007],[141.39446549800005,41.11646771900004],[141.39472983900015,41.10288506600011],[141.39635925500005,41.09173074200001],[141.3863069410001,41.03036180100014],[141.38806285300006,40.977231939],[141.39026167600002,40.93393259100014],[141.3930429450001,40.92175044400008],[141.40555340500015,40.81682286300011],[141.42315604000015,40.72577228600008],[141.43738666000007,40.670140931000105],[141.470398091,40.593418071000116],[141.48241247400006,40.5740690690001],[141.491639652,40.56232670000011],[141.5196960080001,40.537986372000034],[141.532504271,40.53139789500008],[141.54821362400003,40.531595226000064],[141.55939515900008,40.53939933200007],[141.57554958600014,40.54227905900004],[141.58210069000006,40.53279883700009],[141.58926137100002,40.522104635000034],[141.6237085300001,40.501369533000116],[141.68098803715316,40.45100587481204]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-07\",\"name\":\"Fukushima\",\"name_alt\":\"Hukusima\",\"name_local\":\"福島県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Tohoku\",\"postal\":\"FS\",\"region_code\":\"JPN-THO\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[140.32061364240565,37.94584951434362],[140.35291141233182,37.94621124954949],[140.39032514928613,37.95365265654482],[140.40458784385385,37.95463450847578],[140.4432418151576,37.943007311338306],[140.45765954025558,37.935643418708764],[140.4688216493995,37.92517894155489],[140.48499637278434,37.90342316314846],[140.4979671570586,37.8949482291774],[140.50835411984673,37.89313955314785],[140.51739749639728,37.89319122999126],[140.57491336535952,37.90326813441692],[140.61439415896325,37.89422475786628],[140.63191247038412,37.88652497025123],[140.6516011903426,37.88290761999065],[140.66958458885767,37.872365628471016],[140.67940311086483,37.85991160723495],[140.68296878518134,37.84699249890471],[140.6810567572635,37.823893134260715],[140.68281375465097,37.81567658090941],[140.6873096049543,37.80751170530098],[140.69469933510618,37.79854584401551],[140.7330432480474,37.776867580874125],[140.76410078272488,37.77020132113407],[140.77898359581627,37.77038218828733],[140.78461632678204,37.77542064094979],[140.7751078640366,37.78374054618922],[140.77805341982946,37.7859626333688],[140.82492394358508,37.78637604451882],[140.84125369660086,37.790897732344405],[140.84962527868362,37.79877838801218],[140.85272586500668,37.81004384994375],[140.85520633370527,37.83810415288437],[140.85520633370527,37.84735423411058],[140.85272586500668,37.86750804296179],[140.85489627534275,37.87603465377629],[140.86466312140595,37.883036811199844],[140.87784061125512,37.88771352775706],[140.92672651571604,37.88978058530574],[140.9331925950084,37.889804989206255],[140.9333602220001,37.889227606000105],[140.95926646200013,37.84995876300013],[140.99325908700007,37.79983832100004],[141.00459510300004,37.76440553700013],[141.00814863400007,37.750189520000106],[141.01742597700002,37.739976304000066],[141.02222741000003,37.72695547100008],[141.0219832690001,37.706732489000046],[141.01905358200014,37.69863515800003],[141.00928795700008,37.68524811400002],[141.00782311300014,37.67885976800011],[141.0114038420001,37.672267971000025],[141.02515709700003,37.65794505400004],[141.028168165,37.648098049000026],[141.04184004000007,37.37714264500002],[141.0361434250001,37.34471263200004],[141.01441491000003,37.27147044500009],[141.01449629,37.239976304000066],[141.00782311300014,37.22675202000005],[141.00782311300014,37.133856512000136],[141.0051375660001,37.12287018400008],[140.98796634200005,37.08978913000004],[140.9743758470001,37.00177643400011],[140.96029707100007,36.96621328300003],[140.92579186300014,36.93891022300005],[140.91724694100003,36.93646881700002],[140.8917749360001,36.93211497600004],[140.85906009200005,36.914007880000014],[140.8352156910001,36.9084333350001],[140.82032311300014,36.89435455900005],[140.8122664720001,36.89118073100008],[140.80437259200005,36.885972398],[140.8019311860001,36.87457916900013],[140.80225670700008,36.8570010440001],[140.79729251400016,36.84650299700013],[140.797286048648,36.84648905231134],[140.69816165573587,36.868602607124586],[140.68777469294764,36.87351186857785],[140.61439415896325,36.896146145228414],[140.59868452357188,36.90513784403623],[140.5879358255778,36.914723822946],[140.583026565024,36.92247528740458],[140.5757401876595,36.92673859281177],[140.57000410390617,36.92606679924336],[140.56147749219252,36.91790192273558],[140.55972049390567,36.90823843035923],[140.56147749219252,36.8973088652119],[140.5668518407398,36.8829944929015],[140.5698490742754,36.87139313418575],[140.56468143130292,36.86317658083453],[140.53243533732086,36.8456065943688],[140.51476199806774,36.82953522377153],[140.5064937678731,36.819509996189396],[140.49672692270929,36.81274038276251],[140.48122399289286,36.797108263535534],[140.46928673739296,36.79222484050399],[140.44985639895356,36.794576117094124],[140.40691328292172,36.815763454719615],[140.37864627530536,36.83594310199254],[140.3544617050436,36.85666535072531],[140.32821007723334,36.87426117651205],[140.3055757996833,36.89384654458232],[140.2860421075572,36.90586131444809],[140.23731123272722,36.924826564894005],[140.2410319357752,36.94306834582753],[140.23452070476662,36.990248927945686],[140.2312650897121,36.999989936486344],[140.23043826741213,37.00104930278309],[140.2291980330628,37.00203115471405],[140.22547733001483,37.00414988910606],[140.20718387313724,37.01213389756131],[140.1988122910543,37.02019542128161],[140.19416141201953,37.03096995679812],[140.19090579606564,37.04342397713481],[140.1850146826814,37.05293244077953],[140.17628136629196,37.05892690695114],[140.16356896443614,37.0655156523255],[140.1134428238272,37.09802012782663],[140.09809492364178,37.105823269128564],[139.95815514504392,37.1393871118257],[139.9168656763099,37.14357290286722],[139.88585981667717,37.139180406250745],[139.86131351120963,37.130447088961915],[139.82234948064405,37.105539049187826],[139.79118859317896,37.08122528681753],[139.7644202014311,37.07995921494583],[139.74741865484702,37.07701365825366],[139.67853397026653,37.05321666072035],[139.509552036797,36.97490102776024],[139.46299157140385,36.96270538984186],[139.4458866720322,36.956142482889305],[139.40790449429687,36.925136624155925],[139.35958702971755,36.90203725771326],[139.27163374190366,36.91955556913413],[139.24698408364856,36.92017568585922],[139.22646854139003,36.92498159452492],[139.21737348889536,36.929503282350595],[139.21458296093488,36.945652167313625],[139.22698530532756,36.96177521385505],[139.2313261251005,37.00094594999558],[139.23923261918998,37.02792104821776],[139.24062788317025,37.03949656851188],[139.2345817401552,37.090682075417476],[139.2345817401552,37.11556427676996],[139.23628706339719,37.12905182588102],[139.2352018577795,37.13990387576331],[139.23101606673805,37.14930898751982],[139.21003543468748,37.18548249012477],[139.2042993509342,37.19209707302147],[139.19499759286452,37.1923037794958],[139.18740115803706,37.18966828116629],[139.1784094592291,37.1895649283787],[139.17251834494562,37.19607615848793],[139.15949588382801,37.2136978217971],[139.15556847610424,37.225893459715394],[139.16011600235163,37.240776271907464],[139.18833133402387,37.28351268236443],[139.1939123881462,37.29658681942621],[139.20817508361318,37.354593613904115],[139.20817508361318,37.36733185508109],[139.20383426294092,37.37957916894345],[139.1973230328316,37.389785265477656],[139.18879642111796,37.39900950828218],[139.18678104131197,37.40844045756103],[139.18853803869945,37.41812978835898],[139.1955660336455,37.42934357434643],[139.20879520123754,37.4411258002155],[139.22574507097818,37.44691356081216],[139.26357221908262,37.447326971962184],[139.32599734859872,37.46127960906671],[139.3407251320591,37.461822211425925],[139.37105919812348,37.45838572831872],[139.38139448496753,37.460814521073274],[139.39519209244116,37.488409736020515],[139.40387373288644,37.49616120137827],[139.4195316923337,37.50334422505591],[139.46717736244528,37.50122549156316],[139.48035485229462,37.502259020337476],[139.5028341011129,37.50755585451904],[139.51544315018123,37.50897695602174],[139.54350345312184,37.50386098899341],[139.55358035664813,37.506496487323034],[139.5560608262461,37.51814952198282],[139.55285688713565,37.56308218029841],[139.54954959523752,37.57729319892195],[139.54412356984682,37.58866201454029],[139.53869754535543,37.597498683717305],[139.535751987764,37.60364818041913],[139.53327151906535,37.61367340800135],[139.53714725174433,37.62437042915198],[139.54799930162642,37.63832306625653],[139.59533491427487,37.68043935818977],[139.66561486193638,37.763845119756496],[139.68566531800002,37.7803557399254],[139.72462934766622,37.82037913678772],[139.76478193573774,37.81645172816468],[139.77609907361335,37.809268704487124],[139.79067182834217,37.805754706114755],[139.80638146373354,37.80598725011146],[139.82560509659794,37.81149078986809],[139.85015140206553,37.81185252507396],[139.87821170500615,37.80552216211808],[139.88999393087516,37.80466950139632],[139.90430830318564,37.80805430855949],[139.91655561704806,37.81453970024691],[139.93236860612626,37.81159414355494],[139.94559777191955,37.79994110799578],[139.9655448751958,37.77294017225117],[139.97923912988193,37.761984767782764],[139.99055626775748,37.75653290486966],[139.99923790910213,37.754284980167625],[140.007609491185,37.754336656111775],[140.03634158679472,37.75717886001635],[140.04915734323674,37.756041978454476],[140.06104292189332,37.7522179235184],[140.07205000230567,37.745474148513125],[140.09390913260034,37.727361558788886],[140.10429609628773,37.72265900291069],[140.11737023334956,37.722607326966624],[140.13147789918565,37.7268447939521],[140.14930626806972,37.73849783041055],[140.16170861156314,37.74229604782428],[140.17256066324398,37.74307119418029],[140.18222415562025,37.74035818148491],[140.19245608877753,37.738937079082916],[140.20330813955889,37.73849783041055],[140.21447024960236,37.74059072548158],[140.22268680205428,37.74332957569939],[140.23095503134965,37.74741201395324],[140.26118574372714,37.768986925206434],[140.2692472683467,37.780536607078744],[140.27374311685128,37.79955353436807],[140.26707685801057,37.83105032041601],[140.26506147730527,37.87231395072823],[140.26738691637308,37.90670461752407],[140.27043582585264,37.925850735123305],[140.2695573258099,37.964246324008684],[140.32061364240565,37.94584951434362]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-03\",\"name\":\"Iwate\",\"name_alt\":null,\"name_local\":\"岩手県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Tohoku\",\"postal\":\"IW\",\"region_code\":\"JPN-THO\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[141.64030353084496,38.96750341736643],[141.6336601101259,38.96955963806681],[141.59536787312885,38.97630341217271],[141.56772098223746,38.9856826855075],[141.53997073765942,38.99015269648967],[141.51480431456747,38.98961009413054],[141.49945641528132,38.98676789022596],[141.48581383743868,38.97700104416285],[141.48235151590973,38.96361684783926],[141.48235151590973,38.947442125353774],[141.4839534845657,38.9324042835309],[141.4839534845657,38.921242174386805],[141.48131798713536,38.91137197553621],[141.4769771673623,38.90457652368761],[141.470310906723,38.89700592728164],[141.46209435427102,38.88987457954815],[141.45496300743685,38.881968085458766],[141.4507255386526,38.87276968017659],[141.45010542192773,38.863674628581265],[141.45144900906456,38.85318431210632],[141.449950393196,38.83266876894851],[141.45057050992108,38.82409048129071],[141.4488651875783,38.81628733998869],[141.4432841325567,38.80941437377433],[141.41677412322713,38.78858877225397],[141.40669721880158,38.78543650998694],[141.3554341984294,38.80476349473946],[141.33404015522888,38.808716743582835],[141.3207076357487,38.81303172493418],[141.3093904969739,38.81003449229797],[141.3031893252271,38.80435008448879],[141.2974532405746,38.7968053247059],[141.28908165939112,38.79052663769417],[141.26065962124454,38.77840851504092],[141.25135786407407,38.77246572481337],[141.23647505098285,38.75828054551073],[141.2284135281617,38.754172267935786],[141.21776818295524,38.75378469520757],[141.18087120993835,38.77386098879356],[141.1461446472577,38.78621165634286],[141.13343224540176,38.79876902946711],[141.11353681896907,38.822385158947895],[141.11245161425052,38.83137685775574],[141.1177226100105,38.839800117581376],[141.13296715740836,38.8494119331142],[141.13622277156355,38.85274506343396],[141.13699791881885,38.85711172072925],[141.12888471915437,38.86364879015957],[141.1126066429821,38.869462389177926],[141.0741077013094,38.87323476817005],[141.00527469267286,38.872924709807506],[140.99266564270522,38.875198472931146],[140.9763875674322,38.880417791847606],[140.9070377948583,38.91405914980987],[140.81608727440818,38.94658946373272],[140.75944990458936,38.95173126918269],[140.77464277604332,39.00392446554167],[140.79531334883188,39.03911611621575],[140.79841393425556,39.04867625580454],[140.7946932312076,39.0602001001545],[140.78585656113137,39.06784821182549],[140.77278242406953,39.075392970709004],[140.75851972860255,39.08640005112139],[140.751336704925,39.09906077703323],[140.7493730001638,39.11327179655615],[140.75293867358084,39.12756033044485],[140.7639457539933,39.14807587270329],[140.77495283440578,39.157300117306534],[140.7861666203932,39.16272614179795],[140.7940731135833,39.16779043198284],[140.79608849518786,39.174715074141375],[140.79061079295366,39.182053128349196],[140.78244591734517,39.18835765198398],[140.7741776880498,39.19579905897939],[140.76921674975318,39.2057984481398],[140.76999189700837,39.22003530518518],[140.7665812523229,39.2312490911726],[140.76053511020717,39.24086090850409],[140.75154340960057,39.24783722570737],[140.73169966001132,39.25918020290399],[140.72281131399086,39.266285712215875],[140.7173852886001,39.27364960394604],[140.70684329618115,39.28561269786766],[140.69940189008514,39.292072252032824],[140.69335574796935,39.30256256850768],[140.68451907789313,39.33434357449636],[140.66446862182943,39.36741648987905],[140.66043786041882,39.3807490102586],[140.65811242135112,39.39227285370919],[140.6759924661793,39.43550019058134],[140.7143363791206,39.49278351554676],[140.72932254499943,39.529267076514344],[140.73914106700659,39.546888739823515],[140.74813276581455,39.56014374403853],[140.79092085221555,39.599081936182415],[140.80616539871414,39.62161286094474],[140.81035118975558,39.63137970610859],[140.81376183534047,39.64393707923276],[140.81283165935355,39.66235972821892],[140.80864586831223,39.67713918672402],[140.80053266774837,39.69318471889966],[140.79019738180364,39.71977224439351],[140.7971736999063,39.734990953369845],[140.8041500180089,39.74594635693887],[140.81918785983186,39.76522166574733],[140.824613886122,39.7750401877544],[140.82647423719618,39.78434194582414],[140.81624230403906,39.794315497462236],[140.79371137927671,39.80904328092265],[140.7861666203932,39.81733734774036],[140.7827559757077,39.829145412930444],[140.7831693868577,39.84015249334291],[140.78585656113137,39.85033275055602],[140.79438317284507,39.86046133092566],[140.80477013563325,39.865551459532185],[140.81655236240158,39.868471177802604],[140.83034996987516,39.86854869216839],[140.84249393095018,39.866972561034856],[140.85396609935594,39.86666250357169],[140.8640430037815,39.87033152887696],[140.8705542338908,39.878315538231504],[140.8690039411789,39.89123464656163],[140.85505130407438,39.92133616772992],[140.84941857310872,39.938596095833134],[140.84528445891056,39.95815562638106],[140.8444576357113,39.96665639877372],[140.84249393095018,39.977456772711946],[140.84063357987577,40.03954600724242],[140.83784305191523,40.05383454023183],[140.83815311117712,40.066391913356114],[140.84156375496337,40.077166449771795],[140.85055545467048,40.0919975860198],[140.85598148006125,40.10408987115062],[140.85567142079935,40.11749990499655],[140.85691165604806,40.13256358524126],[140.86032229983425,40.14204621046426],[140.8618725934454,40.15015941012871],[140.8595471543777,40.1596161969299],[140.85644656805465,40.16829783827464],[140.85541303928034,40.180596828980526],[140.8705542338908,40.20162913697504],[140.88481692935778,40.21674449316379],[140.9070377948583,40.22948273434076],[140.9262614277227,40.24397797290513],[140.97437218582763,40.21930247712763],[140.98847985166378,40.21434153883092],[141.00150231278144,40.211318467773],[141.01343956918072,40.213101305380945],[141.02542850242344,40.21863068355921],[141.08764692636453,40.26268484093265],[141.24153934116765,40.33417918632037],[141.30251753075942,40.35539236146832],[141.3149198751521,40.35508230220648],[141.32566857314612,40.34996633607753],[141.33543541920915,40.34262828186971],[141.35688113835374,40.340070298805216],[141.3829260596898,40.34554800103942],[141.43057173070076,40.36177440036823],[141.4543428898125,40.36740713223335],[141.4735665217775,40.36854401289585],[141.51108361241876,40.35898387330707],[141.53687015223574,40.35640005092162],[141.55314822840796,40.35919057888205],[141.56544721911382,40.36756216096498],[141.5707182148737,40.376295478253695],[141.57381880119675,40.3858814562642],[141.57846968023154,40.39644928620555],[141.58777143830122,40.40709463231141],[141.67448449086646,40.4481257195263],[141.68098803715316,40.45100587481204],[141.68100019600007,40.45099518400008],[141.69092858200014,40.442287502000084],[141.71914098100007,40.41283517800008],[141.7260845130001,40.39379277500004],[141.74377693500008,40.38392223200012],[141.753573888,40.369490560000116],[141.75855553500003,40.35895416900007],[141.772797071,40.33392975500007],[141.77556399800005,40.32758209800002],[141.7784936860001,40.31549713700011],[141.78565514400015,40.30687083500001],[141.80347741000003,40.29002513200001],[141.830414259,40.251166083000015],[141.8419702480001,40.22577545800007],[141.83423912900008,40.21434153900012],[141.82447350400003,40.2110049500001],[141.81519616000003,40.202866929000095],[141.81023196700005,40.19277578300003],[141.813324415,40.18357982000006],[141.82593834700003,40.17568594000008],[141.8527938160001,40.167222398000035],[141.8650008470001,40.15973541900007],[141.87720787900003,40.13930898600009],[141.86589603000004,40.12726471600007],[141.84742272200003,40.11513906500008],[141.8375757170001,40.094549872000044],[141.8424585300001,40.070379950000046],[141.85499108200008,40.0549990910001],[141.91960696700005,40.00885651200008],[141.93034915500004,40.00409577000008],[141.94117272200003,40.00145091400009],[141.949554884,39.99656810100004],[141.95533287900008,39.97492096600004],[141.95964603000004,39.96430084800011],[141.96143639400015,39.95368073100005],[141.95679772200003,39.94375234600005],[141.94507897200003,39.931301174000026],[141.942230665,39.92450592700001],[141.95297285200002,39.90643952000008],[141.95923912900008,39.89838288000004],[141.9660750660001,39.89175039300008],[141.97185306100005,39.88300202000002],[141.97543379,39.85219961100003],[141.97868899800005,39.83930084800002],[142.002289259,39.77757396000001],[142.0048934250001,39.772691148000035],[142.00391686300014,39.76740143400008],[142.00684655000015,39.76044342700007],[142.0087996750001,39.752752997000044],[142.0048934250001,39.74506256700005],[141.99390709700003,39.735296942000105],[141.99122155000015,39.730454820000034],[141.98389733200014,39.70697663000004],[141.98096764400015,39.662543036000045],[141.97828209700003,39.64785390800003],[141.9648543630001,39.625718492000146],[141.96045983200008,39.6116397160001],[141.96631920700014,39.59845612200007],[142.01246178500003,39.63580963700011],[142.03614342500015,39.63898346600007],[142.03101647200003,39.629624742000146],[142.02898196700005,39.62299225500007],[142.03077233200008,39.61749909100007],[142.03614342500015,39.6116397160001],[142.0273543630001,39.59471263200004],[142.03077233200008,39.58417389500008],[142.05909264400015,39.56289297100008],[142.0641382170001,39.559881903000075],[142.0681258470001,39.55638255400001],[142.07032311300014,39.55019765800003],[142.0693465500001,39.54047272300008],[142.06470787900008,39.536444403000104],[142.05933678500003,39.533270575000046],[142.0566512380001,39.52602773600003],[142.05030358200008,39.522691148],[142.01514733200014,39.48871491100007],[141.9992781910001,39.481024481000134],[141.96631920700014,39.47134023600009],[141.95297285200002,39.460760809000135],[141.97087649800005,39.44326406500008],[141.9962671230001,39.445257880000014],[142.01889082100007,39.45937734600005],[142.02881920700008,39.47793203300006],[142.03638756600003,39.48379140800009],[142.05062910200002,39.47699616100007],[142.05844160200002,39.466294664000145],[142.04615319100003,39.460760809000135],[142.039886915,39.45457591400006],[142.03834069100003,39.44025299700007],[142.0380965500001,39.42401764500005],[142.031586134,39.417303778000075],[142.01059004,39.415716864000046],[142.00114993600005,39.41364166900013],[141.99170983200014,39.41437409100007],[141.98096764400015,39.4124209660001],[141.9716903000001,39.40696849200009],[141.95020592500015,39.39154694200005],[141.93873131600003,39.38507721600004],[141.96412194100003,39.37555573100005],[141.95337975400003,39.36200592700007],[141.92693118600005,39.34642161700009],[141.90528405000015,39.33047109600008],[141.92465254000007,39.32489655200007],[141.94678795700014,39.3303897160001],[141.96713300900007,39.34096914300005],[141.98096764400015,39.35097890800003],[141.98365319100003,39.32713450700007],[141.96371504,39.31256745000013],[141.914886915,39.294867255000014],[141.9228621750001,39.29047272300005],[141.9288843110001,39.28388092700007],[141.9326278000001,39.27521393400008],[141.92432701900015,39.275824286000045],[141.9072371750001,39.274562893000024],[141.8990991550001,39.27521393400008],[141.8982039720001,39.26211172100011],[141.8990991550001,39.24852122600004],[141.93148847700002,39.247707424000026],[141.9470320970001,39.249701239000075],[141.94678795700014,39.24852122600004],[141.9492293630001,39.22801341400009],[141.930023634,39.21222565300005],[141.9021102220001,39.204331773000064],[141.87867272200003,39.207586981000105],[141.88168379,39.19245026200011],[141.89372806100005,39.186753648000035],[141.9262801440001,39.18707916900007],[141.91578209700003,39.1699893250001],[141.8951929050001,39.15936920800007],[141.86947675900007,39.153957424000026],[141.8437606130001,39.15232982],[141.8610945970001,39.13841380400004],[141.90886478000004,39.10976797100005],[141.9133406910001,39.097723700000046],[141.89047285200007,39.089544989000046],[141.83545983200008,39.1043154970001],[141.81714928500003,39.09088776200011],[141.859711134,39.07330963700008],[141.87183678500008,39.0629336610001],[141.8605249360001,39.05923086100006],[141.848480665,39.05703359600005],[141.83611087300005,39.05621979400004],[141.82325280000015,39.05670807500013],[141.82325280000015,39.04926178600007],[141.83399498800014,39.04441966400009],[141.8481551440001,39.03510163000004],[141.85572350400003,39.02606842700004],[141.84742272200003,39.021958726000065],[141.80648847700002,39.02948639500005],[141.76636803500003,39.02484772300011],[141.74561608200008,39.0261904970001],[141.72771243600005,39.03628164300005],[141.72820071700005,39.02171458500004],[141.7336531910001,39.0101992860001],[141.7414656910001,38.99949778900009],[141.7482202480001,38.98786041900007],[141.73666425900007,38.989325262000136],[141.7270613940001,38.98761627800013],[141.71338951900015,38.97418854400013],[141.72828209700003,38.969916083000015],[141.73389733200008,38.9605166690001],[141.73023522200003,38.95115794500006],[141.717133009,38.94684479400007],[141.7041121750001,38.95058828300009],[141.69092858200014,38.95962148600009],[141.680023634,38.97077057500013],[141.6731063160001,38.98102448100005],[141.68620853000004,38.98590729400013],[141.69361412900003,38.995306708000044],[141.6510522800001,38.99681224200009],[141.63835696700005,38.985419012000136],[141.64030353084496,38.96750341736643]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-04\",\"name\":\"Miyagi\",\"name_alt\":null,\"name_local\":\"宮城県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Tohoku\",\"postal\":\"MG\",\"region_code\":\"JPN-THO\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[141.46859785200004,38.28420644700016],[141.48080488399998,38.27798086100002],[141.48178144600016,38.273749091000084],[141.48747806100002,38.268500067],[141.4897567070001,38.262844143],[141.4923608730002,38.25779857],[141.4981388680001,38.25470612200009],[141.49740644600016,38.250718492000104],[141.49903405000018,38.248480536000116],[141.49903405000018,38.24632396000014],[141.49740644600016,38.24636465100012],[141.49024498800011,38.250718492000104],[141.4795841810001,38.250718492000104],[141.472504102,38.258408921000054],[141.465586785,38.265692450000145],[141.46127363400004,38.26752350500011],[141.4566349620001,38.26911041900014],[141.4565535820001,38.273667710000026],[141.45972741000017,38.27968984600001],[141.46143639400006,38.282741604000094],[141.46428470100003,38.28180573100012],[141.46859785200004,38.28420644700016]]],[[[141.42660566500004,38.294338283000016],[141.41016686300023,38.285589911000145],[141.40577233200023,38.29336172100015],[141.40853925900004,38.29995351800012],[141.40707441500015,38.30426666900003],[141.41797936300023,38.31175364799999],[141.42139733200023,38.30255768400012],[141.42660566500004,38.294338283000016]]],[[[141.58342532600014,38.276597398000135],[141.5757755870002,38.26817454600017],[141.55892988400004,38.28119538000006],[141.54664147200006,38.29897695500013],[141.5568139980002,38.30931224200005],[141.5620223320001,38.31285228100013],[141.56706790500002,38.31053294500008],[141.57431074300004,38.307359117000104],[141.58220462300014,38.30491771000008],[141.58537845099997,38.29673899900017],[141.5811466810001,38.28685130400005],[141.58204186300017,38.28164297100015],[141.58342532600014,38.276597398000135]]],[[[141.12671959700006,38.327704169000086],[141.12224368600008,38.326727606000034],[141.11451256600006,38.335475979000094],[141.11801191500015,38.337795315000065],[141.12387128999998,38.342637437000135],[141.12867272200018,38.34792715100012],[141.13005618600008,38.3389753280001],[141.12671959700006,38.327704169000086]]],[[[141.62395267000008,38.85179271000011],[141.62704511800015,38.84874095300002],[141.63168379000015,38.84874095300002],[141.63323001400013,38.84686920800006],[141.63477623800023,38.84389883000013],[141.62981204500002,38.83820221600014],[141.62452233200023,38.83315664300012],[141.6163029310002,38.83746979400003],[141.60971113400015,38.85130442900011],[141.60482832099999,38.85586172100007],[141.60352623800006,38.862901109000134],[141.60637454500014,38.86456940300015],[141.60328209700006,38.867336330000015],[141.60075931100002,38.87319570500016],[141.60914147200018,38.87803782800013],[141.6168725920002,38.879990953000075],[141.62631269600004,38.88292064000011],[141.6307886080002,38.87897370000003],[141.6315210300002,38.87132396],[141.62712649800014,38.86367422100007],[141.62484785200016,38.859605210000026],[141.62395267000008,38.85179271000011]]],[[[141.64527428500006,38.92300039300012],[141.66879316499998,38.872626044000086],[141.67359459700018,38.85195547100007],[141.65211022200006,38.86432526200012],[141.64926191499998,38.86908600500011],[141.64568118600002,38.88263580900018],[141.64210045700017,38.88849518400012],[141.63672936300011,38.89109935100011],[141.6315210300002,38.88971588700004],[141.62525475400017,38.88690827000009],[141.61784915500007,38.88544342700011],[141.59864342500012,38.88519928600009],[141.58855228000007,38.88222890800007],[141.58448326900012,38.873114325000174],[141.58375084700006,38.854437567],[141.586192254,38.835109768000066],[141.58936608200005,38.826076565000065],[141.5870874360002,38.81924062700013],[141.57325280000006,38.806586005000085],[141.53052819100017,38.78082916900003],[141.52165774800002,38.768133856000176],[141.53199303500006,38.7633324240001],[141.53882897200006,38.756252346000124],[141.54908287900017,38.74079010600009],[141.56706790500002,38.72882721600017],[141.57260175900015,38.71865469000012],[141.56641686300006,38.703558661000116],[141.55974368600013,38.69953034100014],[141.55445397200006,38.70258209800015],[141.54908287900017,38.707017320000105],[141.54281660200004,38.707261460000055],[141.53785241000017,38.704087632],[141.52540123800006,38.68992747600008],[141.51587975400017,38.684271552000084],[141.49105879000004,38.677557684000035],[141.480723504,38.672552802],[141.4630639980002,38.66136302299999],[141.46314537900017,38.65546295800014],[141.47388756600017,38.64520905200011],[141.48519941500015,38.64085521000011],[141.53223717500006,38.638413804],[141.53516686300011,38.632025458000086],[141.52808678500006,38.61790599200005],[141.51775149800002,38.603827216000084],[141.5114038420002,38.597398179],[141.46705162900017,38.56952545800014],[141.48308353000007,38.557318427000055],[141.49903405000018,38.541652736000074],[141.51465905000023,38.53481679900007],[141.529144727,38.54901764500009],[141.54224694100017,38.533433335],[141.54761803500006,38.51862213700012],[141.54712975400005,38.50214264500012],[141.54281660200004,38.48139069200012],[141.53174889400012,38.48407623900003],[141.52418053500006,38.489691473],[141.5187280610002,38.497951565],[141.51482181100013,38.508042710000055],[141.5006616550002,38.47044505400008],[141.49496504000004,38.460882880000085],[141.50074303500017,38.45791250200007],[141.50326582099999,38.45465729400003],[141.50489342500012,38.45099518400015],[141.5079858730002,38.446600653000175],[141.48292076900012,38.44061920800006],[141.47144616000006,38.435736395],[141.47046959700018,38.429510809000035],[141.47722415500013,38.41844310100002],[141.476817254,38.409735419000114],[141.47754967500023,38.40155670800003],[141.487559441,38.392035223],[141.49903405000018,38.39960358300006],[141.51840253999998,38.40232982000013],[141.53760826900023,38.398179429],[141.54908287900017,38.38515859600001],[141.53419030000023,38.389837958000115],[141.52035566500004,38.390529690000065],[141.50904381600006,38.38515859600001],[141.50123131600006,38.37152741100006],[141.525238477,38.359930731000034],[141.52930748800017,38.33954498900006],[141.52979576900023,38.31793854400014],[141.54281660200004,38.302639065],[141.537364129,38.29507070500016],[141.52540123800006,38.267808335000055],[141.51856530000023,38.27045319200015],[141.49122155000006,38.29238515800007],[141.484629754,38.29559967700011],[141.46998131600017,38.296087958000115],[141.46338951900012,38.299221096000096],[141.45997155000012,38.306341864000146],[141.46338951900012,38.31240469000012],[141.46753991000006,38.31781647300009],[141.46705162900017,38.32306549700017],[141.457774285,38.330877997000144],[141.42554772200018,38.343573309000035],[141.43083743600008,38.350246486000074],[141.439707879,38.37152741100006],[141.42807050899998,38.380438544000086],[141.4194442070001,38.380682684000035],[141.41049238400015,38.37791575700008],[141.39812259200014,38.37767161700013],[141.38624108200005,38.380845445000105],[141.3761499360002,38.38544342700014],[141.35377037900017,38.40224844000015],[141.33318118600008,38.40900299700009],[141.30445397200012,38.407904364000146],[141.22950280000023,38.39411041900003],[141.202403191,38.38580963700015],[141.18091881600017,38.37116120000006],[141.17212975400005,38.347316799000154],[141.17497806100008,38.32436758000007],[141.16968834700018,38.322821356000034],[141.15211022200006,38.32306549700017],[141.14771569100017,38.32949453300007],[141.13184655000023,38.37152741100006],[141.12322024800002,38.36652252800012],[141.11459394600004,38.36465078300013],[141.10596764400023,38.366156317],[141.0978296230002,38.37152741100006],[141.07943769600016,38.360907294000114],[141.06470787900017,38.34662506700012],[141.0542098320001,38.32916901200015],[141.048675977,38.30947500200001],[141.05836022200006,38.313706773000135],[141.06959069100006,38.31488678600006],[141.07911217500023,38.31159088700004],[141.08350670700023,38.302639065],[141.08057701900012,38.29490794499999],[141.07252037900017,38.289536851000136],[141.06226647200006,38.28473541900014],[141.05274498800023,38.27871328300013],[141.01734459700006,38.24485911700016],[140.98796634200002,38.20701732000013],[140.96680748800023,38.170477606000176],[140.92579186300023,38.04938385600012],[140.9186304050002,38.00722890800016],[140.91976972700004,37.96515534100011],[140.93262780000023,37.891750393000095],[140.93319259500842,37.88980498920621],[140.92672651571607,37.8897805853057],[140.8778406112551,37.887713527757015],[140.86466312140604,37.88303681119989],[140.85489627534272,37.87603465377633],[140.85272586500676,37.867508042961774],[140.8552063337053,37.84735423411054],[140.8552063337053,37.83810415288441],[140.85272586500676,37.810043849943796],[140.8496252786837,37.79877838801214],[140.84125369660094,37.790897732344476],[140.8249239435851,37.786376044518775],[140.7780534198295,37.785962633368754],[140.77510786403658,37.783740546189264],[140.78461632678201,37.77542064094975],[140.7789835958163,37.770382188287286],[140.76410078272485,37.77020132113411],[140.73304324804744,37.77686758087417],[140.6946993351062,37.798545844015464],[140.68730960495432,37.80751170530105],[140.682813754651,37.815676580909454],[140.68105675726346,37.82389313426076],[140.68296878518143,37.846992498904754],[140.6794031108649,37.859911607234906],[140.66958458885765,37.87236562847097],[140.65160119034263,37.882907619990604],[140.63191247038415,37.88652497025119],[140.61439415896328,37.894224757866326],[140.57491336535955,37.90326813441688],[140.51739749639725,37.89319122999122],[140.5083541198468,37.89313955314789],[140.49796715705864,37.89494822917747],[140.48499637278442,37.9034231631485],[140.46882164939947,37.92517894155496],[140.4576595402556,37.93564341870872],[140.44324181515762,37.94300731133826],[140.40458784385382,37.95463450847582],[140.39032514928616,37.95365265654486],[140.3529114123319,37.94621124954945],[140.32061364240568,37.945849514343664],[140.26955732580998,37.96424632400864],[140.27705040874932,38.03018545189711],[140.2823214054085,38.045119940932594],[140.29503380816365,38.05659210933841],[140.3085730332189,38.061656399523216],[140.34484988771217,38.06506704420873],[140.3606628767902,38.068632716726526],[140.3788013040369,38.07870962205136],[140.396526321033,38.091163642388224],[140.41388960192367,38.11441803576368],[140.44070966961553,38.13945526854572],[140.45078657404113,38.15214183197999],[140.45905480423576,38.175137843836424],[140.46215538965956,38.19079580328376],[140.463085564747,38.20821076101778],[140.46138024330358,38.24484935251566],[140.46820153177524,38.26781952595046],[140.47435102757774,38.2817463237338],[140.48277428560485,38.29518219690091],[140.50308312408688,38.31872081201594],[140.5119714701074,38.33311269779274],[140.52251346162697,38.357710680103864],[140.5431323593708,38.37938894324516],[140.55227908691037,38.38579682146617],[140.55972049390576,38.39478852027402],[140.5726396022359,38.42954092137647],[140.58504194572927,38.45289866843886],[140.58793582557783,38.46571442308222],[140.58204471219366,38.48002879539264],[140.5740348653168,38.48865875899459],[140.56519819613996,38.496513577139936],[140.55837690586955,38.508089098333144],[140.55491458613918,38.52436717450557],[140.55429446761562,38.567775376732314],[140.54886844312412,38.584647732107115],[140.53791303865577,38.60738536244459],[140.53460574765714,38.619167589212864],[140.53439904208219,38.63118235907855],[140.54111697776628,38.642137763546955],[140.55212405817875,38.64404979146464],[140.56798872320113,38.64175018991928],[140.58194136030548,38.64327464510883],[140.59108808784495,38.649553331221185],[140.5989945819343,38.65978526527773],[140.60364546096937,38.671955063875046],[140.60597090093646,38.68637278897303],[140.60457563695613,38.710402330503214],[140.6067460472922,38.73231313674192],[140.60891645672908,38.73639557589503],[140.6163578637244,38.7474026563075],[140.6182182147987,38.7555675319161],[140.61604780536183,38.765437729867315],[140.59511885015482,38.790242417753305],[140.5771354507406,38.8222301302164],[140.5426672713776,38.85752513277869],[140.53290042531435,38.88170970304033],[140.5440625353576,38.873699856163356],[140.5580668484063,38.87152944582728],[140.57760053873395,38.87202037224246],[140.64198937391043,38.88933197718909],[140.69562951109302,38.91679800092693],[140.7104089695982,38.92811513880265],[140.72549848916393,38.936951808878874],[140.73428348239688,38.94346303988745],[140.75944990458933,38.951731269182645],[140.81608727440826,38.94658946373271],[140.90703779485833,38.91405914980983],[140.97638756743217,38.88041779184765],[140.99266564270525,38.87519847293122],[141.00527469267283,38.87292470980755],[141.0741077013095,38.87323476817001],[141.11260664298206,38.86946238917797],[141.1288847191544,38.863648790159615],[141.13699791881893,38.85711172072932],[141.13622277156358,38.852745063434],[141.13296715740844,38.849411933114155],[141.1177226100106,38.83980011758133],[141.11245161425055,38.831376857755785],[141.11353681896915,38.82238515894785],[141.1334322454018,38.79876902946707],[141.14614464725778,38.7862116563429],[141.18087120993832,38.7738609887936],[141.21776818295533,38.75378469520753],[141.22841352816172,38.75417226793583],[141.23647505098293,38.75828054551077],[141.2513578640741,38.772465724813415],[141.26065962124457,38.778408515040994],[141.28908165939114,38.79052663769424],[141.2974532405747,38.79680532470597],[141.30318932522712,38.80435008448886],[141.30939049697398,38.81003449229793],[141.32070763574868,38.81303172493422],[141.33404015522885,38.808716743582906],[141.35543419842944,38.804763494739504],[141.4066972188016,38.785436509986894],[141.41677412322716,38.788588772254016],[141.44328413255678,38.80941437377437],[141.44886518757838,38.81628733998876],[141.45057050992116,38.82409048129075],[141.44995039319602,38.832668768948466],[141.45144900906465,38.853184312106364],[141.45010542192782,38.863674628581336],[141.45072553865262,38.872769680176546],[141.45496300743682,38.88196808545872],[141.46209435427104,38.88987457954822],[141.47031090672309,38.89700592728171],[141.47697716736232,38.90457652368765],[141.48131798713544,38.91137197553617],[141.48395348456577,38.92124217438676],[141.48395348456577,38.93240428353094],[141.48235151590976,38.94744212535382],[141.48235151590976,38.963616847839305],[141.48581383743877,38.97700104416289],[141.4994564152813,38.986767890226005],[141.51480431456756,38.98961009413058],[141.5399707376595,38.990152696489716],[141.5677209822375,38.98568268550757],[141.59536787312888,38.976303412172754],[141.63366011012593,38.96955963806677],[141.640303530845,38.9675034173665],[141.64055423300013,38.96519603100016],[141.64527428500006,38.92300039300012]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-15\",\"name\":\"Niigata\",\"name_alt\":null,\"name_local\":\"新潟県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Chubu\",\"postal\":\"NI\",\"region_code\":\"JPN-THO\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[138.43751061300006,38.063788153000175],[138.44320722700016,38.051947333000115],[138.45150800900015,38.052435614],[138.45923912900005,38.06989166900014],[138.49463951900023,38.066961981000034],[138.54078209700018,38.076727606000176],[138.57520592500012,38.074286200000145],[138.57520592500012,38.03510163000011],[138.56592858200017,38.01634349200005],[138.52442467500023,37.96344635600009],[138.51319420700017,37.9448102890001],[138.50049889400012,37.906683661000116],[138.48650149800002,37.891750393000095],[138.370371941,37.82876211100016],[138.26514733200005,37.7983259140001],[138.24317467500023,37.796128648000106],[138.2325952480002,37.797674872000144],[138.22266686300011,37.8022321640001],[138.21509850400005,37.80955638200011],[138.21216881600006,37.81940338700004],[138.21550540500007,37.83026764500012],[138.223887566,37.834173895000035],[138.27214603000007,37.842230536000145],[138.28321373800006,37.84833405200014],[138.28785241,37.861029364],[138.28785241,37.905991929],[138.298594597,37.92243073100009],[138.33082116000017,37.95189036700016],[138.34253991000006,37.96686432500012],[138.3147892590001,37.997463283000044],[138.24789472700016,37.978501695000105],[138.23267662900017,38.00775788000014],[138.2325952480002,38.03131745000009],[138.23560631600012,38.05215078300007],[138.24219811300006,38.071478583],[138.25310306100008,38.090318101000136],[138.29468834700006,38.13617584800012],[138.31006920700005,38.16811758000013],[138.32911217500023,38.18195221600003],[138.34815514400023,38.192816473000036],[138.35670006600017,38.20359935100011],[138.40186608200023,38.24119700700011],[138.42855879000015,38.256293036000116],[138.4694116550002,38.313177802000055],[138.4780379570001,38.31956614799999],[138.48951256600006,38.322455145000035],[138.51417076900023,38.32306549700017],[138.52003014400006,38.31850820500013],[138.51929772200006,38.30744049700009],[138.50709069100017,38.24144114800008],[138.48658287900005,38.18130117400007],[138.44385826900012,38.102240302000084],[138.43816165500002,38.0834821640001],[138.43751061300006,38.063788153000175]]],[[[139.2212833990001,38.43764883000016],[139.21664472700016,38.437567450000174],[139.21574954499997,38.44159577000015],[139.21762129000015,38.444769598],[139.22282962300002,38.44944896000011],[139.22413170700023,38.45567454600008],[139.2221785820001,38.460557359000134],[139.22934004000004,38.4646263690001],[139.23080488399998,38.467067776000064],[139.23568769600016,38.47048574400007],[139.24073326900012,38.478949286],[139.24463951900006,38.48371002800009],[139.2505802740002,38.48562246300007],[139.25196373800011,38.48948802300005],[139.25831139400012,38.49119700700005],[139.25896243600008,38.48309967700014],[139.25806725400017,38.47544993700011],[139.25505618600008,38.46653880400008],[139.24683678500017,38.45506419500013],[139.24268639400023,38.44855377800003],[139.23682701900012,38.44635651200004],[139.22315514400023,38.43764883000016],[139.2212833990001,38.43764883000016]]],[[[139.74896894755884,38.359519355234156],[139.7629215837641,38.354610093780806],[139.79160200432895,38.35641876891107],[139.80560631737765,38.35435171226176],[139.8465857277491,38.330115465156624],[139.85526736909375,38.32231232385472],[139.86224368719647,38.314379991343614],[139.86922000529913,38.30071157507918],[139.87118371006014,38.28590627725289],[139.8636389511768,38.265726629979966],[139.85526736909375,38.2520323761933],[139.8444153183124,38.24087026614991],[139.80808678607625,38.2185202094402],[139.79366906097837,38.21193146406584],[139.77816613206133,38.20857249622374],[139.75548017766815,38.207590644292864],[139.7163611192702,38.209916083360596],[139.70147830617898,38.20908926016118],[139.68737064034272,38.202061266114626],[139.67915408699153,38.187566025751394],[139.66458133406124,38.07194000952383],[139.6445308779977,38.041347561041206],[139.64184370282467,38.029281114331994],[139.64391076127256,38.0011949729696],[139.6436007020109,38.00010976825125],[139.63507409119634,37.97969757698165],[139.63243859286675,37.96946564382445],[139.62954471211881,37.945332750406166],[139.62003624937338,37.921975003343775],[139.61740075104373,37.90957265895112],[139.6194161317491,37.895775051477514],[139.63104332888648,37.87929026973022],[139.65398766479885,37.85890391688231],[139.68830081632953,37.84296173839353],[139.7246293476662,37.820379136787764],[139.685665318,37.78035573992544],[139.66561486193646,37.76384511975654],[139.59533491427484,37.680439358189815],[139.54799930162645,37.638323066256575],[139.53714725174436,37.62437042915205],[139.53327151906538,37.61367340800139],[139.53575198776397,37.60364818041917],[139.53869754535552,37.597498683717376],[139.54412356984685,37.58866201454036],[139.5495495952375,37.577293198921936],[139.55285688713573,37.563082180298366],[139.55606082624612,37.51814952198278],[139.5535803566481,37.50649648732299],[139.54350345312182,37.50386098899345],[139.51544315018126,37.5089769560217],[139.502834101113,37.50755585451908],[139.4803548522947,37.50225902033752],[139.46717736244537,37.50122549156312],[139.41953169233378,37.50334422505587],[139.40387373288652,37.496161201378314],[139.39519209244125,37.48840973602056],[139.38139448496761,37.46081452107332],[139.3710591981235,37.45838572831876],[139.34072513205913,37.461822211425996],[139.3259973485988,37.461279609066665],[139.2635722190827,37.44732697196214],[139.2257450709782,37.44691356081212],[139.2087952012375,37.44112580021546],[139.19556603364546,37.429343574346476],[139.18853803869948,37.41812978835905],[139.186781041312,37.408440457560985],[139.18879642111804,37.399009508282134],[139.19732303283163,37.38978526547761],[139.20383426294094,37.37957916894349],[139.20817508361327,37.36733185508113],[139.20817508361327,37.35459361390416],[139.1939123881463,37.29658681942625],[139.18833133402396,37.283512682364474],[139.16011600235166,37.24077627190751],[139.15556847610426,37.22589345971535],[139.1594958838281,37.213697821797055],[139.17251834494564,37.196076158487884],[139.17840945922913,37.18956492837877],[139.18740115803715,37.189668281166334],[139.19499759286455,37.19230377949587],[139.20429935093418,37.192097073021515],[139.2100354346875,37.18548249012481],[139.23101606673814,37.14930898751986],[139.23520185777957,37.13990387576335],[139.2362870633972,37.129051825881064],[139.23458174015522,37.11556427677],[139.23458174015522,37.09068207541752],[139.24062788317033,37.03949656851192],[139.23923261919,37.027921048217806],[139.23132612510048,37.00094594999557],[139.22698530532764,36.961775213855006],[139.2145829609349,36.94565216731367],[139.20073367661794,36.950974839916896],[139.1901916850983,36.9519308543254],[139.17789269439234,36.95410126376238],[139.1675574084476,36.96022492114311],[139.15649865119175,36.988285224083896],[139.15138268416356,36.99489980698051],[139.14316613171164,37.000170802740385],[139.14177086773137,37.000739244420586],[139.1360347839782,37.003633124269314],[139.12921349460717,37.00818065051662],[139.1245626155724,37.01345164627649],[139.11634606312046,37.024613756319766],[139.11231530170986,37.0289287385704],[139.10451216040795,37.035569158989446],[139.09195478728347,37.03923818609344],[139.08001753088442,37.03657685024139],[139.07226606732522,37.02660329770403],[139.06311933798716,37.00905915055928],[139.05660810787785,37.000997625939604],[139.05397261044763,36.998723862815964],[139.0336120951221,36.986993312891016],[138.97397749176778,36.97198131038921],[138.96519249943438,36.967407944820835],[138.9605416203996,36.957356878816896],[138.95961144441267,36.927177843282934],[138.96395226508494,36.913457750175084],[138.96472741144092,36.90167552340672],[138.95868126842586,36.894440822885755],[138.92514326415042,36.88612091674686],[138.91708174042995,36.87847280507596],[138.9146012717314,36.869326077536485],[138.91057051032092,36.843229478457616],[138.90328413295643,36.83627899787747],[138.8901066431071,36.82666718234465],[138.83672488834296,36.8108283548447],[138.82246219287597,36.803593655223],[138.81419396358055,36.77902151133368],[138.80814782056538,36.767833563767894],[138.79414350841628,36.757808336185846],[138.76902876216766,36.750160224514744],[138.7440173687066,36.74742137339764],[138.72267500234946,36.74917837258387],[138.67952518074256,36.73065237081023],[138.66681277888657,36.76323436157652],[138.66960330594773,36.82258474409072],[138.66681277888657,36.83583974920511],[138.6576143736044,36.84997325346281],[138.64800255807157,36.85806061470562],[138.60459435404633,36.886482651952846],[138.5815466662456,36.90854848872182],[138.57446699355697,36.92296621381972],[138.55942915263327,36.98926707601477],[138.55524336159195,37.00063589073382],[138.5530729512557,37.002082831557416],[138.53586469909672,37.010041002490325],[138.51937991824863,37.01355499906397],[138.49969119739094,37.015389513515245],[138.4813977396141,37.01417511668832],[138.4242436058577,37.001669420407396],[138.38207563708093,36.9861664896918],[138.36574588406543,36.97740733488054],[138.35303348220955,36.96601268173994],[138.32357791618776,36.92560171214923],[138.29717125874666,36.91495636694283],[138.28626753112164,36.907669990477714],[138.2776375666203,36.89531932202904],[138.27376183484083,36.88405386009755],[138.27066124941715,36.85837067306808],[138.26394331283367,36.85082591418457],[138.2521610860654,36.84865550384849],[138.2342810412371,36.856045234000376],[138.2227055200436,36.858706569852345],[138.2154191426794,36.85976593704835],[138.20813276621428,36.85769888039904],[138.17847049371827,36.84436636001949],[138.16405276862045,36.83974131940643],[138.1460176941612,36.837131659498525],[138.1004390806991,36.835478013999236],[138.08534956293195,36.833178412453876],[138.07356733616356,36.82664134392293],[138.05806440634714,36.81044078211637],[138.0442151229295,36.80718516706186],[138.01196902894756,36.825762843880185],[138.00742150270017,36.83160228132017],[138.00561282756988,36.8387853049979],[138.00928185377452,36.86358999288389],[138.00835167868698,36.87433869087788],[138.00545779793916,36.882916978535675],[137.99848147983656,36.89041006147515],[137.98933475229703,36.89697296842779],[137.97331505764387,36.903639228167904],[137.95874230381438,36.905990506556705],[137.94261925727304,36.90699819600992],[137.92959679615515,36.910873927789524],[137.91249189678368,36.918211981997345],[137.8990043476724,36.92110586184607],[137.88474165310473,36.92213939151965],[137.87523318856088,36.92092499379346],[137.86872195935084,36.919245509872454],[137.86050540599982,36.91534394057042],[137.85321902863532,36.90916860634617],[137.8488782088622,36.90095205299487],[137.8479480328754,36.89242544218048],[137.8499634135808,36.873718574152875],[137.84531253544523,36.8613679057042],[137.8371993348815,36.8480353871234],[137.80107750911978,36.80338694874875],[137.78790001837112,36.79424022120919],[137.77534264524698,36.78736725499492],[137.73265791163342,36.77480988187055],[137.6947790866855,36.911959133407336],[137.68454715442775,36.92591177051186],[137.67167972204166,36.934955146163034],[137.63736657141024,36.95066478155444],[137.61674767456557,36.969216619951126],[137.61456389487736,36.975108174165584],[137.68327884200014,36.987982489000146],[137.80754467900007,37.0308052380001],[137.89673912900005,37.057521877000156],[137.9993741320002,37.11105409800017],[138.05339603000007,37.13011302300005],[138.07683353000007,37.14972565300012],[138.09750180400025,37.171059352],[138.16443255600015,37.16169930800005],[138.20695325900024,37.16920530200018],[138.24489497900012,37.183789445000016],[138.32510269600007,37.23104347200017],[138.43751590900015,37.32203684800008],[138.550629102,37.377915757000025],[138.57797285200016,37.40289948100012],[138.5968302050001,37.43529360200007],[138.6225396070001,37.47754346900014],[138.65973457800018,37.524432604000125],[138.7182723320001,37.564846096000124],[138.74659264400023,37.596869208000115],[138.76563561300023,37.63467031500009],[138.80942573500013,37.760771879000075],[138.8270238700002,37.797286966000044],[138.85767662900005,37.82782623900009],[138.93039446900005,37.8749231770001],[139.05988081300004,37.945941670000096],[139.06819343600014,37.955093852000104],[139.0913391500002,37.954658308000134],[139.13246598800018,37.95882893100004],[139.215943141,37.98993395400011],[139.23906219500012,38.00315458200011],[139.30453535200004,38.04193756700012],[139.40805097700016,38.13190338700012],[139.42514082100004,38.15127187700013],[139.43238366000006,38.169134833],[139.46216881600006,38.37498607000005],[139.47282962300008,38.41250234600007],[139.49268639400006,38.45416901200004],[139.52995853000007,38.50800202000006],[139.54871793192686,38.545003113482736],[139.54892947761326,38.544960232029055],[139.5623136739368,38.54224721933376],[139.6533158712306,38.50979441977667],[139.6802392926094,38.50416168881084],[139.69372684082103,38.49940745698858],[139.7017883654406,38.49439484274784],[139.70132327744736,38.486643378289344],[139.69961795510463,38.47822012026235],[139.69884280784927,38.46734223105916],[139.6999280125676,38.451761785977695],[139.696207310419,38.41553660742851],[139.70054813019217,38.39887095852795],[139.70938480026828,38.38768301006296],[139.7216321150302,38.37703766485636],[139.73455122336034,38.36794261326095],[139.74896894755884,38.359519355234156]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-06\",\"name\":\"Yamagata\",\"name_alt\":null,\"name_local\":\"山形県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Tohoku\",\"postal\":\"YT\",\"region_code\":\"JPN-THO\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[139.96275434813464,39.0982339538339],[139.97365807486017,39.09937083629504],[139.9845101256417,39.10169627536277],[139.99489708932919,39.10603709603518],[140.01215701743246,39.109809475027305],[140.02414594887642,39.108362535102884],[140.03520470613233,39.105106920048286],[140.06858768077677,39.077149969895146],[140.10429609628773,39.05738373557091],[140.12620690342578,39.048469550229555],[140.15054650331842,39.04500722870063],[140.16511925714792,39.04526561111902],[140.1780383645788,39.04118317286506],[140.18454959468795,39.034336045971685],[140.1949365583754,39.026739610244846],[140.21069786971094,39.019995836138946],[140.2858354028815,39.00831696215806],[140.31968346641884,39.00718008149556],[140.34278283106283,39.0038469511758],[140.4050529318473,38.97565745702602],[140.4211759792879,38.963875230257656],[140.43326826441887,38.939742336839345],[140.44892622296675,38.91540273694673],[140.4793636418185,38.89220001951523],[140.53290042531432,38.881709703040286],[140.54266727137752,38.85752513277865],[140.5771354507405,38.822230130216354],[140.5951188501548,38.79024241775335],[140.61604780536186,38.76543772986727],[140.61821821479873,38.75556753191606],[140.61635786372437,38.74740265630754],[140.60891645672905,38.736395575895074],[140.60674604729218,38.73231313674185],[140.6045756369561,38.71040233050317],[140.60597090093637,38.686372788973074],[140.6036454609693,38.67195506387509],[140.59899458193433,38.659785265277776],[140.59108808784492,38.64955333122123],[140.58194136030545,38.64327464510879],[140.56798872320104,38.64175018991932],[140.55212405817872,38.64404979146468],[140.54111697776625,38.642137763547],[140.53439904208216,38.63118235907859],[140.5346057476571,38.61916758921282],[140.5379130386558,38.607385362444546],[140.5488684431241,38.58464773210716],[140.55429446761553,38.56777537673227],[140.55491458613915,38.52436717450553],[140.55837690586947,38.50808909833319],[140.56519819613987,38.49651357713989],[140.57403486531678,38.488658758994546],[140.5820447121937,38.4800287953926],[140.5879358255778,38.465714423082176],[140.5850419457292,38.45289866843879],[140.57263960223582,38.4295409213764],[140.55972049390567,38.39478852027398],[140.55227908691035,38.38579682146613],[140.54313235937082,38.37938894324512],[140.52251346162694,38.35771068010382],[140.5119714701073,38.3331126977927],[140.50308312408686,38.3187208120159],[140.48277428560476,38.29518219690087],[140.47435102757777,38.28174632373384],[140.4682015317752,38.267819525950415],[140.46138024330355,38.244849352515615],[140.46308556474696,38.208210761017824],[140.46215538965947,38.19079580328372],[140.45905480423573,38.17513784383647],[140.4507865740411,38.15214183197995],[140.44070966961556,38.13945526854576],[140.4138896019236,38.11441803576372],[140.39652632103292,38.09116364238818],[140.37880130403687,38.078709622051406],[140.36066287679023,38.06863271672648],[140.34484988771214,38.06506704420869],[140.30857303321886,38.06165639952317],[140.29503380816368,38.05659210933837],[140.28232140540848,38.04511994093255],[140.27705040874935,38.03018545189704],[140.2695573258099,37.964246324008684],[140.27043582585264,37.925850735123305],[140.26738691637308,37.90670461752407],[140.26506147730527,37.87231395072823],[140.26707685801057,37.83105032041601],[140.27374311685128,37.79955353436807],[140.2692472683467,37.780536607078744],[140.26118574372714,37.768986925206434],[140.23095503134965,37.74741201395324],[140.22268680205428,37.74332957569939],[140.21447024960236,37.74059072548158],[140.20330813955889,37.73849783041055],[140.19245608877753,37.738937079082916],[140.18222415562025,37.74035818148491],[140.17256066324398,37.74307119418029],[140.16170861156314,37.74229604782428],[140.14930626806972,37.73849783041055],[140.13147789918565,37.7268447939521],[140.11737023334956,37.722607326966624],[140.10429609628773,37.72265900291069],[140.09390913260034,37.727361558788886],[140.07205000230567,37.745474148513125],[140.06104292189332,37.7522179235184],[140.04915734323674,37.756041978454476],[140.03634158679472,37.75717886001635],[140.007609491185,37.754336656111775],[139.99923790910213,37.754284980167625],[139.99055626775748,37.75653290486966],[139.97923912988193,37.761984767782764],[139.9655448751958,37.77294017225117],[139.94559777191955,37.79994110799578],[139.93236860612626,37.81159414355494],[139.91655561704806,37.81453970024691],[139.90430830318564,37.80805430855949],[139.88999393087516,37.80466950139632],[139.87821170500615,37.80552216211808],[139.85015140206553,37.81185252507396],[139.82560509659794,37.81149078986809],[139.80638146373354,37.80598725011146],[139.79067182834217,37.805754706114755],[139.77609907361335,37.809268704487124],[139.76478193573774,37.81645172816468],[139.72462934766622,37.82037913678772],[139.6883008163295,37.842961738393484],[139.65398766479882,37.85890391688227],[139.63104332888645,37.87929026973018],[139.61941613174906,37.89577505147747],[139.61740075104376,37.90957265895108],[139.62003624937336,37.921975003343704],[139.62954471211873,37.945332750406095],[139.63243859286672,37.96946564382441],[139.63507409119632,37.97969757698158],[139.64360070201081,38.00010976825121],[139.64391076127254,38.00119497296964],[139.6418437028246,38.02928111433195],[139.64453087799762,38.041347561041135],[139.66458133406127,38.071940009523786],[139.6791540869915,38.18756602575144],[139.6873706403427,38.202061266114555],[139.7014783061789,38.20908926016119],[139.71636111927023,38.209916083360525],[139.75548017766806,38.20759064429282],[139.7781661320613,38.208572496223695],[139.79366906097835,38.2119314640658],[139.80808678607627,38.21852020944016],[139.84441531831231,38.24087026614987],[139.85526736909378,38.25203237619323],[139.8636389511767,38.26572662998001],[139.8711837100601,38.285906277252934],[139.86922000529904,38.300711575079134],[139.86224368719644,38.31437999134354],[139.85526736909378,38.322312323854675],[139.84658572774913,38.33011546515658],[139.80560631737762,38.35435171226172],[139.79160200432898,38.35641876891103],[139.76292158376407,38.354610093780735],[139.74896894755886,38.35951935523411],[139.73455122336026,38.36794261326099],[139.7216321150301,38.37703766485632],[139.70938480026825,38.387683010062915],[139.7005481301921,38.39887095852791],[139.69620731041903,38.415536607428464],[139.69992801256763,38.45176178597765],[139.69884280784925,38.46734223105912],[139.69961795510454,38.47822012026239],[139.70132327744733,38.4866433782893],[139.70178836544062,38.49439484274777],[139.693726840821,38.499407456988536],[139.68023929260931,38.50416168881088],[139.65331587123052,38.509794419776625],[139.56231367393684,38.54224721933372],[139.54892947761323,38.5449602320291],[139.54871793192683,38.54500311348269],[139.58008873800014,38.60687897300005],[139.5992151490001,38.648254087000026],[139.6270589990001,38.68121429300004],[139.71615644600007,38.7456729190001],[139.747406446,38.77895742400011],[139.7738443330001,38.817116774000056],[139.81527369900004,38.941062635],[139.8575639610001,39.03228377100004],[139.8673056450001,39.06222210900012],[139.87403405000015,39.09845612200007],[139.88062584700003,39.11473216400012],[139.8808549493155,39.115106310035316],[139.88100223206732,39.11510631010816],[139.8856531111021,39.11510631010816],[139.96275434813464,39.0982339538339]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-42\",\"name\":\"Nagasaki\",\"name_alt\":null,\"name_local\":\"長崎県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":null,\"postal\":null,\"region_code\":null},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[129.78126061300023,32.55255768400009],[129.77515709700006,32.546210028000175],[129.77670332099999,32.565293687000164],[129.78166751400025,32.566961981000176],[129.7896427740001,32.56521230700018],[129.79070071700008,32.563015041000185],[129.79004967500012,32.55874258000007],[129.7862248060001,32.553859768],[129.78126061300023,32.55255768400009]]],[[[128.90316816500015,32.56004466400016],[128.90007571700008,32.56004466400016],[128.89014733200005,32.564683335],[128.89885501400025,32.57139720300013],[128.90357506600006,32.572495835],[128.90316816500015,32.56004466400016]]],[[[128.84115644599999,32.600246486000074],[128.8389591810002,32.59495677299999],[128.82618248800006,32.598822333],[128.82545006600006,32.601629950000145],[128.83285566500004,32.60594310100005],[128.84115644599999,32.600246486000074]]],[[[129.75635826900012,32.65171946800014],[129.75342858200005,32.649115302000055],[129.75001061300006,32.655829169000114],[129.74406985800024,32.66095612200003],[129.7476505870002,32.66636790600016],[129.75684655000006,32.66282786700019],[129.75635826900012,32.65171946800014]]],[[[129.7853296230002,32.69192129100007],[129.78012129000004,32.6896019550001],[129.7736108730002,32.69733307500009],[129.7705998060002,32.69993724200019],[129.7701929050002,32.70197174700003],[129.76823978000007,32.70477936400009],[129.76490319100006,32.705755927000055],[129.76026451900012,32.71572500200013],[129.77263431100002,32.70945872599999],[129.78012129000004,32.70062897300012],[129.7853296230002,32.69192129100007]]],[[[128.60238691500015,32.72317129100013],[128.5978296230002,32.70945872599999],[128.589691602,32.72007884300014],[128.59498131600017,32.73224518400012],[128.6026310560002,32.74001699400013],[128.61028079500014,32.73191966400019],[128.600840691,32.72943756700006],[128.60238691500015,32.72317129100013]]],[[[129.00464928500017,32.76211172100001],[129.00619550900004,32.756415106000034],[129.00692793100015,32.753078518],[129.00001061300023,32.75108470300016],[128.98918704500014,32.75372955900015],[128.9859318370001,32.74876536700019],[128.98780358200005,32.74502187700007],[128.98633873800011,32.74249909100014],[128.98259524800008,32.74176666900003],[128.98096764400023,32.74424876500014],[128.97673587300014,32.745266018],[128.96973717500023,32.746771552000055],[128.96517988400004,32.74502187700007],[128.96615644600016,32.75433991100003],[128.97852623800011,32.759670315000065],[128.98316491000006,32.75958893400009],[128.98658287900005,32.76487864800008],[128.9850366550002,32.77732982],[128.98764082099999,32.78839752800003],[128.99154707099999,32.79173411700005],[128.9996037120002,32.78860097900018],[129.00302168100004,32.77883535400004],[129.00586998800006,32.76886627800015],[129.00464928500017,32.76211172100001]]],[[[128.84229576900023,32.75946686400012],[128.8497827480002,32.74274323100009],[128.84206871299997,32.70386139100016],[128.85522575700017,32.68886442000009],[128.87842693200005,32.67392482500004],[128.89371185700023,32.65761750500009],[128.90113366000006,32.64691803600006],[128.90113366000006,32.64598216400019],[128.89926191500015,32.642523505],[128.8809311370002,32.64235931100008],[128.857899157,32.64340912400009],[128.81771562400004,32.634589588],[128.81548846500007,32.646810135000024],[128.7932235040001,32.65249258000007],[128.78809655000006,32.64850495000009],[128.77963300900015,32.646795966000084],[128.77588951900012,32.642523505],[128.77222741000006,32.63190338700004],[128.77515709700018,32.62714264500015],[128.78012129000004,32.62482330900018],[128.79495874300002,32.6100934200001],[128.79222837900008,32.597941535000146],[128.790200827,32.586384515000034],[128.77311026499999,32.571665843000076],[128.75538170700023,32.580511786000145],[128.73604181900012,32.60349384100009],[128.7124129570001,32.60431549700003],[128.66285241000017,32.60154857000008],[128.6526310410001,32.59107501300018],[128.61854433500005,32.609536275],[128.6004388930002,32.61417790899999],[128.60381873200006,32.63059803500015],[128.61060631600006,32.642523505],[128.61353600400017,32.65021393400009],[128.61817467500012,32.65965403900013],[128.62427819100017,32.66730377800015],[128.63184655000023,32.66986725500006],[128.63306725400017,32.665472723000065],[128.63624108200023,32.643744208],[128.63868248800017,32.635158596000096],[128.63965905000023,32.641262111000074],[128.65267988400004,32.66791413000011],[128.65430748800023,32.677964585],[128.65455162900017,32.68886953300016],[128.65349368600002,32.69961172100007],[128.6506453790001,32.70929596600003],[128.6479598320001,32.71385325700008],[128.64503014400012,32.717962958000115],[128.64258873800023,32.722398179000024],[128.64226321700002,32.7280134140001],[128.6440535820001,32.73330312700007],[128.65040123800023,32.74453359600007],[128.64176757200008,32.76138397300012],[128.6509395940001,32.77541308200013],[128.66081790500002,32.78278229400003],[128.67904707100004,32.782904364],[128.68573849700024,32.77091567000015],[128.699652891,32.743771302000155],[128.70536494800004,32.747461232000106],[128.72184869000003,32.75428343600005],[128.74317467500006,32.75828685099999],[128.77922382800003,32.776625290000155],[128.78412547900004,32.78818120000007],[128.80849038900013,32.79990841200005],[128.81202233200023,32.782416083000115],[128.81080162900005,32.774888414000046],[128.81055748800006,32.766058661],[128.8276880550002,32.761902974000165],[128.84229576900023,32.75946686400012]]],[[[128.68165123800011,32.80886465100009],[128.67701256600017,32.80170319200015],[128.6739201180002,32.80438873900012],[128.67571048300007,32.8096784530001],[128.68165123800011,32.80886465100009]]],[[[128.90853925900004,32.801336981000034],[128.90723717500012,32.79401276200004],[128.8948673840001,32.78172435100008],[128.88233483200005,32.76528554900007],[128.87533613399998,32.76349518400009],[128.86646569100006,32.76496002800015],[128.84791100400005,32.774888414000046],[128.84009850400005,32.77757396000014],[128.83521569100017,32.78766510600009],[128.83594811300011,32.81899648600013],[128.8346460300002,32.82705312700007],[128.83725019600004,32.83380768400012],[128.84620201900023,32.83486562700007],[128.85694420700023,32.830023505],[128.86345462300002,32.81883372599999],[128.86622155000012,32.80735911700005],[128.86988366000006,32.801092841000084],[128.87387129000004,32.79873281500012],[128.87501061300006,32.80174388200014],[128.87419681100008,32.804348049000126],[128.87338300900015,32.807847398000135],[128.87077884200008,32.816066799000126],[128.86622155000012,32.82538483300006],[128.86296634200008,32.835272528000175],[128.86817467500023,32.837836005],[128.89226321700008,32.823716539000046],[128.89975019600016,32.81830475500003],[128.90284264400023,32.81415436400006],[128.90853925900004,32.801336981000034]]],[[[128.95541425900015,32.863104559000035],[128.95801842500023,32.841498114000146],[128.95443769599999,32.83071523600013],[128.94695071700014,32.82802969000015],[128.94459069100017,32.82339101800012],[128.94776451900023,32.819810289000074],[128.94939212300008,32.81338125200013],[128.94507897200006,32.804348049000126],[128.94044030000012,32.80442942900011],[128.93848717500006,32.812933661000145],[128.93197675899998,32.81712474200016],[128.92302493600008,32.81806061400006],[128.92058353000002,32.820746161000145],[128.924082879,32.82461172100015],[128.92400149800002,32.82965729400017],[128.920176629,32.83502838700004],[128.91570071700008,32.83885325700005],[128.90992272200018,32.84243398600002],[128.89576256600017,32.856431382],[128.88965905000006,32.85814036700019],[128.88314863399998,32.85594310099999],[128.88070722700016,32.86261627800015],[128.8908797540001,32.870917059000035],[128.90894616,32.86644114800008],[128.92432701900023,32.85150788000011],[128.92945397200018,32.84979889500012],[128.92676842500006,32.86220937700013],[128.92896569100017,32.864691473000065],[128.93767337300008,32.860785223000065],[128.94214928500006,32.86517975500006],[128.94336998800023,32.87628815300009],[128.946543816,32.880194403000175],[128.95118248800023,32.876450914000046],[128.95313561300011,32.87421295800006],[128.95541425900015,32.863104559000035]]],[[[129.60206139400012,32.88092682500012],[129.59278405000023,32.878322658000016],[129.59253991,32.88405996300018],[129.59929446700008,32.88857656500015],[129.60206139400012,32.88092682500012]]],[[[129.19182376400025,32.919094143000095],[129.18791751400025,32.916286526000036],[129.1841740240002,32.92076243700011],[129.19450931100002,32.92426178600009],[129.19182376400025,32.919094143000095]]],[[[128.95207767000014,32.9168968770001],[128.94857832100016,32.90444570500007],[128.93425540500007,32.91071198100012],[128.931407097,32.914862372000144],[128.93181399800014,32.92027415600002],[128.93506920700005,32.924180406000104],[128.94231204500002,32.92446523600013],[128.94670657599997,32.92084381700009],[128.95207767000014,32.9168968770001]]],[[[129.02947024800014,32.891750393],[129.0299585300002,32.88861725500014],[129.02816816500004,32.85565827000006],[129.02409915500007,32.848049221000124],[129.01791425899998,32.848049221000124],[129.01742597700004,32.856675523000135],[129.01368248800006,32.85879140800013],[129.00660241,32.864691473000065],[128.99683678500017,32.86664459800012],[128.98267662900005,32.86225006700012],[128.97754967500023,32.86399974200013],[128.9738061860002,32.869045315000065],[128.969493035,32.87148672100001],[128.96802819100006,32.8736839860001],[128.96412194100006,32.88955312700001],[128.96461022200006,32.89826080900009],[128.97299238399998,32.898504950000145],[128.99170983200017,32.88983795800006],[128.99561608200017,32.89052969000009],[128.99447675900004,32.89533112200017],[128.98878014400023,32.90021393400015],[128.98373457100004,32.90664297100007],[128.98226972700016,32.91242096600003],[128.97966556100008,32.91648997599999],[128.97934004000004,32.92023346600003],[128.98267662900005,32.923244533000016],[128.98471113399998,32.92609284100014],[128.984629754,32.929185289000046],[128.9924422540001,32.929429429],[129.00961347700004,32.92450592700011],[129.02068118600008,32.91669342700014],[129.02458743600008,32.908636786],[129.02784264400012,32.89980703300013],[129.02947024800014,32.891750393]]],[[[129.59815514400012,32.94065989799999],[129.60206139400012,32.93748607000013],[129.6017358730002,32.93834056200002],[129.60336347700004,32.93996816600004],[129.60596764400012,32.93838125200001],[129.60547936300011,32.93968333500011],[129.60547936300011,32.94122955900015],[129.60588626400013,32.94310130400011],[129.60808353000013,32.94257233300014],[129.6112573580002,32.943426825000145],[129.61540774800014,32.94122955900015],[129.62517337300008,32.93809642100008],[129.62517337300008,32.928452867000104],[129.6163029310002,32.916286526000036],[129.60051516999997,32.921616929],[129.59221438900008,32.94065989799999],[129.59400475400017,32.94558340100015],[129.59620201900017,32.94676341400016],[129.59848066500015,32.94798411700019],[129.59864342500012,32.94647858300014],[129.59815514400012,32.94065989799999]]],[[[129.18987063900008,33.01976146],[129.1958927740002,33.01504140800007],[129.18978925900004,33.00731028900016],[129.17839603000007,33.00731028900016],[129.1748966810001,33.00999583500014],[129.1773380870002,33.016058661000145],[129.1841740240002,33.015773830000015],[129.18710371200015,33.018011786],[129.18987063900008,33.01976146]]],[[[129.3536076180002,33.02191803600009],[129.35214277400016,33.00039297100007],[129.3418888680002,33.005072333000086],[129.33733157599997,33.009059963000155],[129.34408613400004,33.01906972900004],[129.3536076180002,33.02191803600009]]],[[[129.2461857430001,33.02285390800007],[129.24691816499998,33.01504140800007],[129.2552189460001,33.02081940300003],[129.2594507170002,33.01850006700009],[129.25684655000012,33.01548899900017],[129.2526147800002,33.00792064000002],[129.25570722700016,33.00320058800013],[129.25188235800024,32.99607982000008],[129.24586022200006,32.99229564000014],[129.2400822270001,32.989813544000114],[129.23633873800023,32.99201080900009],[129.23959394599999,32.99681224200019],[129.23796634200008,32.99730052299999],[129.23487389400006,32.99839915600005],[129.22421308700004,32.996975002000156],[129.2232365240001,33.00039297100007],[129.22803795700005,33.00731028900016],[129.23186282599997,33.012884833000086],[129.24146569100017,33.01976146],[129.2461857430001,33.02285390800007]]],[[[129.60751386800004,33.06386953300007],[129.62199954500002,33.061224677],[129.62891686300011,33.063015041000156],[129.63502037900017,33.06236399900003],[129.63298587300008,33.057806708000086],[129.62777754000015,33.05414459800012],[129.62785892000014,33.05162181200002],[129.62997480600004,33.048692124000084],[129.6289982430001,33.04474518400009],[129.62558027400004,33.03819407800002],[129.61052493600008,33.028469143000066],[129.60783938900025,33.0244408220001],[129.5879826180001,33.02570221600011],[129.58920332099999,33.02195872600008],[129.5865991550002,33.01569245000003],[129.58139082099999,33.011175848000065],[129.56869550900004,33.00551992400007],[129.56495201900012,33.00108470300002],[129.5594181650001,32.99730052299999],[129.5481876960001,32.99823639500006],[129.54086347700016,33.002386786],[129.53907311300011,33.005682684000035],[129.53581790500007,33.00816478100016],[129.539805535,33.009304104000094],[129.5558374360002,33.00775788000006],[129.55827884200002,33.01109446800008],[129.566172722,33.0149193380001],[129.56674238399998,33.01788971600003],[129.5635685560002,33.019232489],[129.56031334700018,33.02252838700004],[129.5593367850001,33.02688222900004],[129.56120853000007,33.02989329600014],[129.5675561860002,33.03009674700009],[129.57634524800008,33.028143622000144],[129.5826115240002,33.03017812700007],[129.59669030000023,33.05190664300015],[129.60401451900023,33.057481187000135],[129.60523522200018,33.060980536000145],[129.60751386800004,33.06386953300007]]],[[[129.6808374360002,33.14150625200001],[129.67994225400017,33.13491445500013],[129.67367597700004,33.13678620000009],[129.6721297540001,33.13894277600009],[129.6724552740002,33.14305247600005],[129.6808374360002,33.14150625200001]]],[[[129.54810631600012,33.147080796000026],[129.54810631600012,33.13491445500013],[129.53988691499998,33.13617584800015],[129.53020267000014,33.13060130400014],[129.52149498800023,33.13585032800002],[129.51254316500004,33.13678620000009],[129.50326582100016,33.14008209800012],[129.50530032600003,33.14248281500009],[129.5136011080002,33.14297109600007],[129.51840254000015,33.14427317900008],[129.5260522800002,33.146144924000126],[129.5298771490002,33.14992910400015],[129.53663170700023,33.150539455000015],[129.54232832100004,33.15241120000009],[129.54810631600012,33.147080796000026]]],[[[129.09927084900008,33.03568710900008],[129.09725354000014,33.01810575300014],[129.09799238400015,32.98826732000008],[129.10971113400015,32.98289622600011],[129.16962821600018,33.00581653600001],[129.18280727800018,32.98468553900001],[129.17595462300014,32.966986395],[129.1563250530002,32.958490107],[129.13721764400006,32.938177802000055],[129.1175434940001,32.93584108099999],[129.10035241,32.919012762000094],[129.09253686500003,32.906607937],[129.0905507800002,32.89147978700002],[129.09848066500015,32.87514883000016],[129.09799238400015,32.86847565300009],[129.09657225200024,32.85694237600008],[129.0861922540001,32.85724518400009],[129.06791971900006,32.861182834000104],[129.06149503700024,32.84838424200005],[129.06869550900015,32.839178778000175],[129.06853274800008,32.82591380400005],[129.06495201900012,32.81781647300012],[129.0457228280001,32.82209273200009],[129.04712975400005,32.841457424000154],[129.04041432000022,32.86272819599999],[129.0414753840001,32.89599958800015],[129.04781951700008,32.909964614],[129.033050977,32.933335679],[129.02426191500004,32.937892971000124],[128.98878014400023,32.94415924700017],[128.97942307000008,32.94760914500016],[128.98146031200022,32.960351967000136],[128.9980757020002,32.96653417900016],[129.0204204190001,32.980624687],[129.03503841200015,32.96740447400005],[129.05445397200006,32.979315497000144],[129.04258414900008,33.00015967200012],[129.04862219400016,33.03960279600015],[129.0652106050002,33.043953982000104],[129.06764512499998,33.01911582900014],[129.07773711800021,33.02039796300009],[129.08399498800011,33.02338288000014],[129.08432050900004,33.043198960000055],[129.08099664400018,33.056781847000096],[129.0858752780001,33.07620750500014],[129.10172462800003,33.0829730060001],[129.10661909900003,33.10118815400007],[129.09854704900013,33.114491157000046],[129.09626241000004,33.12537451900012],[129.10818029800024,33.16668808900009],[129.1176668650002,33.158859227000065],[129.11638737100012,33.140655876],[129.11212829800004,33.131534918000014],[129.10859376800025,33.122417884000086],[129.11590807200005,33.1139754040001],[129.1239111050002,33.10917292300003],[129.11831008100003,33.08853075900005],[129.11196658400016,33.070308784000034],[129.10271233400024,33.05449343500011],[129.09927084900008,33.03568710900008]]],[[[128.90316816500015,33.16608307500003],[128.8985294930002,33.165798244000186],[128.89698326900006,33.170599677000084],[128.89942467500012,33.173529364],[128.90316816500015,33.16608307500003]]],[[[129.59278405000023,33.19285716400013],[129.5931095710001,33.183539130000085],[129.59026126400013,33.18581777600015],[129.58806399800008,33.18463776200004],[129.59221438900008,33.17796458500008],[129.59221438900008,33.16933828300007],[129.5822046230002,33.16111888200008],[129.58098392000002,33.16543203300007],[129.58806399800008,33.170721747000144],[129.5861108730002,33.174465236000074],[129.57894941500015,33.17979564000014],[129.57691491000006,33.18402741100006],[129.58122806100002,33.191758531000076],[129.58570397200018,33.1947289080001],[129.59278405000023,33.19285716400013]]],[[[129.13868248800023,33.18724192900011],[129.1329044930001,33.157985744000186],[129.1229761080002,33.16172923400005],[129.12240644600004,33.16795482],[129.12240644600004,33.17165761900013],[129.11931399800002,33.17479075700011],[129.11801191500004,33.19830963700015],[129.1197208990002,33.21275462400003],[129.13046308700004,33.20563385600015],[129.13664798300013,33.196437893],[129.1389266290001,33.1933861350001],[129.14348392000014,33.18976471600014],[129.13868248800023,33.18724192900011]]],[[[129.0264591810002,33.20689524900017],[129.02214603000013,33.19904205900009],[129.01425214900016,33.19973379100004],[129.01457766999997,33.21153392100011],[129.02247155000023,33.213405666000156],[129.0264591810002,33.20689524900017]]],[[[129.06194095100003,33.2102318380001],[129.06731204499997,33.20982493700011],[129.08448326900012,33.214056708000115],[129.09571373800011,33.211981512000094],[129.0939233730002,33.20844147300012],[129.08806399800008,33.20791250200013],[129.08383222700004,33.20563385600015],[129.08236738399998,33.201931057000124],[129.08562259200002,33.19721100500011],[129.09074954500014,33.1947289080001],[129.09262129000004,33.19216543200018],[129.09115644600016,33.18850332200013],[129.07732181100008,33.18768952000012],[129.0612085300002,33.19033437700001],[129.05713951900012,33.18602122600011],[129.05119876400008,33.182766018000066],[129.0460718110002,33.18581777600015],[129.04363040500007,33.19245026200012],[129.04070071700014,33.19407786700005],[129.032969597,33.19489166900014],[129.029063347,33.19961172100007],[129.03215579500002,33.20538971600003],[129.036387566,33.20937734600001],[129.04102623800023,33.20966217700014],[129.0455835300002,33.21775950700005],[129.0490014980002,33.219387111000074],[129.06194095100003,33.2102318380001]]],[[[129.14958743600002,33.26902903900019],[129.13428795700005,33.258612372000115],[129.12728925899998,33.24750397300009],[129.11915123800006,33.246527411],[129.10320071700008,33.249172268000095],[129.09213300900004,33.25543854400014],[129.085785352,33.26007721600017],[129.0797632170002,33.261297919000086],[129.0750431650001,33.259914455000015],[129.07618248800011,33.26878489800005],[129.089121941,33.28327057500009],[129.10377037900005,33.28896719000009],[129.11312910200016,33.29140859600004],[129.12826582100016,33.301581122000144],[129.13550866000017,33.29987213700015],[129.14332116000017,33.284816799000126],[129.15015709700018,33.276516018000066],[129.14958743600002,33.26902903900019]]],[[[129.6768734760001,33.36241888800005],[129.7010078850001,33.35300693100005],[129.7441637460001,33.36431425700012],[129.8074650400001,33.34813060099999],[129.81454436463432,33.331246004800775],[129.8101957540309,33.32509796769709],[129.79479617700204,33.31724315045112],[129.7641003766311,33.288226834001264],[129.75820926324687,33.27910594308479],[129.7594494975963,33.268202216359256],[129.7666841981173,33.25610993122835],[129.7981034689,33.216396592728486],[129.80321943592824,33.20629384988122],[129.80647505098275,33.196682034348484],[129.8150016617973,33.18595917387684],[129.82921268132023,33.17508128557286],[129.86218224481482,33.159862575697346],[129.90517703679083,33.14986318653693],[129.91933637857093,33.14428213151517],[129.93013675340845,33.132086492697496],[129.93587283716184,33.11846975417576],[129.93540775006775,33.10741099691997],[129.93091189976442,33.09896190047128],[129.915408969948,33.086740424131264],[129.9171142931901,33.078317166104355],[129.92517581601103,33.06878286403803],[129.99716108601527,33.01718394598241],[130.01576460215475,32.99976898914748],[130.03219770705854,32.98754751190809],[130.0595345395873,32.97323314139642],[130.10340783070686,32.959952296960935],[130.20410127959255,32.94357262887702],[130.20378665500007,32.928900458000115],[130.1885362940001,32.915321158],[130.163596061,32.90929081300011],[130.14913025100012,32.896413044000084],[130.13463981600003,32.889044178000105],[130.1154077480002,32.88027578300016],[130.10531660200016,32.87592194200006],[130.12231510100017,32.868816108000104],[130.13252880200017,32.85593935700014],[130.15363495200003,32.842457332],[130.17828428900012,32.8424949530001],[130.19500237200023,32.84494011200009],[130.24358790200003,32.87528409700015],[130.30728451900004,32.8762989750001],[130.33941978699997,32.85657931100015],[130.35124759200002,32.833929755],[130.37224368600008,32.79340241100006],[130.37484785200016,32.78017812700013],[130.37578209400007,32.74788443800004],[130.3737062170002,32.73896888899999],[130.35499108200005,32.717230536],[130.3509363310002,32.70406338500008],[130.36082701600006,32.687963369000144],[130.34170370900003,32.667257674000055],[130.30557751,32.65143396800006],[130.268230677,32.65036133400004],[130.25904566300002,32.64386571100012],[130.25993899800008,32.63271719000015],[130.25391686300023,32.62482330900018],[130.2365080210001,32.622476706000086],[130.23154564,32.61001434800012],[130.22307901800016,32.60588090300014],[130.1984120440001,32.60297819100005],[130.18148028400006,32.59232724600004],[130.16598697300006,32.592955657000076],[130.1730538790001,32.60363378100011],[130.16956158300005,32.61909223],[130.15548756000024,32.625654336000096],[130.14704924400016,32.63399204700012],[130.12945734900015,32.64113420200006],[130.128792608,32.66131741900007],[130.12813256,32.68387054600008],[130.14222326700005,32.68206490400014],[130.15350770000012,32.68619748900012],[130.16692510400006,32.69803794700006],[130.1774802260002,32.70517956500005],[130.19013168300003,32.71410604900002],[130.20132314600008,32.724250206000036],[130.2103138240001,32.7332498410001],[130.20758542900015,32.75216234500006],[130.19583342700017,32.75739113400006],[130.18681992200018,32.76206462600011],[130.18287194100012,32.7690290390001],[130.18604576900017,32.77435944200009],[130.19023316200006,32.783481734000176],[130.17842736700007,32.79292178700008],[130.14809900800006,32.795353716000065],[130.13821426500013,32.78884735300012],[130.12622206300014,32.78887009200007],[130.09378935499998,32.79486030900007],[130.06416680700013,32.78481126800013],[130.05004571000003,32.77236437400002],[130.03241165700004,32.75934986500012],[130.00844579100013,32.75462632199999],[129.99080803500013,32.74691678000009],[129.97037983800007,32.74456321900014],[129.96267180600015,32.752894191000124],[129.96201159100022,32.76180925300004],[129.95850841299998,32.76478227299999],[129.94932582400023,32.7576612160001],[129.9548908950001,32.745171077000165],[129.9421362830001,32.728521850000064],[129.91676459900023,32.69827854900008],[129.9034436420001,32.681137185000054],[129.89492938400022,32.662689399],[129.87445290399998,32.64898869200006],[129.85544474200006,32.64008608000002],[129.84127669600016,32.63941524900004],[129.8377245930001,32.63166779],[129.8322856010001,32.61580479700014],[129.80854332200013,32.59755228000007],[129.78582955400006,32.58310535600005],[129.78290880300008,32.573503108000025],[129.77656135300018,32.57645720100011],[129.763146868,32.579976042000126],[129.74961151099998,32.57269832500005],[129.74035124000014,32.568442387000076],[129.73400490800012,32.580920387],[129.7495901690002,32.59171044200018],[129.76168735000024,32.59598029900006],[129.77519728400003,32.60085152000012],[129.78297457600004,32.60741488500015],[129.79416252400003,32.632314333000025],[129.8046153490001,32.647683737000094],[129.81449596100006,32.662551104],[129.8180835100001,32.67743817500006],[129.8138718150001,32.68337935000001],[129.80324928400015,32.67799558400016],[129.79688259100024,32.684506210000094],[129.79692255500007,32.692244529000064],[129.8089597970002,32.694068048000034],[129.8153195470001,32.701797723000126],[129.82595416600006,32.70480813600015],[129.834378031,32.69412163700004],[129.84569353600014,32.70127403300013],[129.85906009200002,32.724514065],[129.82379517000007,32.72198462400017],[129.82398522200006,32.73607005400014],[129.80372155000023,32.772284247000144],[129.79330488400015,32.78017812700013],[129.78467858200023,32.78489817900014],[129.77865644600016,32.79100169500013],[129.77637780000018,32.803290106000176],[129.7710067070001,32.8139509140001],[129.75450898700004,32.818482657000075],[129.7443432810002,32.80988554],[129.73271004500012,32.805570175000085],[129.7210349420001,32.82707413800013],[129.69809003999998,32.839544989],[129.68702233200023,32.85480377800015],[129.67839603000007,32.87042877800006],[129.67465254000004,32.88051992400001],[129.6733504570001,32.89240143400015],[129.66968834700006,32.904974677000055],[129.6613061860002,32.914740302],[129.6518660820001,32.92279694200012],[129.63843013700014,32.926546363000014],[129.63112936300004,32.95653358600005],[129.63240266500023,32.979106667000096],[129.6462691950002,32.98956329800011],[129.65351883600025,33.004253288000186],[129.64972081700014,33.017049060000105],[129.65186151400005,33.03355786700003],[129.66065830000005,33.039696591000094],[129.6634167340002,33.069023976000054],[129.68161222900008,33.094092267000136],[129.72019752700024,33.07896768700009],[129.73471113400004,33.06024811400012],[129.73536217500023,33.05784739800008],[129.73422285200004,33.049058335000026],[129.73471113400004,33.04661692900008],[129.73796634200008,33.04612864799999],[129.7443368410002,33.05152376200009],[129.75015446500018,33.057039056000164],[129.7552662950001,33.055216089000126],[129.75966533900007,33.0478894380001],[129.75312015100016,33.041761967000085],[129.73863191000024,33.01911825200007],[129.73578382200006,33.00384361900002],[129.7416637690001,32.991035481000054],[129.75183863700025,32.995939633000106],[129.76632809800017,33.017953982],[129.79695900000016,33.010061728],[129.81230757300003,32.99480358000007],[129.82327699500016,32.97953752800014],[129.81600930400023,32.97096745100008],[129.8138449190001,32.96179569800013],[129.81608896700007,32.937947697000126],[129.8146693040002,32.92265535000003],[129.80667695500009,32.91469654700002],[129.80082747500015,32.92386666000009],[129.79567396600024,32.94779418000009],[129.7869448060001,32.94780487100009],[129.78993525000024,32.91224538900015],[129.80379470800008,32.902460108000085],[129.8074772800002,32.88594190900001],[129.79439983400007,32.87492070200001],[129.81481435200013,32.86331302600014],[129.82283367199997,32.85843758800017],[129.83158612900004,32.85050825100011],[129.84178867900008,32.844410601000064],[129.84543955299998,32.838308484000024],[129.8505281490002,32.84137051100005],[129.85488454600014,32.847485752000026],[129.86870212600016,32.85301349600017],[129.8745141320002,32.86096322200014],[129.88320558400008,32.87569270700011],[129.93560964700006,32.860488941000156],[129.98787790900005,32.84171406100016],[130.00314754200022,32.84537871600004],[129.98569111600014,32.86308717000013],[129.93122442100017,32.92889427000013],[129.92972526500003,32.957641312000035],[129.93918371700013,32.97232342400018],[129.94760175900015,32.995062567],[129.94562264200025,33.01575771400012],[129.91208162100006,33.03650814800007],[129.88144366600014,33.062762999000185],[129.85301131300005,33.05416733900013],[129.8340697390001,33.04313491700019],[129.82385938500013,33.043118812],[129.82235552400002,33.06267999000006],[129.79751505900006,33.07730904400016],[129.78658933900024,33.07056414200004],[129.77495184000023,33.058313960000035],[129.76109609900007,33.05706266500006],[129.74647953500002,33.06559011900008],[129.74244225400005,33.07636139500009],[129.73322558500004,33.10163252100007],[129.73906641500005,33.101034589000065],[129.74919681100008,33.09813060100005],[129.74558543700002,33.1163347600001],[129.74628823800012,33.12489568800011],[129.76014485700014,33.12981531600012],[129.75867661200007,33.13894149100018],[129.74773003700003,33.13952432900008],[129.72852623800023,33.12230052300005],[129.7236446410001,33.13277525000014],[129.7214375570001,33.14557295600015],[129.72285634600004,33.15840396900016],[129.709764185,33.16257034400009],[129.71216881600006,33.141058661000116],[129.70801842500006,33.119208075000174],[129.70411217500012,33.11180247599999],[129.696862257,33.114265208000106],[129.68074700600008,33.11487864500005],[129.67348960600006,33.10386878300001],[129.66651451900012,33.10126373900012],[129.66233194300017,33.126511172],[129.68360436300023,33.13104889500012],[129.68702233200023,33.142238674000126],[129.6832788420002,33.152329820000105],[129.67432701900006,33.160223700000174],[129.66325931100008,33.16600169500005],[129.65284264400023,33.17015208500008],[129.656097852,33.17894114800005],[129.656097852,33.184881903000175],[129.65267988399998,33.19025299700003],[129.64551842500023,33.19745514500006],[129.63803144599999,33.19277578300013],[129.6281030610002,33.19179922100007],[129.61744225400005,33.19464752800012],[129.60010826900017,33.20831940300015],[129.59009850400017,33.21112702],[129.5641382170002,33.21112702],[129.55214434600018,33.22583873400011],[129.56334175499998,33.25549040800017],[129.56534239400017,33.27206736000012],[129.5738202470002,33.27683511400009],[129.58091237300002,33.2750844130001],[129.5914984900002,33.2834101590001],[129.58934295200018,33.28991462600014],[129.584325775,33.301145664],[129.57146659700018,33.320639601000025],[129.57628043000014,33.34789650300009],[129.5804127600001,33.37100726900003],[129.5924152790001,33.38111521000015],[129.6206160820001,33.35927969],[129.64437910200004,33.362372137000094],[129.6731846810001,33.39853911700011],[129.67960253200013,33.38967253400013],[129.6768734760001,33.36241888800005]]],[[[129.55568420000023,33.41080491100017],[129.5621633010002,33.366393867000156],[129.55587394100021,33.35314538100006],[129.54202815300008,33.335676701000025],[129.52752846200022,33.32347747300015],[129.53831700300023,33.31559461200011],[129.52634014200012,33.30338426900015],[129.51434189400018,33.29804494000014],[129.514386228,33.28801229300002],[129.50114810099998,33.28003244000014],[129.50247802800018,33.26525992000008],[129.4930951260001,33.245163286000135],[129.44660473600018,33.198519605000044],[129.41259095300003,33.17831786000009],[129.3760213100002,33.16971922700007],[129.35808353000007,33.17694733300003],[129.34250066400003,33.1796201800001],[129.3449601670002,33.19019663600007],[129.34986902400013,33.21346751500012],[129.36070093200013,33.19871806800002],[129.38478198600004,33.183493173],[129.40015709700006,33.18569570500007],[129.40650475400017,33.194037177000055],[129.40308678500017,33.19765859600001],[129.3960067070001,33.202582098000065],[129.38466500200005,33.20409987500007],[129.37765688400006,33.21463952800012],[129.38200542000018,33.22891994300012],[129.39396140600016,33.239525906000054],[129.41676916100008,33.2369296050001],[129.4084318000001,33.253823673000014],[129.40641102400005,33.27495079100005],[129.43726510800005,33.29667988000007],[129.44089618799998,33.32526007500003],[129.44266269100015,33.348066089000085],[129.49512950000022,33.35829457300004],[129.50830966200024,33.38209177600011],[129.51591417800014,33.37839513000016],[129.52291622400006,33.36837711800011],[129.53930970000013,33.37948754900013],[129.5234559190002,33.39056654500011],[129.52532080700004,33.40492725000003],[129.55568420000023,33.41080491100017]]],[[[129.68181399800008,33.42523834800015],[129.68555748800011,33.407782294000086],[129.6808374360002,33.41364166900003],[129.68311608200023,33.41775136900016],[129.68254642000002,33.41856517100008],[129.68091881600017,33.422267971000124],[129.68181399800008,33.42523834800015]]],[[[129.43360436300011,33.356675523000106],[129.42416425899998,33.352321682000124],[129.40967858200023,33.35264720300013],[129.40617923300024,33.36436595300013],[129.4112248060001,33.396266994000044],[129.4198511080002,33.411932684000035],[129.42139733200023,33.41758860900002],[129.42180423300013,33.42654043200004],[129.42457116000017,33.435899156000076],[129.43116295700017,33.44065989799999],[129.43417402400004,33.43903229400014],[129.43523196700014,33.43431224200005],[129.43108157599997,33.424953518],[129.4318953790001,33.41950104400017],[129.4314884770001,33.41388580900018],[129.43132571700014,33.40928782800013],[129.43376712300008,33.40468984600001],[129.43474368600002,33.39634837400003],[129.4322208990002,33.38601308800013],[129.43360436300011,33.356675523000106]]],[[[129.54013105600004,33.44643789300012],[129.54493248800023,33.44220612200003],[129.53752688900025,33.44228750200001],[129.53565514400006,33.439764716000084],[129.53500410200004,33.43724192900008],[129.53191165500007,33.436102606000034],[129.528575066,33.43569570500013],[129.5255639980002,33.436712958],[129.52149498800023,33.433986721000124],[129.51286868600008,33.4302025410001],[129.51221764400012,33.42523834800015],[129.50603274800002,33.42523834800015],[129.5070093110002,33.438015041000185],[129.517832879,33.44082265800013],[129.52458743600002,33.449204820000105],[129.52637780000012,33.453558661],[129.53142337300008,33.45278554900007],[129.5330509770001,33.449204820000105],[129.54013105600004,33.44643789300012]]],[[[129.72689863400004,33.45909251500002],[129.72722415500007,33.45014069200009],[129.7154240240001,33.46385325700011],[129.7256779310002,33.46080149900003],[129.72689863400004,33.4604352890001],[129.72689863400004,33.45909251500002]]],[[[129.78980553500017,33.43227773600002],[129.78899173300024,33.42861562700007],[129.78012129000004,33.42727285400018],[129.76897220099997,33.429917710000055],[129.76408938900013,33.42877838700004],[129.7598576180001,33.42409902600012],[129.75814863399998,33.418361721000124],[129.75562584700018,33.416530666000156],[129.75171959700018,33.415961005],[129.7476505870002,33.41144440300003],[129.73829186300006,33.40700918200015],[129.72673587300008,33.40997955900018],[129.7207951180002,33.417466539000046],[129.72510826900023,33.42344798400016],[129.73568769599999,33.4310570330001],[129.74154707100004,33.43903229400014],[129.75188235800013,33.453192450000174],[129.75261478000007,33.45579661700016],[129.74691816499998,33.46458567900014],[129.74878991000006,33.469102281000104],[129.75285892000002,33.47101471600014],[129.76872806100002,33.46295807500012],[129.77165774800008,33.45949941600013],[129.7735294930002,33.45595937700013],[129.7735294930002,33.45384349200013],[129.7710067070001,33.45091380400011],[129.76954186300023,33.44737376500002],[129.77027428500017,33.44432200700014],[129.78158613400015,33.43740469000012],[129.78980553500017,33.43227773600002]]],[[[129.55486087300002,33.49982330900018],[129.55591881600017,33.493963934000035],[129.57341556100002,33.49607982000005],[129.57642662900005,33.48834870000006],[129.5680444670002,33.478501695000105],[129.55445397200006,33.47345612200009],[129.53939863400004,33.47516510600009],[129.5268660820001,33.48045482000008],[129.51905358200023,33.4781761740001],[129.51644941500004,33.47353750200007],[129.51197350400017,33.47296784100011],[129.50318444100017,33.47370026200004],[129.50668378999998,33.483099677000055],[129.524668816,33.4975446640001],[129.54175866000017,33.507310289000046],[129.55176842500023,33.506903387000065],[129.55486087300002,33.49982330900018]]],[[[129.64071699300004,33.73725006700015],[129.64144941499998,33.73371002800015],[129.63388105600004,33.734645901000036],[129.62891686300011,33.73232656500009],[129.6268009770001,33.74241771000014],[129.63705488400004,33.74673086100013],[129.64039147200006,33.744045315000065],[129.64372806100008,33.741725979000094],[129.64071699300004,33.73725006700015]]],[[[129.76220277400014,33.82313843700017],[129.7628717910001,33.81234364600006],[129.76678701100005,33.80209537200015],[129.781095119,33.79239255600008],[129.79688561300023,33.78644440300015],[129.79127037900005,33.78497955900009],[129.7819116550002,33.78115469000009],[129.77637780000018,33.77968984600004],[129.77637780000018,33.77342357000008],[129.79957116,33.76483795800006],[129.79965253999998,33.751044012000065],[129.78386478000002,33.73818594000012],[129.76037880300024,33.73730452000002],[129.7350640350002,33.73673856400008],[129.72276735300008,33.71674819700006],[129.7210392590001,33.69773997599999],[129.71045983200023,33.70083242400007],[129.69095668100024,33.71561246200004],[129.67729361500002,33.72909541000017],[129.6811607020002,33.74421752800011],[129.6707632470001,33.750139093000044],[129.65712690899997,33.74255478300002],[129.649304171,33.756039813000044],[129.646658083,33.77277290600016],[129.6769311860002,33.76072825700011],[129.68702233200023,33.75918203300016],[129.68702233200023,33.766017971000096],[129.66028335100012,33.78359193100012],[129.65826924799998,33.80357374700016],[129.65693388100007,33.81545368899999],[129.67840180699997,33.81493097400009],[129.67314449900002,33.83221792800002],[129.67895207600023,33.84898507700008],[129.6867199220001,33.859795866000084],[129.68864750300023,33.871145815000105],[129.7006942070001,33.86155833500008],[129.76726321700008,33.844549872000115],[129.76804010200024,33.83665025900011],[129.76220277400014,33.82313843700017]]],[[[129.39096113400015,34.29930247600008],[129.38860110800013,34.29132721600011],[129.360118035,34.27000560100005],[129.35670006600017,34.27281321800011],[129.35263105600015,34.289862372000144],[129.3536076180002,34.29368724200016],[129.35726972700016,34.298000393000066],[129.3648380870002,34.30023834800015],[129.37256920700023,34.29909902600012],[129.37631269600016,34.30101146000008],[129.3831486340001,34.30353424700009],[129.39096113400015,34.29930247600008]]],[[[129.224052602,34.29344308800002],[129.2306722920001,34.29336287400007],[129.23871754000007,34.299287051000036],[129.2482202480002,34.30044179900001],[129.26204317900013,34.30776139300018],[129.2752883100001,34.307596814000064],[129.2707872390001,34.31532030700005],[129.26892495000018,34.32246424500006],[129.2801969870001,34.32287172800004],[129.28938532900023,34.31782388700016],[129.30246202400022,34.30778735000008],[129.30143578800013,34.28809728200012],[129.31252838900016,34.279756376000094],[129.3167839,34.294468719000164],[129.342583158,34.293051839000086],[129.34710276200022,34.28697774700014],[129.34296047100023,34.278277529000135],[129.3256099060002,34.27138467800002],[129.31807550400006,34.258349633000094],[129.32105260400024,34.240796492000115],[129.3248032170001,34.2292526240001],[129.30938369900005,34.21905214800016],[129.2974639830002,34.21865848400007],[129.29802122700002,34.21317660300009],[129.30257932300006,34.20928496000006],[129.2997137360002,34.19782318000004],[129.29809006700023,34.18196436600006],[129.29721196100004,34.170476062000134],[129.29449413700016,34.16667814600014],[129.28784828500014,34.16512018200011],[129.28373507900008,34.15750473300007],[129.28351905200023,34.14600476600005],[129.2765874400002,34.129111206000104],[129.2645831670002,34.123786830000185],[129.26242352800014,34.114496337000034],[129.25109020100015,34.10757029000011],[129.24829683900023,34.09992685800016],[129.2358575110002,34.10076336000019],[129.23180011400015,34.094814056000146],[129.22042147100015,34.088877923000084],[129.21606240500014,34.09833929200006],[129.2107307100001,34.09996089100004],[129.19830716400023,34.110991656000024],[129.19030802600005,34.1148107420001],[129.1776290280002,34.10892999900007],[129.17163425900006,34.10680681200007],[129.17035364400024,34.1468156830001],[129.17880296200005,34.16976520200002],[129.175880289,34.19550913500014],[129.1804997080002,34.22721701300016],[129.18925958300005,34.234227385000125],[129.18882185600003,34.249552909000116],[129.195885,34.27299251300009],[129.1985345410002,34.316228084000116],[129.20736082700014,34.32927727400015],[129.2252449850001,34.32740336900015],[129.2284614390002,34.32078103500005],[129.22435607200006,34.31261085300004],[129.224052602,34.29344308800002]]],[[[129.4645605850001,34.70080530600002],[129.47066369700022,34.69268493700004],[129.4807235040001,34.683986721000124],[129.48775113000025,34.67639689],[129.48327784600022,34.66269661200003],[129.4775793690001,34.657035871000076],[129.4691199590002,34.652558845],[129.47814236700006,34.65072063800007],[129.48780182800007,34.64600565000002],[129.48827771500012,34.63567767100001],[129.48091280400004,34.61990957500008],[129.472422722,34.616766669000086],[129.46192467500023,34.621893622000115],[129.4526473320001,34.61945221600017],[129.46290123800011,34.60911692900008],[129.47820071700008,34.59711334800015],[129.47537364000013,34.58845104699999],[129.47310177600016,34.57933246000012],[129.46424106500004,34.55427396300014],[129.44608318900006,34.51477564],[129.402326374,34.48164673200007],[129.37947278100015,34.45262297300009],[129.3842879570001,34.424017645],[129.36183086700024,34.40548574800012],[129.35946395400018,34.390621507],[129.37550622400013,34.394416032],[129.39318101000006,34.41080021800009],[129.39442173700016,34.40333191200007],[129.388597794,34.38965888300014],[129.3843115320001,34.383416319],[129.38814520100007,34.366755092000076],[129.39958743600008,34.35504791900017],[129.40796864500024,34.35157990099999],[129.40075593500003,34.33792425400016],[129.38605282200004,34.331258264],[129.3791610040001,34.324652411],[129.36824161399997,34.30743171200008],[129.34383322200003,34.300314315000136],[129.32451188300004,34.304593338000146],[129.30546399500005,34.32487545400009],[129.3209047600001,34.333849042000125],[129.34506369500016,34.32724397800003],[129.34700270800013,34.35585198900016],[129.33459571400013,34.36059135700016],[129.31620389800017,34.342496496000095],[129.30510374700012,34.34320609200002],[129.30405895900017,34.362122712000044],[129.30493826400019,34.372424179000106],[129.30932224300003,34.38440191000011],[129.29738425500003,34.37710499100014],[129.28744395200025,34.36576828200005],[129.29339848800007,34.34907806600013],[129.28635873699997,34.34400826400004],[129.27199211700022,34.356219282],[129.24829130400022,34.35192546000009],[129.2289130230001,34.355601426000035],[129.2353838000001,34.368132348000174],[129.25972666300012,34.369552777000095],[129.27234911399998,34.37569660700014],[129.27375144500004,34.41293237800012],[129.2685686250002,34.43305128800016],[129.27708909300023,34.441527051000136],[129.28686405500017,34.443112446000114],[129.2916898660002,34.44075623400012],[129.29738949600008,34.44754637200016],[129.31086745200005,34.46051106800003],[129.30889545800002,34.46684104200004],[129.2933275930001,34.45332664000016],[129.28073429900022,34.45064666500009],[129.27604412399998,34.4621884940001],[129.28902402900005,34.48723729400008],[129.30493076700012,34.52028727200003],[129.32611919000024,34.53543877000003],[129.32554584200014,34.542326534000054],[129.308456188,34.56038743000006],[129.2909447430001,34.55607171200002],[129.2891069140002,34.56986674800011],[129.30335920299999,34.58687697000015],[129.30784427,34.6034634950001],[129.32330009600005,34.64807215700013],[129.33656962500018,34.64841143800014],[129.36096825500013,34.64800679100013],[129.3783374340001,34.64485176400005],[129.39037028500005,34.65447130000017],[129.40966199400006,34.675172826000065],[129.42574516500017,34.677801186000025],[129.418228469,34.685392005000054],[129.423304881,34.695070890000025],[129.43861542400012,34.69371319700004],[129.45061995200004,34.70100212400014],[129.4645605850001,34.70080530600002]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-46\",\"name\":\"Kagoshima\",\"name_alt\":null,\"name_local\":\"鹿児島県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kyushu\",\"postal\":null,\"region_code\":\"JPN-KYS\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[129.2179468110002,29.135362046000026],[129.21957441500004,29.126654364000146],[129.21029707100016,29.134385484000134],[129.20150800900004,29.138495184000092],[129.1928817070001,29.14691803600006],[129.19564863400015,29.15879954600011],[129.20354251400013,29.15806712400017],[129.220469597,29.1571312520001],[129.21924889400012,29.14907461100016],[129.2173771490002,29.14529043200004],[129.21892337300008,29.141587632],[129.2179468110002,29.135362046000026]]],[[[129.61231530000023,29.44334544500005],[129.59961998800006,29.44334544500005],[129.59156334700006,29.4501000020001],[129.5874129570001,29.458238023000106],[129.5913192070001,29.467962958000058],[129.59815514400012,29.472357489000146],[129.60108483200023,29.471340236000074],[129.60352623800006,29.469549872000087],[129.60808353000013,29.46922435100008],[129.61068769600004,29.465562242000104],[129.61540774800014,29.455226955000015],[129.61231530000023,29.44334544500005]]],[[[129.7438257170002,29.622300523000135],[129.7210392590001,29.606146552000084],[129.71290123800017,29.62929922100004],[129.71143639400012,29.642523505000057],[129.71485436300011,29.654527085000083],[129.74594160200004,29.66103750200007],[129.753754102,29.6454125020001],[129.7438257170002,29.622300523000135]]],[[[129.85098067800018,29.886729695000056],[129.87533613400015,29.8783226580001],[129.8795679050002,29.87913646],[129.88404381600006,29.87889232000005],[129.88868248800006,29.877508856000176],[129.89356530000023,29.87474192900011],[129.8974185120002,29.846915495000175],[129.92725670700023,29.825873114000117],[129.92212975400005,29.823146877000156],[129.92042076900023,29.819647528000147],[129.8634018920002,29.82460653000014],[129.85234765200013,29.839234318000123],[129.84018475400015,29.849729430000124],[129.83277226500005,29.873077651000173],[129.85098067800018,29.886729695000056]]],[[[129.551524285,29.893622137000065],[129.53988691499998,29.889878648000106],[129.5330509770001,29.89350006700009],[129.52767988400004,29.902329820000134],[129.528575066,29.908921617000104],[129.53541100400005,29.909857489],[129.54330488400004,29.908148505],[129.55014082100004,29.90387604400017],[129.55445397200006,29.897528387000037],[129.551524285,29.893622137000065]]],[[[129.94800866000006,29.95856354400003],[129.9409285820001,29.953192450000145],[129.92381173300012,29.957920034000082],[129.91333333100008,29.960901639000113],[129.90308678500006,29.97085195500007],[129.900645379,29.972560940000093],[129.89975019600004,29.97565338700015],[129.9011336600001,29.98069896],[129.90357506600006,29.98436107000005],[129.912364129,29.991888739],[129.90902754000015,29.999335028000175],[129.91342207100016,30.00031159100014],[129.92198954500006,30.00472988100016],[129.92713547100007,29.993404422000097],[129.93415344000002,29.982101624000066],[129.94369550900015,29.96527741100006],[129.94532311300006,29.961615302000112],[129.94800866000006,29.959458726000108],[129.94800866000006,29.95856354400003]]],[[[130.49895073600024,30.467962261000153],[130.52369225400005,30.44965241100006],[130.56782501700005,30.435941711000012],[130.59958849400013,30.412458198000152],[130.63867817600013,30.40266076900015],[130.67457116,30.374579169000143],[130.6694442070001,30.33966705900012],[130.64714603000007,30.290513414000188],[130.603751051,30.25246851800013],[130.57727971400018,30.235905859000113],[130.54682984500008,30.231416228000157],[130.51771967100004,30.227492394000123],[130.485977453,30.225777010000044],[130.43771320800013,30.238970153000153],[130.42713993800024,30.25553593700009],[130.4106338930002,30.29155631900015],[130.38626403800018,30.339605661000107],[130.37901254000022,30.365875685000148],[130.37624650800015,30.394435997000144],[130.38648522200018,30.398138739],[130.42336102800002,30.398968251000056],[130.441172722,30.422308661],[130.46118241000025,30.441141568000106],[130.46711892599998,30.457225267000055],[130.48834512500017,30.457026279],[130.49895073600024,30.467962261000153]]],[[[130.1650506890002,30.4898900780001],[130.19101626800003,30.47617226199999],[130.20230578800007,30.481901060000084],[130.22006269599999,30.4715843770001],[130.2428670000002,30.463611804],[130.26214412700008,30.4470323850001],[130.2621568440001,30.435023779000105],[130.24063399200017,30.434935680000095],[130.23184544100005,30.42354163300014],[130.21246764200023,30.420690741000087],[130.19308209900024,30.439563337],[130.19438650700025,30.452148094000066],[130.1903808230002,30.461299148000066],[130.17311500100007,30.45442186800007],[130.16310837600005,30.468716573000123],[130.15177683600004,30.477863163],[130.14176604600019,30.489301081000136],[130.1650506890002,30.4898900780001]]],[[[130.86500084700006,30.74774811400009],[130.85466556100002,30.725612697000102],[130.84839928500017,30.727077541000156],[130.84156334700006,30.727484442000062],[130.83814537900005,30.727484442000062],[130.83855228000007,30.736965236000074],[130.84164472700004,30.74526601800015],[130.84066816500015,30.751125393],[130.85149173300024,30.7606468770001],[130.85775800900004,30.76654694200012],[130.86101321700008,30.75238678600003],[130.86500084700006,30.74774811400009]]],[[[130.302582227,30.808457749000027],[130.30884850400005,30.804063218000138],[130.31755618600002,30.804063218000138],[130.32211347700016,30.793198960000055],[130.3107202480002,30.78038157800013],[130.30193118600008,30.781846421000083],[130.28931725400017,30.779160874000112],[130.2784123060001,30.779974677000112],[130.27784264400012,30.77171458500014],[130.27393639400012,30.77440013200011],[130.26522871200015,30.77883535400018],[130.25904381600017,30.781642971000124],[130.25969485800013,30.782863674000154],[130.26351972700004,30.788560289000046],[130.2671004570001,30.794134833000143],[130.27385501400013,30.79246653900013],[130.28679446700008,30.80211009300011],[130.2957462900001,30.805975653000118],[130.302582227,30.808457749000027]]],[[[130.4422306650001,30.811835028000147],[130.44662519600016,30.804266669000114],[130.44353274800008,30.799709377000156],[130.41985110800024,30.807847398000078],[130.41049238400004,30.808457749000027],[130.40316816500004,30.80613841400016],[130.40040123800023,30.811550197000102],[130.40780683700015,30.810166734000134],[130.4088647800002,30.813869533000073],[130.41863040500002,30.816270249000027],[130.42611738400004,30.819037177],[130.43580162900017,30.813462632000082],[130.4422306650001,30.811835028000147]]],[[[131.03556375600013,30.557209431],[131.01791169000003,30.55108809800005],[131.00340350800016,30.551166622000082],[130.9940341170002,30.53257178200012],[130.9826388590001,30.519305321000147],[130.9804740110002,30.497095505000132],[130.96696052800021,30.479399958000087],[130.9678993060002,30.464294822000014],[130.96475337600012,30.456319105000105],[130.97295382600024,30.449170034000034],[130.97803086800005,30.437596801],[130.97176863900003,30.42608701200008],[130.9696385780002,30.415438636000104],[130.9777970910002,30.40028023399999],[130.9632821280001,30.38614366800006],[130.96217717500022,30.372808183000146],[130.94365060900023,30.374681186000046],[130.92408287900017,30.36953359600001],[130.90651933000018,30.366014516000078],[130.8910196090001,30.355421322000055],[130.88581760500008,30.343850664000158],[130.87038107900005,30.343904295000144],[130.8673456280001,30.353699646000067],[130.85711789299998,30.36619154600008],[130.86027567600016,30.37952507300018],[130.86551539500024,30.397292703000076],[130.8625715100002,30.423947003000123],[130.85339754700013,30.440830762000147],[130.84938214600007,30.46126404200008],[130.85149508200018,30.470143740000182],[130.8617714440002,30.465662831000074],[130.87000225800014,30.463860979000103],[130.87627834200012,30.480704794000033],[130.90113980700008,30.503691978000134],[130.930221199,30.540859645000083],[130.94214928500017,30.56981028900016],[130.95093834700006,30.595038153000033],[130.95127378600003,30.610018201000045],[130.94837684900014,30.641109393000093],[130.9433085870002,30.654460150000105],[130.94543043900015,30.664215375000182],[130.93929969700005,30.672251498000108],[130.9527968900001,30.685495816000028],[130.96634534600005,30.70144146200009],[130.985131827,30.729775881000037],[130.98523937100006,30.74135231100017],[130.99970311700022,30.745686335000116],[131.00813866600018,30.76786485800001],[131.006257128,30.7892467570001],[131.02717589000022,30.81937162100006],[131.0392358730002,30.831447658000016],[131.04525800900004,30.836859442000062],[131.05201256600006,30.840236721000068],[131.06153821600006,30.840587948000106],[131.0624069840001,30.82452899499999],[131.07359469399998,30.799581581000055],[131.08793357200008,30.78260629900008],[131.07120798000008,30.76930179900016],[131.07096896599998,30.73732987800004],[131.078937331,30.699084278],[131.0537605950001,30.661867794],[131.05780289700024,30.64233795000014],[131.05761514300016,30.611269028000052],[131.05131930700006,30.597092024000162],[131.03784798300003,30.589171336000092],[131.03769896100002,30.57229418700014],[131.03556375600013,30.557209431]]],[[[129.43384850400005,30.844549872000172],[129.42945397200006,30.840887762000037],[129.42823326900023,30.846421617000047],[129.430918816,30.848211981000034],[129.43384850400005,30.844549872000172]]],[[[129.93946373800023,30.81342194200009],[129.92774498800023,30.81342194200009],[129.911957227,30.819769598000036],[129.91041100400005,30.821966864],[129.9098413420002,30.825873114],[129.90398196700002,30.834418036000116],[129.90821373800017,30.83844635600009],[129.915863477,30.84194570500007],[129.92554772200006,30.849595445000105],[129.93213951900012,30.851752020000035],[129.93995201900012,30.848944403000147],[129.94629967500023,30.84471263200014],[129.95826256600017,30.836981512000037],[129.95980879000004,30.828680731000148],[129.9503686860002,30.81932200700011],[129.93946373800023,30.81342194200009]]],[[[129.44532311300006,31.17755768400012],[129.43685957100016,31.17405833500011],[129.4356388680002,31.179185289000046],[129.4405216810001,31.186061916000043],[129.44410241000017,31.186590887000122],[129.44507897200006,31.190334377000156],[129.44361412900017,31.19310130400011],[129.44507897200006,31.194932359000106],[129.44613691499998,31.199652411],[129.45370527400016,31.199652411],[129.4487410820001,31.188218492000047],[129.4487410820001,31.1841087910001],[129.4517521490001,31.180690822000187],[129.44532311300006,31.17755768400012]]],[[[130.16635175900004,31.35297272300012],[130.16203860800013,31.349758205000157],[130.15902754000004,31.352036851000136],[130.15886478000007,31.35618724200016],[130.16480553500017,31.362046617000104],[130.16936282600014,31.36102936400006],[130.16635175900004,31.35297272300012]]],[[[129.71924889400012,31.637355861000017],[129.69597740599997,31.621447336000116],[129.67769407500023,31.629699673000076],[129.65937367500018,31.641934705000054],[129.6553814330001,31.65978483800005],[129.67629765500024,31.666966426000144],[129.69060286800018,31.700198056000104],[129.716644727,31.730373440000093],[129.731700066,31.73761627800009],[129.75359134200002,31.74380117400007],[129.76775149800008,31.75934479400017],[129.77670332099999,31.779527085000026],[129.78321373800011,31.799627997000144],[129.79086347700004,31.784409898000078],[129.79859459700006,31.76365794499999],[129.79957116,31.74542877800009],[129.78638756600006,31.73761627800009],[129.77613366000006,31.729478257],[129.7417098320001,31.676174221000124],[129.72934004000015,31.644232489000117],[129.71924889400012,31.637355861000017]]],[[[129.83179772200006,31.78066640800013],[129.82992597700016,31.773342190000122],[129.82064863400004,31.778265692],[129.8164982430001,31.78030019700013],[129.8150333990002,31.78851959800012],[129.81413821700002,31.794826565000065],[129.81877688900025,31.805690822000045],[129.82455488400004,31.8146019550001],[129.8336694670002,31.816921291000043],[129.8372501960001,31.817735093000167],[129.84278405000012,31.814805406000133],[129.8424585300002,31.810085354000037],[129.84131920700023,31.80259837400017],[129.84652754000004,31.79918040600016],[129.84017988400004,31.795762437000135],[129.83432050900015,31.794501044000057],[129.83008873800023,31.791245835],[129.82992597700016,31.785671291000185],[129.8336694670002,31.782538153000118],[129.83179772200006,31.78066640800013]]],[[[129.86451256600017,31.875555731000176],[129.9020288420002,31.848130601000136],[129.90992272200018,31.84568919499999],[129.9127710300002,31.85187409100014],[129.908457879,31.86180247600008],[129.91342207100016,31.86774323100009],[129.92855879000015,31.869696356000034],[129.93336022200006,31.867824611000074],[129.92847741000017,31.863918361000074],[129.92774498800023,31.8595238300001],[129.92969811300011,31.853420315],[129.92807050900015,31.846502997000115],[129.92847741000017,31.84039948100012],[129.93116295700023,31.836371161000145],[129.93148847700016,31.833482164000102],[129.9292098320001,31.826727606000176],[129.92221113400004,31.81981028900016],[129.9127710300002,31.81948476800015],[129.90333092500006,31.81806061400009],[129.88990319100012,31.81073639500009],[129.87598717500012,31.811265367000075],[129.8686629570001,31.81842682500009],[129.86329186300023,31.820868231000034],[129.85906009200002,31.823675848000093],[129.85743248800011,31.82855866100006],[129.85377037900017,31.835150458000115],[129.84742272200006,31.84341054900004],[129.84652754000004,31.85089752800009],[129.85352623800011,31.855169989],[129.85043379000004,31.85740794499999],[129.83952884200008,31.85366445500013],[129.83179772200006,31.84373607000005],[129.82422936300011,31.84227122599999],[129.82056725400005,31.845526434000035],[129.82032311300011,31.85187409100014],[129.82309004000015,31.863023179],[129.83179772200006,31.87352122600005],[129.84498131600017,31.88060130400011],[129.86451256600017,31.875555731000176]]],[[[130.61087039595205,32.15702973123702],[130.67448408387324,32.112226264130825],[130.71236290882112,32.0909097361954],[130.70011559495865,32.06703522429622],[130.71096764484096,32.04964610408446],[130.7211995788974,32.04106781732584],[130.783883090832,31.954845689377166],[130.8027966662331,31.935053615731746],[130.8237772973844,31.91960236275891],[130.84703169165928,31.909525458333277],[130.8734383491005,31.88756297525113],[130.8811898126597,31.870225531882895],[130.8763839048934,31.82444021284546],[130.8810347839282,31.801340847302285],[130.88909630674928,31.790230414102425],[130.89700280173795,31.78426178635233],[130.9654740742691,31.76067149529335],[130.97601606668812,31.747209783704605],[131.002371047286,31.723516139858063],[131.00764204304588,31.71364594190665],[131.0102258654314,31.705946153392304],[131.0108976581004,31.69605011612002],[131.01260298134255,31.686309109377888],[131.01663374185378,31.675922145690507],[131.03585737471818,31.645355536528868],[131.04722619033632,31.637268175286025],[131.0711007022355,31.633624985704458],[131.10076297383213,31.619413967081],[131.10696414557893,31.617889512790825],[131.11580081565515,31.616726792807313],[131.12634280807416,31.616959336804015],[131.1560050796708,31.61274770824015],[131.1747636254411,31.60026784948174],[131.18623579294749,31.586831977214004],[131.19181684796925,31.57466217681805],[131.19150678870736,31.564120185298307],[131.18732099766592,31.540943305389263],[131.18664920499694,31.5250011260011],[131.181378209237,31.51177195930852],[131.1553849629456,31.478414822186252],[131.152904494247,31.47389313436075],[131.15064537914046,31.471340236031565],[131.13038170700023,31.469427802000084],[131.10938561300006,31.464544989],[131.07178795700005,31.449611721000153],[131.05689537900005,31.439642645000063],[131.04525800900004,31.42747630400008],[131.02807477500008,31.401785761000113],[131.01361073600006,31.363069850000144],[131.02545128600016,31.35409522499999],[131.05958092500012,31.344875393000095],[131.08334394600004,31.338446356000176],[131.10401451900012,31.328843492000104],[131.10738759400024,31.317184411000156],[131.10438183500005,31.309449248000178],[131.08298623699997,31.29705597000016],[131.07421690500004,31.282104405000112],[131.09259076700013,31.276050664000152],[131.12474719500014,31.28834554600003],[131.1321139440001,31.279513102000024],[131.060249739,31.225314840000138],[131.03295089700012,31.22752444600009],[131.0081594020002,31.221082284000047],[131.00660241000017,31.199774481000176],[130.99708092500018,31.186590887000122],[130.97999108200023,31.15599192900011],[130.96859785200016,31.141750393],[130.95622806100002,31.13117096600014],[130.940684441,31.12132396000011],[130.92400149800008,31.11298248900006],[130.90723717500023,31.10691966400016],[130.87068066200007,31.096018518000122],[130.8452254570001,31.08991120000009],[130.81951105400006,31.08726047400016],[130.79235710500015,31.077847115000125],[130.77379645200008,31.06548277200001],[130.76758873800006,31.056463934000035],[130.72542805000003,31.047039567000084],[130.69488036000004,31.018824916000185],[130.67794304400005,31.005010434000056],[130.65772202600007,31.003191200000103],[130.6633394100002,31.069506364000134],[130.68897545700005,31.09015534100014],[130.72868899800008,31.111558335],[130.74268639400023,31.12128327000012],[130.76478445300003,31.18650933200003],[130.75394057400015,31.203882653000093],[130.78036911500024,31.242514662000033],[130.789670147,31.275148674000135],[130.79664629800018,31.325345784000078],[130.7952516060001,31.344425107000134],[130.78272545700005,31.372788804000137],[130.76954186300023,31.402411200000174],[130.74732506600017,31.435492255000113],[130.70341738600015,31.459304551000017],[130.69252519000017,31.48557201000004],[130.70533287900017,31.52875397300012],[130.69556725400005,31.5458031270001],[130.63689212300014,31.55524323100012],[130.61947675900015,31.56427643400012],[130.60938561300011,31.56631094000015],[130.59831790500007,31.573431708],[130.59088884600007,31.58598791100009],[130.61389305800017,31.612066478000102],[130.6378780590001,31.62259469100009],[130.66228274800008,31.617865302000112],[130.68783613400015,31.61945221600014],[130.711680535,31.614406643000038],[130.72234134200008,31.59731679900007],[130.71924889400017,31.570705471000124],[130.72266686300006,31.56342194200012],[130.7360132170002,31.559475002000127],[130.74903405000006,31.559719143000066],[130.75863691500015,31.563788153000033],[130.77637780000023,31.579901434000092],[130.7990014980002,31.611721096000124],[130.81031334700018,31.650946356000148],[130.80453535200004,31.687241929000052],[130.77637780000023,31.710272528000033],[130.73796468800018,31.708438590000085],[130.68604576900023,31.725531317],[130.644786004,31.71474844],[130.61304772200006,31.68235911700019],[130.61011803500006,31.672308661000116],[130.60621178500006,31.646633205000157],[130.60222415500007,31.638251044000086],[130.57496178500017,31.611273505000142],[130.56763756600006,31.600734768000095],[130.5584416020001,31.56631094000015],[130.54224694100006,31.541734117000132],[130.52914472700016,31.51325104400003],[130.51807701900012,31.472398179],[130.51612389400006,31.45331452],[130.52051842500023,31.43244049700003],[130.55046634200002,31.3751488300001],[130.5626733730002,31.334377346000124],[130.57349694100012,31.3146019550001],[130.58912194100017,31.306219794000057],[130.60287519599999,31.301988023000106],[130.62368198400011,31.275646770000137],[130.6603296230002,31.271429755],[130.66195722700004,31.258612372000144],[130.65007571700008,31.223700262000037],[130.64665774800008,31.206935940000037],[130.63982181100008,31.186468817000147],[130.62330162900017,31.178859768],[130.6036889980002,31.172349351000108],[130.58643639400012,31.155422268000123],[130.5735783210001,31.167425848000065],[130.56251061300023,31.166937567000062],[130.55079186300023,31.162420966000028],[130.5367944670002,31.162176825000174],[130.52173912900017,31.16868724200016],[130.516856316,31.175441799000012],[130.51612389400006,31.189520575000145],[130.50660241000017,31.212795315000065],[130.4948022800002,31.227240302000112],[130.45114447600005,31.249388072000144],[130.29737389400012,31.253729559000178],[130.27507571700008,31.25299713700015],[130.24878991000006,31.244126695000105],[130.22220471400013,31.246299862000157],[130.21205593,31.260865751000082],[130.20871522600007,31.279282515000133],[130.20309321400023,31.293824192],[130.1929524180001,31.315156593000083],[130.17938199500006,31.32778106900004],[130.19072152900006,31.326789327000043],[130.20659505900008,31.32481915000001],[130.20777428500006,31.336615302000027],[130.19654381600006,31.35419342700014],[130.17025800900004,31.377020575000145],[130.14063561300006,31.397650458000086],[130.1189884770001,31.408636786000145],[130.14503014400023,31.4200707050001],[130.15308678500017,31.422267971000096],[130.16236412900005,31.421942450000145],[130.1754663420002,31.417303778000033],[130.18409264400012,31.416083075000117],[130.19597415500007,31.412054755000142],[130.21517988400004,31.391058661],[130.22828209700018,31.38129303600006],[130.24488366000006,31.40485260600012],[130.29326812900004,31.44470403200016],[130.3047602330001,31.47663418000009],[130.31706790500007,31.4930687520001],[130.32846113400004,31.52903880400008],[130.33448326900012,31.56240469000015],[130.32447350400005,31.58673737200003],[130.33765709700018,31.607896226000108],[130.336192254,31.626166083],[130.32545006600012,31.642075914000102],[130.29737389400012,31.669989325000145],[130.2592879570001,31.72085195500007],[130.24626712300014,31.72964101800015],[130.19971764400012,31.753729559000178],[130.18409264400012,31.758693752000127],[130.18181399800002,31.768255927000112],[130.17143062700003,31.79013665700016],[130.18100019600004,31.82697174700003],[130.19031873800012,31.835088246000126],[130.22282962300008,31.82444896000011],[130.235118035,31.819525458000143],[130.20785566500004,31.843695380000057],[130.19700374200025,31.85848542800015],[130.2147297910001,31.88561847400011],[130.220437744,31.92959451500009],[130.21391755000005,31.950174432000054],[130.2051266440001,31.971670373000066],[130.1941024100001,31.980902411],[130.18995201900023,31.986029364000117],[130.17410608700018,31.998755987],[130.17299366000017,32.017449556000045],[130.186307185,32.026795814000096],[130.20404812200005,32.04828530800002],[130.20404738700003,32.07258078500003],[130.1798608730002,32.09345123900006],[130.20679772200006,32.12067291900014],[130.24830162900017,32.13373444200012],[130.27865644600004,32.10053131700012],[130.29851321700002,32.105861721000096],[130.31959069100006,32.11823151200012],[130.33187910200004,32.12860748900012],[130.33513431100008,32.135199286],[130.34465579500008,32.161769924000126],[130.34465862896903,32.161778425075354],[130.3448401230731,32.161706447794344],[130.3453568870108,32.161215522278454],[130.35589887942976,32.15302480734904],[130.3826155952336,32.12646312117609],[130.39331261638407,32.118375759034066],[130.40788537021368,32.11372487999928],[130.42437015196097,32.11341482253603],[130.44979495657194,32.11884084702753],[130.47186079334082,32.12723826843127],[130.52550092962431,32.139847317499786],[130.5387300972162,32.146151842033944],[130.55268273342145,32.14997589786931],[130.5635347842028,32.15400665928],[130.57438683498438,32.160672919019945],[130.5897864120134,32.16346344698057],[130.61087039595205,32.15702973123702]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-47\",\"name\":\"Okinawa\",\"name_alt\":null,\"name_local\":\"沖縄県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Okinawa\",\"postal\":null,\"region_code\":\"JPN-OKN\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[123.79509524800008,24.040757554000137],[123.78174889400006,24.039089260000125],[123.760020379,24.04511139500012],[123.75766035200016,24.053819078000018],[123.766856316,24.060370184000092],[123.78345787900017,24.062404690000122],[123.80290774800008,24.06061432500006],[123.80746504000004,24.050767320000105],[123.79509524800008,24.040757554000137]]],[[[124.02458743600008,24.22138092700014],[124.01351972700004,24.213771877000013],[124.00432376400025,24.215155341000084],[123.9981388680001,24.222438869000186],[123.99447675900015,24.235703843000024],[123.99708092500023,24.246771552000055],[124.00619550900004,24.24595774900014],[124.01311282600014,24.241929429],[124.02133222700004,24.241034247000087],[124.02873782600003,24.234361070000134],[124.02458743600008,24.22138092700014]]],[[[124.09734134200002,24.309230861000017],[124.089610222,24.304877020000035],[124.08187910200004,24.310044664000102],[124.078868035,24.31940338700015],[124.08269290500002,24.328233140000023],[124.09197024800008,24.328558661000116],[124.10067793100009,24.320013739000032],[124.10222415500019,24.314683335000055],[124.09734134200002,24.309230861000017]]],[[[123.99765058700021,24.329982815],[123.99927819100006,24.321763414000102],[123.99154707100004,24.319606838000098],[123.98576907600014,24.323228257000082],[123.97966556100008,24.324652411000145],[123.97209720100003,24.32453034100017],[123.96648196700014,24.32615794500019],[123.958262566,24.326931057000095],[123.95403079500008,24.32884349200016],[123.9639591810002,24.33421458500011],[123.96566816500004,24.34137604400017],[123.96876061300011,24.346828518],[123.981211785,24.347194729000122],[123.99765058700021,24.329982815]]],[[[123.79127037900017,24.409613348000065],[123.81470787900005,24.391750393],[123.83472741,24.395493882000054],[123.87208092500018,24.390122789000188],[123.9104923840001,24.37954336100013],[123.93441816500015,24.367499091000028],[123.93604576900017,24.341457424000154],[123.91553795700011,24.306830145000063],[123.87305748800023,24.258246161],[123.85124759200019,24.256984768],[123.74577884200019,24.281236070000162],[123.68344160200004,24.28530508000013],[123.66822350400005,24.29661692900011],[123.68376712300002,24.323472398000106],[123.68767337300002,24.32050202],[123.69092858200011,24.31883372599999],[123.69800866000006,24.315985419000143],[123.70704186300017,24.32868073100009],[123.71753991000006,24.323675848000065],[123.72925866,24.31574127800009],[123.7423608730002,24.319728908000073],[123.74805748800006,24.329575914000102],[123.75318444100017,24.364447333000115],[123.76531009200008,24.401109117000047],[123.766856316,24.409409898000106],[123.76986738400004,24.418361721000153],[123.77686608200011,24.419745184000035],[123.78492272200006,24.416083075000174],[123.79127037900017,24.409613348000065]]],[[[123.00261478000019,24.474310614],[123.01636803500011,24.469794012000065],[123.01823978000007,24.459295966000084],[123.01107832100004,24.44769928600006],[122.99838300900015,24.439520575000145],[122.98780358200011,24.443304755000085],[122.94752037900005,24.439520575000145],[122.93864993600019,24.44424062700007],[122.93816165500019,24.45482005400011],[122.944590691,24.466253973000065],[122.95557701900017,24.47313060099999],[122.96794681100019,24.471177476000136],[123.00261478000019,24.474310614]]],[[[124.26050866000017,24.445257880000113],[124.25391686300011,24.401800848000093],[124.24187259200019,24.363714911],[124.21192467500012,24.337103583000058],[124.17701256600012,24.33299388200011],[124.14031009200019,24.34198639500012],[124.12224368600008,24.360581773000106],[124.14307701900023,24.3849144550001],[124.13257897200018,24.391994533000158],[124.12875410200016,24.40355052299999],[124.12273196700008,24.41429271],[124.10572350400011,24.419094143000095],[124.08863366000006,24.42194245000003],[124.07789147200006,24.428452867000104],[124.07837975400005,24.435492255],[124.09522545700023,24.439520575000145],[124.1017358730002,24.446600653000118],[124.11231530000018,24.463364976000136],[124.11622155000018,24.467474677],[124.12916100400017,24.46938711100013],[124.1329858730002,24.464016018000095],[124.13445071700008,24.456773179000052],[124.13990319100017,24.45319245000009],[124.14795983200023,24.44635651200015],[124.15650475400005,24.441636460000055],[124.16781660200004,24.439520575000145],[124.19125410199999,24.440008856000034],[124.20215905000006,24.44220612200003],[124.21192467500012,24.446966864000117],[124.22242272200018,24.46100495000009],[124.23031660200016,24.479152736000074],[124.24024498800017,24.494859117000047],[124.25660241000017,24.501613674000097],[124.26840254000015,24.51044342700014],[124.30062910199999,24.569891669000086],[124.31446373800011,24.587307033000158],[124.32129967500012,24.59027741100006],[124.32911217500012,24.58356354400003],[124.33228600400017,24.571030992000104],[124.32691491,24.561183986000103],[124.31902103000002,24.553045966000084],[124.30909264400023,24.534857489],[124.27393639400017,24.487941799000154],[124.26050866000017,24.445257880000113]]],[[[124.70826256600006,24.630723374000112],[124.70175214900004,24.629909572000187],[124.69133548300024,24.63108958500011],[124.67855879000004,24.63654205900015],[124.67628014400023,24.651963609000077],[124.68938235800013,24.666896877000127],[124.70508873800011,24.66925690300009],[124.71989993600008,24.66124095300013],[124.72584069100017,24.64655182500006],[124.71713300900004,24.635036526000093],[124.70826256600006,24.630723374000112]]],[[[124.70411217500012,24.748317776000093],[124.70785566500015,24.737127997000087],[124.6973576180001,24.741481838000155],[124.68864993600019,24.74457428600006],[124.68742923300024,24.755113023000106],[124.69613691500015,24.75450267100014],[124.70045006600017,24.75014883000007],[124.70411217500012,24.748317776000093]]],[[[125.20525149800002,24.80499909100017],[125.16814212300014,24.80097077],[125.15064537900017,24.804348049000126],[125.14014733200011,24.81639232000005],[125.14112389400006,24.828599351000108],[125.14966881600012,24.831935940000122],[125.15756269600016,24.833807684000092],[125.15967858200011,24.844427802000055],[125.16358483200005,24.854641018000095],[125.17701256600017,24.85639069200009],[125.19450931100002,24.850287177],[125.20289147200018,24.846258856000034],[125.21078535200016,24.83698151200015],[125.217051629,24.81659577],[125.20525149800002,24.80499909100017]]],[[[125.35824629000004,24.782619533000158],[125.36557050900015,24.779933986000074],[125.38721764400023,24.782171942000062],[125.40463300899998,24.778387762000037],[125.42628014400006,24.768866278000033],[125.44483483200011,24.756740627000127],[125.45264733200011,24.744940497000172],[125.44402103000019,24.73720937700007],[125.42351321700002,24.73224518400012],[125.38379967500012,24.726955471000124],[125.29965253999998,24.725653387000037],[125.25879967500023,24.733384507000054],[125.25684655000012,24.75169505400011],[125.261485222,24.763861395],[125.27393639400023,24.820013739],[125.27475019600016,24.856431382000082],[125.27100670700023,24.86782461100013],[125.26026451900012,24.885199286000116],[125.26246178500011,24.884344794000114],[125.27442467500023,24.882228908000016],[125.28516686300023,24.877346096000124],[125.29021243600008,24.873928127000042],[125.29444420700011,24.869574286000116],[125.30225670700011,24.857570705000015],[125.314219597,24.82990143400012],[125.32162519600004,24.816880601000108],[125.32667076900023,24.81073639500015],[125.35824629000004,24.782619533000158]]],[[[125.25294030000012,24.929103908000158],[125.2574162120001,24.918443101000108],[125.25261478000019,24.919338283000016],[125.25098717500023,24.919256903000033],[125.25058027400016,24.91530996300004],[125.23755944100017,24.934027411000116],[125.24577884200008,24.935899156000076],[125.25294030000012,24.929103908000158]]],[[[123.60629316500027,25.738999742000132],[123.60466556100013,25.738755601000165],[123.60043379000004,25.73936595300013],[123.60279381600017,25.745469468000024],[123.60629316500027,25.738999742000132]]],[[[123.59424889400034,25.75279368700005],[123.59302819100006,25.744370835],[123.58863366000017,25.75153229400003],[123.59424889400034,25.75279368700005]]],[[[123.55990644600004,25.756822007000196],[123.55819746200021,25.754339911000088],[123.55559329500008,25.75755442899994],[123.55990644600004,25.756822007000196]]],[[[123.5289005870001,25.761786200000145],[123.51148522200029,25.758083401000093],[123.51392662900017,25.769110419000143],[123.5314233730002,25.773342190000065],[123.5374455090001,25.773260809000092],[123.54533938900036,25.761786200000145],[123.5289005870001,25.761786200000145]]],[[[123.62045332099999,25.774481512000122],[123.62045332099999,25.773586330000214],[123.61801191500015,25.774481512000122],[123.62045332099999,25.774481512000122]]],[[[123.59530683700021,25.801906643000066],[123.59546959700018,25.797267970999954],[123.59237714900016,25.79743073100009],[123.59530683700021,25.801906643000066]]],[[[131.25245201900006,25.819037177],[131.241465691,25.818752346000068],[131.230723504,25.8216820330001],[131.2120874360002,25.82904694200009],[131.21273847700016,25.841457424000012],[131.22120201900012,25.85569896000011],[131.23609459700006,25.87205638200014],[131.24105879000004,25.875677802],[131.24708092500012,25.876857815000122],[131.25513756600006,25.87498607000005],[131.27711022200018,25.858384507],[131.27051842500006,25.8536644550001],[131.26417076900023,25.846340236000074],[131.2592879570001,25.83722565300009],[131.25733483200023,25.82709381700012],[131.25245201900006,25.819037177]]],[[[124.56885826900017,25.896226303999967],[124.56771894600027,25.89443594],[124.56527754000015,25.89573802300005],[124.56682376400019,25.898423570000134],[124.56885826900017,25.896226303999967]]],[[[123.6730249360003,25.92869700699994],[123.67204837300014,25.92747630400011],[123.6717228520001,25.92975495],[123.6730249360003,25.92869700699994]]],[[[123.68303470100003,25.920030015000222],[123.6758732430001,25.918158270000063],[123.67269941500004,25.92682526200015],[123.67920983200017,25.93077220300013],[123.68458092500035,25.924627997000144],[123.68580162900017,25.922796942],[123.68303470100003,25.920030015000222]]],[[[131.31657962300002,25.923081773000106],[131.30958092500023,25.922756252000013],[131.301524285,25.926743882000082],[131.2908634770001,25.938666083000115],[131.28972415500007,25.95136139500009],[131.3020939460001,25.953192450000145],[131.32129967500023,25.947699286000116],[131.327891472,25.937079169000086],[131.32227623800011,25.92706940300009],[131.31657962300002,25.923081773000106]]],[[[127.361094597,26.164007880000057],[127.35352623800006,26.15399811400009],[127.34538821700008,26.15827057500009],[127.34278405000023,26.170233466000028],[127.34229576900006,26.17999909100017],[127.34262128999998,26.18573639500012],[127.34620201900006,26.206366278000147],[127.35417728000002,26.218939520000063],[127.36410566500004,26.221340236000103],[127.36963951900023,26.214585679000052],[127.37077884200019,26.210028387000122],[127.37086022200018,26.20319245000009],[127.36793053500006,26.19061920800003],[127.36361738400015,26.177557684000035],[127.361094597,26.164007880000057]]],[[[127.94597415500007,26.251898505000113],[127.93946373800006,26.239528713000183],[127.93474368600008,26.243841864],[127.93262780000023,26.250555731000034],[127.93401126400013,26.25202057500009],[127.9368595710001,26.256537177000055],[127.94084720100014,26.262030341000084],[127.94507897200018,26.26076894700016],[127.94597415500007,26.251898505000113]]],[[[127.98910566499998,26.35846588700015],[127.97820071700019,26.354803778000118],[127.96770267000014,26.359686591000084],[127.97299238400004,26.376410223],[127.97763105600004,26.379746812000135],[127.98707116000017,26.38405996300004],[127.9904077480002,26.380357164000102],[127.98869876400019,26.374782619000186],[127.99382571700019,26.372503973000036],[127.99520918100009,26.36721426000004],[127.98910566499998,26.35846588700015]]],[[[126.79517662900011,26.305609442],[126.78891035200004,26.30345286700019],[126.78239993600008,26.30451080900015],[126.77540123800006,26.308539130000113],[126.77003014400012,26.31419505400011],[126.76783287900005,26.32054271000014],[126.75993899800002,26.334418036000116],[126.741465691,26.34170156500015],[126.71998131600017,26.346991278000033],[126.70362389400006,26.355292059000092],[126.69776451900023,26.369045315000037],[126.70679772200006,26.37913646],[126.72364342500023,26.38597239800002],[126.74170983200005,26.389715887000037],[126.75212649800008,26.390692450000117],[126.76986738400015,26.388006903000033],[126.78711998800023,26.380072333000058],[126.80144290500007,26.367661851000136],[126.81006920700023,26.351874091000084],[126.81031334700018,26.342515367000047],[126.80738366000017,26.332586981000148],[126.80209394599999,26.32355377800006],[126.79574629000015,26.317124742000132],[126.79371178500006,26.31289297100001],[126.7950952480002,26.308905341000028],[126.79517662900011,26.305609442]]],[[[127.24415123800011,26.586411851000108],[127.23682701900006,26.579169012000094],[127.22706139400023,26.579535223],[127.21485436300006,26.57746002800009],[127.20883222700016,26.581976630000142],[127.21485436300006,26.592922268],[127.22217858200005,26.598944403000118],[127.23275800900004,26.600775458],[127.2423608730002,26.596747137000122],[127.24415123800011,26.586411851000108]]],[[[127.80982506600017,26.735419012000037],[127.81120853000007,26.706976630000113],[127.78565514400006,26.70258209800012],[127.75855553500006,26.71287669500005],[127.75464928500006,26.728583075000117],[127.76880944100017,26.734035549000154],[127.79574629000004,26.731512762000037],[127.80982506600017,26.735419012000037]]],[[[128.3097436860002,26.704738674000126],[128.30811608200017,26.700873114000117],[128.30420983200005,26.701320705000015],[128.29997806100008,26.70258209800012],[128.296641472,26.701320705000015],[128.26441491000017,26.65241120000003],[128.24333743600008,26.63328685100008],[128.21713300900004,26.62563711100013],[128.17221113400004,26.623480536000116],[128.1588647800002,26.618801174000012],[128.15162194100017,26.612453518000095],[128.142832879,26.601752020000063],[128.13526451900017,26.590073960000055],[128.13209069100012,26.58124420800003],[128.13502037900005,26.57794830900015],[128.14087975400017,26.576727606000148],[128.14576256600006,26.573431708000115],[128.14519290500007,26.56411367400007],[128.14177493600008,26.556586005000113],[128.13835696700008,26.554022528000118],[128.13306725400017,26.55320872599999],[128.10873457099999,26.545152085000055],[128.09034264400023,26.542669989000117],[128.07162519599999,26.544012762000037],[128.05591881600006,26.550482489000117],[128.03638756600006,26.527329820000162],[127.98414147200006,26.478461005],[127.97201582100016,26.471869208000115],[127.95997155000023,26.470851955000157],[127.950938347,26.466701565000037],[127.94727623800006,26.451157945000105],[127.94027753999998,26.447739976000108],[127.87061608200011,26.44741445500007],[127.85987389400012,26.445624091000084],[127.85173587300008,26.44122955900012],[127.83887780000023,26.425238348],[127.84302819100017,26.417466539000102],[127.85474694100017,26.412990627000127],[127.86475670700005,26.406480210000055],[127.86980228000019,26.39345937700007],[127.87338300900015,26.364813544000086],[127.87826582100004,26.351874091000084],[127.8888452480002,26.33909739800005],[127.91016686300023,26.323065497000144],[127.91993248800011,26.30963776200015],[127.90186608200005,26.304999091000056],[127.88209069100006,26.310736395],[127.8615014980002,26.319403387000094],[127.84083092500012,26.32391998900006],[127.8282983730002,26.316229559000035],[127.77751712300008,26.23334381700012],[127.77100670700017,26.216498114000117],[127.76840254000015,26.197658596000068],[127.77540123800023,26.185126044000086],[127.79151451900012,26.185492255],[127.80990644600016,26.189195054000137],[127.82357832099999,26.186753648000106],[127.81421959700006,26.165920315000122],[127.79688561300006,26.139715887000094],[127.77572675900015,26.12177155200014],[127.75489342500006,26.125311591000028],[127.74170983200023,26.115627346000068],[127.72535241000006,26.089585679],[127.71013431100008,26.08437734600001],[127.65170332100016,26.08437734600001],[127.65707441500004,26.12177155200014],[127.63591556100002,26.191351630000142],[127.63819420700011,26.214748440000122],[127.658457879,26.212347723000093],[127.67774498800023,26.230617580000157],[127.69263756600012,26.25625234600004],[127.70020592500023,26.276109117000132],[127.71827233200005,26.274074611000103],[127.73129316500015,26.28937409100014],[127.73910566500015,26.30923086100016],[127.74187259200019,26.32054271000014],[127.73861738400015,26.341457424000012],[127.72437584700006,26.385443427000112],[127.72071373800011,26.406480210000055],[127.7225041020001,26.42991771000011],[127.73129316500015,26.439886786000116],[127.74773196700008,26.44183991100006],[127.78109785200004,26.44122955900012],[127.793711785,26.442694403000175],[127.80518639400023,26.44757721600014],[127.81006920700023,26.45799388200014],[127.81373131600017,26.45799388200014],[127.82878665500019,26.48305898600013],[127.83057701900006,26.4884300800001],[127.86426842500006,26.505682684000092],[127.90267988399998,26.516424872000115],[127.93751061300023,26.53213125200007],[127.96021569100017,26.56411367400007],[127.96241295700005,26.581976630000142],[127.95590254000015,26.59479401200015],[127.94060306100019,26.602484442],[127.89429772200006,26.609279690000122],[127.88559004000015,26.620184637000094],[127.88477623800023,26.635687567],[127.88575280000012,26.653509833],[127.87794030000012,26.686590887000037],[127.87826582100004,26.694525458],[127.88542728000019,26.69611237200003],[127.91993248800011,26.694525458],[127.96461022200006,26.697170315000093],[127.98731530000012,26.692938544000143],[128.00123131600017,26.68024323100009],[127.99952233200005,26.671820380000057],[127.99252363399998,26.663275458000115],[127.98853600400005,26.652980861000017],[127.99561608200005,26.63922760600009],[128.00440514400023,26.634751695000134],[128.014903191,26.634833075000117],[128.05738366000017,26.64476146000011],[128.11109459700006,26.67401764500012],[128.10328209700018,26.69122955900015],[128.11133873800011,26.703436591000028],[128.15235436300011,26.731512762000037],[128.15699303500006,26.73712799700003],[128.1619572270001,26.75714752800009],[128.169200066,26.761175848000065],[128.17611738400004,26.762518622000144],[128.17945397200006,26.76585521],[128.18799889400023,26.78050364799999],[128.2259220710001,26.800970770000063],[128.23462975400017,26.813950914000046],[128.23853600400017,26.832424221000068],[128.25440514400012,26.864406643000066],[128.25505618600002,26.886297919000114],[128.33139082100016,26.81342194200009],[128.337657097,26.79694245000009],[128.33334394600004,26.79303620000009],[128.33025149800002,26.78725820500013],[128.32984459700006,26.780340887000037],[128.334239129,26.77301666900003],[128.337657097,26.76430898600013],[128.33334394600004,26.756659247000115],[128.32683353000002,26.75047435100005],[128.32341556100013,26.746039130000085],[128.3097436860002,26.721177476000108],[128.3088485040001,26.71775950700011],[128.3097436860002,26.704738674000126]]],[[[127.95346113400015,26.913845119000044],[127.94817142000008,26.911281643000123],[127.93710371200021,26.91282786700016],[127.92611738400015,26.917141018000066],[127.91976972700004,26.92206452000012],[127.9181421230002,26.925197658000016],[127.92099043100004,26.934637762000037],[127.91854902400021,26.94216543200018],[127.92619876400025,26.95128001500008],[127.9458927740001,26.951605536],[127.95639082100016,26.941026109000134],[127.95313561300011,26.923325914000046],[127.95346113400015,26.913845119000044]]],[[[128.45484459700012,27.02875397300015],[128.44857832100016,27.02069733300003],[128.43140709700006,27.02167389500009],[128.41089928500017,27.030585028000033],[128.4060978520001,27.035101630000085],[128.40154056100002,27.03693268400015],[128.39625084700018,27.04075755400008],[128.39625084700018,27.049017645000063],[128.401621941,27.050726630000057],[128.40870201900006,27.05084870000003],[128.41570071700008,27.059759833000058],[128.42896569100017,27.066473700000117],[128.44434655000023,27.06256745000003],[128.45167076900023,27.054144598000065],[128.454356316,27.047512111000017],[128.45484459700012,27.02875397300015]]],[[[127.95215905000023,27.01829661700016],[127.92579186300023,27.00975169500005],[127.93572024800002,27.025091864],[127.98707116000017,27.078599351000108],[127.99537194100006,27.091742255000085],[128.00098717500023,27.091742255000085],[127.99317467500023,27.06671784100014],[127.97575931100008,27.039740302],[127.95215905000023,27.01829661700016]]],[[[128.66976972700016,27.421087958000058],[128.6557723320001,27.410142320000162],[128.64047285200016,27.384670315000093],[128.630707227,27.375677802000055],[128.61817467500012,27.37970612200003],[128.61312910200016,27.36432526200018],[128.604746941,27.36180247600008],[128.59546959700006,27.363918361000074],[128.58741295700023,27.362046617000104],[128.57447350400005,27.353338934000035],[128.55990644599999,27.350734768000123],[128.54542076900023,27.35374583500014],[128.53248131600006,27.362046617000104],[128.52686608200023,27.37189362200003],[128.52515709700006,27.384670315000093],[128.52613366,27.397162177],[128.5288192070001,27.40643952000015],[128.53386478000013,27.41526927300002],[128.53842207100016,27.417629299000154],[128.54656009200008,27.41787344],[128.56153405000012,27.420396226000108],[128.60027103000007,27.415350653000175],[128.61882571700008,27.41559479400003],[128.63526451900023,27.423773505000113],[128.65040123800023,27.434719143],[128.7006942070001,27.454820054000052],[128.70248457099999,27.451605536],[128.70281009200014,27.450751044000086],[128.70346113400015,27.450425523000078],[128.70687910200016,27.448635158000073],[128.69760175900004,27.435736395000063],[128.66976972700016,27.421087958000058]]],[[[128.24341881600006,27.85724518400012],[128.24154707100016,27.84536367400007],[128.23226972700004,27.852850653000033],[128.2298283210001,27.868963934000092],[128.236582879,27.866522528000147],[128.23853600400017,27.86090729400017],[128.24341881600006,27.85724518400012]]],[[[128.96070397200006,27.685777085],[128.94703209700012,27.68138255400011],[128.92945397200018,27.686712958000086],[128.92188561300011,27.700913804],[128.91928144600004,27.715562242000104],[128.91635175900004,27.722357489000117],[128.89454186300023,27.72821686400009],[128.8908797540001,27.741888739000032],[128.89926191500015,27.769517320000105],[128.87891686300017,27.824774481000034],[128.88428795700023,27.83685944200012],[128.88851972700004,27.853176174000154],[128.89454186300023,27.89130280200014],[128.8999129570001,27.895900783000073],[128.91521243600008,27.900336005000142],[128.94703209700012,27.914129950000145],[128.96550540500002,27.894354559000035],[128.9773055350001,27.836615302],[128.98878014400023,27.81110260600009],[128.99952233200023,27.802801825000117],[129.0131942070001,27.79523346600017],[129.02466881600006,27.78607819200009],[129.02963300899998,27.77326080900015],[129.02426191500004,27.756577867000104],[129.00025475400017,27.727728583],[128.98951256600017,27.707464911000088],[128.976328972,27.69550202000015],[128.96070397200006,27.685777085]]],[[[129.22974694100017,28.038600979000037],[129.23625735800024,28.035101630000057],[129.25790449300004,28.035915432000152],[129.26579837300002,28.034979559000178],[129.26889082100004,28.031642971000153],[129.2676701180001,28.017116604000122],[129.26531009200002,28.012396552000112],[129.26002037900017,28.006293036000116],[129.24317467500018,28.018866278000118],[129.2326766290001,28.02248769700016],[129.2145288420002,28.021755276000036],[129.21159915500007,28.023057359000134],[129.20744876400013,28.031642971000153],[129.2076929050002,28.042263088000098],[129.21159915500007,28.048854885000154],[129.2159123060001,28.051459052000084],[129.22388756600006,28.046210028000175],[129.22974694100017,28.038600979000037]]],[[[129.1583764980002,28.01902903900016],[129.1504826180002,28.014960028000118],[129.1466577480002,28.015326239000117],[129.14543704500002,28.031398830000015],[129.15129642000014,28.04840729400017],[129.16073652400016,28.062119859000106],[129.16846764400023,28.066392320000105],[129.17156009200002,28.063462632],[129.17147871200004,28.058172919000114],[129.17448978000007,28.05255768400012],[129.16944420700023,28.04189687700007],[129.16757246200004,28.032782294000086],[129.164317254,28.025010484000077],[129.1583764980002,28.01902903900016]]],[[[129.2215275400001,28.17601146000014],[129.2325952480002,28.170721747000172],[129.269297722,28.174872137000094],[129.26392662900017,28.16738515800013],[129.2560327480002,28.15916575700014],[129.25049889400006,28.151027736000046],[129.25180097700016,28.143784898000106],[129.26417076900012,28.134711005],[129.28777103000007,28.12518952],[129.29664147200018,28.119574286],[129.32553144600004,28.123480536],[129.34734134200008,28.10691966400013],[129.35043379000015,28.086493231000148],[129.32398522200018,28.07859935099999],[129.30640709700006,28.094142971000096],[129.30347741,28.09601471600014],[129.30079186300023,28.100531317000033],[129.29517662900005,28.09906647300015],[129.2905379570001,28.09516022300015],[129.29037519600016,28.092922268000066],[129.29070071700008,28.08881256700012],[129.2854110040001,28.08148834800012],[129.27914472700004,28.07709381700012],[129.27613366000006,28.08201732000019],[129.27361087300014,28.08331940300009],[129.26392662900017,28.096340236000074],[129.26303144600016,28.099107164000046],[129.25180097700016,28.100002346000124],[129.23064212300008,28.09666575700011],[129.2214461600001,28.099107164000046],[129.21810957100016,28.106105861000017],[129.21599368600008,28.13084544500019],[129.21412194100017,28.140082098000065],[129.18702233200017,28.174994208000086],[129.1808374360002,28.190252997000144],[129.19581139400023,28.19546133000013],[129.22771243600008,28.19525788],[129.2215275400001,28.17601146000014]]],[[[129.99545332100016,28.29092031500015],[129.97820071700008,28.28375885600012],[129.95655358200005,28.28481679900007],[129.9135848320001,28.29710521000011],[129.97559655000018,28.343247789000046],[130.01832116000017,28.3646507830001],[130.03028405000012,28.373480536000145],[130.03101647200006,28.35651276200015],[130.02149498800023,28.331284898000106],[130.00766035200016,28.306545315000122],[129.99545332100016,28.29092031500015]]],[[[129.7210392590001,28.434881903000033],[129.71599368600008,28.429144598000065],[129.70850670700005,28.428778387000065],[129.70053144600004,28.42999909100017],[129.69385826900017,28.428697007000082],[129.68165123800011,28.421087958000115],[129.63941491000017,28.40534088700015],[129.62525475400005,28.402736721000068],[129.6193139980002,28.400783596000124],[129.6168725920002,28.396470445000134],[129.61524498800017,28.38959381700012],[129.611175977,28.383124091000113],[129.60189863400015,28.380316473000065],[129.58155358200017,28.370672919000086],[129.55396569100006,28.32843659100017],[129.53345787900017,28.31883372599999],[129.53003991000017,28.314764716000028],[129.49887128999998,28.287502346000124],[129.48731530000018,28.28253815300009],[129.461192254,28.291693427000084],[129.4474389980002,28.29092031500015],[129.45484459700018,28.277248440000037],[129.44776451900012,28.266302802000055],[129.4396264980002,28.259019273000135],[129.43043053500006,28.254095770000063],[129.420176629,28.249945380000142],[129.43165123800023,28.241034247000087],[129.45687910200004,28.225734768000123],[129.46794681100008,28.215765692000147],[129.41521243600008,28.185736395],[129.40650475400017,28.1844750020001],[129.40154056100013,28.16331614799999],[129.38868248800011,28.16282786700019],[129.37110436300017,28.16722239799999],[129.35181725400005,28.160549221000124],[129.3644311860002,28.154038804000137],[129.37452233200005,28.140041408000073],[129.37940514400023,28.12596263200011],[129.37598717500023,28.119574286],[129.36573326900012,28.12319570500007],[129.33757571700014,28.147528387000037],[129.2866317070001,28.177923895],[129.28028405000006,28.190497137000094],[129.2814233730002,28.20482005400008],[129.28077233200005,28.218573309000092],[129.269297722,28.22943756700009],[129.25635826900012,28.214667059000092],[129.24244225400005,28.214300848000093],[129.20785566500015,28.22943756700009],[129.17359459700018,28.236070054000052],[129.15894616000017,28.240627346000096],[129.145762566,28.249945380000142],[129.145762566,28.256170966000084],[129.1544702480002,28.260321356000034],[129.16285241000006,28.262640692],[129.17139733200005,28.261664130000113],[129.18051191500015,28.256170966000084],[129.20883222700004,28.264227606000034],[129.2413843110002,28.25633372600005],[129.27084394600016,28.25535716400016],[129.29037519600016,28.284084377000127],[129.25367272200006,28.284572658000016],[129.239512566,28.290350653000175],[129.22771243600008,28.304592190000093],[129.24146569100017,28.31256745000006],[129.25245201900012,28.321519273000078],[129.26392662900017,28.32884349200016],[129.28825931100002,28.333075262000094],[129.29566491,28.336371161000116],[129.31625410200016,28.350165106000034],[129.33057701900023,28.363714911],[129.33130944100017,28.366034247000172],[129.35726972700016,28.368353583000115],[129.38062584700006,28.366766669000086],[129.39958743600008,28.37173086100013],[129.41334069100017,28.393296617000075],[129.4323022800002,28.38690827000012],[129.45093834700018,28.39203522300015],[129.46615644600016,28.404852606000176],[129.47535241000006,28.421291408000073],[129.48243248800011,28.411037502000156],[129.49268639400012,28.404852606000176],[129.50440514400012,28.39988841400013],[129.51636803500006,28.393296617000075],[129.53345787900017,28.430487372000144],[129.56080162900017,28.451117255000085],[129.626231316,28.48273346600014],[129.61394290500007,28.447984117000104],[129.61646569100017,28.43854401200018],[129.63249759200008,28.434881903000033],[129.6440535820001,28.439764716000113],[129.65430748800011,28.45213450700014],[129.6733504570001,28.48273346600014],[129.6725366550002,28.487616278000033],[129.66879316500015,28.495062567],[129.66724694100006,28.50299713700015],[129.67310631600017,28.50983307500009],[129.68775475400017,28.508978583],[129.69800866,28.494859117000047],[129.70753014400012,28.462144273000135],[129.71143639400012,28.455633856000034],[129.71729576900023,28.45026276200018],[129.72169030000012,28.444037177000112],[129.7210392590001,28.434881903000033]]]]}},{\"type\":\"Feature\",\"properties\":{\"iso_3166_2\":\"JP-10\",\"name\":\"Gunma\",\"name_alt\":null,\"name_local\":\"群馬県\",\"type\":\"Ken\",\"type_en\":\"Prefecture\",\"region\":\"Kanto\",\"postal\":null,\"region_code\":\"JPN-KNT\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[139.21458296093488,36.945652167313625],[139.21737348889536,36.929503282350595],[139.22646854139003,36.92498159452492],[139.24698408364856,36.92017568585922],[139.27163374190366,36.91955556913413],[139.35958702971755,36.90203725771326],[139.34026004496508,36.87175486939171],[139.3363843122861,36.86007599541085],[139.33917483934732,36.84870717979251],[139.35064700685388,36.83656321871763],[139.3607239121788,36.82953522377153],[139.3682686710623,36.823049832084024],[139.36894046373146,36.81429067637359],[139.3666150246636,36.80827037178025],[139.35932864819853,36.800363877690785],[139.35204227083415,36.79026113484349],[139.34212039603938,36.78054596652311],[139.33261193329406,36.76907379811726],[139.3288912293467,36.75656810183642],[139.3304415220586,36.71930939451295],[139.32568729113552,36.70465912541829],[139.31276818280537,36.67634044005919],[139.30718712778372,36.658925483224465],[139.30594689343434,36.64474030212324],[139.31204471239346,36.63215709147664],[139.3413452496835,36.61536225046743],[139.35374759317693,36.60970368108005],[139.36366946797148,36.60662893317871],[139.4234074241133,36.60045359985375],[139.43586144355078,36.595105088828745],[139.4458866720322,36.58593252196826],[139.44764367121843,36.57270335527568],[139.44454308489537,36.56151540770986],[139.43865197241058,36.55151601854945],[139.42712812716127,36.54358368603826],[139.41891157381013,36.532705796835074],[139.4164311060107,36.52588450836333],[139.41178022697585,36.51813304300546],[139.40309858563114,36.50810781542333],[139.39705244441478,36.498444322147805],[139.39302168210486,36.48748891947804],[139.39079959492528,36.47508657508541],[139.3835648953036,36.45906688133147],[139.3783972523313,36.443848172355246],[139.36320437997813,36.42227326110205],[139.356073033144,36.41514191336856],[139.34506595273155,36.402016100362744],[139.34088016169005,36.39571157492921],[139.33777957626643,36.38883860871482],[139.33653934101773,36.37912344039444],[139.3383996929914,36.368555610453086],[139.34351566001965,36.35855622039318],[139.35374759317693,36.348065903918325],[139.3835648953036,36.32346792340596],[139.3913163597622,36.31098806374817],[139.40077314656344,36.30036855696332],[139.42356245284492,36.279103704972044],[139.44051232258568,36.268484199086544],[139.4548783708401,36.268845934292415],[139.46826256716372,36.27300588601284],[139.4823702329998,36.27515045792728],[139.5028341011129,36.27437531247068],[139.5886686554341,36.2601126170037],[139.60546349464448,36.25241282848927],[139.63848473498257,36.22574778952884],[139.6537292832798,36.20313935040073],[139.67403812086252,36.15221222591353],[139.61001102089187,36.17944570655408],[139.56308882119208,36.19096955000467],[139.51420291583196,36.1928299019784],[139.4730684758295,36.178076280096235],[139.44423302563365,36.19631806102967],[139.41250369738776,36.21174347648025],[139.378603957007,36.222492174474326],[139.30429324793505,36.228228258227546],[139.23241132981906,36.242180895331984],[139.1987182959126,36.254428209194515],[139.19277550658433,36.25760630988327],[139.17763431197386,36.258381456239164],[139.1380501646833,36.270602932579266],[139.11185021371642,36.2708613140984],[139.10823286435522,36.26874258150494],[139.0896293482158,36.249028022225446],[139.0496317888759,36.19649892818293],[139.04436079311614,36.18665456775415],[139.04229373556734,36.1757250035061],[139.04229373556734,36.16440786383191],[139.04074344285556,36.14975759473725],[139.0337671247529,36.13867300085829],[139.0182125180932,36.128906154795175],[138.95702762292638,36.1111294618552],[138.85827396117435,36.07273387296985],[138.7827230168537,36.0351651072838],[138.7642745303453,36.03216787374831],[138.75073530529005,36.02718109792934],[138.74143354722042,36.019868883042534],[138.73569746256788,36.01209158016232],[138.73228681878163,36.00188548452749],[138.73213178915074,36.000696926122174],[138.73182173078823,35.999560045459674],[138.71642215375937,35.98105988210784],[138.700919223943,35.96953603775788],[138.62784874922033,36.01702667913793],[138.61451622884078,36.031030992186516],[138.61188073141057,36.042089749442425],[138.61467125937116,36.051882433027856],[138.61560143355933,36.06255361575671],[138.61265587776654,36.084257717319815],[138.61389611211592,36.10497996695187],[138.61327599539084,36.116400458514335],[138.6021138862468,36.12534048137812],[138.58950483627922,36.13249766753333],[138.57539717044307,36.14347890952408],[138.57028120341482,36.15334910747529],[138.56811079307863,36.16549306944955],[138.57555219917458,36.169549669281764],[138.59260542170284,36.16978221327855],[138.60025353427318,36.17621592902191],[138.60469770683375,36.18637034781332],[138.60469770683375,36.20497386305334],[138.5950858913008,36.226988022978816],[138.58857466029224,36.254970812453024],[138.58702436668113,36.270215358951674],[138.59281212817717,36.27941376423385],[138.6115189962046,36.290446682168664],[138.62211266456785,36.30011017544419],[138.6298641299257,36.31065216696393],[138.63265465698686,36.32390717207832],[138.6295540706638,36.332252915739446],[138.6283138372138,36.33744639713345],[138.6236629581789,36.38395518748192],[138.61482628810273,36.400284938698945],[138.60097700378566,36.411679591839714],[138.5837687516265,36.416898912554814],[138.56904096906544,36.41839752932245],[138.55415815597405,36.41757070612323],[138.54051557813142,36.41410838549365],[138.5141089206901,36.40434153943053],[138.4992261093974,36.40230032030357],[138.48170779797653,36.403359687499574],[138.46413781061142,36.39811452926213],[138.450650263299,36.397571926003565],[138.43840294763788,36.400284938698945],[138.40858564730974,36.4186817492633],[138.38889692555267,36.43452057496461],[138.38062869715662,36.44847321206913],[138.37680464132114,36.46782603614264],[138.37897505075796,36.48314809790642],[138.38378095942375,36.502733465976604],[138.39897382997822,36.54552155237775],[138.40470991373155,36.57037791620789],[138.41132449842684,36.58076487899589],[138.41912763882942,36.59084178342155],[138.42408857622678,36.59929087987015],[138.4278609561182,36.60794668189382],[138.4306514831793,36.61812693910696],[138.43654259656353,36.62543915489309],[138.44692955935164,36.63091685622791],[138.4842916203619,36.63848745443241],[138.49390343589482,36.645773830897525],[138.4988643741915,36.6561091168422],[138.49674563979943,36.671069444299405],[138.4992261093974,36.68205068629007],[138.50744266184927,36.68887197476184],[138.51829471173153,36.69251516434332],[138.54185916616763,36.69812205688734],[138.57973799021602,36.701584378416385],[138.60345747338363,36.70654531491434],[138.61358605375335,36.71264313387354],[138.6295540706638,36.71822418889522],[138.6609216664017,36.72416697822342],[138.67952518074247,36.73065237081019],[138.72267500234943,36.74917837258383],[138.74401736870658,36.7474213733976],[138.76902876216764,36.7501602245147],[138.7941435084162,36.757808336185775],[138.8081478205654,36.76783356376791],[138.81419396358052,36.77902151133364],[138.8224621928759,36.80359365522304],[138.83672488834287,36.81082835484463],[138.89010664310706,36.82666718234461],[138.90328413295634,36.83627899787743],[138.91057051032084,36.84322947845766],[138.91460127173133,36.86932607753644],[138.91708174042992,36.878472805075916],[138.92514326415034,36.886120916746904],[138.95868126842578,36.894440822885684],[138.96472741144083,36.90167552340668],[138.9639522650849,36.91345775017504],[138.9596114444126,36.92717784328289],[138.9605416203995,36.95735687881685],[138.9651924994343,36.96740794482079],[138.97397749176776,36.97198131038917],[139.03361209512207,36.98699331289106],[139.0539726104476,36.99872386281601],[139.05660810787788,37.00099762593965],[139.06311933798708,37.009059150559324],[139.0722660673252,37.02660329770407],[139.0800175308844,37.03657685024143],[139.0919547872835,37.0392381860934],[139.10451216040786,37.03556915898946],[139.11231530170977,37.028928738570414],[139.11634606312037,37.02461375631981],[139.1245626155723,37.01345164627642],[139.1292134946071,37.00818065051658],[139.13603478397812,37.00363312426927],[139.14177086773134,37.00073924442063],[139.1431661317116,37.000170802740314],[139.15138268416354,36.99489980698047],[139.15649865119178,36.98828522408385],[139.16755740844758,36.96022492114315],[139.1778926943923,36.95410126376234],[139.19019168509823,36.95193085432544],[139.20073367661786,36.95097483991691],[139.21458296093488,36.945652167313625]]]}}]}"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/maps/usa-albers.geo.json",
    "content": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-37.234577658947664,42.42305144937504],[-38.784869254483475,42.52617515068762],[-40.821491912220374,42.66703942418085],[-43.216763290047105,42.839625066021696],[-45.56072716424567,43.01233522835387],[-46.538175226511676,43.08955633160228],[-46.6177589120615,43.05738064153352],[-48.23345722196269,43.183261000037284],[-50.109743107180634,43.340412258518924],[-52.40725562614251,43.540504349825184],[-52.65069038701232,43.57951142612157],[-54.47932958715376,43.74507991483814],[-55.73300624460669,43.852502825925285],[-56.9654127029659,43.968087215455974],[-59.47341198213282,44.19242807378208],[-62.38038055042575,44.47219881571612],[-62.48386084323597,44.51548585059443],[-65.71647001771628,44.82048411587307],[-67.5048813366632,45.019192915399955],[-67.95841571959816,45.027894457441185],[-68.76653618898702,45.13675348007],[-68.97418089510765,45.124516625112186],[-70.4811571129134,45.28082894466711],[-70.86627676112258,45.3771927038873],[-72.21237114457928,45.5183957061764],[-72.37254577993053,44.849779228508325],[-72.63669976978649,43.626570796806],[-72.77560308200106,42.84692105176968],[-73.28828807352137,40.45063917558032],[-73.40151502879577,39.84505711229115],[-73.87439037819183,37.50407454855722],[-74.13631972475936,36.1576828777831],[-74.44256184642381,34.59230711509369],[-74.72989914360193,33.082599456695036],[-75.02552976760673,31.61537016081338],[-75.55878313374993,28.718537400695144],[-76.12242569123185,25.550444220481076],[-76.28398527909648,24.658013578848355],[-76.69636591983853,22.29343437643702],[-73.81972542251606,21.854014498111205],[-72.43169180677559,21.66220935240622],[-71.20716311425856,21.50792059068772],[-68.75892383687957,21.179028575911232],[-67.21012892403772,20.99168531302167],[-66.14937212144588,20.85133770303588],[-63.37771496347499,20.4930575128233],[-61.551479664287534,20.275157217139913],[-60.8367305258635,20.20016346781668],[-59.26929550047583,20.01429319404913],[-56.56858251529825,19.711234146408312],[-55.305687574094705,19.575618365847927],[-53.30418577750853,19.351015399427133],[-52.31716901737298,19.24645572814525],[-51.65431238331545,19.15696277903088],[-50.3124971612377,19.00736048014103],[-48.53497843202123,18.830570914072386],[-46.48869548711621,18.643306715049064],[-43.90485516831376,18.408579862527656],[-42.681102830587875,18.324642497628123],[-39.637481438049264,18.070139517390814],[-39.39076578897901,20.750077180286173],[-39.27587610992219,22.04539643209445],[-39.0789588593283,24.183827365674798],[-38.92487776386826,25.8043948932116],[-38.725341846856516,27.930908990784204],[-38.62525963784572,28.940691393061947],[-38.42089975162439,31.046166356157094],[-38.1776121869848,33.557716725082756],[-37.982732639546064,35.464426137922366],[-37.823232677634316,36.95800419534266],[-37.67532564637129,38.3695839830172],[-37.51612977447475,39.848442779957004],[-37.29964610900672,41.77605794596337],[-37.234577658947664,42.42305144937504]]]},\"properties\":{\"name\":\"Wyoming\",\"ns_code\":\"01779807\",\"geoid\":\"56\",\"usps_abbrev\":\"WY\",\"fips_code\":\"56\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[86.0203417031575,20.26334600887488],[86.3576828522193,18.32655101657395],[86.89369984619013,15.206710156230184],[87.0892439286871,14.04077807509085],[90.1925781747983,14.539213121281538],[92.67748223760837,14.95807147531828],[94.98246814466799,15.366730831821236],[97.23264519825273,15.762965166709657],[98.73118402790978,16.028249449454353],[100.94729202537096,16.4319416473696],[103.22721468466341,16.840284606182554],[105.95618524632485,17.361106714327057],[108.48955891895352,17.86618255147349],[110.50741534163748,18.26920992772743],[112.34975158979492,18.650546356829768],[112.42844151920987,18.666657043059057],[112.48155272207383,18.91447266458286],[112.61292384595062,19.179403594401926],[112.78408256181218,19.37837366103872],[112.97736474959864,19.52229975310984],[113.3624134230362,19.674483799978663],[113.81816964895312,19.697833632565867],[114.21033256534885,19.588752782427065],[114.58593025771071,19.993365950532837],[114.89434386354408,20.057925048777648],[115.14415392161564,20.196292861405333],[115.26849250496733,20.34303913198919],[115.54483560748544,20.47650441955551],[115.55632026138787,20.639118897667363],[115.46054575482466,20.87786683943845],[115.52077468855103,20.991657909164207],[115.7328847126472,21.123117949045692],[115.833389168443,21.363429550769293],[115.9958939714613,21.46852661578317],[116.33366000659103,21.86505327977783],[116.7033770681652,22.06205575074993],[116.81842834159809,22.35097117727565],[117.02398570346605,22.370275085728114],[117.31963881777467,22.608556332272556],[117.30753352264205,22.70353262285802],[117.09999635255255,22.8266340606484],[116.96848532124822,22.998303400779],[116.53271748696623,23.151824014299617],[116.32546868724116,23.41173111710756],[115.88164717803328,23.609448351176596],[115.6394943860357,23.970012991151847],[115.47224516996847,24.013635809187974],[115.29233745699943,23.939193498348875],[115.11262780655908,24.016437626040386],[115.00755539825691,24.226893969462633],[115.01128353747316,24.3878052592151],[114.88664279725484,24.774575575199584],[114.67958892112331,24.905915485249754],[114.48226113009443,24.92120748603816],[114.30123990917106,24.809420774284337],[114.07351902104267,25.09900611363453],[114.02418104697068,25.31976241264672],[114.10642499437441,25.486398410292036],[113.93998181363963,25.58423452211511],[113.98462544637415,25.873063640049985],[113.88470336921932,25.97225878472674],[113.97433863518815,26.163071100443045],[114.15062318384118,26.170957171786206],[114.28316138773887,26.313119427628692],[114.42404578929323,26.71890916575502],[114.43990498793418,26.88869284694525],[113.98089511138157,27.317979291218435],[113.85444823060385,27.542018903386335],[114.03565067072951,27.734127350004282],[114.32385717444805,27.95272109693584],[114.34896809991949,28.131464269144356],[114.48660952538015,28.24094721803975],[114.44271391921411,28.42090216805526],[114.70248790905828,28.665911434985166],[114.84810740899236,28.970455127072242],[114.90369026318061,29.23698348118835],[114.8697965465033,29.413286155187475],[114.94967687349475,29.674189641624427],[115.18496969476226,29.95918116546346],[115.2416899437473,30.102906270853417],[115.53818983339688,30.238673686360286],[115.39065866813282,30.415823281801103],[115.10661556166956,30.573752082310218],[114.92188308944938,30.532761480762275],[114.79505392688901,30.636898530580794],[114.39875402862,30.52921920559912],[114.24617760022508,30.716851768262185],[114.05988498591898,30.75110139513145],[113.85938334206308,30.689526210484843],[113.78298775694043,30.85705237681091],[113.56162890587882,30.968695314484556],[113.49144629138371,31.14702205605463],[113.18093670686518,31.353109888234357],[113.31862431591581,31.45654964098802],[113.1640226659617,31.868516216175674],[113.0620858808108,32.244086907853486],[112.9138356572456,32.33963855822754],[112.7617313713734,32.32760503664408],[112.8001771981824,32.567404750206464],[112.64082552565445,32.606606957725646],[112.60275831551368,32.728377886805966],[112.10039164330409,32.79694821780689],[112.01815527266488,32.704743344412016],[111.72677993855497,32.83543897962435],[111.60650019254712,33.131367044291885],[111.4300247320815,33.16085667411981],[111.09879171388876,33.4098811972223],[109.13358306976,33.04089404776504],[107.89233212508255,32.82402271013328],[105.51011370631115,32.39836914917245],[103.49769983079707,32.067773152687224],[101.99704816222652,31.79253814785182],[100.04243581555562,31.465181245766505],[98.64271619734217,31.221081257463425],[96.84106600538847,30.93715273609976],[96.08556336969,30.80494435603086],[94.40052305705753,30.53970502702343],[92.96374865304895,30.297536192707188],[92.28116997921677,30.20042001259096],[89.86561558220373,29.822187762767697],[88.36735288309217,29.599137575901754],[87.90140459009446,31.886693144972256],[87.7300570204462,32.73002326025765],[86.2392968432793,31.763717443469293],[84.04775290055217,31.03539069631209],[84.46356322924458,28.85786607583704],[85.0444936047889,25.71626757782572],[85.55168732788741,22.920211877217444],[86.0203417031575,20.26334600887488]]]},\"properties\":{\"name\":\"Pennsylvania\",\"ns_code\":\"01779798\",\"geoid\":\"42\",\"usps_abbrev\":\"PA\",\"fips_code\":\"42\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[64.46206799465298,6.496961030298087],[64.64592560618182,6.606695784008606],[64.80633040235348,6.840988125885624],[64.99118456090999,6.78358754433464],[65.17616693833833,6.564745365028389],[65.39034458960228,6.513383699916217],[65.50853889167,6.4122794049185785],[65.81492657679011,6.482701231871937],[65.91970361143774,6.619852436268781],[66.15508719583768,6.61321494282005],[66.41508190832232,6.8451335556982045],[66.56056678552913,6.714407787697023],[66.62369401008276,6.418295427073123],[66.73242858691985,6.346631939835574],[67.01976960764912,6.342369318915685],[67.29652347778972,6.196506491784181],[67.50194946256157,5.759523621104794],[67.84261916445017,5.36001133151638],[67.92159840963224,4.900085331597385],[68.03296937064961,4.7649569383932455],[68.35123009001207,4.726732915373601],[68.84242810328963,4.60987361669178],[68.98548859904783,4.628920610519897],[69.3375482133378,4.795542039709238],[70.00637124816502,4.662055377896051],[70.14430106608431,4.424613580389986],[70.47534959762376,4.283278870194467],[70.59068080724636,4.003156958842346],[70.85421728668638,3.9736160552919637],[71.17306180304782,3.8662239827897453],[71.29576750936221,3.958568603523413],[71.31711618691678,4.202985961628573],[71.43224310071408,4.3212738789560765],[71.91052725395694,4.517401728610043],[72.22428625143797,4.35874086165155],[72.73241572572621,4.327158591738465],[73.02844234885508,4.193050948001591],[73.10509980746232,4.005155395598175],[73.28287370483811,3.940782672632556],[73.48790224311638,4.1884646013526785],[73.73890455887198,4.135467861377105],[73.99184450260657,4.198997945965315],[74.08881365318514,4.293662480716848],[74.17413149668951,4.593930260222615],[74.42730292826704,4.756048679416471],[74.58446490122506,5.053484348152193],[74.88708860860315,5.0942375473471255],[75.04565878914997,5.243152934822119],[75.26467207358812,5.354524698109538],[75.39410006021647,5.304526354846226],[75.43409479810408,4.930682930774339],[75.59257705983369,4.611655470896041],[75.69486852627378,4.250532370458052],[75.86133986742134,4.111952932984528],[76.05484144310795,4.040159208891868],[76.39971280083411,4.077195227111696],[76.56336488821118,3.9955741359172494],[76.85909717228465,3.673813116165067],[77.11457246558182,3.5520044022090453],[77.26022528545836,3.2085467658203735],[77.39696573391997,3.1067565190891004],[77.58387618422806,3.126209190092612],[78.27550625057162,3.480806274335099],[78.36353128048276,3.4572049159510616],[78.70819830129304,3.615258480727081],[78.76796845526846,3.985981670863565],[78.74479019499566,4.536143516970425],[78.84429841722034,4.695659762532754],[79.12175515325677,4.706220089044961],[79.33307077767876,4.798673935094019],[79.35659967510733,4.987791072464702],[79.1960293807988,5.363116942360239],[79.21223259414364,5.571268929439613],[79.06420817316067,5.913854432755951],[78.91737601565035,6.0961567947178],[79.06341999977161,6.324202301016678],[79.28187395732323,6.544907347231779],[79.22851364519316,6.9434060788350305],[79.29410014127322,7.025949311584218],[79.43575181960112,7.531168903044372],[79.62309079929533,7.693519252486719],[79.66933736028484,7.915871548956626],[79.83594130169178,7.980133078640402],[80.0078315703817,7.752710571957372],[80.27549152796642,7.7439084517263925],[80.50572092447621,7.417133592435823],[80.40263511236414,7.139219715013213],[80.6263311214322,6.974411057645834],[80.82676066397269,7.2028594075589405],[80.8201995731397,7.399377641746118],[80.96595729399431,7.5468946980576295],[81.14272788895197,7.429023024310303],[81.2952323846153,7.504942621334923],[81.12193951631471,7.705648954275659],[81.16325549485761,8.075036567240168],[80.8528808017183,8.360351496959382],[81.13945890606382,8.598274427441543],[81.17880106991406,8.769033844567373],[81.03263392074864,9.219980424417285],[81.15675069348917,9.518125252170096],[81.32930174643545,9.63039402975487],[81.2702849626114,9.82396666006472],[81.35370287030594,9.96582566633694],[81.95903074337154,10.071075872469708],[81.93242474072284,10.51577519814958],[82.40278190552384,11.091311905102014],[82.69294805945646,10.983810736577935],[82.86161537091422,10.689970844457719],[83.07193300698819,10.745996673921104],[83.4410715436981,11.085322933731263],[83.73993055854814,11.178853926618652],[83.83989932487786,11.46942896491929],[84.0980604601108,11.606354462913146],[84.26708037671752,11.996414089788939],[84.57264802543123,12.444466286258969],[84.98916017293159,12.953771755280922],[85.26697726386686,13.039002898331539],[85.30425878909529,13.271421640743881],[85.27011135769878,13.50634977024208],[85.43232554155162,13.727007053422183],[85.15998255073659,14.035023998874323],[85.3551189933532,14.313101030559633],[85.30108014883443,14.608812068704374],[85.4570058529785,14.788972465349172],[85.32097179449366,15.06305416808998],[85.37961332694384,15.171155490141043],[85.59818075129603,15.160791085044645],[85.51704402781576,15.378522197095213],[85.6176186208716,15.567408953133096],[85.56323358306324,15.826305108751882],[85.58172942467785,16.077470390481672],[85.5025370437139,16.28160865019699],[85.63383496672074,16.48548878964528],[85.59271068727955,16.847513049532846],[85.73827509208398,17.18760390565185],[85.7383087887109,17.397344822869055],[85.90919223223396,17.646110040584944],[85.96471685407607,18.042789962439887],[85.8612530061011,18.23464392961925],[85.86354646343358,18.38654942074748],[85.70478217325606,18.528707553270102],[85.7998409768074,18.60628250652424],[85.76593665990532,18.78548234131167],[85.81399123356661,19.08206063047437],[85.65453775840608,19.26860463645483],[85.56500083930595,19.486910376496308],[85.32989400454318,19.6858800083327],[85.3103149864385,19.825261813458084],[85.47282276757308,20.05169987625275],[85.70287244906785,20.056515597999976],[86.0203417031575,20.26334600887488],[85.55168732788741,22.920211877217444],[85.0444936047889,25.71626757782572],[84.46356322924458,28.85786607583704],[84.04775290055217,31.03539069631209],[81.64985593591807,30.233953918179786],[80.39467354117818,29.835954651263744],[79.25354596799521,28.921574664621325],[78.36359428309183,28.187041926892874],[78.24517580679743,28.17121470717409],[76.87585056320127,27.11245357647014],[74.98836266748613,25.64680605537213],[73.5082357676484,25.455129466236592],[71.29187179868462,26.40408325411446],[70.97395349209883,26.982017074388303],[69.60446643077707,25.348164558723276],[68.07424525413165,25.10989000970587],[65.55589907149322,24.725286921036016],[64.07441029768982,24.51176026584786],[62.34878730805479,24.272303645536002],[62.71681757976824,21.506162424885872],[62.986553126148976,19.39005762118276],[63.2692092386263,17.161307055451946],[63.55954251603414,14.718873077449544],[63.758149871900734,12.77329354099837],[63.89578134364314,11.501048129000212],[64.17054497401708,9.143401607471407],[64.33903817453199,7.532787558595136],[64.46206799465298,6.496961030298087]]]},\"properties\":{\"name\":\"Ohio\",\"ns_code\":\"01085497\",\"geoid\":\"39\",\"usps_abbrev\":\"OH\",\"fips_code\":\"39\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-70.06615646820839,-7.199983140779321],[-70.41696558431377,-9.6984640341206],[-70.68518534223044,-11.590768436308707],[-70.9382265162427,-13.388186587584192],[-71.29605605667153,-15.869301677173693],[-71.52733612066665,-17.466140110725757],[-71.7941053760312,-19.316171511465754],[-72.0890521622258,-21.327039620607557],[-72.5130751974101,-24.145700147117658],[-72.85718263319062,-26.375565755857558],[-73.28603790761778,-29.108330784372782],[-73.64139271229433,-31.35232237658829],[-73.94859456101388,-33.19871589178014],[-74.23162123045276,-34.909192966754944],[-74.57934636526043,-36.9299058055318],[-74.8431870617263,-38.422973340792886],[-75.20584300687074,-40.41503614082921],[-75.66672067756258,-42.87870827077865],[-74.25965318454041,-43.021040723792304],[-72.36041798887828,-43.20084707158556],[-70.60259856553182,-43.36805789320576],[-70.18846737172152,-40.99892667135732],[-68.19547843309225,-41.19033157318218],[-65.47932844827046,-41.441545526643814],[-62.95148042814089,-41.66619301729478],[-60.11877282891669,-41.90839834598665],[-60.21299139256785,-41.77086392694232],[-60.391074635530565,-41.73932804647357],[-60.6983005555485,-41.41241564196411],[-60.731545970681054,-41.25462960466579],[-60.560079919540605,-41.04568132475225],[-60.628605077616854,-40.79825282043941],[-60.486531990786126,-40.7002276832302],[-57.53493890768654,-40.93432655062959],[-55.17545508789623,-41.125723278127246],[-53.367431728254395,-41.26867749734346],[-50.7976674670213,-41.457111895520065],[-47.59593062478979,-41.679483766598096],[-45.380024749776325,-41.82453533742719],[-43.28841465475426,-41.953560297268346],[-41.05067528277907,-42.085377021019234],[-39.183416535501486,-42.18978778747151],[-39.06499942679885,-40.97873789125463],[-38.91890577599681,-39.48755482895051],[-38.785012211063425,-38.067669528922025],[-38.65224228164232,-36.67179220592704],[-38.31871434071027,-33.71004562798746],[-38.17054164269268,-32.32517493592396],[-38.01262163867694,-30.881984496177594],[-37.83021059438795,-28.779162187074043],[-37.59656998612709,-26.060917930965182],[-37.39917350316916,-23.65205673675902],[-37.24548351756329,-21.769812299292997],[-37.119183063057974,-20.224301378073704],[-36.91456613305394,-17.678785956695073],[-36.65416668109671,-14.224725853685417],[-36.43045257757133,-14.240447606823222],[-36.16422165377581,-10.736986504068158],[-38.96509412679895,-10.53308834404615],[-40.75368422510607,-10.40416716489011],[-43.68534476498087,-10.185462161406354],[-45.4061527763659,-10.034806046172159],[-47.565332986345396,-9.841132605054584],[-48.648637511446495,-9.72092100147679],[-51.131652327134695,-9.471803807439555],[-54.14510436927682,-9.172408601251224],[-56.33192226014067,-8.94381983253033],[-57.89451012470379,-8.77233624792452],[-57.93017929093201,-8.712987944836321],[-59.21903468185595,-8.565909577948586],[-61.21849623307697,-8.32998402965444],[-62.70294771651034,-8.149849313805525],[-65.61542294127172,-7.791159036737113],[-69.11663339287611,-7.330990650637876],[-70.06615646820839,-7.199983140779321]]]},\"properties\":{\"name\":\"New Mexico\",\"ns_code\":\"00897535\",\"geoid\":\"35\",\"usps_abbrev\":\"NM\",\"fips_code\":\"35\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[118.61556646702499,10.932796329643827],[117.28892724536449,10.638673039837714],[116.28857264095097,10.430831770715029],[114.78134545014746,10.16153430152521],[114.23519404399333,12.070079948009354],[113.46694209900181,14.730132793450496],[113.01207230094438,16.28960854584161],[112.4627006754848,18.156026599618144],[112.34975158979492,18.650546356829768],[110.50741534163748,18.26920992772743],[108.48955891895352,17.86618255147349],[105.95618524632485,17.361106714327057],[103.22721468466341,16.840284606182554],[100.94729202537096,16.4319416473696],[98.73118402790978,16.028249449454353],[97.23264519825273,15.762965166709657],[94.98246814466799,15.366730831821236],[92.67748223760837,14.95807147531828],[92.90152752652904,13.654655399490924],[92.88273864707881,13.65144966384187],[93.26409869828908,11.391671411512743],[93.35890840852167,11.392495726190258],[93.57572575785841,11.595460617542976],[93.57950872697701,11.679273624129976],[93.90872113038101,12.115022647641243],[94.35467454769856,12.518951354838338],[94.31534184342591,12.643178471164024],[94.53356586050332,12.737826469704876],[95.02295724902235,13.443643487441271],[95.05763283574585,13.60646669732645],[95.25250011129002,13.644687905047013],[95.81768049025978,13.509207828753857],[95.88179962038565,13.848945352947316],[95.97607751532567,13.865824355578205],[96.29610529216453,14.495831870172832],[96.3311902987459,14.651101559622726],[96.4664696937268,14.834415457738492],[96.9111130336593,14.579040978560169],[97.31862494346862,14.446942450663377],[97.49900063696671,14.512039769206588],[97.81336026710756,14.427561427507927],[98.01407061026242,14.502164393732615],[98.33805948764545,14.501602958873002],[98.57085511814402,14.778422821158474],[98.36857104420092,14.890680378560528],[98.36468765497126,15.037362656058875],[98.58289757107053,14.986099117946777],[98.62547566908785,15.075842096191092],[98.43155457562918,15.283412376253954],[98.73092326589223,15.246762770128532],[98.78173448756891,15.460577177966648],[99.28497959123233,15.398575383274762],[99.3231871447232,15.554002956654282],[99.69066807364987,16.0135123272331],[100.06835609658876,15.98249627139758],[100.48650937987419,15.74625259841723],[100.69872070376707,15.534329906613175],[100.94539361241411,15.632031419811588],[101.3329572526846,15.623320520288347],[101.33259602777174,15.752858799000515],[101.59603748930043,15.731855724796016],[101.6602161966444,15.470174793749905],[101.45820612738939,15.410741292759333],[101.59603037242569,15.084213781594961],[101.80638225554546,14.97320920474158],[101.97220525428978,14.978664790026814],[102.01149778405231,14.751106669750895],[102.11376650336979,14.575491719086282],[102.2952866238383,14.574337596722664],[102.43599934025131,14.368304879957075],[102.3609298375404,14.267802876262053],[102.40817331438667,13.955949457763097],[102.61980388839608,13.906021594134234],[102.85195172971495,13.971644326188752],[103.04188544235926,13.896497218025093],[103.34854898444803,13.903809242564225],[103.44924718779949,13.957582019946177],[103.65622653354558,13.703633358692551],[103.98417499624469,13.629353967900819],[104.1586527947449,13.512474715193989],[104.08419125701731,13.233427759504695],[103.95169115562369,13.16123559173858],[103.89334227214505,12.904001535121626],[103.9788795583627,12.72589643248786],[104.19984253622071,12.667473167947952],[104.35269871072487,12.475921282043297],[104.76735822375218,12.465739464204814],[105.0175638232132,12.519051529224463],[105.58649122524825,12.337500818983386],[105.55752398012893,12.187190189758573],[105.64329033725602,12.064808452571778],[105.91959900414291,12.004873330505683],[106.20849115641765,12.03577091393761],[106.38580292615627,11.863649052603156],[106.7226389247445,12.369760716237415],[107.57852947899858,11.801574062023647],[107.02450930933394,10.962213468970118],[107.10555832374921,10.449941629139829],[106.91377004148188,10.335655864502204],[106.72432058583891,10.134171508578872],[106.70447042222509,9.855101870874512],[106.75802681570752,9.775026681914209],[106.38098125190368,9.580670824623118],[106.18498372412752,9.362784532874336],[106.17692253658599,9.142806962729404],[105.96155155630765,8.4552122797489],[106.0066345139295,8.179100664374609],[106.17493884982565,7.776248319513229],[106.33544775141952,7.572001970673087],[106.61401146600438,7.552027785342525],[106.98629573448682,7.722731291221307],[107.09319283008956,7.905609080569285],[107.37053886973963,7.960236779041609],[107.50704304545368,8.212217099466644],[107.72065366460163,8.132241548419186],[107.8116028498248,7.7896504098989094],[107.7549448201775,7.627205271568506],[107.9603678037902,7.439302838248477],[108.20592913913912,7.319514231146407],[108.34658141031935,6.993806006983769],[108.53957556350498,6.968444504341259],[108.8144942263293,6.820315367767952],[109.00759593996705,6.800503754111768],[109.21310937642242,6.880729891049097],[109.76423944970331,6.9381762491140595],[110.27534463995715,6.949119177250992],[110.41368495631839,6.67352154417733],[110.79292178299136,6.508536736465467],[110.96392881820609,6.201334789340535],[111.26497024178423,6.164431845021574],[111.61778313716428,5.894552790988313],[112.05400179117531,5.8313624280248995],[112.55480126611815,5.646505444821286],[112.71322020741007,5.547140766699677],[113.60353019997696,6.217904783329489],[113.92333528821048,6.2850607463882975],[114.20483340053009,6.291074745864971],[114.21520455624739,6.005390282154803],[115.03224053131896,6.219182814020738],[115.2035100220737,6.469764396734147],[115.56479776441456,6.650970782676073],[115.81570007043307,6.643084432114052],[115.78208881922629,6.78039113526925],[115.86945854147275,6.999974523535072],[117.85676156328286,7.671075057818993],[118.3067932714886,7.778400879750187],[118.43017000233124,8.288457473434317],[118.47982180702137,8.995977095782909],[118.57711373399547,9.728707547808403],[118.65297237335058,9.91093461143703],[118.66978200987265,10.364580916998682],[118.61556646702499,10.932796329643827]]]},\"properties\":{\"name\":\"Maryland\",\"ns_code\":\"01714934\",\"geoid\":\"24\",\"usps_abbrev\":\"MD\",\"fips_code\":\"24\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[131.4861100240046,32.68810605048356],[131.4395449685675,32.53192636206991],[131.46149234361283,32.31345047088596],[131.5712463917471,32.14493197354582],[131.9196976739194,32.028057356406435],[132.33986248359287,32.15753877173856],[132.48891936073326,32.32908947071526],[132.4846753077371,32.59831826586743],[132.36946232229883,32.73889932504556],[132.27267242549252,33.00589352785216],[132.0922036456285,33.157154486476465],[131.8391034117813,33.24120409441672],[131.53116347484922,33.04969000764511],[131.4861100240046,32.68810605048356]]],[[[133.9933694062156,34.56384024033656],[133.71219476376652,34.87794423648536],[133.47009238906404,35.43926711892609],[133.4040807229222,35.49478515177352],[133.3422904506853,35.81422141069382],[132.99269650816493,35.82767331505208],[132.5160374603242,36.18793688400782],[132.1305324599862,36.27128638186928],[131.9450790801495,36.490836301138984],[131.94012025331722,36.72372002114902],[131.8496066738795,36.91931555016701],[131.64007796643645,36.8439469635909],[131.410467021794,37.53913536682372],[129.30327410471773,37.03541946101093],[129.44964621865003,36.60799814422786],[130.0017956483847,35.06950927592851],[130.3812406841101,33.684456639040995],[130.1648509582426,33.59971196562281],[130.35340574319244,33.2172399542754],[130.24804048899696,33.059689266235935],[130.05319408964203,33.076378641754495],[130.0212371708676,32.908748720381695],[130.83769658327955,32.33407052452063],[130.6755183267606,32.849822676499784],[130.9367688815405,32.92749711547972],[131.25652753960867,33.082524070874115],[131.6989402644958,33.377776329876056],[132.13736042980818,33.38971165229837],[132.69735956210513,33.7093104945815],[133.26523609309172,34.07358864115884],[133.8456400826091,34.409469766286826],[133.9933694062156,34.56384024033656]]]]},\"properties\":{\"name\":\"Rhode Island\",\"ns_code\":\"01219835\",\"geoid\":\"44\",\"usps_abbrev\":\"RI\",\"fips_code\":\"44\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-133.8503386459278,59.61639161524287],[-133.85820848342595,59.49590612328396],[-133.80076218718065,59.3881383678777],[-133.57435949502562,59.28131623073686],[-133.50467121372589,59.01255995193285],[-133.51272414060787,58.882154904395605],[-133.6538044000962,58.62626542231719],[-134.02607090827192,58.545528698465375],[-134.21106289366983,58.42050349463959],[-134.2201170408458,58.35193082269085],[-134.08001696136316,58.228809794811035],[-134.14290431856182,58.046324686169584],[-134.28039137847418,57.96930247853839],[-134.46210842147323,57.78578514908697],[-134.4607735047947,57.69330602255175],[-134.29092238121893,57.540943332749904],[-134.47656231700105,57.26556268235969],[-134.57782860144536,57.202919620631626],[-134.71329288602095,57.02522725043807],[-134.71969652037646,56.92409256197777],[-134.89525550523197,56.85691981351788],[-135.046796514151,56.69155152796415],[-135.07304925859034,56.59114057581815],[-135.00370061562998,56.49747628480932],[-135.11839337634635,56.36008852468374],[-135.36743211011745,56.22738655516873],[-135.40289844626716,56.13234641691137],[-135.322585197493,56.0173730126854],[-135.34289183551783,55.91920848633979],[-135.56433189090637,55.69635937801208],[-135.5508766841178,55.58092812039268],[-135.65636930414857,55.433823516771426],[-135.92170633939804,55.24528561630914],[-136.11983725414578,55.03184765487276],[-136.11649955006285,54.92631517128027],[-136.32536583548094,54.64778062490435],[-136.54718230011213,54.41256102354145],[-136.67761272214352,54.361360811348696],[-136.89991591686695,54.05088810914824],[-136.98087514488478,53.807708226826655],[-137.14199478011287,53.6274630836958],[-137.11776920322473,53.52454998450139],[-137.2640107417171,53.381573488518036],[-137.2232113685218,53.28607409229572],[-137.50716048771625,53.02300265062874],[-137.562502126555,52.81734810069872],[-137.79989762273775,52.451960243803114],[-138.09762554682646,52.11274081405611],[-138.36427652541443,51.58839264738756],[-138.45621877370206,51.51890532449278],[-138.5350243001318,51.30952725216313],[-138.80497385007317,50.90252747207282],[-139.35176077176206,50.10982730308441],[-139.70910039825415,49.651782391953304],[-139.86585033346321,49.54290315720891],[-139.89258283951946,49.429777340623886],[-140.10456451064587,49.16703778606352],[-140.59066208955866,48.66932957188749],[-140.90120862381323,48.423951639597604],[-141.1606533808902,48.392564900548365],[-141.53313760484636,48.186948561427236],[-141.60912188515047,47.99812039165499],[-141.55659663752022,47.88504980641215],[-141.67792013632388,47.79567569902882],[-141.74837067738244,47.58204634799522],[-141.91583146323623,47.40975996174591],[-142.07030383571424,47.360465915939734],[-142.20826062610087,47.181411096985265],[-142.2269659188568,47.00856939496967],[-142.49659652233117,46.71588242924857],[-142.86771902378092,46.39566341226914],[-143.14705921267102,46.30184552317174],[-143.357945276409,46.18613815371246],[-143.79774553650955,45.87491797058048],[-143.86526862585234,45.72451266135493],[-143.82379623014182,45.598128984454284],[-143.65342519363827,45.48693983389274],[-143.3608680311438,45.440037403924684],[-143.23439186456338,45.225051836715146],[-143.3002589646115,45.03098418560992],[-143.19322138999405,44.87499704430039],[-143.02931843028063,44.81334993458643],[-143.1449799334055,44.621022720105515],[-143.11910029080852,44.503356040511534],[-143.2627457187185,44.48070858891235],[-143.47421369678472,44.35661288973934],[-143.6419865299713,44.33333124207116],[-143.78719726402477,44.246952831200005],[-143.8506107652136,44.07650309237468],[-143.76151728174634,43.86639793449826],[-143.59557008410977,43.76259553236196],[-143.67130335699517,43.67115523041739],[-143.68007474797645,43.4669574515066],[-143.8000452045957,43.233585232353626],[-143.88922249603928,42.77592667909607],[-143.78177903039983,42.53654926351427],[-143.84628653661642,42.36829832464993],[-143.87346477358375,41.997021745398825],[-143.7748764725037,41.77942498753916],[-143.6076597113233,41.56756840099671],[-142.4611204188709,41.295477701828375],[-140.23021105890652,40.77831344005613],[-140.05691335427923,40.76572425967242],[-139.0969448534485,40.55893143592608],[-138.6609792019808,40.4462493127599],[-137.62262820999211,40.270834995699744],[-137.12689704655835,40.1245999042852],[-136.3623234269235,39.951562193692624],[-135.04782212124658,39.672010804335066],[-133.74073824642883,39.41024770038051],[-132.653014473519,39.16119510404131],[-131.83767972922226,38.961069865163005],[-131.0608259078927,38.78668152979424],[-129.6822992376996,38.46147022410515],[-128.50296282223104,38.19501583694355],[-127.41698749832199,37.96051176421868],[-126.96017070981259,37.84735729797708],[-124.9871767849233,37.42802939101513],[-123.17607574590927,37.04791850023876],[-121.6844942768826,36.74731269520441],[-121.10590985297684,36.64534836904613],[-119.51948064483179,36.3092169118485],[-117.65139533915288,35.915283099073854],[-115.03741613716069,35.38380400562876],[-114.03154824508984,35.20686983880594],[-112.47374468845277,34.91073739831384],[-109.54499330436221,34.35179594748848],[-107.01499254564288,33.88682589611573],[-106.46404420226641,33.78026638811077],[-105.87478092560248,35.93645644097883],[-105.1545324548961,38.48082692622877],[-104.6921617607558,40.08372732755261],[-104.30105399909925,41.39720865640458],[-103.61979656954036,43.62926353878937],[-103.61561042528409,43.73824615623293],[-103.30301538936111,43.89980017688626],[-103.22189303587803,44.129443622808296],[-103.12137095499337,44.13200145191199],[-103.00338620127158,44.45502489515333],[-102.82546862415712,44.623885331530225],[-102.97420866281541,44.794643366406284],[-102.93661451640374,44.977842512500246],[-102.69820928179095,45.013837403699775],[-102.42535492595087,45.25148721250877],[-102.42345519130927,45.383663589225854],[-102.56001716755374,45.47703635859386],[-102.71732103215253,45.5049289144849],[-102.68283247813967,45.74473741557345],[-102.95375974158272,45.81911546605488],[-103.10417573634216,45.75188259694662],[-103.34430514928228,46.031574474262435],[-103.48178606578387,45.95247379623876],[-103.62563466229177,45.977012701257124],[-103.82341391563706,46.20877291339343],[-103.60208381061173,46.36003822942989],[-103.76512834538075,46.58178693664673],[-103.76661774417218,46.696326136256204],[-103.58132936949927,46.821330115410376],[-103.53647238091946,47.09756135015471],[-103.40520390237555,47.11867658257322],[-103.26397524479476,47.25107314587208],[-103.043016885453,47.32996334835393],[-103.01605181510736,47.439780228893675],[-102.8868260584878,47.49550584459218],[-102.8173313553911,47.6373603267232],[-102.66614846514551,47.74280670020851],[-102.61667841818345,47.853694677211976],[-102.39732548417328,48.01147082328849],[-102.23537551250205,48.19025137408067],[-102.00159328835987,48.20863981774457],[-101.63455390654505,48.28941128702149],[-101.59271300657448,48.37520081311834],[-101.34893674241998,48.55154108639806],[-101.06751324847971,48.70856197244175],[-100.89629100378279,48.90243473293274],[-100.94501140556109,49.147402915918356],[-100.83736685505268,49.31886443069326],[-100.52836886882972,49.4576329441944],[-100.3478143330297,49.64644448747876],[-100.06149525014901,49.789067262171194],[-99.790828269545,50.20046963810494],[-99.59379917245694,50.33963863116914],[-99.53295748137555,50.513788979317546],[-99.16079383182831,50.82706870572649],[-99.00508976444887,50.8921515332837],[-98.91362005671911,51.01821752020097],[-98.7199203114458,51.08332781583287],[-98.67145286637954,51.26241006201507],[-98.43923014808503,51.43613338372843],[-98.06116547174709,51.63086620374217],[-98.1107380550883,51.854598741904375],[-98.27447080090356,52.05879795486116],[-98.21164919802338,52.24270393227402],[-98.42764197495399,52.471609788781855],[-98.7685494626916,52.53061182406527],[-98.86831457518072,52.71589909271222],[-99.03670384983627,52.763559423652126],[-99.19224283465542,52.74049345960149],[-99.34412523996022,53.02984336899676],[-99.53744474609252,53.22790843557864],[-99.52449807345116,53.39112308053666],[-99.65519411180787,53.58499338642594],[-101.691203591329,53.87127990504971],[-102.37941560477907,53.96033693072191],[-103.52732086629378,54.12519621948708],[-105.9817612815105,54.46445968345878],[-108.59426227063867,54.83187980576948],[-109.65285970941079,54.97637817018781],[-109.79125177413361,54.909244045088414],[-110.43737054220327,54.805259329873344],[-110.78055668840496,54.833317425101704],[-111.05676395917158,54.92387040992788],[-111.38336336352003,54.94234321142187],[-111.6052602764371,54.925441404957596],[-112.02635557843034,54.96830191126389],[-112.22662896903906,54.95068203534847],[-112.39993935305469,54.98549503910587],[-112.59665543746394,55.08498274966601],[-112.8808795520754,55.04283082528548],[-113.19042230943441,54.882120220924996],[-113.70692915921758,54.91008922985628],[-113.84673855409517,54.93887910026438],[-114.38608636701545,54.93634998022849],[-114.67361188527518,54.962905830954696],[-115.24660897778101,54.882056544138436],[-115.61123542291706,54.88559734276033],[-115.77156990824062,54.862099047682854],[-116.03047573944093,54.748089872545464],[-116.39418433179655,54.78198405922647],[-117.01022472268563,54.7823706570807],[-117.39691272592924,54.820595370292246],[-117.50002285414476,54.86001751133606],[-117.6885954438038,55.05075918873896],[-118.0385188401837,55.13541451574395],[-118.35537528474254,55.056934942157824],[-118.88897141877919,55.01322272836178],[-119.2378409957217,55.004477088485636],[-119.4828554453222,54.9209552061232],[-119.69111040966355,55.01025475758482],[-120.38590693802111,55.08132711416142],[-120.68341633007745,54.96597081747248],[-120.93958678014943,54.991759163272576],[-120.97229504164495,55.27054817928192],[-121.28698046540413,55.38559627782858],[-121.49595474081877,55.49310204314805],[-121.82622329232774,55.49375992049335],[-122.1040318858975,55.57234078073915],[-122.2417206172067,55.67501092615321],[-122.39407998112577,55.72236150057828],[-122.90667206011003,55.710849543479945],[-123.08455027182,55.740330129388454],[-123.42618967138792,55.74765202283844],[-123.76785491627473,55.85395637444821],[-124.0619081047129,55.8439500980345],[-124.33388029241551,55.72483157589509],[-124.86635000232852,55.64832095497419],[-125.07422158008288,55.654457113575255],[-125.39137765019946,55.59902176696066],[-125.80657094722989,55.637352204710666],[-125.91813747092297,55.598992048748016],[-126.27003378394186,55.57027739471496],[-126.57291380035855,55.63729795215453],[-126.63388274438591,55.73569302269658],[-126.75328065474334,55.781944972649086],[-127.05781411821225,55.77926232562888],[-127.50851628723443,55.98727378298148],[-128.08495646748935,56.170175568725135],[-128.43329564803875,56.387177586015],[-128.43838820341188,56.483604202493375],[-128.26248158058482,56.67620580550404],[-128.21611034477448,56.85661818570046],[-128.28247045232592,56.986235780373484],[-128.11754727567933,57.19287748416701],[-128.1546766830405,57.379205232086555],[-128.07088508519053,57.55864579906777],[-128.1442373113732,57.64887747695836],[-128.16569467432342,57.785499330742375],[-128.23678108583408,57.86289716690573],[-128.2564023464232,58.07226536102502],[-128.4930400450816,58.19120821410508],[-128.63277003082533,58.325846621324],[-129.06044777755628,58.590962904958424],[-129.29283171213697,58.640831928092545],[-129.9217127775598,58.566673406925126],[-130.3519770320266,58.63887025760216],[-130.5633518385455,58.80712350711431],[-130.4506287012739,58.97314197545696],[-130.61645853153436,59.150348000802964],[-131.15167507577817,59.18171382906721],[-131.438507556662,59.23670100086436],[-131.63991013813737,59.23492408744335],[-131.85607627982253,59.3399499769877],[-132.06021951024687,59.31475072226461],[-132.5436217350538,59.315883888556094],[-132.8130735313798,59.37203631028614],[-132.96557011394586,59.51311558933344],[-133.25962723946873,59.53678584280185],[-133.8503386459278,59.61639161524287]]]},\"properties\":{\"name\":\"Oregon\",\"ns_code\":\"01155107\",\"geoid\":\"41\",\"usps_abbrev\":\"OR\",\"fips_code\":\"41\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[]],[[]],[[]]]},\"properties\":{\"name\":\"Puerto Rico\",\"ns_code\":\"01779808\",\"geoid\":\"72\",\"usps_abbrev\":\"PR\",\"fips_code\":\"72\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[31.295089013593266,27.103713553000162],[32.52155476900662,27.157648369977238],[33.817156066254555,27.23623740197722],[34.872672187369986,27.27789428285057],[36.36120355580179,27.360233404879427],[38.48718337071383,27.446401710559925],[39.90115985006474,27.523315905967053],[40.88024096510782,27.563380049999616],[41.79131926350792,27.64416347903055],[43.0368092951476,27.727890656163375],[44.64527192640692,27.85150127751966],[46.094012221873825,27.940797548044877],[48.322146643868074,28.12324540306667],[50.17036245745686,28.272522495195947],[49.78538958062279,29.952026531083973],[49.286773535881906,32.13290739541768],[48.91692561252267,33.64485185636286],[48.83621674825784,35.362100129047796],[48.849785474472924,35.73584214408787],[49.004177820789955,37.284631397999334],[49.09629566034854,38.04232376321015],[49.147608325043485,38.17578785493595],[49.6837123359882,40.140891669866754],[49.64587695484697,40.13809888865439],[50.23275586022013,42.23824805045567],[51.02615864677117,43.348438608511515],[51.32885988867803,43.600700277376305],[52.16340765499934,44.23448120604896],[51.99222072868171,44.29509974044662],[49.5013592685482,45.1049356440445],[47.77295834863797,44.991156904066706],[47.48470209481893,44.505341916201935],[46.83780180774498,43.88754472856755],[46.4115107009268,43.65923518491036],[46.30796150580435,42.997736239794285],[45.191957719334155,43.091901200568884],[45.055913811484075,43.270599531291644],[44.778512941090284,43.42198407920589],[44.79567992940876,43.58623765656174],[44.941117167023414,44.05828909577924],[45.113774765007484,44.29048998332916],[45.043732311613965,44.43416157466776],[44.85245435210603,44.52652607332497],[44.5773811944692,44.302406526390264],[44.393764136546935,44.311522229196605],[44.09772598824687,44.262715323998826],[43.91647320303387,44.33002114496851],[44.06356269690392,44.57789724119396],[43.98833184328351,44.700456374667475],[44.21340221742255,44.86595344811973],[44.29172733446243,45.05131402610465],[44.1865919264344,45.27053631307221],[44.31890531477694,45.56680599968289],[44.0428545453019,45.853382295929784],[44.24316163391212,45.92140092621145],[44.06457986277384,46.09640262560865],[43.92621181102633,46.12043214855423],[43.70304286545991,46.29052679206296],[43.285928053117615,46.282662789112806],[43.109209541002876,46.45702818225335],[42.83966315509113,46.36920619102547],[42.61797832862974,46.379889445397104],[42.40829929722763,46.55703556280667],[42.68578615729823,46.81900658276894],[42.525596454084194,46.8532145629638],[42.49942714902919,47.03893657627347],[42.37950049990607,47.03333352326877],[42.09739161667927,47.14640451005606],[41.852028739693,47.12764778468816],[41.719909890606466,47.19932657845954],[41.494366685987345,47.15257583974933],[41.08065169850329,47.301474210971065],[40.87542162942364,47.24308615694371],[40.66084595382664,47.32197579750872],[40.49914850221931,47.289328828979464],[40.43940436277576,47.396141250478614],[40.15585419097405,47.367550039423776],[39.91737994693348,47.219244392403915],[39.70778710053308,47.21575538972853],[39.59443819521896,47.32763213893424],[39.286146858687765,47.37850262996212],[39.082504549827696,47.313579215881816],[38.72788452904937,47.412761397930545],[37.48771800736876,47.8225520216253],[35.615865747462124,48.07607670354151],[33.32826966166799,48.391477980214034],[32.31666429901804,48.534931951492716],[32.31539065970811,48.640946694472156],[32.098777049409215,48.86788397956371],[31.774512751926572,49.282382032823826],[31.54303639493411,49.2797076636006],[31.294249835693943,49.425987896957935],[31.096770224557307,49.43701723176665],[30.881444575106286,49.398326873274925],[30.753345462133645,49.54308311812171],[31.993864356727865,51.644280612952706],[32.51653994158565,52.51347735517689],[32.6914593661213,52.84830092013629],[31.196055531530874,52.83160876694376],[29.295942850893024,52.80560583108209],[27.677328601024563,52.05845971490952],[26.17401552421586,51.351030829823245],[25.321749939675996,50.93888994579757],[24.88483981933787,50.75361399862922],[23.534616701029805,50.20984114160884],[23.026464445947774,50.01244049188273],[22.84111265036028,49.92025395360462],[22.48845306324833,50.11740607791418],[22.254004120099754,50.06700468475312],[22.219582083847467,49.95865656715873],[22.009234864868407,49.96010232247501],[22.07990907749717,49.83790964985987],[21.914094236219544,49.64728351899211],[21.650616327806333,49.64559653245087],[21.517825264401516,49.711284745741324],[21.57774130407355,48.667519612102595],[21.67289354373347,46.91442579726936],[21.47242950743686,46.83624910416229],[21.409531881692356,46.624249084334146],[21.111103243425756,46.66969554510653],[20.954224052637464,46.61145070616144],[20.81442967785834,46.395783574686185],[20.55150959647546,46.44271761167195],[20.43071592285467,46.284122842929875],[20.005149933736654,46.185940821887954],[19.645050449654573,45.96570291549164],[19.396514930918876,45.63937726865175],[19.438869054620966,45.5603781275989],[19.317558169737833,45.322219191497894],[19.196824723546275,45.2188308378387],[18.907552461208276,45.07821379836368],[18.834128041079076,44.62543756863332],[18.86826391024103,44.35701056384629],[19.173412714758495,44.28996672178723],[19.420751723970092,44.33321160593779],[19.66367488568345,44.20055431622517],[19.66724900070536,44.06412877524926],[19.9117507666094,43.812639096692045],[20.08560606920014,43.699958064219715],[20.077968999392972,43.47810141718307],[19.82004486906774,43.23736252648631],[19.84980309176973,43.148126283572694],[19.667483141129534,42.95611118951166],[19.545749977782517,42.88902064644142],[19.614324944588898,42.525738370298185],[19.546614042800005,42.349010687206714],[19.675276408484045,41.94357163953732],[19.394189412195566,41.70800665874334],[19.610725114412837,41.48207051147134],[19.585144240458398,41.21341992223126],[19.6883409535947,41.0284142550249],[19.582391992073003,40.77010122625181],[19.636044565350506,40.5057229632988],[19.44177589089301,40.01284938875452],[19.83684806485278,39.81877742450687],[20.190026663056774,39.54768049351847],[20.41310697043716,39.419266437448265],[20.4154476013018,39.29230560150185],[20.690050796617772,39.22967519557695],[20.846437026952565,39.03474663073056],[21.390950327792172,39.04065846964225],[21.823739612981118,39.00587169433496],[21.980864911348554,38.92915141614688],[22.08984672213719,38.66533760608337],[22.17632974747136,38.57339438444454],[22.49779998121391,38.3741895719447],[22.997566838176596,38.28584329418501],[23.35966179447737,38.18247452092611],[23.441642124888098,38.11396959552893],[23.847353995940544,37.96426904200924],[24.016236705115162,37.83252096920267],[24.250479892848016,37.36981714746444],[24.226464388232632,37.270087039750784],[24.327828310206485,37.07626099859232],[24.6339096498412,36.87899371100886],[25.19171931768627,36.647795142262716],[25.53750746930195,36.3312598210325],[25.875234665281862,36.135996570683986],[26.263392262703803,36.10206832409163],[26.609304021172644,36.014216538096115],[26.7986372085802,35.803376375599974],[27.01974535358221,35.643039186050544],[27.066939268098132,35.5381345462518],[27.46281560667962,35.147584136670744],[27.593487037896985,34.830411365483],[27.693477771231322,34.731418821946434],[27.592020915847815,34.344148490399995],[27.624172763088477,33.784066266728296],[27.798859940322433,33.62605902574001],[27.775917242334216,33.39463238009364],[27.92765601723543,33.10950255608115],[27.86745746231414,32.86299366831677],[28.031754147211803,32.62722424294913],[28.068803052300847,32.480767332930995],[27.991337963016516,32.29891579759119],[28.064008006431965,32.200210342274445],[28.447266232070852,32.0784367147982],[28.684192232114384,31.878831947987972],[28.841438503458434,31.65442181186268],[28.740678527468997,31.492703540701573],[28.545230058703137,31.31315753621773],[28.37462033920655,30.952759809144972],[28.279119178409097,30.8975852878614],[28.281780060491307,30.489891890014377],[28.31694350556365,30.306149322949565],[28.490268703657076,29.84534944197953],[28.52761571336651,29.480936563693],[28.758349955817714,29.355971423831484],[28.98679048074195,28.57229579890704],[29.06868413057472,28.451236250748636],[29.601955225602257,28.158172964660938],[30.828687323935846,27.9251049521726],[30.924008011619627,27.86625156164734],[31.021382339683505,27.623045359959598],[31.25610002559266,27.330651569250037],[31.295089013593266,27.103713553000162]]]},\"properties\":{\"name\":\"Wisconsin\",\"ns_code\":\"01779806\",\"geoid\":\"55\",\"usps_abbrev\":\"WI\",\"fips_code\":\"55\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-2.514240910251004,59.0930468110162],[-3.656066134398899,59.10102835320142],[-6.743866304319884,59.13038254557754],[-9.131192073510674,59.160060309680425],[-12.091610041946453,59.204988250564895],[-14.32832067983332,59.24554687533025],[-15.96932081996025,59.279907119294734],[-18.619943280892553,59.34239980047304],[-21.55950204985034,59.420540017634714],[-23.333677781263916,59.47165711512359],[-26.13171871130318,59.55800356361874],[-27.55254733209258,59.60768319461736],[-29.743353325773867,59.68742967883911],[-32.548688163436076,59.79559311378915],[-34.786523235013895,59.89079855553816],[-34.959251090885886,58.84084449641368],[-35.221144971110206,57.135444477530015],[-35.378820674899764,56.047480593928064],[-35.43052107845807,55.627674548321735],[-35.50695311231086,55.1753631206094],[-35.68648115023779,53.98664740695673],[-35.88879595881776,52.5738490171517],[-36.06701163775746,51.325402443626004],[-36.25964444908887,49.84649416289695],[-36.49208434197749,48.05504201486602],[-36.601311095276046,47.195627540121286],[-35.1141936051611,47.11021496440881],[-33.25309958547422,47.009638280102415],[-31.343564572368198,46.91069195222916],[-29.19599669078322,46.80477536701994],[-27.09753913918916,46.70807056729681],[-25.410848982078694,46.633709601812456],[-23.59819515891553,46.55756959667688],[-21.717346212991128,46.48454771410048],[-20.27619857183955,46.434380822887896],[-18.08146365896906,46.36020675461896],[-15.365269208643562,46.268471281033364],[-13.062862931282208,46.205610516292694],[-10.381834019830912,46.14020928244068],[-8.683204064720615,46.09681846028056],[-6.767051825159562,46.06220706721373],[-4.055748394564249,46.024048986448896],[-2.7805528354880926,46.01281168094331],[0.5081726085095676,45.99068655467205],[0.4416006544472208,46.443393739325835],[0.5602373225255404,46.72266782933081],[0.3961619133026314,47.184574911065475],[0.417072108190286,47.30268025106105],[0.3518479852870505,47.42514239548147],[0.34717285356843175,47.91211802256283],[0.014881392192430341,48.13079467249016],[-0.11350729882314771,48.349203689777454],[-0.22254336620976925,48.418711108950724],[-0.2085683228377623,48.552228245276886],[-0.31306909234893515,48.62595422798637],[-0.3853381571626031,48.923847399841506],[-0.3604512997842686,49.04961701283427],[-0.6045297695882347,49.33056397200346],[-0.6189462640742759,49.53423099981933],[-0.5257899500516574,49.74915319872003],[-0.5516720301971151,50.04046274260632],[-0.6256705711949769,50.15877111102839],[-0.4892844369879443,50.28915062887876],[-0.4941801352770946,50.539307336182105],[-0.40926454098402076,50.62947149412579],[-0.5658838668028204,50.6877211044601],[-0.7717499678844988,51.04196960471558],[-0.6854971699036456,51.23689978917899],[-0.7250013154828518,51.349778436675855],[-0.6744457126057841,51.49108510077976],[-0.7618711897790037,51.70277920807086],[-0.6983888140141886,51.82885560349115],[-0.7819846000218355,51.87831742768142],[-0.7297482677395977,52.04577196863621],[-0.797818284450651,52.299420065234706],[-0.7306109846913699,52.49414143897698],[-0.8364522968376281,52.65415623314273],[-0.7635805970742475,52.68696413109471],[-0.8966908700908015,52.84319291859724],[-0.8466217106203335,52.931677881676585],[-0.8140340832208064,53.17687471062925],[-0.8689036138645797,53.364039054254796],[-0.8083793507302905,53.571913097176996],[-0.9900753432214491,53.77810229127356],[-1.0406657886228197,53.986868488402884],[-1.5407438272238536,54.73512908626708],[-1.6359576402967417,54.79980401487811],[-1.5980046121266376,54.939232373046174],[-1.7763993577440162,55.057482997341396],[-1.844247497902176,55.44789184773539],[-1.9745977363039544,55.55161718226401],[-2.1977227705035807,55.84235907306892],[-2.209631775009159,56.007898773766755],[-2.1209589695903497,56.09134275865352],[-2.1385172378549897,56.20882817600163],[-2.0425939107751665,56.375839617675794],[-2.1095831523296584,56.532014110957846],[-2.0861169756899858,56.61980585484494],[-2.221822286042145,56.70467562745023],[-2.228207458939015,56.78979725872343],[-2.1033599343086897,56.86956957419518],[-2.1851397845439573,57.011779704636474],[-2.1492408897379183,57.088322870864694],[-2.1945631719553176,57.34453033667036],[-2.247808155049092,57.4466774184207],[-2.049753243730531,57.736378843672206],[-1.9318050619431526,57.827994891256864],[-1.9556035746368639,57.94488349710897],[-2.099975593618694,58.08063180043035],[-2.1624871910125267,58.29231907744561],[-2.316896744599685,58.48374481396241],[-2.2632713371150275,58.54777201638002],[-2.3881284110987364,58.662507587206754],[-2.544673563402833,58.902113712374515],[-2.514240910251004,59.0930468110162]]]},\"properties\":{\"name\":\"North Dakota\",\"ns_code\":\"01779797\",\"geoid\":\"38\",\"usps_abbrev\":\"ND\",\"fips_code\":\"38\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-121.6844942768826,36.74731269520441],[-122.3545150195674,34.62316191888699],[-123.0495624179153,32.403491180404934],[-123.47449859770224,31.00449693500313],[-123.87509384606707,29.64423757941368],[-124.22591202346189,28.402504712360784],[-124.57452416607735,27.202039771017887],[-125.29479966882506,24.73503295443007],[-125.72438329085857,23.278240948122182],[-125.98430058518477,22.343457905028846],[-126.43105770798756,20.841499870134598],[-126.61506232664622,20.180854160109757],[-127.07792708247742,18.374909099066006],[-125.54145907271865,16.163419789919196],[-124.861097177026,15.18652364784632],[-123.35296097309259,12.986988234095776],[-121.99846930579847,10.984917540347595],[-121.755031689671,10.636963455959537],[-120.3226277158217,8.497234178853809],[-118.21692728970686,5.365692264819126],[-117.52375577896079,4.30281502810857],[-116.61261400100724,2.952157706071371],[-116.07140275413252,2.1234382874526307],[-114.46244223175184,-0.2882601819980282],[-113.37845213637786,-1.9269060996531262],[-112.23134949852958,-3.647186700076669],[-111.76124887651667,-4.368210294186503],[-111.19030713605365,-5.211439664277684],[-109.72552602132556,-7.4046970106968875],[-108.51546033149725,-9.20248864588888],[-106.78608715069849,-11.75420317281139],[-106.07454348134796,-12.814172027768393],[-105.59283122160056,-13.504153237296105],[-103.95635518857927,-15.884671342375682],[-103.9547468422213,-15.74233503596342],[-103.68926941971598,-15.428193693979512],[-103.87800329065054,-15.170740895650214],[-103.74949530950785,-15.086130292789543],[-103.46511409694874,-15.081169407746138],[-103.36875359761396,-14.86217129094591],[-103.29091778912795,-13.742908130845827],[-103.34916792857719,-13.101469253839477],[-103.50106367800551,-12.80315085154365],[-103.51262577600201,-12.417935558765425],[-103.34045560298607,-12.209879355546278],[-103.34804405645531,-11.860260106499497],[-103.2166531358894,-11.682760161572633],[-103.36016213553556,-11.37763323152724],[-103.27059135226342,-11.119497926026362],[-103.3662841596472,-10.933746558594963],[-103.24022250530544,-10.634863281789784],[-103.27127962472096,-10.275829481659759],[-103.14062016110769,-10.100205604872817],[-103.18103684900105,-10.002027053177896],[-102.901109344497,-9.855803619162337],[-103.10228816489881,-9.554091849588364],[-103.07406471020224,-9.429193623378897],[-103.19178349896153,-9.061135973897656],[-103.14090900309125,-8.800049750619609],[-103.02331386679813,-8.724140783996043],[-103.11400788372124,-8.273575264538827],[-102.9735374573064,-8.166913643827721],[-102.67465901916233,-8.193062385748888],[-102.32369107142355,-8.032723044651947],[-102.30006598242925,-8.102790903406197],[-101.99579047134034,-8.021946356483408],[-101.64510524407184,-8.100620501857588],[-101.65750766980236,-8.261820556021087],[-101.37232622862969,-8.238648138257332],[-101.31818712678053,-8.339501774782002],[-101.11417285451137,-8.227259794377257],[-100.882596119104,-8.300390081587215],[-100.67805508822282,-8.640615713462662],[-100.6052440877174,-8.840309554367726],[-100.68350572585013,-8.934350232686866],[-100.4598297410693,-9.086070582045924],[-100.34994986157949,-9.326226524304776],[-100.17133335995302,-9.363574645632715],[-99.94607923288198,-9.321000752720265],[-99.83142468409413,-9.368214141490043],[-99.13323876452229,-8.371689721075727],[-98.98583520622175,-8.296261402047778],[-98.8651226862623,-7.625865259013503],[-98.72704270042878,-6.985690707365453],[-98.31772662452275,-4.66365116894885],[-97.92594531658744,-2.60993149468631],[-97.78373732573543,-1.8128504176916094],[-97.61295303732692,-0.9586691515522047],[-97.13296720415488,1.582863266663138],[-96.80151059674037,3.209502427693635],[-96.56577275765368,4.455012550884487],[-96.20007884154643,6.319406580002536],[-95.96260561367144,7.577772987880608],[-95.36312071260011,10.585608581440296],[-95.19134488283866,11.493019763430844],[-94.50854712544809,14.893870813086018],[-94.15703230386845,16.65913597410227],[-93.77996973480626,18.517237608063823],[-93.24511973773282,21.108090274187937],[-92.65675311457215,23.85396000017576],[-92.14240865724777,26.246061775941115],[-91.76843779713549,27.94955814237814],[-91.23160264150522,30.375076535674733],[-91.0947051057272,31.037517701670165],[-93.29275078279323,31.41412303255641],[-93.96900094680741,31.52191471915314],[-95.03593678533751,31.748734799760364],[-96.20430334539437,31.912657858899273],[-97.61678214331693,32.15818197719285],[-100.5242642068822,32.67530139609796],[-102.44677704993042,33.02674323248517],[-103.0893826266503,33.13511965910285],[-104.31192778132018,33.36581054392819],[-106.46404420226641,33.78026638811077],[-107.01499254564288,33.88682589611573],[-109.54499330436221,34.35179594748848],[-112.47374468845277,34.91073739831384],[-114.03154824508984,35.20686983880594],[-115.03741613716069,35.38380400562876],[-117.65139533915288,35.915283099073854],[-119.51948064483179,36.3092169118485],[-121.10590985297684,36.64534836904613],[-121.6844942768826,36.74731269520441]]]},\"properties\":{\"name\":\"Nevada\",\"ns_code\":\"01779793\",\"geoid\":\"32\",\"usps_abbrev\":\"NV\",\"fips_code\":\"32\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[77.76766199990318,-21.098173501567533],[75.5188348739196,-21.43251655954602],[74.83795166476989,-21.521124841791174],[74.84423927188125,-21.557471776139266],[74.09674523107819,-21.64061966151776],[72.43353989249063,-21.843294573020348],[70.80794660775955,-22.022139403687262],[68.0052751159567,-22.341512169884833],[67.17860657418238,-22.428840514878786],[64.22481316338741,-22.773006512299627],[63.42482494774179,-22.843137506469024],[63.86179930676535,-24.354809416419766],[64.18677820422954,-25.381793440605563],[64.72273234144755,-27.019045293040904],[65.35334285155002,-29.03775810334111],[65.8480041532393,-30.583753467405295],[66.36916397872132,-32.17132347335651],[67.08924550741122,-34.3459528649659],[67.57114866541467,-35.72772811820122],[67.77312322829931,-35.889565216242666],[67.73501045815965,-36.07702956021999],[68.01915815585218,-36.248052991669496],[67.90723494684319,-36.332523181840656],[68.10750119120276,-36.481665679220754],[68.06453344133321,-36.583028957532534],[68.1530836744863,-36.76485713830245],[68.31546943926574,-36.897161194283164],[68.22542965486726,-36.979482333880725],[68.38662244849968,-37.1299010688702],[68.50631592508438,-37.33141230299216],[68.7949487424158,-37.513362231384036],[68.94965907399772,-37.66058724153553],[69.05024616124861,-38.01633300778022],[69.18583399420908,-38.04883118728757],[69.18099863135441,-38.50533539445314],[69.07190754408029,-38.61230975807568],[69.10142218738754,-38.73796241118222],[69.53244999377507,-38.83479967797245],[69.76003231829094,-39.038060405353754],[69.61684113609299,-39.27177345283864],[69.36387342240704,-39.30189926695326],[69.43923174763418,-39.416767262986596],[69.17958296188097,-39.51925946743698],[68.9047927631641,-39.816885494550206],[69.04290122177706,-40.05045850423539],[68.99475225613098,-40.28305254478265],[69.04679878793986,-40.47603173541186],[68.90039470203128,-40.77885141860553],[68.93928072988811,-40.84698734010655],[68.65979026224501,-41.34525545176124],[68.7512169315937,-41.565507746882034],[68.71454245174245,-41.745937058577255],[68.89275736397595,-42.00670145172606],[68.88032911038079,-42.19342573483923],[69.11123676274981,-42.36742702417077],[69.15926418407966,-42.46448759337489],[69.35520905914618,-42.566557615849966],[69.39159978990268,-42.81671227629591],[69.5169954666728,-42.97271795561201],[69.39364556014804,-43.3559976366992],[69.45338635393999,-43.50208746311834],[69.35505109869972,-43.900827273710576],[69.43619531077319,-44.08907089212956],[69.42431500046223,-44.26250647022132],[69.30992185918646,-44.422739972031636],[69.4410624763717,-44.61288591342831],[69.39840475097725,-44.731904836499524],[69.47351938350666,-44.9186831221116],[69.61133003072719,-44.94340793318844],[69.79360794514375,-45.12403413274162],[69.89719048375117,-45.165049048518654],[69.9775134221617,-45.33687212833131],[70.0988287079811,-45.43342027703474],[70.19629910032872,-45.69210057800601],[70.20953297766985,-45.82780414208346],[70.3618910502851,-45.872563319718104],[70.36512158559299,-46.00554183064941],[70.6526340487566,-46.21696207426428],[70.78280328808448,-46.4158626848595],[70.74532090150586,-46.55722541125091],[70.95731145290854,-46.872479718732926],[71.03912872515367,-46.86493515083746],[71.27240879618165,-47.04208629375494],[72.6162468225409,-46.98006281445921],[73.65923920063081,-46.939180411621834],[74.2662686138261,-46.90319657353128],[76.70790269482727,-46.79008610197094],[79.61860515051032,-46.653280753265],[82.28112074345374,-46.53408809832304],[85.18833479026303,-46.393266899224514],[87.49198765598551,-46.282005258916236],[87.36773644212734,-46.44800238766053],[87.49179585341936,-46.58508756798463],[87.6597657180251,-46.68022913733847],[87.66360204903384,-46.97379060552429],[87.82220310765028,-47.18823141756077],[87.96926693996579,-47.26637393868941],[88.36487507816017,-47.18325716762123],[88.61562583133015,-47.21066698321153],[88.77375306799121,-47.12732571292057],[88.68899999509495,-46.924003873336446],[88.78899250221109,-46.61573231053122],[88.72253568955561,-46.35441835660682],[88.761686095956,-46.168700074523514],[88.37829035278807,-45.65916190610519],[88.43167479164337,-45.49340464619388],[88.36525660145234,-45.29169136872919],[88.52049238371494,-45.20128387885285],[88.54693575265951,-45.056092964685256],[88.73823661694664,-45.08644059365175],[88.7496316735033,-44.89001132484405],[89.1855423288073,-44.82234863155122],[89.26474782360171,-44.928481945633514],[89.80942789705898,-44.935735317304584],[90.1533977487059,-45.01209020107904],[90.31329735646354,-45.09891295413994],[90.65884827512433,-45.02888182129786],[90.759016593618,-45.14004793742913],[90.8930629974791,-45.08143856741177],[91.21896785333067,-45.11859381218923],[91.7131102964824,-45.04330432389546],[91.8861189045048,-45.1020206280613],[92.59183171420038,-45.01739862811755],[92.4245641556666,-44.83045915722437],[92.20954744903318,-44.78267004768782],[92.3087888630096,-44.50936279725155],[92.37855702903867,-44.130109968819546],[92.36211889177093,-43.91123983892073],[92.51022651901964,-43.805888719657496],[92.54610490808085,-43.60706284906949],[92.27122107648955,-43.468007134031936],[92.22102064826115,-43.256494183673794],[92.48270069064583,-43.11277864057253],[92.52342053930353,-42.957293913427435],[92.43503290922023,-42.723239077684184],[92.76608375188823,-42.60612123645054],[92.96973416179952,-42.07055530479598],[92.89451050703536,-41.737662464415635],[92.90764037613114,-41.4779815813412],[92.77287434780357,-41.36861687111592],[92.88936736932578,-41.22650675401017],[92.97410833858085,-40.95378346937878],[93.077596480662,-40.90848129655412],[93.24948550611842,-40.60441829131729],[93.22686524541993,-39.980742407862046],[93.29351314620182,-39.71520924434156],[93.22328166476665,-39.57721527697119],[93.4010508802725,-39.525527301134325],[93.539991887946,-39.350038854860784],[93.64164230259104,-39.05722306214111],[93.82276312824703,-38.97468776281976],[94.01937499396814,-38.70261277851652],[94.20145431931411,-38.350409544140845],[94.38978863886896,-38.19528543979907],[94.53000542416471,-38.003047452157375],[94.53070994001267,-37.73369258283771],[94.67930476941575,-37.58765270040925],[93.8367392871202,-37.68659212747877],[93.62824435365563,-37.674450500474606],[93.43311280856832,-37.5441518906878],[93.0560889171512,-37.410001956262995],[92.94991339899079,-37.50810545681085],[92.74048511987056,-37.5061963827762],[92.41151986042901,-37.390655847245235],[92.28916397734528,-37.108529147294924],[92.28277850119764,-36.92257869808115],[92.03966933392867,-36.694800704287246],[92.09548022583003,-36.53481882489895],[92.19746016398612,-36.493092772968595],[92.16772844688722,-36.32149849381005],[91.93362878346059,-36.13252524052419],[91.74224586756941,-35.87117690611482],[91.54637862100248,-35.7139078415734],[91.52751470020566,-35.45423676089371],[91.17309004982907,-35.206388787129384],[90.98459396710524,-35.12113169563251],[90.83512668319813,-34.980791834283586],[90.72479118694099,-35.015411381539444],[90.27143018564671,-34.85995107103623],[90.0602427445512,-34.68418854893035],[90.18718227520947,-34.535663771153594],[90.07156204023303,-34.327747134342424],[89.92820325653882,-34.26499710309212],[90.01136666853141,-34.01767950660334],[89.86920971060275,-33.81365018277697],[89.78244170684594,-33.44837168183225],[89.59185600721314,-33.41465024140462],[89.36001092819406,-33.010656508890214],[89.2267965352167,-32.924966943635724],[89.21617313781691,-32.510808708425884],[89.08552792165587,-32.41064131922269],[88.73897829881193,-32.320061325671496],[88.39670504425347,-32.071392612611085],[88.21964827675845,-32.10347634741017],[87.84801022129442,-32.01345872800421],[87.59110465954325,-31.8924187165502],[87.41954835574735,-31.750934624123147],[87.45378102439234,-31.56172920799068],[87.25325486803582,-31.523147428252198],[86.83751715053404,-31.326792236681978],[86.91364636041432,-31.177336547281563],[86.73194593937228,-31.083816545608787],[86.80478811815317,-30.966188902942143],[86.3641722502691,-30.863276565938108],[86.17656638879782,-30.692677885647612],[86.23458334202361,-30.572055265510965],[86.18645107083925,-30.28823171157283],[86.28279909046586,-30.213966550997192],[86.12817336669214,-30.045464553832065],[85.80617471545726,-29.97251229967371],[85.6204247736154,-29.720686413790276],[85.37197039720114,-29.541099279981037],[85.08634800252754,-29.463836030743344],[84.98889392174904,-29.38921873185103],[84.83496748281011,-29.441211270368196],[84.42518358382762,-29.240098318215292],[84.3716238178001,-29.047630165050258],[84.13316778584606,-28.837770240274658],[84.00182986169833,-28.518504117575663],[83.66580648074446,-28.354311930873195],[83.64493880498972,-28.257316065012073],[83.33822096256898,-28.08097105081662],[82.94786322160105,-27.925433886045525],[82.86635233341497,-27.95223614877518],[82.39849400596847,-27.70369808025753],[82.19356703797618,-27.526062978684735],[82.00404776599267,-27.53737923222012],[81.83677864326374,-27.33709887357899],[81.70453535561428,-27.041139123912714],[81.47054139918431,-26.83721108104131],[81.12993022274708,-26.454673951298332],[80.8640482578189,-26.35319798280792],[80.6731931799653,-26.007748184526584],[80.613888942852,-25.72566469617386],[80.52559629335536,-25.578534506970765],[80.34166689680922,-25.454440223914848],[80.20265040996036,-25.186563923361813],[79.97843933083279,-25.068233410018525],[79.70526867777363,-24.4719794905524],[79.46391638633034,-24.324134103619112],[78.95560673489317,-24.433062219187676],[78.69850806944197,-24.44143728805723],[78.36350915757598,-24.258524235978335],[78.1136466922666,-24.00773603428736],[77.82177403898275,-23.839166248091946],[77.43078635522446,-23.750849203340668],[77.2746902825852,-23.596685756997413],[77.04011803258982,-23.515221311738983],[76.95940695825877,-23.423561869813213],[76.75182460645489,-23.3793444266158],[76.6371147865837,-23.14769190365468],[76.639129636322,-23.015735619307264],[76.75672547064481,-22.906298569503296],[76.73411842652578,-22.66393653547174],[77.11556541184892,-22.15312488188986],[77.11672510368311,-22.009876248129572],[77.33032459103508,-21.966121160718938],[77.54791349928631,-21.60968097318748],[77.75426678073293,-21.49643645198443],[77.76766199990318,-21.098173501567533]]]},\"properties\":{\"name\":\"Georgia\",\"ns_code\":\"01705317\",\"geoid\":\"13\",\"usps_abbrev\":\"GA\",\"fips_code\":\"13\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[115.53818983339688,30.238673686360286],[116.94347315625474,29.820239250732623],[117.87303630019342,29.522150469521574],[118.57689714869815,29.336999906260125],[120.23410618546232,28.875251524958042],[120.29218983617032,28.786245451731943],[120.27289038605407,28.177875698876736],[120.20139676319339,27.759617542363213],[120.05374201698032,27.26789417079528],[120.0820270197065,26.900703189700813],[120.00755349656075,26.567263825209537],[119.56615648678113,26.411013774776578],[119.3598089971422,26.396416094220587],[119.27629641469133,26.27581934397854],[119.34513165471877,26.06899975016623],[119.31268875751685,25.7927850958454],[119.16886736103139,25.661777409463244],[119.1973251393997,25.37008378935204],[119.39170831154998,25.27872966646907],[120.76905083575932,25.891243646110137],[121.1592524382962,25.7364043746665],[121.42075478410032,26.01236116035378],[121.70266200157783,26.14484740929999],[122.45517497669057,26.311297715252966],[123.20031879385385,26.37909015767839],[123.975465743686,26.788135739144614],[124.25775314201186,26.81245402088298],[124.36625851779581,26.910403951965584],[124.95716010349767,27.275260057414474],[125.48752818946872,27.566930980698324],[126.1276489366671,28.03434693610645],[126.71136667101344,28.413143574721346],[127.30510704303305,28.753116747033772],[128.47477239566942,29.48441719764909],[129.0375384883021,29.919426354308108],[130.17251952229736,30.742918038559576],[130.47113741869921,30.974544102199527],[130.70054142513735,31.091150364523134],[130.9133221816976,31.25681390092955],[131.08794923369496,31.46113865083545],[131.1189538114143,31.659999734042795],[130.98850335133102,31.84941623629072],[130.83769658327955,32.33407052452063],[130.0212371708676,32.908748720381695],[129.8963861681831,32.91842215972009],[129.46992665613607,32.702295566453195],[129.1985508886449,32.466032060031544],[127.04730839080045,31.469680276885715],[124.7664030636971,30.405228390517564],[123.57606149475072,29.759976799319713],[121.81707867280363,28.905483575869155],[121.5328256871971,29.063050584249776],[121.49495941687137,29.231752746392697],[120.96999729152816,29.694488689396216],[122.0513799029547,30.63558854486694],[121.55903641091639,31.05854200775914],[121.16732624740258,33.01164532026735],[120.98504303618371,33.8325032988035],[120.68775435052373,35.24283992721372],[120.61600394280916,35.52724198182706],[120.4478370553605,35.71422906664168],[120.52224526442674,38.16192502168683],[120.56803459168982,39.59428954639799],[120.5099039490052,39.58381706460384],[120.34149340337619,39.8697994227833],[120.3500205077386,40.04965065815525],[120.17631542950036,40.66796950637492],[119.7368023475769,42.307679364297016],[119.3480702372152,43.62662886005533],[119.34874981027826,43.70875770181161],[119.20810196313921,43.856143209039345],[119.05242934596345,43.90492540858018],[118.88554932575377,44.12198157351217],[118.57462732408288,44.05567868275201],[118.5054101971334,43.7493989720351],[118.32598560502586,43.80281651683977],[118.31123684645961,44.00915055485067],[118.24460345360545,44.05639705248496],[118.26683473113651,44.34530847400301],[118.37724426818609,44.558907606083125],[118.39852141703776,44.79579766601065],[118.1183569900502,45.001304549319826],[118.16464022327919,45.14945046907818],[118.10653648915725,45.294979054186854],[117.83369564480829,45.55470047781071],[117.67102364855317,45.999893524315915],[117.5029783646203,46.066873166367756],[117.51978285710966,46.41932059852067],[117.4915969486154,46.805669205843984],[117.59837412999993,46.90732460871907],[117.64606012903432,47.06820255070011],[117.74392587857683,47.143137828444914],[117.74566593504751,47.26544481234301],[117.61953293999632,47.42295044009229],[117.46772813678066,47.70737607172842],[117.53204753173225,47.79820675385528],[117.55845718499167,47.998996732756765],[117.45772862630004,48.22010393066117],[117.1183511146703,48.533162097456994],[116.86352103953985,48.67426540624245],[116.77726082614879,48.79582044824158],[116.79214086674855,49.0344040438406],[116.68946590371982,49.39856877403645],[116.7651674384062,49.622228344181025],[116.73136472269425,49.68604733518393],[116.45623355392952,49.80396486007932],[116.45814042267364,50.11871563433572],[116.51929756848914,50.198295830139465],[116.44457455353606,50.39983009845968],[116.32620581312236,50.48731913607965],[116.34293700742873,50.59961959981198],[114.90310094907467,50.34395182320673],[113.75593371850144,50.15856092571674],[112.42051640303751,49.90999947619921],[111.51216307201665,49.77610217752866],[110.9034097286871,49.709406485401296],[109.78736782347619,49.55622128337199],[109.56957688931323,49.479911600902916],[109.06133172648191,49.52600404700651],[108.78490842955087,49.40823807786989],[108.7127045410786,49.31994259428971],[108.39539128182486,49.27349672103365],[108.27396151082552,49.136082963018424],[108.02565582238024,48.96064193223004],[107.70550843057445,48.84456542137151],[107.58817735029898,48.722289718993025],[107.11637337223341,48.45813738874637],[107.01706710903295,48.3149913705499],[106.83413979797254,48.202813163141165],[106.31360675056796,47.69024586888875],[106.19712540253624,47.59838883960377],[105.77120856318636,47.11182071470829],[105.59542680479643,46.94755545853345],[105.19597904709835,46.50664910779168],[105.05662849088651,46.26278805921725],[105.05122563306999,46.06230756650569],[104.68974877588015,45.68135753250501],[104.44317598451916,45.515111890210484],[104.28283212770108,45.5152195074204],[104.0864087025774,45.40468834603861],[103.87487258882207,45.20748137014898],[103.71222507275716,45.168160533333236],[103.5816631247416,45.0667807064111],[103.63058398157818,44.86103836595286],[103.52287247742483,44.74956387870176],[103.27876991628749,44.621520794823645],[102.95061786679798,44.548260518052835],[102.8453189784971,44.19633550297303],[102.47711372614778,43.927781223182926],[102.30833718509814,43.40174051084571],[101.4913669541574,41.57729460823592],[101.45596789247988,41.27162070364479],[100.3330224004953,41.0976089999672],[99.69822672582255,41.011444686946945],[96.81583764195335,40.57992210843156],[94.65337253401526,40.262266324843196],[93.38937082280391,40.08487383042258],[91.80134586104742,39.87346582435056],[91.52356847476713,39.69155070693351],[89.7917842515801,38.709839899637586],[89.44086313780267,38.50118971447229],[90.34157022318524,37.55007824978823],[90.42532506525102,37.514095214816464],[90.53230757112271,37.2138197036698],[90.5439008038251,37.063331247487945],[90.63565591811962,36.90305134052766],[90.52680778414152,36.75086844753109],[90.60281705375672,36.68416416159941],[90.55567408541879,36.507126424932565],[90.91448393318778,36.48822277524892],[90.96610972849426,36.438868549371335],[90.89518888918921,36.19538038066786],[90.9945354173747,36.026734950148075],[91.28401039055687,35.90687166326679],[91.43662009700377,35.918076118962034],[91.61471034528013,35.75219484809591],[91.64423586932246,35.61796359920509],[91.58134799771094,35.312576159560464],[91.28570031029868,34.99609688238835],[88.8063042314644,33.422662922242644],[87.7300570204462,32.73002326025765],[87.90140459009446,31.886693144972256],[88.36735288309217,29.599137575901754],[89.86561558220373,29.822187762767697],[92.28116997921677,30.20042001259096],[92.96374865304895,30.297536192707188],[94.40052305705753,30.53970502702343],[96.08556336969,30.80494435603086],[96.84106600538847,30.93715273609976],[98.64271619734217,31.221081257463425],[100.04243581555562,31.465181245766505],[101.99704816222652,31.79253814785182],[103.49769983079707,32.067773152687224],[105.51011370631115,32.39836914917245],[107.89233212508255,32.82402271013328],[109.13358306976,33.04089404776504],[111.09879171388876,33.4098811972223],[111.4300247320815,33.16085667411981],[111.60650019254712,33.131367044291885],[111.72677993855497,32.83543897962435],[112.01815527266488,32.704743344412016],[112.10039164330409,32.79694821780689],[112.60275831551368,32.728377886805966],[112.64082552565445,32.606606957725646],[112.8001771981824,32.567404750206464],[112.7617313713734,32.32760503664408],[112.9138356572456,32.33963855822754],[113.0620858808108,32.244086907853486],[113.1640226659617,31.868516216175674],[113.31862431591581,31.45654964098802],[113.18093670686518,31.353109888234357],[113.49144629138371,31.14702205605463],[113.56162890587882,30.968695314484556],[113.78298775694043,30.85705237681091],[113.85938334206308,30.689526210484843],[114.05988498591898,30.75110139513145],[114.24617760022508,30.716851768262185],[114.39875402862,30.52921920559912],[114.79505392688901,30.636898530580794],[114.92188308944938,30.532761480762275],[115.10661556166956,30.573752082310218],[115.39065866813282,30.415823281801103],[115.53818983339688,30.238673686360286]]]},\"properties\":{\"name\":\"New York\",\"ns_code\":\"01779796\",\"geoid\":\"36\",\"usps_abbrev\":\"NY\",\"fips_code\":\"36\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[11.15817993632201,-15.607246340684972],[11.396540942671932,-17.223653031119962],[11.840133270364339,-19.96336506732185],[12.22636207276349,-22.37453686849098],[12.353277338634339,-23.10254991108338],[12.329989212212384,-24.47098857097598],[12.305100980201159,-26.309001822355476],[12.287363867055065,-27.943504782665443],[12.270375938695848,-29.812523047712347],[12.260564199495866,-31.43380849270457],[12.236049440867859,-34.15307230330095],[12.434057490177294,-34.27219432958021],[12.434106754820833,-34.424013469734014],[12.570809975542652,-34.42605222609139],[12.815205790911714,-34.68531639130821],[13.027691958054028,-34.69847325250416],[13.031212193208116,-34.60016236719403],[13.286489126546305,-34.649429159486786],[13.391065686219665,-34.47153566152299],[13.700538736215915,-34.494892082941746],[13.77853041803739,-34.634348772460854],[13.91004251918931,-34.55212470935728],[14.020002408710457,-34.38427806374433],[14.15081472709045,-34.415830488832846],[14.241670868305176,-34.55487560428714],[14.624163250942505,-34.48624522177896],[14.677297365436807,-34.60203397363033],[14.857164167706863,-34.62120304262815],[14.907324639973321,-36.43508032501178],[14.938844508169291,-37.70994947256987],[18.221534641938934,-37.65330840414843],[21.134675563728955,-37.59106294839715],[23.754396494801465,-37.54852914091689],[25.77272847394905,-37.508023486561044],[26.834115214817892,-37.48159696708304],[29.530089104121256,-37.39567088628889],[32.031614119470305,-37.31145749031048],[32.04795114406484,-37.221690939086976],[32.24036723910311,-37.13441267617159],[32.25551088218023,-36.95838464047205],[31.78579988472388,-36.7240674292944],[31.87715617846465,-36.52704660964304],[32.07853803687284,-36.58231389779677],[32.351126526585695,-36.56587324943336],[32.44523855039617,-36.48351709950182],[32.45109448765348,-36.276745611513945],[32.392783305813154,-36.04723827126362],[32.542656978843546,-35.970175641237205],[32.65698205948871,-35.750587727955036],[32.44758706035764,-35.68078168816333],[32.33670572047939,-35.94888115895373],[32.07121674772953,-35.60834799794082],[32.046722422323,-35.31304840868492],[32.1701756084808,-35.22086605249628],[32.51386418639424,-34.82410614355076],[32.33800306368329,-34.69646506334433],[32.291384034969,-34.938083758869084],[32.159469594071744,-35.075863612729265],[31.893142617998507,-35.13898959268901],[31.630008242526117,-35.001665091548354],[32.077994187380895,-34.83457589370279],[32.1513721387754,-34.69119251257627],[32.102250925940695,-34.580620870724225],[31.860062182217654,-34.44266601540599],[31.78938961436975,-34.522628963517086],[31.84806737204113,-34.713812709104516],[31.65748533946808,-34.82612587838855],[31.464837186683777,-34.807502020581126],[31.749197467018945,-34.44543811145125],[31.524893065964214,-34.25166880809522],[31.459040488201897,-34.06214544398206],[31.761905104173664,-33.94032940484002],[31.90243816249474,-33.93586595791077],[32.021761688521224,-33.79154590568082],[31.962922097649127,-33.676655991791336],[31.766616725547433,-33.5525537553956],[31.466996421670878,-33.473893022060174],[31.420072789352922,-33.35525980860059],[31.53192094528189,-33.24820631075088],[31.8125407342852,-33.20018592389597],[31.97824099432881,-33.36935154715778],[32.3083177206211,-33.46628216760103],[32.56148678758652,-33.366797725548395],[32.47712713437096,-33.17019820192299],[32.346782797250036,-33.11464890867297],[32.012893662115914,-33.175449579870396],[31.880106078299494,-33.03846503261657],[31.931639837641697,-32.73519765703889],[32.09716562559279,-32.798626755770755],[32.4082630805434,-32.736177129211],[32.57324248653316,-32.82003259811354],[32.72798800634388,-32.77979800518231],[32.768242144422445,-32.63614298266105],[32.4359038878898,-32.51277506759323],[32.26652181124474,-32.26450151143345],[32.328116941863605,-32.13967283289665],[32.60229844285172,-31.83925995462461],[32.547253628600416,-31.765911093874287],[32.13943948783211,-31.643460684622966],[32.13519996973751,-31.550125008445463],[32.36347212576189,-31.480241461791973],[32.529476395966064,-31.36183331855403],[32.65343697730249,-31.581697162984955],[32.84504855739626,-31.59210464660742],[32.87437697423823,-31.488820529867347],[32.704809769572876,-31.25625769586202],[33.2361795668234,-31.19765589088807],[33.366316815680506,-30.823620552655274],[33.29196574874568,-30.750805430796955],[33.06699728596216,-30.777668691666154],[32.85882354473401,-30.633192499926547],[32.85642056096659,-30.511435185029594],[33.10516275798062,-30.332344235953713],[33.360599735109666,-30.465507972856038],[33.62478264907049,-30.441303583338094],[33.706963817891925,-30.315134583302626],[33.64473408478863,-30.198188080468583],[33.22816099964971,-30.224181721709645],[33.057708752211845,-30.139111816025732],[32.92031598422559,-29.905996408569948],[33.09926795204111,-29.84650730067714],[33.24809658152999,-30.0053322069286],[33.45294462614141,-30.06084339605142],[33.514989432652406,-29.697502377004092],[33.68545932600704,-29.575510383912288],[33.906710388176464,-29.577094512884166],[34.038120364518385,-29.407730566989922],[33.88200209823276,-29.187426018311335],[33.92468961108111,-29.031618097280802],[34.18334130786145,-29.040803649941104],[34.38692302636397,-28.982954366501453],[34.31047079954046,-29.29389648355494],[34.517242395817455,-29.254151393253082],[34.4507917302791,-29.078010152685927],[34.602945331063424,-28.886399117222354],[34.93063082814083,-28.709108801241015],[35.00710648067387,-28.548182989946707],[34.846411848678535,-28.24329964023276],[34.89131276762164,-28.052146229945286],[35.08190242531556,-27.89483782854078],[35.07043338605405,-27.732913586568273],[34.8453762971154,-27.482811249854443],[34.778956262286584,-27.058867510227657],[34.92647540947744,-26.885726314309668],[35.06372864565275,-27.0446794708857],[34.98267029284965,-27.207029835502475],[35.20945945416986,-27.29346504411911],[35.41500542880794,-27.097007188620573],[35.50306696692393,-26.937502649986282],[35.41338959596093,-26.83227900721276],[35.01223144262989,-26.847173665289233],[34.87905914632875,-26.71755215778272],[35.05421011219985,-26.536142873580236],[34.969942517279385,-26.352553292515392],[35.152880187334574,-26.19727687439185],[35.24057713108985,-26.36699925733684],[35.12966948514558,-26.50268087678503],[35.150099313504185,-26.648196594256046],[35.42420462393373,-26.672242904079276],[35.543044929278025,-26.563358874762176],[35.39862699646865,-26.17996694568489],[35.47884115759642,-26.04045190057234],[35.30682062348506,-25.80349819181752],[35.39711821736398,-25.606216401324016],[35.56105932384703,-25.63920003832832],[35.63460265128983,-25.915484008120664],[35.71918466751438,-25.97295949784212],[36.289683562297895,-25.68398544639786],[36.641352147086906,-25.43327901254198],[36.64828855963235,-25.191865477716558],[36.34332758135578,-24.99947710295227],[36.260561391102065,-24.877537699148828],[36.34067143957052,-24.57137231989583],[36.824770001777395,-24.64463115256318],[36.99134808483628,-24.03423759721473],[37.183942084686805,-23.918530655142664],[37.413525895566686,-24.019520503721356],[37.60116227583746,-23.881683823435],[37.284821433880644,-23.55715780858012],[37.39701957203101,-23.5051376545552],[37.4859820298953,-23.27478038661021],[37.35974313717196,-23.132544482233914],[37.054517774448634,-23.11944261569013],[36.948076629050334,-22.972226200968645],[36.9692624583171,-22.857956826279775],[37.28050706059203,-22.781276677289313],[37.27375185695396,-22.527782607500786],[37.44141941587227,-22.244726330510037],[37.20283488117142,-22.078464961862007],[37.041870610807564,-22.110666064024144],[37.04586882916092,-22.24442241769457],[36.841011578323325,-22.272152299898227],[36.88442095620468,-22.022511217501044],[37.102443208103175,-21.8781377905487],[37.15117138324114,-21.698587389498243],[37.34945703098811,-21.62032547379543],[37.443660476723956,-21.6920482950006],[37.46262166930792,-22.083158969773432],[37.671510792708254,-21.940665300707508],[37.7263183897461,-21.68704483404015],[37.57405907457836,-21.513092302783054],[37.52338128652198,-21.357937952445372],[37.571176129617825,-21.135606060059928],[37.85324140468391,-21.041098520946136],[38.04842054868656,-21.16624025531676],[38.127175717141576,-21.29889929278476],[38.28478865066546,-21.32479806979053],[38.31243911465389,-21.090338856633778],[38.10426561595151,-21.01663468287733],[38.02303755334256,-20.860374185479493],[38.07419559280895,-20.758770204982476],[38.358878696042865,-20.64193958495011],[38.58136726845251,-20.496400586696577],[38.55803872871316,-20.268823195331972],[38.39841144349679,-20.377747533512476],[38.152583174630315,-20.356451297675484],[37.98724728476727,-20.15220621789405],[37.996704094847125,-19.827145578212868],[38.23560969059252,-19.668804638554544],[38.41349327656696,-19.78248915193974],[38.729052934177005,-19.65799362889604],[38.8527203735984,-19.406053892008003],[39.061378365848924,-19.272920323047632],[39.15951272632336,-19.29975046608843],[39.365949756733215,-19.191680430608812],[39.32888383148892,-19.062421925099763],[39.021402722705794,-18.956595843834705],[38.9476715714111,-18.831455263700974],[39.106143041893446,-18.61380612200213],[39.26441551619333,-18.606670578351885],[39.55521562762062,-18.748149145098164],[39.67288079515994,-18.65927749986791],[39.59084101201864,-18.452247484392093],[39.24887367351738,-18.22843424928867],[39.115908061472275,-17.969574673603287],[36.90761359457927,-18.122665884942],[35.438314094355995,-18.22226757141799],[35.58811204977749,-17.899151070892714],[35.83580246162693,-17.50058992458322],[35.8719449394597,-17.38171628712585],[36.189061565608185,-17.19137000872146],[36.25227202860081,-16.879639932275566],[36.43233941631891,-16.720545319438113],[36.77424071301694,-16.523612154069223],[36.81816172698048,-16.285359005391065],[36.9932152069695,-16.228813581290115],[37.08986949611283,-16.01391345003748],[36.978684343590245,-15.882952555307314],[37.05761161146479,-15.445518532191814],[36.96408022082457,-15.345955190989583],[36.68404460859383,-15.281283543551083],[36.50370562805858,-14.686484268208192],[34.45372225390078,-14.801411529299058],[31.98889446653313,-14.933454502205953],[29.302143987639354,-15.074262151981163],[28.635107754361403,-15.090305286856136],[25.911999109283276,-15.213349504714875],[23.8933140913726,-15.299232000271587],[23.40600110467133,-15.306723188525936],[21.74441641874613,-15.368783805472427],[19.570358621570605,-15.436279173323959],[17.924495149820324,-15.47489464770237],[15.376344051405509,-15.53707531736063],[14.019792351059385,-15.565278568712136],[11.15817993632201,-15.607246340684972]]]},\"properties\":{\"name\":\"Arkansas\",\"ns_code\":\"00068085\",\"geoid\":\"05\",\"usps_abbrev\":\"AR\",\"fips_code\":\"05\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-30.763083521479004,-11.15169750347356],[-28.284934066932465,-11.286413585811227],[-25.39946404533427,-11.425704629877558],[-23.394491479033483,-11.518991482002313],[-19.86171980136749,-11.657117329803075],[-17.17393420968468,-11.780851881190827],[-14.212078967739142,-11.887293093194],[-12.471249895676253,-11.94014672676275],[-9.670877405710208,-12.026134544544073],[-7.942835536731039,-12.058517590987886],[-5.691256274698052,-12.09982731581384],[-2.5581342682349137,-12.139373396106125],[0.4852282462001948,-12.16570913646166],[3.303442165004269,-12.17144365306711],[6.366511136529513,-12.160284396967521],[8.018159904386971,-12.145793365145732],[10.346174655638514,-12.128191751134366],[11.10561780019015,-12.115701625453237],[11.083149581423152,-10.733698997627359],[11.060071699086222,-8.564991764529054],[11.025854269945661,-6.594127954948864],[11.024645641602872,-5.17660369154813],[11.007986447680219,-3.40698894608187],[10.978285250285774,-1.697595599832909],[10.969712175675513,0.8458103818709393],[10.944250743197683,2.9653427391317586],[11.040756064804082,3.294577136152732],[10.942232904280978,3.3626137350808376],[10.700842176267143,3.3107095067228953],[10.652157957630978,3.4540645010576663],[10.488994171881494,3.5172271696255835],[10.233200588046849,3.4128526976295266],[10.083669546426995,3.4885326859336088],[9.971123403995515,3.682391955438755],[9.703583741090133,3.7421152173382177],[9.723557172824412,3.9863732123435405],[9.61884764167137,4.1186096616912415],[9.330838183573544,4.330449262237731],[9.261929297114474,4.7090496118149],[9.430503177647187,4.886164805076974],[9.375110441683805,5.005543384641402],[9.210506495934165,4.9515808857691725],[8.914253495274922,5.210351132585056],[8.818386583707644,5.384913836765333],[8.5813917339368,5.487728014115429],[8.48522792944248,5.749330359525648],[8.168761145218173,6.05001968621848],[8.169694740714364,6.292176606901212],[8.444696583367827,6.343655602116607],[8.451259382710589,6.637699039205099],[8.614129389818752,6.974141840157881],[8.913001504006026,7.136232482805796],[8.90385132772932,7.431756453996362],[9.027152727370488,7.5274437317141665],[9.273046529912655,7.37930997171756],[9.419147423627242,7.425915916967337],[9.507319937495396,7.595597377163614],[9.448011815026327,7.728928455231902],[9.217531150903955,7.629497559782909],[9.175572240030853,7.8498572565853095],[9.33306771250659,7.859742984913162],[9.40928593788372,7.974511003403423],[9.324274489846369,8.167776811344714],[9.060353726570169,8.288783444107393],[9.083827596009659,8.586303334472309],[8.973301239533571,8.638506393383208],[8.648344165534755,8.623366743268518],[8.541762298081405,8.382171638633482],[8.272121094067826,8.350770932637694],[7.974278041169988,8.481264381095682],[7.938170505738833,8.648889248662297],[7.702531242337706,8.61810780735034],[7.612336436514287,8.702164050110822],[7.6149905139732414,8.899177902438877],[7.370682879678943,8.960469713921997],[7.0535168363995515,9.323093145878019],[5.0959935903304325,9.315088025986528],[3.4000865691760636,9.313828448494904],[1.6779882632048355,9.318150671150464],[-0.7861694960246347,9.335680175273927],[-2.3755822754670337,9.350025299490456],[-4.358790808118781,9.37993290142338],[-6.604392298328723,9.418224273126366],[-8.35363237591614,9.45568662249773],[-11.417643563603285,9.53319525992688],[-14.610137692841983,9.630293661284975],[-17.111663726624382,9.721774252554805],[-19.599187926796183,9.82401373342716],[-21.0254238464607,9.88935631658247],[-24.0143070405043,10.041321464985192],[-27.129878294690812,10.216596782146615],[-29.438000494282,10.359026182719287],[-29.54964031643803,8.565261677234583],[-29.71153210195261,5.884414175138174],[-29.874707663042038,3.0733682855467483],[-30.012316451238505,0.8302801151070469],[-30.14217736442976,-1.2198186228302819],[-30.32281829261948,-4.090539137087066],[-30.43849538148796,-6.152684837351922],[-30.570701703559973,-8.1963047021963],[-30.763083521479004,-11.15169750347356]]]},\"properties\":{\"name\":\"Kansas\",\"ns_code\":\"00481813\",\"geoid\":\"20\",\"usps_abbrev\":\"KS\",\"fips_code\":\"20\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-23.802568877144278,30.150390325038412],[-25.900603997596086,30.242488249781623],[-27.089082874793526,30.323986620631644],[-28.449178181411146,30.39699461505357],[-31.038509520669585,30.550494434731803],[-32.40168420170831,30.63817936807173],[-35.435470206022416,30.835870855677726],[-38.42089975162439,31.046166356157094],[-38.62525963784572,28.940691393061947],[-38.725341846856516,27.930908990784204],[-38.92487776386826,25.8043948932116],[-39.0789588593283,24.183827365674798],[-39.27587610992219,22.04539643209445],[-39.39076578897901,20.750077180286173],[-39.637481438049264,18.070139517390814],[-37.59322726934955,17.909202668195448],[-35.9763818856077,17.78999892346702],[-33.11141891629698,17.587866065381615],[-31.670678751960846,17.491372041018977],[-28.980312746595526,17.32193630084639],[-29.097518612734977,15.534579610933758],[-29.20939593005242,13.848449319910548],[-29.438000494282,10.359026182719287],[-27.129878294690812,10.216596782146615],[-24.0143070405043,10.041321464985192],[-21.0254238464607,9.88935631658247],[-19.599187926796183,9.82401373342716],[-17.111663726624382,9.721774252554805],[-14.610137692841983,9.630293661284975],[-11.417643563603285,9.53319525992688],[-8.35363237591614,9.45568662249773],[-6.604392298328723,9.418224273126366],[-4.358790808118781,9.37993290142338],[-2.3755822754670337,9.350025299490456],[-0.7861694960246347,9.335680175273927],[1.6779882632048355,9.318150671150464],[3.4000865691760636,9.313828448494904],[5.0959935903304325,9.315088025986528],[7.0535168363995515,9.323093145878019],[6.750435022901872,9.544697304392711],[6.601560663029592,9.512215114572014],[6.446421997339522,9.67722108834969],[6.569915340338304,10.20450602868001],[6.381175599284466,10.305796793936675],[6.3297043336144885,10.46917956119775],[6.1277377114886695,10.620629472667467],[6.169493007748666,10.895615014473329],[6.12119598177792,11.043464083788667],[5.919099224807783,11.081449431966108],[5.7016294100410345,11.232749965879616],[5.717030731083257,11.376775147162574],[5.174570297093842,11.596189706233424],[5.34135207230563,11.735951728911141],[5.234283699761395,11.929296083622926],[5.130611975215265,12.253243833874672],[5.139060004098508,12.454092709302369],[4.958770487169235,12.628488777917802],[4.9211662714740925,12.880188203929183],[5.126717302863511,12.96528448192216],[5.176765771937176,13.11124695257141],[5.071323642050462,13.282660578188313],[4.951272460836428,13.242447693604879],[4.935511477066024,13.042923323227733],[4.613560418195268,13.024601980758263],[4.524304813228201,13.390169844207072],[4.566633702986271,13.439284734745812],[4.643381104437475,13.606319591367356],[4.543882487588355,13.702752981410557],[4.437417203622507,13.957548473062506],[4.151163760908274,14.08341555974057],[4.098249197017881,14.256097910467863],[3.934243662055091,14.362658551457494],[3.942786992715526,14.592770582134653],[4.181576179115136,14.773123460110572],[4.144606822544939,14.962929195059871],[4.179334910070468,15.182242769362636],[4.122279003520577,15.383260267491366],[4.312981658312283,15.521642598406116],[4.182269651757157,15.780487194215638],[4.204941017650553,16.191944222820375],[4.03688106433218,16.303554571863984],[4.0504897477605795,16.5878308986067],[3.9362684316088865,16.759875147245435],[4.041606104449733,16.93999603471849],[3.93162669061964,17.385712980164325],[4.01290474689483,17.617933465820226],[3.758373037296556,17.595235773106445],[3.7013042738169726,17.77657203595921],[3.7800122386440322,17.967056330525764],[3.687465630835406,18.253278768302415],[3.9595308712422734,18.275741007372854],[3.9163060171285817,18.50436202152423],[3.705644335830788,18.526376403012577],[3.5357692942157293,18.718680921955404],[3.68630673415468,18.858217384764597],[3.6388146224709006,19.026087460007872],[3.7324659969125578,19.418763207557575],[3.6456637837208516,19.50478642640128],[3.286549328898408,19.551038287006893],[3.2158645466870652,19.68642998332438],[3.3462961594941834,19.838352609917347],[3.3081925861373076,20.006525582231475],[3.166775590995487,20.01252868113025],[3.126262769132797,19.827832968952553],[3.0041283583182303,19.814464239922643],[2.7961448642923474,20.04827833548479],[2.8780622447406214,20.24168603368916],[2.706442810628245,20.428273498465643],[2.6941523375848706,20.56340798512874],[2.8047524049215204,20.736435784720225],[2.667914510051546,20.93296388311348],[2.9070574489596264,21.186143874575272],[2.752180995823043,21.29620497689652],[2.9654018379238956,21.700412395240086],[2.728898856892638,21.916238428320103],[2.7211726037835264,22.09104717485961],[2.5880045961504554,22.183871221014627],[2.453777453250886,22.442620226298253],[2.558283877426512,22.524337321987396],[2.627695344919608,22.854073475946016],[2.2973517195739834,22.988327656418125],[2.33584705934872,23.11798969340588],[2.135574860918952,23.242839131993424],[2.0751905634456413,23.354447743388793],[1.897896580573708,23.3614268798015],[1.8399784153668197,23.571384414228366],[1.9023080765035387,23.809580592865178],[1.6806816491878092,23.9568001436344],[1.4875224015211121,24.171119171262383],[1.4195502037299796,24.47154674011529],[1.6097082259552298,24.597245422633755],[1.5706342938662072,24.78999406610143],[1.3792358968876897,24.994355771899674],[1.3693593237953312,25.12577642688133],[1.1709047408934936,25.29887387407281],[1.1611366076195317,25.788414424950464],[1.3170001323383436,26.00373940607817],[1.2070468058815351,26.262269786758782],[0.9797148313671948,26.290151267204052],[0.6875945186650881,26.240717939910198],[0.5644421654734105,26.417835325608497],[0.39407977125824806,26.484244925053765],[0.15464266536529156,26.38321370115526],[0.03755105341655823,26.442517699830056],[-0.023655380726697754,26.689321546245615],[-0.2779742136903237,26.86564308418313],[-0.40166750811082036,27.051386278565555],[-0.2775917097526164,27.33785558119672],[-0.4872750187000615,27.426893516824006],[-0.6754029460769706,27.397003133666814],[-0.873494259763609,27.463761649225187],[-0.8925890481803961,27.666987242256493],[-1.4204706910430704,27.859832217640847],[-1.634919889410364,27.770330691146093],[-1.7893199040689165,28.029378719726797],[-2.27106218092595,28.11131377219759],[-2.5751325356643417,28.109584928693156],[-2.679776646177394,28.26265765809505],[-3.000313508323079,28.370422582929802],[-3.0252969807448653,28.57913684441256],[-3.182958437002049,28.645645775429898],[-3.3976597693743695,28.64887365070716],[-3.4838930889274944,28.7261048816832],[-3.664414575101721,28.65452570246484],[-4.0578175763258555,28.722707899585146],[-4.174435585525769,28.605824498781104],[-4.514906571215436,28.682037559255566],[-4.812902988400563,28.617458701532655],[-5.029074572726973,28.688483048409903],[-5.461083639749054,28.593541454411834],[-5.9170938643394235,28.64825178890919],[-6.210809280007701,28.774107298356764],[-6.441649665071563,28.71322255187798],[-6.515735936773009,28.453114904710162],[-6.827029462407007,28.165026422850907],[-7.17118774488943,28.117616105344137],[-7.4281209911331345,28.241304006588752],[-7.766264756361203,28.50082381434388],[-8.232035974124473,28.712446506538623],[-8.42826650254188,28.85005942414416],[-8.777326799110496,28.933523733913088],[-8.8607365743802,29.025387582130133],[-9.50099789307368,29.332200761821905],[-9.653284017760964,29.654230937140433],[-11.660156498285353,29.701273966984807],[-13.268451237170234,29.745366861077578],[-15.55126541776475,29.8150479264762],[-17.6157680736938,29.889274176661395],[-20.164288154664945,29.99437226334395],[-22.038078009167002,30.068388709065175],[-23.802568877144278,30.150390325038412]]]},\"properties\":{\"name\":\"Nebraska\",\"ns_code\":\"01779792\",\"geoid\":\"31\",\"usps_abbrev\":\"NE\",\"fips_code\":\"31\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-91.0947051057272,31.037517701670165],[-91.23160264150522,30.375076535674733],[-91.76843779713549,27.94955814237814],[-92.14240865724777,26.246061775941115],[-92.65675311457215,23.85396000017576],[-93.24511973773282,21.108090274187937],[-93.77996973480626,18.517237608063823],[-94.15703230386845,16.65913597410227],[-94.50854712544809,14.893870813086018],[-95.19134488283866,11.493019763430844],[-95.36312071260011,10.585608581440296],[-95.96260561367144,7.577772987880608],[-96.20007884154643,6.319406580002536],[-96.56577275765368,4.455012550884487],[-96.80151059674037,3.209502427693635],[-97.13296720415488,1.582863266663138],[-97.61295303732692,-0.9586691515522047],[-97.78373732573543,-1.8128504176916094],[-97.92594531658744,-2.60993149468631],[-95.27385736116857,-3.11524807527112],[-92.39305975068585,-3.6449368696579887],[-90.71450887810519,-3.9423124359646518],[-88.22023969126292,-4.37513457277261],[-86.54481122636447,-4.662414955192292],[-85.15728377563966,-4.893552579475372],[-82.52391577881168,-5.331130099729538],[-79.61788028712324,-5.771627496170111],[-78.2331802207451,-5.980228143496496],[-78.02327518136048,-6.0556952853108355],[-75.5279546486134,-6.425952144565376],[-74.00796249026585,-6.651367327837256],[-72.58110158247436,-6.84904083779033],[-70.06615646820839,-7.199983140779321],[-69.69895376554851,-4.53168439623577],[-69.52407181961003,-3.427563605934836],[-69.14812008412093,-0.7421638190247458],[-69.10350646213794,-0.3475001253983465],[-68.89832582517238,1.0954021708186683],[-68.88968021516625,1.899633559407581],[-68.44824695422317,5.064539343107781],[-68.26618779951605,6.161077689722425],[-68.00514988462709,7.929118542286623],[-67.76281911819255,9.632472520714792],[-67.51091439873119,11.44409119843858],[-67.26095392753037,13.186069034856116],[-66.91125085603706,15.644835375522801],[-66.70181901240139,17.103674779916705],[-66.51414029254477,18.28913267755052],[-66.14937212144588,20.85133770303588],[-67.21012892403772,20.99168531302167],[-68.75892383687957,21.179028575911232],[-71.20716311425856,21.50792059068772],[-72.43169180677559,21.66220935240622],[-73.81972542251606,21.854014498111205],[-76.69636591983853,22.29343437643702],[-76.28398527909648,24.658013578848355],[-76.12242569123185,25.550444220481076],[-75.55878313374993,28.718537400695144],[-77.87014524621921,29.033486328164564],[-80.5767476663345,29.421118773069484],[-81.41513541661094,29.534585688355065],[-81.5113417713196,29.578137009654668],[-83.75030608011934,29.911971823374394],[-85.59393538827628,30.18499100633823],[-88.27544927309998,30.57685599057522],[-90.33473517882668,30.877303989026007],[-91.0947051057272,31.037517701670165]]]},\"properties\":{\"name\":\"Utah\",\"ns_code\":\"01455989\",\"geoid\":\"49\",\"usps_abbrev\":\"UT\",\"fips_code\":\"49\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-135.27288953927726,-66.76452265461053],[-135.30946306390595,-66.72056216787115],[-135.40488764422756,-66.70124212867695],[-135.46170745240752,-66.70832787744185],[-135.5283353227972,-66.74122067866567],[-135.52211563688527,-66.77263922612303],[-135.43489101657704,-66.8037717896667],[-135.34262909562537,-66.79903125569076],[-135.27288953927726,-66.76452265461053]]],[[[-132.84486841416776,-66.51498443655139],[-132.91411915109686,-66.47094176375646],[-133.00794247431205,-66.46717488737941],[-133.07912122593208,-66.48632480312308],[-133.11453978042633,-66.51425826256278],[-133.06449138760306,-66.55625559300009],[-132.9719165907854,-66.57056854953919],[-132.87397006402884,-66.55027613962493],[-132.84486841416776,-66.51498443655139]]],[[[-154.79144303882194,-67.63726082935764],[-154.72168802391786,-67.62796696804355],[-154.6565717493878,-67.60469560151468],[-154.6556540957903,-67.57122190261619],[-154.72952138531429,-67.53274237608427],[-154.86984178712453,-67.5364873280633],[-154.9196567802184,-67.55634074542093],[-154.92377882909497,-67.59749877180964],[-154.86872585276143,-67.63258113201067],[-154.79144303882194,-67.63726082935764]]],[[[-144.0624835319597,-63.53688131667755],[-144.07164501053677,-63.49379981919087],[-144.1133413675643,-63.47228359479148],[-144.17899504890093,-63.463814854817656],[-144.24851222364094,-63.47014535883603],[-144.30545725307957,-63.487195401609746],[-144.33175076886448,-63.530288628595244],[-144.31588010045562,-63.55582965199756],[-144.23771108408943,-63.58283656855464],[-144.1372573259889,-63.574032816627636],[-144.0624835319597,-63.53688131667755]]],[[[-143.3057012537638,-67.02891695874828],[-143.41939520182825,-67.01501397139086],[-143.46536892524333,-67.02197211503437],[-143.51945870417305,-67.05207289531003],[-143.5193957825163,-67.08662564492592],[-143.4769561230233,-67.11896418202032],[-143.39455072021468,-67.13381163040785],[-143.33246588411976,-67.1275080505615],[-143.2703263987335,-67.09830651025203],[-143.25624392610973,-67.05724176394949],[-143.3057012537638,-67.02891695874828]]],[[[-131.31838764756137,-56.53760398505362],[-131.36694102317605,-56.499994492227174],[-131.43354524697142,-56.48800684809309],[-131.49738304677754,-56.49476798139884],[-131.550168963926,-56.52612013839943],[-131.54699529758884,-56.59751425374033],[-131.49154755651298,-56.62408936081288],[-131.4216719838561,-56.62866492857189],[-131.32405138861768,-56.58672463809989],[-131.31838764756137,-56.53760398505362]]],[[[-138.01436329196162,-52.89697569455468],[-138.0882936394755,-52.95519208172716],[-138.0806408445675,-52.99988300347867],[-138.0503807146002,-53.025534822145815],[-137.9780403457549,-53.04503093218795],[-137.86184920867058,-53.01598381048996],[-137.84511797943375,-52.95900612396422],[-137.9000091140524,-52.90031712516207],[-138.01436329196162,-52.89697569455468]]],[[[-155.220552804626,-67.56911991273813],[-155.2244136613955,-67.54513863482495],[-155.3053898071016,-67.5026028692058],[-155.36547091562628,-67.49585743085721],[-155.44615681583673,-67.5018956446146],[-155.49926785911433,-67.5166753300147],[-155.5327436512156,-67.5644677445477],[-155.49731491226459,-67.59313895428473],[-155.43796508663047,-67.61592052078929],[-155.3974972271839,-67.61908156698225],[-155.30785059077792,-67.60713856931405],[-155.220552804626,-67.56911991273813]]],[[[-145.6764242207349,-59.537985625783605],[-145.7326435680374,-59.53797641394872],[-145.8667517082085,-59.56229854703619],[-145.92258220919624,-59.606937748615636],[-145.89607536054135,-59.65486688413419],[-145.84507730541125,-59.675518661297765],[-145.70877434039093,-59.67465335385909],[-145.6055639664506,-59.62890534675449],[-145.5981447108979,-59.58227940867287],[-145.6764242207349,-59.537985625783605]]],[[[-159.87055649246741,-67.63902749093178],[-159.90283907743841,-67.58610152110815],[-159.9425268317089,-67.56903819398404],[-160.07312268161843,-67.55351634420609],[-160.190311662671,-67.56522008897997],[-160.23081692032326,-67.59215214774149],[-160.20281312573397,-67.63654545921344],[-160.095135973745,-67.66789949373644],[-160.01205178205404,-67.67813462094712],[-159.92044096205285,-67.67075215673593],[-159.87055649246741,-67.63902749093178]]],[[[-130.47031876792073,-62.9583093039883],[-130.4687539538436,-62.92195581595732],[-130.51995690225542,-62.87437976231153],[-130.63308934827924,-62.85210146688064],[-130.7294859035287,-62.87759607767136],[-130.7679510353451,-62.921793500938705],[-130.73008598088384,-62.98295716937768],[-130.6677395997939,-63.00054828150716],[-130.55284445895663,-62.99464913795021],[-130.47031876792073,-62.9583093039883]]],[[[-138.1395146926605,-52.8044452173376],[-138.15371908234442,-52.84139138829508],[-138.0361447043351,-52.85379432726363],[-137.9514822964025,-52.82666495000103],[-137.9092354734023,-52.77859438809477],[-137.9235638855803,-52.70854035865481],[-137.97105278200712,-52.6669338108735],[-138.08270005611422,-52.65636733051706],[-138.1395146926605,-52.8044452173376]]],[[[-158.2500685019185,-67.4146722602014],[-158.26811785855398,-67.3811867813623],[-158.37428620939198,-67.34549846506548],[-158.46092814570238,-67.35066133452217],[-158.53631222347082,-67.37114899305509],[-158.5727332203305,-67.39344981111277],[-158.5678096043866,-67.4368350488249],[-158.53347867897565,-67.46458420035712],[-158.4676658907517,-67.47722546497671],[-158.3342463543118,-67.47175762856425],[-158.26034695047454,-67.44513936091072],[-158.2500685019185,-67.4146722602014]]],[[[-130.69759776014823,-55.92188715251139],[-130.66144471967888,-55.846678687221825],[-130.70409460741047,-55.80228987672384],[-130.79426979884613,-55.77843030403889],[-130.84474830662157,-55.783191393845115],[-130.9214041002713,-55.81423821293759],[-130.94641115222262,-55.84779096170367],[-130.93514402734326,-55.9145205715058],[-130.89518575815848,-55.94102397302959],[-130.8245635121614,-55.956361302889576],[-130.7652087645038,-55.9543935361366],[-130.69759776014823,-55.92188715251139]]],[[[-137.5213733447396,-53.91558749548448],[-137.59245925707384,-53.89999390968562],[-137.68235134297723,-53.90980258534349],[-137.74937189023342,-53.940179677149196],[-137.77916962560053,-54.01438681373602],[-137.72105857647404,-54.07119821014008],[-137.622401142757,-54.09736797549811],[-137.52316692632732,-54.07430583036395],[-137.46336122156694,-54.02640593955087],[-137.4571385377973,-53.97799680899257],[-137.5213733447396,-53.91558749548448]]],[[[-135.62597900807108,-66.09119380259726],[-135.66433873141048,-66.05023087083745],[-135.7366432147086,-66.00894750503485],[-135.81011934178036,-65.99912170303735],[-135.88742140003086,-66.00790309918874],[-135.94212143914598,-66.04650508316251],[-135.92304003926367,-66.0713321957075],[-135.95701231023236,-66.09200747864497],[-135.95809796533595,-66.13027432067969],[-135.92991541405684,-66.16052044568751],[-135.82343378427998,-66.18993299127231],[-135.70059435278483,-66.1782470641229],[-135.61998733435212,-66.1220548412519],[-135.62597900807108,-66.09119380259726]]],[[[-160.26519064170694,-67.24910231365656],[-160.27279951368382,-67.20420521870182],[-160.37697074919424,-67.14874336476979],[-160.461364186125,-67.13582783488218],[-160.54009273687407,-67.13918955428903],[-160.64698833724734,-67.16379029373758],[-160.72070297818314,-67.20718492408973],[-160.72885000261556,-67.22719112389197],[-160.65519432885534,-67.28849658898655],[-160.61194419760798,-67.31246377245786],[-160.52864496293643,-67.32374705931946],[-160.40810810133533,-67.32083545970313],[-160.32612209390254,-67.30147694270187],[-160.26327599683546,-67.2667307420104],[-160.26519064170694,-67.24910231365656]]],[[[-148.88545630665712,-68.0685909847474],[-148.76673168916017,-68.03877776267663],[-148.6910956228878,-67.98665425036342],[-148.61274309322758,-67.98118855368168],[-148.54975131762168,-67.96108279454303],[-148.49853651153373,-67.91281815289238],[-148.55341251581237,-67.88481122322302],[-148.6491287393489,-67.86313550775894],[-148.75509259778812,-67.8668312798585],[-148.84398381547365,-67.89811561952399],[-148.94636120055821,-67.90018742074336],[-149.0592475860148,-67.92139719946582],[-149.13207062816767,-67.98075069109659],[-149.1156191105177,-68.03598329344787],[-149.0334280688207,-68.06285938291558],[-148.932991735832,-68.07228908786384],[-148.88545630665712,-68.0685909847474]]],[[[-113.32882564609913,-61.8317709157509],[-113.39125189487292,-61.88409473765322],[-113.40307966142657,-61.93112085662326],[-113.36035799443825,-61.96475349224122],[-113.27300285246164,-61.982328776943305],[-113.19696148165876,-61.97821499477393],[-113.10653075837277,-61.958582252810544],[-113.03833122622179,-61.913658547303136],[-112.97324167866026,-61.8858399416135],[-112.95955710886197,-61.85707973364947],[-113.00520019217862,-61.7973771171663],[-113.05268161331976,-61.76702380466812],[-113.10886236465454,-61.75507889463327],[-113.12138148534254,-61.71136257528921],[-113.15915195704484,-61.69172881854251],[-113.2727213204087,-61.68510858271245],[-113.33479919933578,-61.70967573928423],[-113.35264622858267,-61.73700210292925],[-113.32977970364608,-61.77482549076602],[-113.32882564609913,-61.8317709157509]]],[[[-125.02298621848264,-65.95220998389334],[-125.15690329194275,-65.95090430110743],[-125.2175954569431,-65.96842428246954],[-125.32550370609209,-66.02162699150483],[-125.4013006539609,-66.03888703505105],[-125.43241095455677,-66.05661589439906],[-125.44990759392037,-66.09253059356682],[-125.3939380359631,-66.14721458535048],[-125.29044718826813,-66.16631676849067],[-125.09664730310496,-66.18025317857928],[-125.02143901139796,-66.1749715940487],[-124.94451242298932,-66.139693662585],[-124.9548964825871,-66.0614547815097],[-124.90635604718302,-65.99557508507259],[-124.93440392565041,-65.96512182419603],[-125.02298621848264,-65.95220998389334]]],[[[-130.68512408089552,-66.9978217774287],[-130.66456394296878,-66.9656216909468],[-130.69365311533898,-66.93984049289482],[-130.76750121756263,-66.90022147890548],[-130.84614375718255,-66.87325773173535],[-130.96271583123007,-66.87771906416313],[-131.02383863215746,-66.86027839023419],[-131.13707856816438,-66.85260389044686],[-131.25038716869688,-66.86942196671],[-131.32533820614483,-66.88954517403981],[-131.37075388937413,-66.92521127864956],[-131.3011663368823,-66.97823623343656],[-131.2450457384458,-66.99709963383194],[-131.14874207002856,-66.99817661940165],[-131.0602338304566,-66.97871351096838],[-131.01849253866484,-67.02690577223221],[-130.98078663156852,-67.03779191288031],[-130.87202789928205,-67.04979361627464],[-130.75455741000485,-67.03243272801177],[-130.68512408089552,-66.9978217774287]]],[[[-143.79232330634198,-64.18690059783198],[-143.8318248501325,-64.16363434991455],[-143.9179439891507,-64.13928580552131],[-144.07154709968384,-64.13016353209967],[-144.2305145332366,-64.0980700336839],[-144.30456519208568,-64.09144532083148],[-144.3804756253638,-64.10035652517575],[-144.43014825205057,-64.12693377435113],[-144.44344899606455,-64.17435623122896],[-144.38220870626284,-64.22526930942665],[-144.3313181480733,-64.23806315132023],[-144.29826051067803,-64.27068794242868],[-144.24027400063525,-64.29730442906757],[-144.12096652058094,-64.32419649058548],[-144.04219753066346,-64.32147909610309],[-143.959417351114,-64.29225636543372],[-143.86768682863078,-64.27669493747874],[-143.7882107317123,-64.24190662450754],[-143.79232330634198,-64.18690059783198]]],[[[-161.23097106487296,-67.67033356875315],[-161.14373137878613,-67.66570609419482],[-161.08462581956852,-67.65402718752001],[-161.00698784755664,-67.65122964168829],[-160.9554298633213,-67.63998212648487],[-160.91134810551665,-67.61157946965226],[-160.89802236489285,-67.58030761148952],[-160.92251739173162,-67.54205005362861],[-160.96423674823168,-67.51597553867875],[-161.0333317740921,-67.49239009590009],[-161.0892344530834,-67.48848864858567],[-161.17660656741347,-67.49999066519696],[-161.274071542435,-67.52350619217184],[-161.329118539748,-67.55316601504693],[-161.45394185048858,-67.54968950220523],[-161.55127398464253,-67.57486101934718],[-161.58835695474014,-67.60154693198055],[-161.614050601357,-67.66352316702165],[-161.59624973352572,-67.68544056065231],[-161.53562152360954,-67.70340108195651],[-161.43926884093253,-67.71391705129899],[-161.33880996502253,-67.6978471152],[-161.23097106487296,-67.67033356875315]]],[[[-131.2886551110318,-62.850500581908825],[-131.2488329697307,-62.869718902949906],[-131.14649503895103,-62.88104862077719],[-131.17984694981396,-62.91894603885807],[-131.1653016120617,-62.95738791848704],[-131.13037662649262,-62.97256471362397],[-131.04577323872195,-62.983365898643804],[-130.98904154149128,-62.975738130531774],[-130.94125038961383,-62.95380327565778],[-130.91698518790915,-62.921063086269754],[-130.83226123412632,-62.87645497343647],[-130.82562502425768,-62.852535543193646],[-130.74479281652765,-62.81105158448881],[-130.74482584653586,-62.77938770660388],[-130.78518130781976,-62.749948660346384],[-130.88191837415818,-62.73823603154797],[-130.96507356815016,-62.7121641275106],[-131.01750208948553,-62.709772153405574],[-131.06994168036437,-62.685488293341656],[-131.13099385070157,-62.67776083810703],[-131.2324188528943,-62.701882210068476],[-131.28854981918744,-62.74941121071553],[-131.3261459178439,-62.812091211533385],[-131.2886551110318,-62.850500581908825]]],[[[-126.47626729215193,-65.85193010849156],[-126.4308300130859,-65.83069980836981],[-126.42855441289447,-65.7660347380976],[-126.52197175521759,-65.71310486661211],[-126.58381526911488,-65.70369117794692],[-126.55664947010023,-65.68432108107743],[-126.5496335671178,-65.65003603110608],[-126.59868866465364,-65.59107909352032],[-126.70647472160216,-65.56592015039274],[-126.80673778506402,-65.57422062684259],[-126.85104291958578,-65.59728386373287],[-126.89574924211176,-65.65371409916763],[-126.90388592273905,-65.69125684613695],[-126.88025939624956,-65.72484338536574],[-126.7900052316435,-65.74571154389494],[-126.83731945047286,-65.82901680725739],[-126.81311015546247,-65.84432466423347],[-126.8115073929258,-65.88529602768736],[-126.73185315995761,-65.90153787831618],[-126.56880400682192,-65.8947504573387],[-126.47626729215193,-65.85193010849156]]],[[[-147.66911102057333,-67.95964656703191],[-147.64760421200768,-67.9115432542188],[-147.66899242906544,-67.87979941056908],[-147.7381396561156,-67.85282508328093],[-147.82977684020193,-67.83491167296803],[-147.90454078386927,-67.82963074017208],[-148.0543346461894,-67.84270370247314],[-148.27341000772583,-67.89086683319394],[-148.37089419409446,-67.95309604967821],[-148.39234471104928,-67.975204449933],[-148.35620384691276,-68.0134488586681],[-148.23430725187401,-68.05095520324625],[-148.14414961486852,-68.04715853304168],[-148.0252998548426,-68.02038705461035],[-147.87025823295753,-68.02081715957866],[-147.80022194325196,-68.00925972581628],[-147.66911102057333,-67.95964656703191]]],[[[-160.28326386863174,-67.52741250055706],[-160.20801540395976,-67.52766127666916],[-160.1486354767288,-67.51769324363187],[-160.11860184414329,-67.49618091855321],[-160.13290896780944,-67.45970156216289],[-160.19768730460663,-67.43732120513044],[-160.2321737614759,-67.41297399240734],[-160.39465983348398,-67.37485233899417],[-160.50200552939134,-67.37230078244374],[-160.5955188207578,-67.39377258559135],[-160.68779274750423,-67.35973377307475],[-160.76302230661352,-67.35769405943641],[-160.88252036934253,-67.37755995555514],[-160.90809070884092,-67.3397274239928],[-160.98663453842195,-67.30572451553357],[-161.08207314196954,-67.30511560834607],[-161.16896383558603,-67.32994539019089],[-161.20528559279896,-67.3667000562105],[-161.19550289402162,-67.404319138878],[-161.10694111774643,-67.42567698646718],[-160.9809327044061,-67.42462006506777],[-160.9085417297672,-67.40507522728974],[-160.8734154070015,-67.44991826142748],[-160.80799199482848,-67.49067683483888],[-160.76084571451847,-67.50405657290369],[-160.629307149474,-67.51562474099637],[-160.48724496119652,-67.50522773344666],[-160.41250763173213,-67.53390806941047],[-160.37628574376552,-67.5383023330455],[-160.28326386863174,-67.52741250055706]]],[[[-150.4254433237882,-67.94950030006898],[-150.44271637289077,-67.9177439808496],[-150.62825445486956,-67.84829334643533],[-150.75805662066168,-67.84199862114056],[-151.0108961411634,-67.86222623517102],[-151.19448368654074,-67.93293776295187],[-151.22484854117516,-67.95916507121484],[-151.22623495775653,-67.98826317695038],[-151.19608347457495,-68.01445171906711],[-151.11562180336333,-68.03425558300779],[-150.95907585743933,-68.05022770897364],[-150.81845935379593,-68.04061415250564],[-150.74364238458617,-68.04573063231616],[-150.66400549859932,-68.03104561697111],[-150.5851243564228,-68.02649403596521],[-150.47701370138734,-68.00837919871454],[-150.43732865419716,-67.99314274619104],[-150.4254433237882,-67.94950030006898]]],[[[-120.14780923055139,-62.66576733077312],[-120.23026350303029,-62.623753960525434],[-120.33748629704488,-62.59917114689247],[-120.41876329637738,-62.6070738998364],[-120.47809427163747,-62.594322375777324],[-120.55380586618497,-62.595989112004176],[-120.62582922088346,-62.623038880798084],[-120.72638786982279,-62.60809218679174],[-120.84145121563016,-62.62693873387408],[-120.90266298244084,-62.705869964523785],[-120.88461200187312,-62.75086169038347],[-120.79210156080036,-62.81470711187046],[-120.70316191289263,-62.823190639402725],[-120.62085217273285,-62.79853952170203],[-120.5509930178289,-62.79773157304751],[-120.47335822697129,-62.77671026014149],[-120.3849800175666,-62.8043250382775],[-120.32078395072595,-62.80860852326523],[-120.2756473664581,-62.79795280887942],[-120.2198302140143,-62.76265490852589],[-120.16322716488979,-62.75150128304985],[-120.11711378477939,-62.713646550145384],[-120.14780923055139,-62.66576733077312]]],[[[-144.49392783400458,-63.56248367378695],[-144.43764701181743,-63.55119053531162],[-144.37407988929988,-63.520570958660635],[-144.3636574504168,-63.4954842330499],[-144.27969833513257,-63.471833493832186],[-144.23499846676913,-63.424068751379124],[-144.2801057230824,-63.3829205934975],[-144.3332991197131,-63.36854662370408],[-144.44455098580391,-63.36532709625163],[-144.5569747509465,-63.39116156641535],[-144.6470180158434,-63.37153847656614],[-144.81148651578215,-63.37961262397154],[-144.94145768303844,-63.42954433506757],[-144.95643573413957,-63.4638467069918],[-144.9455843667555,-63.50412357239566],[-144.89263316928174,-63.54007757642614],[-144.9709751327202,-63.55663869992152],[-145.0133432748444,-63.590658822067546],[-145.0063234005455,-63.630331917226826],[-144.95965421249406,-63.654352581066505],[-144.83744799608453,-63.66093502205481],[-144.75770054427932,-63.63868100789772],[-144.72786063302152,-63.611456383179075],[-144.62024288633825,-63.59998693504138],[-144.49392783400458,-63.56248367378695]]],[[[-135.43856601997814,-67.29721222571605],[-135.3453586734078,-67.30812496366676],[-135.28780310246208,-67.29701988538775],[-135.2312905821854,-67.26706528904269],[-135.134216413525,-67.26527209504714],[-135.02297160544563,-67.24156369012638],[-134.91122994586075,-67.24463281164749],[-134.8600254694115,-67.23176581052395],[-134.835848259013,-67.2103752033447],[-134.84097702828996,-67.18153646615919],[-134.9761944375544,-67.11887117358289],[-135.05801203001872,-67.11245329403033],[-135.0964985658952,-67.09468022576026],[-135.1893882034309,-67.08353446195662],[-135.28937402049456,-67.0551788532922],[-135.3311997639095,-67.05829879431049],[-135.4392834763473,-67.02729105087072],[-135.70215976758863,-66.98725196491225],[-135.7963655288211,-67.01383001876819],[-135.81476499100302,-67.05783052752012],[-135.85784548661675,-67.10197615444679],[-135.93718869746905,-67.12504002639868],[-135.94824822366672,-67.17196249570071],[-135.8816936080253,-67.20070393293415],[-135.78967005244508,-67.20061133866271],[-135.69073327764147,-67.22129496405402],[-135.64299904922123,-67.24479499825554],[-135.63346339371168,-67.26743932082073],[-135.5823751088431,-67.29762205108574],[-135.54548494432714,-67.3034768724158],[-135.43856601997814,-67.29721222571605]]],[[[-146.32403133091077,-67.95022425647046],[-146.2679203977772,-67.93315660276174],[-146.22345654528309,-67.85149984888042],[-146.2524688105075,-67.81293910619647],[-146.1365219863817,-67.75863606667801],[-146.12219336310605,-67.72265060584812],[-146.13975120455342,-67.69206007184191],[-146.2134700233587,-67.66234385422406],[-146.23557077808195,-67.63463610978407],[-146.32393240137083,-67.60507502128512],[-146.40848511951918,-67.6045049044734],[-146.48934379353344,-67.61812497990695],[-146.55743208962477,-67.67011836229261],[-146.50462142439784,-67.70925387011337],[-146.54295746323274,-67.7449922449705],[-146.54681743392615,-67.77757513515407],[-146.66227732022193,-67.76724685591542],[-146.69416457802947,-67.7414335279898],[-146.8385227650779,-67.7140747824123],[-146.94758903903767,-67.7135668715075],[-147.05278210216807,-67.7308889843245],[-147.1141263536851,-67.76152176849212],[-147.11561587352867,-67.79514714477473],[-147.082399046861,-67.82413949888468],[-147.1641129688536,-67.82939189670607],[-147.28830907424938,-67.87428263859368],[-147.31559433959276,-67.89740999956634],[-147.3131000014789,-67.95213870304285],[-147.21153716660268,-67.97913483770874],[-147.083106539498,-67.99132232340021],[-146.99563476777723,-67.98680348361773],[-146.89919246606132,-67.96084148332025],[-146.87450728663353,-67.93801623318092],[-146.76741690841337,-67.94549617723757],[-146.6729624735773,-67.93246644899745],[-146.6296619769574,-67.94135032823411],[-146.59447700883285,-67.96497314752524],[-146.48570099891117,-67.9946290097269],[-146.41585974724956,-67.99121555512802],[-146.354079529701,-67.97332270473701],[-146.32403133091077,-67.95022425647046]]],[[[-145.5702747333091,-59.159725997904516],[-145.53635148915328,-59.09026763233661],[-145.59564782410433,-59.040246564490666],[-145.65561525202858,-59.022390093336995],[-145.6354907652338,-58.96652913529317],[-145.65198033053062,-58.9137237244635],[-145.72151860946846,-58.881634231641556],[-145.8510844221718,-58.88745129567764],[-145.95836572273157,-58.94191000766418],[-146.00267460043395,-58.988229620989515],[-145.9387767096069,-59.08045611853638],[-145.99226051557486,-59.12226935188372],[-146.03847875443216,-59.19194918206555],[-146.02860279779426,-59.22553895156952],[-145.9496946841184,-59.26046363620773],[-145.82506918665666,-59.34615248584236],[-145.83528425761753,-59.41121895466518],[-145.77660267879492,-59.44814466021364],[-145.72254582925612,-59.45579397202601],[-145.63526899131975,-59.49838798183949],[-145.6052863306188,-59.52686821421674],[-145.5201027903159,-59.56496484696201],[-145.39064912773344,-59.56316710029321],[-145.28255888264223,-59.57278541694311],[-145.2613070460804,-59.599651735626935],[-145.17689671874206,-59.64057682135854],[-145.04278686884936,-59.64390444620131],[-144.93599514655568,-59.6109806330724],[-144.90458991554183,-59.580293703058075],[-144.95089334080424,-59.5276935696429],[-145.05487231717754,-59.46074108859953],[-145.08442407748132,-59.418792526829904],[-145.15790878697348,-59.38204719797505],[-145.31945153315203,-59.35957565095817],[-145.47468384401986,-59.27772523792764],[-145.5361298090041,-59.22318613162886],[-145.5702747333091,-59.159725997904516]]],[[[-137.1345967344613,-60.42081426751648],[-137.1299464412895,-60.39239039733719],[-137.21736736259876,-60.342094787509154],[-137.2708930944595,-60.33828199381732],[-137.33306948304116,-60.351430143703155],[-137.41297929615484,-60.3277147537817],[-137.5530802963435,-60.33463678680071],[-137.53420832753412,-60.29613604418591],[-137.570070497099,-60.229791594985635],[-137.66969091832343,-60.16841567973091],[-137.7398428423853,-60.16899374353886],[-137.82712459826882,-60.194578947977305],[-137.8460695953016,-60.22702981936666],[-137.9503596714372,-60.26018338687334],[-138.05104922792415,-60.21689100207866],[-138.15385240186407,-60.207445440890496],[-138.213753184234,-60.228815396431116],[-138.27866651163734,-60.23526353743245],[-138.34726646728905,-60.26310503902488],[-138.4343977221834,-60.252105682619366],[-138.58209629442774,-60.29205241977924],[-138.70884059363863,-60.342368797985664],[-138.73423788188214,-60.36186980316452],[-138.8298850386184,-60.36749891903976],[-138.88358744068827,-60.33881417349333],[-138.94157495161846,-60.324885279201574],[-138.98568846787643,-60.33163557670861],[-139.17053918892609,-60.30266054651307],[-139.3390083261703,-60.3209347166111],[-139.42391555095983,-60.3119550855652],[-139.4869898309809,-60.33408187633758],[-139.51605146964505,-60.37490477253718],[-139.50113048936572,-60.41146734910289],[-139.4150954996803,-60.50785989711118],[-139.44668671581377,-60.57047893059448],[-139.39724999156533,-60.61972919296427],[-139.3238160519464,-60.63052206613358],[-139.29597016466013,-60.657454790913164],[-139.21246497004938,-60.67241268661295],[-139.13348637761794,-60.74249506685901],[-139.02050376526947,-60.763851663673215],[-138.908116117762,-60.80446180967066],[-138.81796970841674,-60.863950396178694],[-138.80008900411593,-60.88892801819663],[-138.69383837342784,-60.916416630929014],[-138.6461671345512,-60.94938361680496],[-138.57759318579886,-60.978446248907716],[-138.33400149201907,-60.99533979013196],[-138.2146219706317,-61.04145518913822],[-138.1736570076934,-61.093635157100316],[-138.09238766452992,-61.13846086755859],[-137.92476344025067,-61.163047649006714],[-137.79753654096433,-61.14054861040978],[-137.58764016187925,-61.055796732059584],[-137.46301764791397,-61.02579828485572],[-137.36903183723368,-61.038327457461875],[-137.2419022902481,-61.02873555341621],[-137.15455992075306,-61.014810424921656],[-137.101367860342,-60.98870053391752],[-137.0569792717084,-60.94179417129872],[-136.99722478675844,-60.90429866378959],[-137.04924587556928,-60.875187898335746],[-137.0889702914083,-60.825380194364485],[-137.13714170732388,-60.81246737764355],[-137.16108935489407,-60.76063151854259],[-137.13141645837652,-60.73317573438641],[-137.11930576079985,-60.691722075168265],[-137.13464726429973,-60.63361478993708],[-137.09200264547476,-60.59137875986071],[-137.10719756842894,-60.55785346844544],[-137.07818738830926,-60.538416634130996],[-137.04845482130747,-60.487419235014116],[-137.07918966207123,-60.438079197219054],[-137.1345967344613,-60.42081426751648]]],[[[-139.3426698690217,-56.12815157407976],[-139.50754403530428,-56.087191182163814],[-139.72259078624893,-56.054659024734065],[-139.94857720136466,-55.990045567862985],[-139.96731462467292,-55.957897423239864],[-140.0694978911097,-55.87937483617496],[-140.26977237909628,-55.83770454676938],[-140.39272430593337,-55.76877877370058],[-140.39901133571442,-55.628235858439986],[-140.53662730246367,-55.4677158959456],[-140.6528017625603,-55.399087042755944],[-140.8755213556154,-55.370375119782565],[-141.0178507225729,-55.3936936662434],[-141.115376336146,-55.39470924126707],[-141.44182831364984,-55.474573128627796],[-141.6410682129789,-55.39630403159599],[-141.72784437122746,-55.370808106168504],[-141.99580172525967,-55.24969636293045],[-141.9614109315612,-55.21308921489032],[-141.95855200533586,-55.139215418368956],[-142.0097209828377,-55.08209768809818],[-142.06380838518226,-55.05877069028145],[-142.17545669865663,-55.050130434099245],[-142.2285883986416,-55.066378081359034],[-142.29433654589164,-55.10871755045044],[-142.30056090986082,-55.17047847745946],[-142.35744540116255,-55.21761114534235],[-142.37259779722496,-55.26073915526403],[-142.44391876198665,-55.3019899254108],[-142.53216011051998,-55.38939990779442],[-142.61951788835876,-55.51957753632037],[-142.59670375032923,-55.66293947044932],[-142.57077233515125,-55.7180440078727],[-142.5167182306066,-55.766366839608615],[-142.4024085808632,-55.81934647796166],[-142.37961433312515,-55.843055433441066],[-142.24291141558044,-55.896722043743694],[-142.1510024143091,-55.90605312479121],[-142.05616559659208,-55.8838846553616],[-141.9688494377804,-55.88259089774271],[-141.84093658928066,-55.82083511741813],[-141.76481978912844,-55.819970290851266],[-141.7191509113437,-55.80550019262265],[-141.6376857200054,-55.81207832813192],[-141.54859021094808,-55.83224965253208],[-141.45892901912578,-55.86703482621703],[-141.2480674347328,-55.96019792118466],[-141.1741853273298,-56.00496987545666],[-141.11375155695282,-56.065901306866195],[-141.07825571577484,-56.12970058818939],[-141.11440715483582,-56.181691958559576],[-141.0617919782309,-56.235460931110644],[-140.9189363262552,-56.27231688604212],[-140.8365765277619,-56.283082675999296],[-140.6964116735471,-56.39248517004864],[-140.6559980503496,-56.43602401942119],[-140.64804882019666,-56.518520302525545],[-140.67051096657568,-56.56585834789608],[-140.6534144457175,-56.59483953975311],[-140.54845480890853,-56.65071263203563],[-140.45686898020955,-56.67899143666919],[-140.40244010678248,-56.68196911193461],[-140.30859713794337,-56.642233597343406],[-140.2335379787402,-56.640466581958414],[-140.16394850212095,-56.59146911647403],[-140.16097524121727,-56.500097312896905],[-140.0567291454132,-56.48172267977538],[-139.9322291531629,-56.42210717280203],[-139.8703770062909,-56.417173172698945],[-139.68795851785595,-56.43389583145008],[-139.5460361540203,-56.49255330085832],[-139.58623575695017,-56.52306770454117],[-139.59506892297858,-56.56176702585923],[-139.53080208019762,-56.60737000336251],[-139.46002856109538,-56.61924085380989],[-139.3254845456533,-56.5804870674244],[-139.29050976024942,-56.54559944221795],[-139.32041064882898,-56.4755072799413],[-139.1306051968959,-56.36030132451771],[-139.08239349605122,-56.30498790652463],[-139.05474225513694,-56.23329226134321],[-139.1040582728548,-56.19913669241791],[-139.3426698690217,-56.12815157407976]]],[[[-156.26964866948308,-67.81439509278526],[-156.18062617879067,-67.83070206567676],[-156.11317707167188,-67.819797479369],[-156.0591015607502,-67.78200000267223],[-155.9161184770542,-67.78768388186337],[-155.7743974595771,-67.76817154826976],[-155.7052242685456,-67.78321762420617],[-155.4807797913547,-67.78626125616408],[-155.42789741338436,-67.79334462024524],[-155.31254976678605,-67.77580719463396],[-155.2591736197177,-67.79266577268888],[-155.14960772272948,-67.79731852636479],[-154.8945894757472,-67.8422476101651],[-154.80735058300584,-67.84678255674794],[-154.60152663540134,-67.84113884779276],[-154.43110274189954,-67.89172266546834],[-154.34559249712905,-67.9002528422652],[-154.19190138340238,-67.89832013781572],[-154.1180890793427,-67.92720230217257],[-154.04452459602712,-67.94142756272505],[-153.97939192904477,-67.93807093601515],[-153.88168941100085,-67.94516564753495],[-153.83133061479157,-67.93959339251727],[-153.73585097115304,-67.89618805318153],[-153.5320257644928,-67.90938954787451],[-153.4907297090601,-67.91833278815393],[-153.345347319771,-67.92715893690207],[-153.2012546511355,-67.99002747052974],[-153.0349521086711,-68.02349407164165],[-152.86720196947942,-68.02618085647383],[-152.7346563434239,-68.04702800844646],[-152.6999756028694,-68.06890513936291],[-152.56375403516208,-68.0952396931859],[-152.46597378388245,-68.09299668939839],[-152.343259300051,-68.108179166127],[-152.26879995514932,-68.09517992287499],[-152.1693388173494,-68.10311569442715],[-152.181258710967,-68.1214470403047],[-152.1513507718945,-68.15180712292099],[-152.10634249512026,-68.16991292999937],[-152.00323028936845,-68.16897584836398],[-151.9655594935304,-68.15822180863813],[-151.92715918734254,-68.12098117110844],[-151.8069577924552,-68.12446862932235],[-151.77202546534423,-68.13573032200682],[-151.69083316736166,-68.14085289737702],[-151.5336588702667,-68.11224472264247],[-151.4856138815327,-68.0897498118038],[-151.482156818366,-68.06774234771481],[-151.5203418755692,-68.0389110933287],[-151.60241348729912,-68.02385021352731],[-151.63577553454851,-68.02855075509882],[-151.97061175270625,-67.97675636856064],[-152.0454228239399,-67.97437089070439],[-152.2107273252331,-67.94527815963978],[-152.3531879999008,-67.92700998033003],[-152.3587428540391,-67.90401386688742],[-152.41776887278482,-67.87633325591663],[-152.5111328133548,-67.87495060202637],[-152.53757951939718,-67.86355002004876],[-152.61736234873703,-67.86042578646789],[-152.77144538528808,-67.8650361272676],[-152.84763640168654,-67.85398219788311],[-152.91770519734092,-67.83038419137861],[-153.0045655217454,-67.82254530574606],[-153.07071339940896,-67.8290147583101],[-153.15118402904994,-67.82099020967676],[-153.11555539909236,-67.79717723577451],[-152.9880847281407,-67.7567829715019],[-152.92258331321563,-67.71378768479546],[-152.92960621203795,-67.68509758942643],[-152.9087704109852,-67.65727156458863],[-152.9206959983023,-67.63786402705712],[-152.97580521043147,-67.6156461768914],[-153.02126446251327,-67.58320672684204],[-153.06751886885766,-67.57148303609296],[-153.11609789140036,-67.54315414472967],[-153.22412817729344,-67.52690564527488],[-153.32201289921696,-67.53190714791691],[-153.41929445773948,-67.51832217859659],[-153.5373996836117,-67.53887223284752],[-153.62233847536518,-67.57373828078926],[-153.75203291823894,-67.56744115733213],[-153.81020619731945,-67.57906345730143],[-153.85241982430844,-67.60489470641703],[-153.84144813252436,-67.65555457601752],[-153.9191043538712,-67.65393991504136],[-153.99435137675465,-67.67530513814111],[-154.1133176667401,-67.66429080975445],[-154.16577887829445,-67.67359118033728],[-154.24653583056005,-67.70129568146012],[-154.3555295374016,-67.70574338241828],[-154.44785771205113,-67.68230996208564],[-154.53570875413442,-67.6731651467386],[-154.65637509473905,-67.68550986813572],[-154.75307059736255,-67.67327364038913],[-154.83761277825866,-67.69053564621474],[-154.97368276214235,-67.67773713718002],[-155.04109775729066,-67.68334724751777],[-155.13725070077723,-67.6726057136913],[-155.28558976824993,-67.67924278172582],[-155.34819686054027,-67.69846743459519],[-155.42756944484972,-67.68157430242081],[-155.56889512027388,-67.69635526405719],[-155.7958312985692,-67.65088125301753],[-155.83801126018392,-67.65042535145234],[-155.92286117426804,-67.61282467554963],[-155.91952265976548,-67.57578648138683],[-155.944684169456,-67.56135625945944],[-156.05713146369777,-67.55214306600172],[-156.08846379038607,-67.49759371134074],[-156.1469155533758,-67.47088339407681],[-156.22471153970773,-67.46174268232937],[-156.31315576509874,-67.43748354218191],[-156.4386542201176,-67.43928243790307],[-156.5574148504568,-67.47345386453554],[-156.61027587021525,-67.50480170363514],[-156.6503379021945,-67.63869328082306],[-156.6953381549068,-67.64051322949022],[-156.77535591906496,-67.62647554426326],[-156.93178633761022,-67.61265037253288],[-156.95593380857463,-67.56100269948378],[-156.93648286776784,-67.54586031886846],[-156.927075515121,-67.50199126807904],[-156.98325873958564,-67.46934617915679],[-157.06594307328507,-67.45240123358363],[-157.16778602384298,-67.45520129797806],[-157.23677109715067,-67.46701871920959],[-157.39167867786847,-67.44830317994618],[-157.4753263701909,-67.4582266053563],[-157.5456918080724,-67.48480737843006],[-157.62174833487498,-67.5275416195651],[-157.67391537540425,-67.54547420936967],[-157.76757429972938,-67.55379767472297],[-157.71282445337943,-67.49734694859124],[-157.74681416277195,-67.45164489139943],[-157.8412087782971,-67.41307181419657],[-157.93722531524853,-67.39016060382042],[-157.9980398521515,-67.38815673103595],[-158.11547978277557,-67.40572036101216],[-158.17968168803972,-67.44390838459701],[-158.22026766583767,-67.48897774608369],[-158.28055171092274,-67.50713814191397],[-158.42270387478933,-67.48773851447098],[-158.5031292007596,-67.50350438609463],[-158.61956377612077,-67.50480547536759],[-158.67861702684195,-67.49311174251119],[-158.62801449837457,-67.47424578740704],[-158.58316097866114,-67.42759947852694],[-158.61515707579596,-67.3944954521934],[-158.66679200327758,-67.38324501046995],[-158.88521710853826,-67.36234545742626],[-158.98161401761644,-67.34679104748275],[-158.977361513215,-67.3293578657842],[-159.0239988318282,-67.28317559581897],[-159.09017060167753,-67.2543258996123],[-159.21250875921396,-67.2350697275596],[-159.26646406259593,-67.23526479049589],[-159.31722155959343,-67.21573647225135],[-159.54297066895305,-67.20954519416075],[-159.6585977144465,-67.24025744035413],[-159.69116725616445,-67.2581639048671],[-159.7088331774083,-67.29293185723833],[-159.67805956388003,-67.33154404595169],[-159.56053315126167,-67.38795443976299],[-159.66638238176347,-67.42648152267014],[-159.73484389633794,-67.47712871450847],[-159.7391756301283,-67.50462632250749],[-159.6762754526319,-67.53792355290986],[-159.6211504284192,-67.55217520448318],[-159.58449546472082,-67.58162843844089],[-159.43903720452118,-67.64098400276868],[-159.36471839483926,-67.65017935263664],[-159.30106273950068,-67.64391135429909],[-159.24443969169576,-67.62589554530277],[-159.2119069196454,-67.59588138195873],[-159.1350463759471,-67.57742232531477],[-159.09828559927467,-67.5932819598873],[-159.08582582276682,-67.6217006862108],[-158.97788965319668,-67.64902139862535],[-158.83440950316114,-67.64522592641764],[-158.65773919397643,-67.65265933779177],[-158.56015647967678,-67.63595119970856],[-158.53996381306524,-67.6677510078744],[-158.41847285968947,-67.69867250455216],[-158.3379103757066,-67.70540207986471],[-158.25799356474826,-67.69985687423026],[-158.17079138039205,-67.7070852089049],[-158.08451647606654,-67.69805008236712],[-157.96794774888164,-67.65538644931709],[-157.929535300451,-67.62619987330383],[-157.877773611609,-67.61247412822412],[-157.86532833209512,-67.65847348199358],[-157.991987393225,-67.6809363379469],[-158.06884444445768,-67.70979626960045],[-158.1213251869495,-67.74637951889561],[-158.13675776145556,-67.77236315326192],[-158.12103338139858,-67.80116652163335],[-158.03674851899626,-67.83965714958836],[-157.96117352591176,-67.8504290444244],[-157.86534558099032,-67.83842501552824],[-157.7724087435094,-67.8551201036675],[-157.69577197638077,-67.85227992914302],[-157.60563331712396,-67.85864306945538],[-157.51464857586677,-67.84551750189743],[-157.39213756596882,-67.84357245637844],[-157.31398390149565,-67.82807510642252],[-157.08407203140325,-67.82337061417034],[-156.96078604770693,-67.84215499632404],[-156.85733501990154,-67.84188889058808],[-156.76211366808658,-67.81692026469567],[-156.70185803543075,-67.83787067559939],[-156.6266498689285,-67.84100616478608],[-156.49473406629448,-67.82932392788662],[-156.44311566242752,-67.84336025085008],[-156.38176024303644,-67.84440262155957],[-156.3091517098967,-67.83338659167964],[-156.26964866948308,-67.81439509278526]]],[[[-140.72022572763942,-67.17680948596019],[-140.75095905366348,-67.18615624883506],[-140.8306717213215,-67.15988366206417],[-140.92741669189888,-67.14224162320781],[-141.1247158389438,-67.13165822450657],[-141.38145010536567,-67.10310990426652],[-141.5156834001021,-67.09425737733547],[-141.57466739992486,-67.10750879061467],[-141.708462096183,-67.10522899656904],[-141.75229451705044,-67.11458107080695],[-141.82898474789914,-67.11185727537658],[-141.9305376483129,-67.1191697151773],[-142.02181113144943,-67.14504355645676],[-142.1111929972535,-67.1591785977009],[-142.15429171271984,-67.19728336781053],[-142.24358289618593,-67.21356551336322],[-142.27867246628625,-67.24387270301658],[-142.2858697275156,-67.28304491140804],[-142.2055468749776,-67.32891461131226],[-142.1984405898819,-67.39249150919235],[-142.239054779482,-67.41971293727053],[-142.33547195461895,-67.43191363950109],[-142.40316198565696,-67.46908821205086],[-142.4025871636655,-67.51579749404956],[-142.51194084311794,-67.5335754428005],[-142.57539432565005,-67.5248907826012],[-142.65809234780036,-67.53334554089976],[-142.7164711197469,-67.55804707089202],[-142.83491392697653,-67.55148002734491],[-142.92660448158406,-67.57327649519338],[-143.00737654177948,-67.57403248712842],[-143.07399078445687,-67.584235335135],[-143.22597387115198,-67.58740216310473],[-143.12811130942004,-67.53568867221529],[-143.10517140994267,-67.50191056517433],[-143.1218405213712,-67.47679660297294],[-143.17602474725834,-67.45350991240726],[-143.33938847479394,-67.43927892552628],[-143.39586059234102,-67.40249968742218],[-143.43992984716874,-67.38825237669586],[-143.55800771887613,-67.38102097016606],[-143.62566537960487,-67.36621662434331],[-143.77461390812573,-67.37444076319784],[-143.932305838838,-67.38822167001989],[-144.0228024365815,-67.41242948728551],[-144.15586232476073,-67.42981677737896],[-144.18278489901655,-67.45017963308129],[-144.2767959993236,-67.4771370041477],[-144.32136322252128,-67.50218969465847],[-144.3168840895045,-67.54390977621338],[-144.36431821368828,-67.55528814140277],[-144.66660145434707,-67.58756641121737],[-144.69330157800633,-67.60755644964884],[-144.7744349424123,-67.61407933226378],[-144.8774981638026,-67.64008244101137],[-144.97838160202505,-67.67258138647031],[-145.03394589685837,-67.70459628665998],[-145.06815418854904,-67.74193231160856],[-145.05672701989297,-67.76884509184694],[-145.11380636760273,-67.77956014059286],[-145.20968654622433,-67.7855427866739],[-145.29564317185088,-67.83036950442072],[-145.3958214024555,-67.83637460918585],[-145.47283843774844,-67.85171941742144],[-145.598089227528,-67.83449411799235],[-145.70474931816386,-67.8642225633186],[-145.72926696489645,-67.90035642675274],[-145.65970865315532,-67.94024342766478],[-145.82782719863664,-67.96166196168089],[-145.88244508299888,-67.98360448408299],[-145.90853074491523,-68.01020573982207],[-145.8742095451347,-68.04276150606216],[-145.7850739551213,-68.05823133757796],[-145.6772570794508,-68.05036202229934],[-145.51121872014198,-68.02334839615726],[-145.41750494199772,-68.02231042279402],[-145.1554244937403,-67.99183268008467],[-145.1127288132619,-67.9787590979776],[-144.990755292952,-67.99260853853572],[-144.79547156083183,-67.96084622951871],[-144.7080851735661,-67.92859000142202],[-144.65854834924903,-67.92455716411159],[-144.62195014981668,-67.94801890478216],[-144.5621893576151,-67.96662826677559],[-144.44844519174592,-67.96671312914769],[-144.3370663384156,-67.94943774141822],[-144.28381137987654,-67.92316781327757],[-144.29982579770277,-67.88316437852866],[-144.2629049475469,-67.8662830553366],[-144.26509356967915,-67.83428323828267],[-144.22747130185542,-67.83083979937284],[-144.12188412421816,-67.78960837641532],[-144.0867153266201,-67.79550888428048],[-143.98421238095173,-67.78483524207694],[-143.8969249558407,-67.73346739338152],[-143.8197626200526,-67.73293085429664],[-143.75480323331553,-67.72102507881498],[-143.7381858014444,-67.73979658589118],[-143.63022273868722,-67.76225072795874],[-143.52073033435553,-67.77681299359455],[-143.46691836676314,-67.79454330651268],[-143.24229941234887,-67.83150826247498],[-143.1285063821615,-67.83853049066687],[-143.01907863168972,-67.83560175196821],[-142.89701338746244,-67.81830935008246],[-142.74670701050337,-67.77256895420891],[-142.6295330763831,-67.77936050799806],[-142.5766400875552,-67.77247982647388],[-142.50611200486716,-67.7411516643535],[-142.32006230840042,-67.71537936829844],[-142.2049368765284,-67.7124031462856],[-142.14539168647968,-67.72253846988588],[-142.0485537259901,-67.71835175586097],[-141.98707137747743,-67.73021864507606],[-141.845990189421,-67.73460379951547],[-141.71009441080014,-67.72716315660709],[-141.54623446884275,-67.69418553034069],[-141.50031069627812,-67.67463132215353],[-141.43859149070286,-67.66963861049491],[-141.14414946322827,-67.57530636967638],[-141.08850368131837,-67.56723013548495],[-140.82070548352084,-67.54262228700289],[-140.70645242886133,-67.51687324928962],[-140.6549124983262,-67.49068318338311],[-140.48145391450507,-67.45859030582238],[-140.39323237649734,-67.41828250213341],[-140.3700390623495,-67.39353654684078],[-140.3944113526175,-67.32343954331795],[-140.2629210399703,-67.27967823717448],[-140.174284616103,-67.27806107632311],[-140.10095484747313,-67.24594125477635],[-140.04460274119708,-67.24948111449442],[-139.9016098896191,-67.24000501546715],[-139.85444645262118,-67.301428261118],[-139.7956191863631,-67.31476556387794],[-139.73058679343927,-67.31450453528072],[-139.51061838936914,-67.28595069699517],[-139.4126541491418,-67.32731426346201],[-139.36447775849228,-67.33050458305277],[-139.2258739720047,-67.30424401345947],[-138.99984811838183,-67.3229255193888],[-138.84213094898993,-67.3179301978839],[-138.77556568030656,-67.3076066169078],[-138.708726550432,-67.27301191155624],[-138.6546064956991,-67.22998655371275],[-138.58644832063507,-67.22937918505578],[-138.4851398024006,-67.20617394195092],[-138.4244932548074,-67.17498412078255],[-138.41295576562243,-67.12836935089386],[-138.5036760572666,-67.09504505017286],[-138.5694224948707,-67.08670625223186],[-138.73312891197372,-67.1034513156366],[-138.84124555748306,-67.13558872869059],[-138.9252282644957,-67.13603294604997],[-138.98811057231384,-67.16020714607484],[-139.10820600974918,-67.15190591217333],[-139.25205721356897,-67.16325127452947],[-139.21108375531608,-67.10971520549934],[-139.24723881770137,-67.08759371252296],[-139.3593694377861,-67.06580590039871],[-139.40587930556282,-67.02604788999368],[-139.41573265885106,-66.98726681075128],[-139.48711513849008,-66.96246345334642],[-139.60040682651797,-66.95282935016716],[-139.65614769262095,-66.956208946754],[-139.76396848024086,-66.94317919165721],[-139.828697579643,-66.95337510467762],[-139.9153967012838,-66.99028478610052],[-139.98033220451674,-67.0428883744041],[-140.0629037788208,-67.01031099231159],[-140.19902765410208,-66.98209980315825],[-140.3450801006788,-66.987571424226],[-140.40411455923345,-67.00418283000838],[-140.48669666817293,-67.01486113353573],[-140.56331888566072,-67.03710090589065],[-140.6392650443331,-67.09046672799676],[-140.65375898268113,-67.12102034599657],[-140.59050850564438,-67.17125294700075],[-140.72022572763942,-67.17680948596019]]],[[[-123.72069368014832,-64.85199033324429],[-123.65844223474795,-64.89272674213218],[-123.55672414834196,-64.90579920516097],[-123.4722351235464,-64.9242866859582],[-123.40148349685045,-64.95412967794195],[-123.39944594958453,-65.02326252527092],[-123.17175224904481,-65.13943641779305],[-123.20135653687925,-65.16841703086192],[-123.19799145557432,-65.19496793662731],[-123.36746949124644,-65.23035708707235],[-123.44247202545783,-65.21114700451918],[-123.56901788778512,-65.20325519413922],[-123.69274070023256,-65.22674480822798],[-123.79129475711154,-65.26637027744181],[-123.91503338602266,-65.30556559468613],[-123.99064769094221,-65.34277332741375],[-124.1053108020225,-65.42457366473259],[-124.10500187680816,-65.44730715921118],[-124.06471293410479,-65.48803791451243],[-123.97160846170296,-65.51399345780534],[-123.87616723055034,-65.51866358017536],[-123.7416867974603,-65.4872210752662],[-123.67609463962545,-65.44518140488903],[-123.56007591995976,-65.41544446969979],[-123.4974803769424,-65.42868061811575],[-123.42940884437455,-65.43049671060821],[-123.34285146031304,-65.40704676291153],[-123.25281609792677,-65.42571442243522],[-123.1957676504161,-65.42908439544557],[-123.0850914214168,-65.41197486622175],[-122.95365424078611,-65.42414681609382],[-122.78615328168779,-65.41891450137739],[-122.66592224056161,-65.38491149302669],[-122.60795973229934,-65.36055046233706],[-122.54763576388048,-65.31101176817243],[-122.55135778617557,-65.27825650498033],[-122.58714965820339,-65.25525980975627],[-122.65744305818801,-65.24438719914173],[-122.84940077623196,-65.25659277919023],[-122.855168315663,-65.23893482366088],[-122.76419439904609,-65.2214406627088],[-122.72342240027922,-65.19081815064573],[-122.60082766115026,-65.17156713248234],[-122.55908468096658,-65.14851846392379],[-122.5289866669115,-65.11350559193316],[-122.54685385410535,-65.08042422045793],[-122.49045807461657,-65.07797086021327],[-122.41823969037802,-65.06081899643154],[-122.34941909433174,-65.01891641078849],[-122.25328027247738,-65.01022534353636],[-122.19465230778597,-64.99492096658506],[-122.12694467106094,-64.94847224126316],[-122.15414560028302,-64.90722241392068],[-122.11228844466862,-64.88093185357164],[-122.06492230754118,-64.89475839461245],[-121.94983390949561,-64.90472899098323],[-121.8878014663785,-64.9025486692211],[-121.76150148725857,-64.88286674255397],[-121.66744752956863,-64.82823270018548],[-121.6519310696534,-64.7975415148163],[-121.49804744578253,-64.76872148387386],[-121.43576589926184,-64.76860801114145],[-121.29639773038201,-64.73745126883794],[-121.22173935444567,-64.68660043290551],[-121.23465402552532,-64.63205836401349],[-121.3037778116123,-64.60586498647545],[-121.0897855589046,-64.59351049001154],[-121.04459325464813,-64.5844161339855],[-120.90663007385136,-64.51282050314607],[-120.89612273899571,-64.48407652992405],[-120.74741910179952,-64.42090208515006],[-120.7009032253871,-64.47340496578002],[-120.60815447880213,-64.49055549066594],[-120.53260410259088,-64.48449211484835],[-120.43768070224137,-64.45128821164768],[-120.40378532983733,-64.42146276010857],[-120.40128297637291,-64.39391218692859],[-120.48387624490441,-64.35983049762058],[-120.49433252184103,-64.31975472278936],[-120.2886239481149,-64.21297420851036],[-120.24951759154531,-64.14983332446242],[-120.25647625893751,-64.12294900270594],[-120.3186954939448,-64.08767141625377],[-120.3864990928543,-63.9508932775076],[-120.43775251688757,-63.917148748080294],[-120.56561028620776,-63.8904051211645],[-120.52187327110573,-63.82188456700496],[-120.5845615124255,-63.777364838083294],[-120.65238687600285,-63.76876538124892],[-120.6708065841177,-63.74719112665857],[-120.78487646861873,-63.70706033193216],[-120.77277702156867,-63.68177693883371],[-120.66809087352256,-63.693202694824734],[-120.584803253858,-63.663996114381625],[-120.410773729278,-63.64114584097559],[-120.28868299727297,-63.61758614606858],[-120.20261216810651,-63.57140436648623],[-120.12444294665988,-63.609314414486015],[-120.06193171273459,-63.61578119688326],[-119.93660566338677,-63.58495334114972],[-119.86563691120581,-63.52674468227011],[-119.84864796201539,-63.473577688493684],[-119.87069336122782,-63.43429222611092],[-119.9188783286033,-63.40492221702859],[-119.8789371245194,-63.36616003462909],[-119.8898207879053,-63.31838811768951],[-119.94072837744174,-63.29589321848244],[-119.9992282120887,-63.29008360581197],[-120.07608015493554,-63.30151442831788],[-120.12754981429397,-63.32447861983656],[-120.17738890176298,-63.303841377124456],[-120.29892908778729,-63.28078197772356],[-120.3630030260428,-63.250731492649145],[-120.43455455654306,-63.2408671992663],[-120.35309191458298,-63.181940408281086],[-120.3609775008481,-63.15271278410178],[-120.4257807804575,-63.114026416033646],[-120.51659201306586,-63.101219833192864],[-120.52817357218197,-63.04884376712346],[-120.607799647729,-62.981047813642625],[-120.69293634778668,-62.95959168795758],[-120.78170489830295,-62.94826646141186],[-120.7986493621301,-62.926575013459264],[-120.90423718558793,-62.89712914951925],[-120.99631460506635,-62.91497537696062],[-121.07686499860179,-62.9729394373913],[-121.1889961707422,-63.02361085560216],[-121.27560287389974,-63.09479064658827],[-121.28370356109195,-63.13698979543717],[-121.34125987666567,-63.14508651921163],[-121.3948951716287,-63.171104750961035],[-121.41845074606071,-63.20547801637582],[-121.47160333887776,-63.225750475666736],[-121.72815294811997,-63.38720632951582],[-121.82677824466204,-63.45944495966951],[-121.94109665367142,-63.50556040738854],[-121.97494069214484,-63.54203445894945],[-122.04386553382842,-63.557645071745036],[-122.12615578731804,-63.61490652394849],[-122.17726380961838,-63.636783678308156],[-122.21607664855183,-63.669309945636805],[-122.22143466732551,-63.71286794114979],[-122.18863862075912,-63.736574512777096],[-122.26589790518759,-63.74954321265236],[-122.39122152800795,-63.789731122105074],[-122.5368227209431,-63.82469395338397],[-122.59180290881125,-63.849396777843225],[-122.70284549388111,-63.83730671169843],[-122.76435563273384,-63.85083882735956],[-122.79908624758401,-63.88492495424717],[-123.01444882394424,-63.99169807049035],[-123.04442554485018,-64.01133072005527],[-123.19315103093052,-64.07164392722],[-123.22796108407326,-64.07105495384263],[-123.31268236446213,-64.0958097283448],[-123.41554044517538,-64.09846214982657],[-123.51498132006121,-64.16226233863327],[-123.63757123360574,-64.17412865024333],[-123.68350578634417,-64.19285341578224],[-123.70862981281223,-64.22581002001856],[-123.81353650925108,-64.2589584791671],[-123.87134431160993,-64.29661674776086],[-123.88141707689539,-64.31819521639498],[-123.93220739076371,-64.34587392594442],[-123.96750366659698,-64.39834394424966],[-124.0667493343647,-64.43116513231959],[-124.10400016416709,-64.4655463006286],[-124.12741142282167,-64.5381450584024],[-124.10480449059341,-64.55510067340226],[-123.99426651913471,-64.59450714861799],[-123.90844440303346,-64.61227231943535],[-123.7987858386984,-64.61309241363128],[-123.79110352503369,-64.63043732870962],[-123.71100734144018,-64.6690531239907],[-123.68867255258372,-64.80369183531367],[-123.72069368014832,-64.85199033324429]]],[[[-93.63302348917087,-62.821231744723384],[-93.67532056181636,-62.63409322815795],[-94.12136800012522,-62.65032603291262],[-94.06563064618526,-62.49506385136543],[-94.32008602059118,-62.372268673551],[-94.68687444976342,-62.23904067390119],[-94.90380162295352,-62.13051311353796],[-95.0960864656854,-62.05469882099991],[-95.34615961797566,-61.9227834846231],[-95.72860869478612,-61.786968819265475],[-95.97024761249916,-61.63651848095553],[-96.27183935466437,-61.53163124531215],[-96.50601891257084,-61.42351064494674],[-96.43711841541428,-61.359568138852964],[-96.73223120884316,-61.28564989161474],[-96.9695544353814,-61.21442631209753],[-97.23373420702167,-61.10159055242402],[-97.45539862305677,-61.07591311627753],[-97.83520269989009,-61.02672150922166],[-97.9767197330135,-60.96952504439318],[-97.97801978821632,-60.91927934991468],[-98.10654808675268,-60.91669759162879],[-98.12330387533243,-60.84431858316265],[-98.23470198517339,-60.79947681732258],[-98.3151729638429,-60.753783778140345],[-98.41974106710208,-60.76894241114262],[-98.60557802405104,-60.71622131521399],[-98.6714713125523,-60.65289058478244],[-99.01106361982511,-60.660620197868326],[-99.13896253396307,-60.59582053716288],[-99.12518555705962,-60.539370645387876],[-99.28061724516068,-60.510428709259614],[-99.26612683869863,-60.47551196353162],[-99.22672178304593,-60.44232352501206],[-99.2909642628834,-60.33674991757927],[-99.43831571893334,-60.280886122234605],[-99.48605482022356,-60.285538695979156],[-99.58740014299552,-60.24945075277379],[-99.66121649530793,-60.21215814844318],[-99.9983024721458,-60.132977427264485],[-100.37892617104457,-60.32590869182872],[-100.47094973575265,-60.37473128812229],[-100.7510229794184,-60.44375732685764],[-100.91870277752389,-60.518231283502075],[-100.75426808060678,-60.54801153513125],[-100.7279635181414,-60.58788426933927],[-100.77025022146488,-60.670775731699536],[-100.83765963309796,-60.700269074346345],[-100.9806549099767,-60.69867258287714],[-100.85383114182486,-60.90979067462029],[-100.91702634370635,-61.068243773707216],[-101.21485647969175,-61.11478392154958],[-101.24806943016846,-61.1439434812562],[-101.65802642193877,-61.3648413114857],[-101.82733933215447,-61.50038403331455],[-101.92411689782573,-61.51540917006643],[-101.93861599942412,-61.418568278360134],[-102.22697140983931,-61.13519863323379],[-103.22044870323357,-60.83251668232413],[-103.78686265107469,-60.658488877387974],[-103.86261908111172,-60.61274692636715],[-103.96567896439197,-60.50250111121763],[-104.09035253002376,-60.49346175378645],[-104.41279346154855,-60.44779273595849],[-104.67111286084092,-60.353722358721505],[-104.65843359778265,-60.05454820992114],[-104.68262212845188,-60.01386996861868],[-105.40771309778552,-60.11684740052473],[-105.67605466201053,-60.34325908161859],[-106.32427729450994,-60.251756382564004],[-106.3564632283167,-60.36450966882356],[-106.96147132935785,-60.3213666768987],[-107.05665528910416,-60.08288180212039],[-107.25773091220869,-59.573323897294465],[-107.40301439062591,-59.20025417526965],[-107.57413404287796,-58.755625236891824],[-107.80533612109419,-58.145758106376135],[-108.04840468900383,-57.4929281579828],[-108.26342883087413,-56.90548666038567],[-108.44186579755898,-56.410924492522504],[-108.55467470562442,-56.09485835702866],[-108.76621500159388,-55.4951045585693],[-108.91413239863932,-55.07022836003019],[-109.11934431851122,-54.473227584694705],[-109.27116859579968,-54.0259179197149],[-109.39124673078668,-53.66874227730796],[-109.54296895919256,-53.21300550305859],[-109.73206758548525,-52.638240942537806],[-109.87214542237429,-52.2074870859531],[-110.10575901400837,-51.47972240978511],[-110.23013516719136,-51.087398975707586],[-110.40811203302904,-50.520154668195325],[-110.54063158384795,-50.09325875185044],[-110.66464588641601,-49.69031257525565],[-110.78813729812016,-49.28565148536907],[-110.97201939323232,-48.67684601895657],[-111.09557775795112,-48.26358542930743],[-111.23084766645447,-47.80732311439721],[-111.34439361385158,-47.42117569870127],[-111.51644926824027,-46.83082577908407],[-111.63965141271271,-46.81021077173408],[-111.85909617835735,-46.807176377946476],[-112.07542186996062,-46.71471671702448],[-112.22364422019375,-46.67609731159816],[-112.37853183079834,-46.654992673356205],[-112.53631433838954,-46.612275686936854],[-112.8773404039807,-46.47751638943645],[-112.93379590032856,-46.4458806028634],[-113.14946997193485,-46.37844619872536],[-113.4492696073456,-46.305266421232936],[-113.58525613112562,-46.24815277415832],[-113.63838518954998,-46.23956655242656],[-113.75035129187393,-46.261905076721746],[-113.78845716142769,-46.28804584974727],[-113.92327366112922,-46.266366166590245],[-113.99189541495079,-46.27585065211913],[-114.01971493953437,-46.30040124296122],[-114.15585965427178,-46.339527312638154],[-114.21175746331907,-46.33961720735867],[-114.28825609355636,-46.36793885407286],[-114.3275522491458,-46.420026325153266],[-114.37093659496192,-46.42628573546393],[-114.45907674146142,-46.47697531254049],[-114.57947131553877,-46.5276887977531],[-114.66716742390349,-46.53939040221476],[-114.76435310829359,-46.61753365486082],[-114.81019224864941,-46.683243223413285],[-114.98155653350082,-46.65143047447139],[-115.11964338607794,-46.687066609411396],[-115.17816153450406,-46.61628653009724],[-115.24081920699008,-46.59277828952915],[-115.36623945384336,-46.6030348304994],[-115.56256972473471,-46.53622774863361],[-115.62162777032034,-46.53416173715575],[-115.85763355096209,-46.437818507108595],[-115.99719355022376,-46.399613326182276],[-116.19241984956882,-46.39351628209842],[-116.37350407649481,-46.354075884107694],[-116.40781757967045,-46.365244499104385],[-116.56914415049792,-46.36651318347384],[-116.63839703190806,-46.37813683979091],[-116.78454919031346,-46.286523785999485],[-116.89891595257703,-46.26717309642218],[-117.03849551917642,-46.30672390022235],[-117.06277247544617,-46.25917679292588],[-117.30335365605241,-46.153942619933886],[-117.43610722265473,-46.18169037833629],[-117.47721352913928,-46.14348539794934],[-117.58761743615327,-46.13001447689743],[-117.6241702718435,-46.06261989045473],[-117.71064790989455,-46.02359277577984],[-117.82023178393368,-46.03560864266048],[-117.8907679691307,-46.10123208525045],[-118.02680922942184,-46.05978940176171],[-118.12297235429438,-46.06316510600337],[-118.1844177710907,-46.09051864024709],[-118.21426819006922,-46.14062219298954],[-118.20873372502561,-46.19026328119315],[-118.33511160903882,-46.16337516945552],[-118.35538645270162,-46.13549885982747],[-118.45217622973644,-46.08860047914798],[-118.6111214687086,-46.086909703403045],[-118.67368974902804,-46.050653063622796],[-119.00402895256147,-45.978158445216955],[-119.23704746062761,-45.95908340839898],[-119.32076359131389,-45.98538954749193],[-119.35943907753014,-45.97750318420615],[-119.4535665670409,-45.993182890397314],[-119.49789966175433,-46.01792239651035],[-119.58716861189374,-45.99134841751418],[-119.66249693468836,-45.99447608119379],[-119.73111741607825,-46.01545231394234],[-119.7777545330666,-46.09229891632828],[-119.84453654119086,-46.08896735969233],[-119.89862075631729,-46.10830534188324],[-120.11835181804666,-46.114664373462894],[-120.22403132679106,-46.14473247634332],[-120.2629237429546,-46.183167375748766],[-120.3700393788062,-46.215962322920205],[-120.46863100824804,-46.223543868017074],[-120.53216027847904,-46.2541069054022],[-120.68491858134942,-46.26094539889579],[-120.78673607294876,-46.24355642180354],[-120.75433727546267,-46.126949482120466],[-120.80771979953451,-46.0761812118865],[-120.85607795354647,-46.06418489999731],[-121.23474132215014,-45.69636221383884],[-121.24734502679722,-45.64805600779209],[-121.30621088200594,-45.59148000497481],[-121.37009696549173,-45.561269975446635],[-121.5193817013122,-45.52381620008958],[-121.60438477522119,-45.490672223301694],[-121.75321606535427,-45.48698513211273],[-121.89382313553763,-45.43931800210264],[-121.9697442717044,-45.44564646230902],[-122.02849279877258,-45.42753331348803],[-122.07909307544267,-45.44696556050325],[-122.14390274693797,-45.43508010167497],[-122.24355948086972,-45.440122913348354],[-122.45216361374436,-45.516614009224945],[-122.66508079081873,-45.50217014504628],[-122.79085698607805,-45.52085487301738],[-123.14509365882589,-45.39208650191026],[-123.1835169609357,-45.383761413963576],[-123.34644929475022,-45.26817991713279],[-123.5099491551186,-45.14332058830912],[-123.62526295150424,-45.08032529344119],[-123.77658634318439,-45.01300341907814],[-123.94354840211543,-44.98197444367584],[-124.05381422878948,-44.92810954141701],[-124.23545933196601,-44.893026704669126],[-124.31842186390065,-44.901353526384156],[-124.38431100610941,-44.80841965609725],[-124.42300856959682,-44.77757738278679],[-124.4928093721215,-44.760937902706495],[-124.67026789081063,-44.700583758850165],[-124.82131768347402,-44.6280616325801],[-124.87975866510935,-44.61624752562233],[-124.9897605261802,-44.64429710536029],[-125.02604113655669,-44.687915458575596],[-125.15490280289774,-44.72537273810092],[-125.28879407082671,-44.82782194863002],[-125.45013293030094,-44.98576695328147],[-125.65562132565194,-45.19516121750826],[-125.67807078339077,-45.227806474258855],[-125.82758851034215,-45.34799341391008],[-126.07670871103913,-45.47602865452794],[-126.21402462749438,-45.519625302055466],[-126.34404952674213,-45.54725172707208],[-126.4248783221625,-45.549245711735075],[-126.58174157728149,-45.51667688762285],[-126.65752503950604,-45.481350785247486],[-126.70460694035366,-45.4010765786104],[-126.77422343776428,-45.372256236522446],[-126.95026058683257,-45.37685661106302],[-127.01439676561932,-45.39258775421731],[-127.18736040465774,-45.405777472287646],[-127.51114830478394,-45.47961100170547],[-127.65074504212876,-45.520432852251105],[-127.82445279857791,-45.64390908278381],[-127.99625663962587,-45.7499677709846],[-128.12988681720125,-45.84908004887474],[-128.37414211321763,-46.007221143094085],[-128.59519504580751,-46.12554406837409],[-128.81725862871215,-46.20494677833786],[-129.00397657479428,-46.261759737515646],[-129.09264031618554,-46.2720545637728],[-129.2301384596311,-46.26486327362362],[-129.3310183324936,-46.246013780612806],[-129.5865949480307,-46.18036910844476],[-129.6993233773617,-46.208022557204536],[-129.90018784461702,-46.29249890041401],[-130.07652379768695,-46.38128553654798],[-130.24480768826564,-46.484032932022856],[-130.587716735958,-46.79696165002718],[-130.74437524044865,-46.90740812369111],[-130.88968151273252,-47.04053063616986],[-131.03588042775107,-47.306798050923874],[-131.095293879767,-47.61041560210435],[-131.13241026049386,-47.70587823553949],[-131.2098727109864,-47.81057530154345],[-131.4723569338653,-47.98461957623595],[-131.5238503567188,-48.04249519455056],[-131.63598408040002,-48.11662426037172],[-131.81124697139492,-48.1848529908968],[-131.88031911073836,-48.220939112829896],[-132.10601207768806,-48.29346513907353],[-132.16227849222108,-48.3049969415557],[-132.4629976617884,-48.29188102203949],[-132.61298487770586,-48.30468870877354],[-132.78344469158816,-48.296566928041955],[-132.81785213434577,-48.284473771491776],[-133.0824307493581,-48.296654515018226],[-133.27139999986503,-48.28819034541382],[-133.42108909282666,-48.268134342639264],[-133.55261422766986,-48.24019776397477],[-133.8109272266874,-48.1970126521808],[-133.8752488518695,-48.177991151516636],[-134.00505696778063,-48.195259163701124],[-134.07268530272714,-48.28281654150691],[-134.06925297683617,-48.42258273326528],[-134.11620624597072,-48.52997466411371],[-134.11317119384833,-48.55334844738023],[-134.17140406241447,-48.61946423096087],[-134.20668838571618,-48.744161886235254],[-134.27176319560908,-48.790690073001244],[-134.31786057532898,-48.887890151808875],[-134.41886002849589,-48.920168986515804],[-134.6430073393043,-48.94756731830383],[-134.83306730484378,-49.01574513231893],[-134.87791633349406,-49.05301192703704],[-134.87806666876367,-49.09169083669747],[-134.82284988059763,-49.13748740709044],[-134.7733786596231,-49.15284672668721],[-134.69071353431886,-49.14328882138763],[-134.61258713769368,-49.15783642487043],[-134.49697856755733,-49.195367805759545],[-134.3902143550343,-49.250503845801525],[-134.28312889550244,-49.325790046295374],[-134.18707378883457,-49.409621328136026],[-134.12825862909267,-49.55401945070659],[-134.0550616626715,-49.60882040972549],[-133.94515305517686,-49.66414535720384],[-133.8220897477241,-49.6872060011053],[-133.7897846550944,-49.66884094616549],[-133.6529636608448,-49.71687387827708],[-133.42803468144348,-49.92642844768245],[-133.0839781902855,-50.22666186786727],[-132.99788599698437,-50.31468348780965],[-132.90977154069748,-50.37255094505593],[-132.684014004993,-50.50775080645516],[-132.60050195993045,-50.586281983437075],[-132.49415066780202,-50.834699506048764],[-132.44502797914436,-51.128365779753736],[-132.52864353512157,-51.14927332541784],[-132.5763569095692,-51.182769054105066],[-132.57448206522244,-51.22178127732433],[-132.51349001632187,-51.29544174666189],[-132.40084792247836,-51.36078779550038],[-132.24993684333984,-51.4075987455672],[-131.98950309427156,-51.4666337374796],[-131.72051257998712,-51.5513374427994],[-131.56562228997817,-51.62483914153219],[-131.52892544164044,-51.660596619176054],[-131.94007085502923,-51.85044041506972],[-132.16130389576705,-51.94257401993113],[-132.41065491551868,-52.05372401928461],[-132.58033068793372,-52.01187265375514],[-132.8323068487201,-51.98758684242732],[-133.19271584461592,-51.97114600885176],[-133.5293165278823,-51.9962705552724],[-133.66365204140143,-52.01359612469308],[-134.24924450331088,-52.103669429630024],[-134.62142330743987,-52.18411516251318],[-134.97007122776205,-52.271409565272066],[-135.22010573099692,-52.342189280970445],[-135.400343201185,-52.383302931253986],[-135.66782623700732,-52.45078908794355],[-136.00106660309348,-52.56031255894429],[-136.19638846018688,-52.6166295548488],[-136.3533903038026,-52.682575891954485],[-136.5989893164146,-52.75407261746703],[-136.8192662591737,-52.79293059871485],[-137.0004662193133,-52.81856798078029],[-137.21939280142226,-52.86296456505788],[-137.31230713784163,-52.89355659707828],[-137.40680421260726,-52.9625564632194],[-137.44549146338306,-53.03870185987711],[-137.4461599996293,-53.079172732591196],[-137.42060308090382,-53.15788745194106],[-137.38782578970043,-53.18718471652657],[-137.30212097554994,-53.230465741167194],[-137.21283455637854,-53.25643778062113],[-137.18313907375938,-53.29560803930265],[-137.05601304974354,-53.35650613612242],[-136.93449685168437,-53.46522132273879],[-136.74422993351686,-53.546663550351006],[-136.53492636655466,-53.58646465048188],[-136.41622649928044,-53.615884115091795],[-136.30151382006477,-53.65891421154571],[-136.377705269902,-53.69707559253591],[-136.48070830068863,-53.800618392006456],[-136.51447281911982,-53.86286039987727],[-136.4924219442938,-53.93510222307458],[-136.44785078153183,-53.99994427718148],[-136.36273862578793,-54.06876850999618],[-136.31318735116352,-54.09310992824315],[-136.32657260644967,-54.11863351887993],[-136.2741644231542,-54.177992396348635],[-136.12096907945892,-54.27735992734912],[-136.06924277720333,-54.33829718694605],[-136.06995860968573,-54.35854782602813],[-136.13641906882302,-54.38664285194577],[-136.168697529661,-54.43032456254739],[-136.20168787721965,-54.51903838199646],[-136.16589018001227,-54.68149071205819],[-136.03145209626098,-54.78902572870738],[-135.96771646278256,-54.82987766195607],[-136.06190482249485,-54.87606714724293],[-136.07220687930945,-54.916713285641045],[-136.00698571988744,-54.982293818864605],[-135.88420669968454,-55.00240308463292],[-135.81049079876684,-54.971092297874385],[-135.77052445548597,-54.931023377743124],[-135.7641397340638,-54.88744404077061],[-135.53564894334355,-54.95016040642421],[-135.36086906330576,-54.98304290067503],[-135.15633360892372,-55.04145664800234],[-135.01260171045504,-55.0750169255108],[-134.71373333761048,-55.18584758400738],[-134.46596586731184,-55.190405252134084],[-134.41067408655934,-55.18579677842],[-134.07273983603474,-55.106916491383615],[-133.94668400602012,-55.08273486298686],[-133.8484317685854,-55.07722517663044],[-133.75737774613592,-55.08352385348079],[-133.54444455225067,-55.118429310989846],[-133.40716017008793,-55.114222476367814],[-133.36386420944496,-55.10088098977218],[-133.30051680121548,-55.12293877988643],[-133.20047361456236,-55.12803543588635],[-133.07877895769792,-55.16464759560718],[-133.0030207761922,-55.201823904381996],[-132.9158523279851,-55.26140915328251],[-132.88600439714386,-55.31810872999533],[-132.81548828239605,-55.3913595560654],[-132.7378532394202,-55.41309643814134],[-132.41378407733788,-55.53350924934543],[-132.3665427477113,-55.5477771522347],[-132.28999855422668,-55.536699764198204],[-132.17334899202578,-55.48191502638171],[-132.1305681359781,-55.47577682281105],[-132.03335002346802,-55.41649794983034],[-131.97261722469605,-55.29777531628047],[-131.9143104971865,-55.25435721535514],[-131.76547976664884,-55.20957924806012],[-131.69021885198245,-55.24142977886132],[-131.48613678567628,-55.34609482763696],[-131.45933987550964,-55.35156077662873],[-131.05517837454937,-55.55447056093535],[-130.9526564228825,-55.571273044787404],[-130.8588604915933,-55.53427430561601],[-130.77219423035814,-55.5746933297236],[-130.53608879861406,-55.769774084682936],[-130.56323392199036,-55.93986310913705],[-130.5527636817157,-56.0284183473752],[-130.52462954575032,-56.09071954394742],[-130.46941518026227,-56.17488787952658],[-130.45876670988494,-56.261898335005576],[-130.4273084784126,-56.33008875499699],[-130.4445934391916,-56.38591144881307],[-130.62205830861166,-56.51405913931045],[-130.7074043013894,-56.530762837301296],[-130.76176731050964,-56.570407210048934],[-130.77024673760747,-56.599070022084575],[-130.8403538603577,-56.61816793430774],[-130.93091325583148,-56.69182461576585],[-130.9827983094919,-56.70383419195181],[-131.1050936406346,-56.700678048006345],[-131.20078939094037,-56.689703256217676],[-131.30585563790316,-56.70466817936299],[-131.36591400297254,-56.68671136620869],[-131.57101128991167,-56.70269498291062],[-131.6281912318435,-56.67829503446872],[-131.6805711612501,-56.606961691661354],[-131.96098896318685,-56.53291461948848],[-131.98066542005844,-56.462603432970404],[-132.027269667283,-56.423756782718556],[-132.12082108906557,-56.39450562274125],[-132.21684550863662,-56.3923185754173],[-132.32792423249245,-56.37545755625226],[-132.45282857495968,-56.399461148035215],[-132.57115401765668,-56.45173266229894],[-132.63115863547893,-56.513802403811944],[-132.58767693126072,-56.58185276289155],[-132.4976499614908,-56.64134591997093],[-132.36021773295514,-56.66372566170502],[-132.23061900316665,-56.65985943259844],[-132.5857253993647,-56.912194197331196],[-132.6365705505558,-56.939182225793104],[-132.78754535326493,-56.949361706694795],[-132.87470224912164,-56.99588918162037],[-132.9103184729241,-57.04593358712184],[-132.99400424844296,-57.071771212170106],[-133.1148720538197,-57.133981786134086],[-133.2673137752058,-57.13495219013691],[-133.36496361532525,-57.08460315021181],[-133.46416928294252,-57.00640128782621],[-133.59715861618605,-56.95343122975246],[-133.6627615539664,-56.887583993167254],[-133.77918643178285,-56.84154801707889],[-133.88564718697853,-56.82179805253674],[-133.94870547743201,-56.79452061503],[-134.07313334759738,-56.76035706374676],[-134.15976304356167,-56.755935330019504],[-134.2982327665074,-56.760646823404926],[-134.46818048809027,-56.778757699186386],[-134.59033904929635,-56.8080235936431],[-134.73664563183192,-56.86679461810377],[-134.92503842247874,-56.97346668701362],[-134.92860393892448,-56.99687243161954],[-135.02927898211428,-57.00305742123958],[-135.12956532699667,-57.045627561275516],[-135.15015361807465,-57.098586520616834],[-135.15644638015272,-57.175843759030116],[-135.23450341119178,-57.266547704570534],[-135.30312497039418,-57.275367530963166],[-135.3687471657386,-57.332423521691865],[-135.3598856553265,-57.382811799398134],[-135.37717091322168,-57.41984981216255],[-135.43262494810182,-57.44522701098851],[-135.50072817471343,-57.430113518433714],[-135.61612077145645,-57.4442394838989],[-135.67065238304548,-57.480579600114474],[-135.67297769507707,-57.5596514181382],[-135.80975513022065,-57.62610764219959],[-135.83926100612968,-57.69046090580136],[-135.89428773076517,-57.72575558443868],[-135.90139957123552,-57.76220645070455],[-135.95827837338692,-57.79648847937982],[-136.0136996275188,-57.85823689315144],[-136.11745694685206,-57.89940478003036],[-136.25282565028584,-57.969996981598015],[-136.32503790896772,-58.01845635435152],[-136.42588940145606,-58.070837192115356],[-136.49274744949085,-58.08405726651856],[-136.53557436925755,-58.11239730533038],[-136.63041726447216,-58.14109633573974],[-136.64939643194003,-58.164638188757294],[-136.7665329084922,-58.147793481341125],[-136.82322689786054,-58.16303793266338],[-136.9427163409002,-58.244455301687175],[-136.94703222924613,-58.28292780201232],[-136.91045129726265,-58.322136166630315],[-136.94868801464014,-58.367091946504864],[-137.00241530912902,-58.459997959857375],[-137.11852697516053,-58.50053900739079],[-137.18912013269934,-58.56512923464949],[-137.20780366189248,-58.61604786416749],[-137.257697996203,-58.67432828048818],[-137.27140068854936,-58.71873903988857],[-137.34025118546592,-58.81504722976308],[-137.32699733926037,-58.89353067395061],[-137.28603332630328,-58.97951041382299],[-137.12444609852628,-59.07950903779108],[-137.13358024902047,-59.100715540013034],[-137.11200085638188,-59.183749810835394],[-137.0362436857228,-59.26832420916983],[-136.96699315330176,-59.298419207519956],[-136.92525792896987,-59.35498841914954],[-136.90610503944274,-59.451226215291875],[-136.8413075275642,-59.5441424073235],[-136.70673478834092,-59.59616390835946],[-136.56792856593174,-59.62682380209636],[-136.48344364672815,-59.630408064186156],[-136.4084958083393,-59.61298889296643],[-136.38014205097593,-59.63157984894319],[-136.4401375577768,-59.66655534302424],[-136.45144099856998,-59.69612895869013],[-136.4232106894392,-59.754085285819166],[-136.3873390479912,-59.79132490807064],[-136.3323713643421,-59.82098962692184],[-136.35284426994224,-59.85623268942818],[-136.3367971702334,-59.8814209715931],[-136.37294971144388,-59.92037596488548],[-136.334709405633,-59.99958614127777],[-136.3958442191005,-60.03118955594016],[-136.47037798760476,-60.04047532788127],[-136.50760520030525,-60.06315313864025],[-136.56620886754084,-60.07014960292696],[-136.63645059512712,-60.09871263559077],[-136.7694244375382,-60.10592337925101],[-136.88165373678936,-60.16901264151568],[-136.87861438227955,-60.21965662940224],[-136.8105195222903,-60.29092384109844],[-136.769768413459,-60.313172570926014],[-136.7046865655163,-60.31904373199935],[-136.6004396657708,-60.37081551296526],[-136.54792659217128,-60.43036699275272],[-136.47263976661384,-60.494965483587045],[-136.34254737009718,-60.57780614726276],[-136.20703058112875,-60.62378945713695],[-136.08591744699956,-60.64434050793324],[-136.00193705691598,-60.731549945566904],[-135.93407087715482,-60.75835984210904],[-135.8674784121565,-60.84562850643096],[-135.8413749710514,-60.89336988358994],[-135.79887322760825,-60.926819992257435],[-135.71115963480204,-60.972095928559774],[-135.7881471096191,-60.99220047799992],[-135.83399348119437,-61.019135347377066],[-135.81902101263955,-61.10434789817668],[-135.74797372831765,-61.15448005208403],[-135.72306469581704,-61.20780491432166],[-135.64194696396035,-61.27883414460667],[-135.52448505101773,-61.33233320645516],[-135.48725003778998,-61.39067688030099],[-135.41579428446917,-61.43561827202617],[-135.29844798916218,-61.44082381402932],[-135.21647194297606,-61.473983595185764],[-135.1013249735548,-61.45579265333155],[-135.06459714038547,-61.4233481380531],[-135.06920514094767,-61.40147858031664],[-135.13837895899158,-61.359526920373696],[-135.2424721459373,-61.35486122047578],[-135.30393473493842,-61.324564562402024],[-135.33094896613846,-61.29151428411296],[-135.09333572357804,-61.33403343993594],[-134.92241346573917,-61.342419758291335],[-134.83840916315492,-61.33854506584995],[-134.51954325503013,-61.33857968929313],[-134.2822015680981,-61.32298114451813],[-134.2383415944301,-61.34700782099959],[-134.14264351230025,-61.36628234810108],[-134.04515030567282,-61.37173832993068],[-133.94361034015932,-61.36365033683749],[-133.88405388983068,-61.34289503297884],[-133.86093732492736,-61.296306684427925],[-133.71099360770933,-61.25739544495839],[-133.69581892750085,-61.244204222235574],[-133.58318692588568,-61.25557576396991],[-133.52627459871692,-61.24877190492468],[-133.3906040533722,-61.27200801946248],[-133.39334815374465,-61.3210291986037],[-133.3624114869243,-61.348156464084184],[-133.3079636978005,-61.36647019224462],[-133.13991634701352,-61.3818666761364],[-133.07588883677803,-61.375583291377446],[-133.03946572996657,-61.42904329562887],[-132.96922963678318,-61.50065828793794],[-132.95104765498576,-61.55671170443929],[-132.90959378869667,-61.590441343166326],[-132.78062526177595,-61.74137610105141],[-132.85125372436636,-61.803637152784475],[-132.98738458317294,-61.840175085733264],[-133.04411216867763,-61.86448255408951],[-133.07297812511305,-61.92105000151805],[-133.14014804547242,-61.94937253641584],[-133.22724728878737,-62.01294842440728],[-133.23927519648188,-62.07216141140213],[-133.19062408146587,-62.18791179146921],[-133.13549637726368,-62.24948893977453],[-133.08018109526768,-62.26302192037743],[-133.0307524828815,-62.30983597234697],[-132.98247788397273,-62.37706448475943],[-132.96604213809613,-62.41708817735166],[-132.99177577365137,-62.51672487397203],[-132.96732503914905,-62.57566872582904],[-133.02292577811525,-62.60012994124849],[-133.04066130516108,-62.62498762896453],[-133.13490155542064,-62.661312828825174],[-133.30011335272184,-62.656150947092556],[-133.40328126292655,-62.66476437571889],[-133.54539793031748,-62.70613885273273],[-133.56137142831125,-62.75777202399592],[-133.51708627226049,-62.78753184909754],[-133.44440903317644,-62.80158880882973],[-133.399219603918,-62.82554842006201],[-133.23024310999838,-62.84682793633393],[-133.111361453415,-62.83992889306772],[-133.05664668521865,-62.85056754384041],[-133.05376822019485,-62.88974098116262],[-133.0132126072026,-62.92231024971183],[-132.9278271514026,-62.93802693212243],[-132.7852383075081,-62.93834083519056],[-132.71184306359996,-62.923720980393114],[-132.6846350127428,-62.896050164242865],[-132.57419582639918,-62.880939594979246],[-132.52612582751237,-62.855743750215936],[-132.38671926607955,-62.84260029817012],[-132.22191498467197,-62.805009166944274],[-132.15845654546837,-62.850036352739465],[-132.18013142120736,-62.88776889357512],[-132.16062218022304,-62.9585947193029],[-132.08502157095685,-62.98788982602519],[-131.97786423630052,-62.9781645784029],[-131.8734511898735,-62.99111875850242],[-131.79316248653726,-62.97895713768271],[-131.6889903694402,-62.93649389136222],[-131.6625568694108,-62.903175522891395],[-131.52714179191912,-62.805493194161734],[-131.37033353430493,-62.672877507417056],[-131.35285349542173,-62.64082927454219],[-131.36689924513684,-62.614842273229655],[-131.01778126965237,-62.58778799329382],[-131.01343033432792,-62.62529912995119],[-130.97886393191888,-62.68364997834644],[-130.90316930153068,-62.71601373632542],[-130.79672746060544,-62.72283643900112],[-130.70971928207786,-62.68694195275331],[-130.62436394371144,-62.74416179046263],[-130.614863958453,-62.76203193154727],[-130.5368981381113,-62.79597515050125],[-130.47934353507674,-62.80162489198634],[-130.36915327196476,-62.78260250155024],[-130.28193147488162,-62.757485085211506],[-130.2145150424267,-62.718581530215516],[-130.18254788139737,-62.7278069993376],[-130.01660138665656,-62.746986047015945],[-129.85962118264035,-62.88206927096941],[-129.5814111651091,-63.17724397732134],[-129.58633326011218,-63.22857378270252],[-129.5379992067536,-63.25600120212199],[-129.42444052787926,-63.275047198460626],[-129.35162534120371,-63.256423703399186],[-129.28336227021413,-63.269893948668226],[-129.1505703681837,-63.26948172095191],[-129.06215055230672,-63.25362241290152],[-128.98359419801687,-63.213214331117904],[-128.89574846648324,-63.19295828392918],[-128.86457675502595,-63.155911354585925],[-128.34128411989778,-63.07286473668986],[-128.25665515679674,-63.073943643715],[-128.15532997389877,-63.142917111667494],[-128.19998478005783,-63.21112416595244],[-128.32644672787134,-63.37803882044965],[-128.509964302317,-63.62899364151774],[-128.7627765842169,-63.94994705607278],[-128.8535443141968,-64.09281675767708],[-128.89003027677288,-64.12587358660504],[-128.92343348660637,-64.17884722431398],[-128.96808289818955,-64.22288124287992],[-129.1496177131652,-64.4325763547485],[-129.4708061302059,-64.74360910441645],[-129.51716257849222,-64.79825047427589],[-129.6430106448786,-64.83054456483487],[-129.78976861303698,-64.8878926957942],[-129.92267817939543,-64.92443919737804],[-130.17514232623554,-64.97623028164018],[-130.2801779395456,-64.99122878224277],[-130.39215320343655,-65.02697264967864],[-130.50571531422804,-65.05461426970274],[-130.82892941734767,-65.10912208829893],[-130.96889423994975,-65.13787874932306],[-131.10439209764726,-65.19791018674502],[-131.2154354570674,-65.23170596993212],[-131.44053330196078,-65.27204010829848],[-131.5657854757898,-65.33466364439592],[-131.7250585459523,-65.37802024945051],[-131.7667583973419,-65.40031486848768],[-131.84476532195927,-65.46950521406828],[-131.89240645503583,-65.49521289515314],[-131.95813470993278,-65.58379810213648],[-132.0201177228237,-65.6185077836785],[-132.0673874058575,-65.62896906962709],[-132.49840988513935,-65.59834855123954],[-132.65988298784555,-65.59958954910984],[-132.8911704884792,-65.59344086094096],[-133.20771856715612,-65.63695699103177],[-133.4526973339816,-65.6512672322926],[-133.6408116028957,-65.67530639206514],[-133.77785849031366,-65.68132544728186],[-133.89845623780553,-65.70697121661884],[-134.0181101450682,-65.75567335179741],[-134.11330804458035,-65.77923279863249],[-134.24629755214497,-65.796280152934],[-134.31017484304712,-65.8297081537283],[-134.48892505522807,-65.8707364082748],[-134.69457849227823,-65.9611135819993],[-134.8306675417939,-66.0091329609894],[-134.91055557817157,-66.05008499485098],[-135.00615490195557,-66.07670281971775],[-135.0605698874301,-66.08329855147947],[-135.12000897889396,-66.10598641363589],[-135.1851066440811,-66.10691049837867],[-135.3651615316741,-66.13286967234554],[-135.50278933548398,-66.168040901086],[-135.58534757680815,-66.21077088977097],[-135.8575088536784,-66.303426389975],[-135.94135465364644,-66.30870388659459],[-136.0677582307118,-66.32819927453068],[-136.32823917513764,-66.3793742680281],[-136.49224911264648,-66.39817065868888],[-136.5834311845889,-66.39985690336057],[-136.77503056933384,-66.37279793917422],[-136.9071518593043,-66.38498897342964],[-136.98291816923677,-66.37881735814007],[-137.04024440128217,-66.38726588354476],[-137.1995668168115,-66.43636331845867],[-137.3576851853608,-66.43172427741811],[-137.46456012890755,-66.45068888498119],[-137.59414498063163,-66.48184183372905],[-137.73987802433885,-66.4443269863866],[-137.88977954336832,-66.44534492156626],[-138.03946235727753,-66.48143793327581],[-138.081650005807,-66.50204001340401],[-138.12352943024803,-66.55630594159899],[-138.20779100542174,-66.59375938942988],[-138.22978339734536,-66.62050676481881],[-138.37182419255922,-66.6932662560353],[-138.44822780159691,-66.69266604856709],[-138.55342524730509,-66.70668080062917],[-138.7073908175504,-66.73676176287078],[-138.78019553525826,-66.78152047407205],[-138.79010064893438,-66.83648416046164],[-138.7713681726811,-66.90078162123642],[-138.6699099068895,-66.98925344262912],[-138.54392119153988,-67.02611853893868],[-138.45415568078053,-67.04001293401129],[-138.40155267080712,-67.03651716954833],[-138.27588935011102,-67.04970012221206],[-138.0767579010215,-67.03186665313879],[-137.97343986653766,-67.03903072769597],[-137.8647538525387,-67.01837419991097],[-137.7482458594463,-66.98540480915553],[-137.69817302057473,-66.95811140234377],[-137.6450574940947,-66.90620797337205],[-137.48203228462896,-66.8839332579044],[-137.38397422722173,-66.8823649043124],[-137.1237272405208,-66.891054749482],[-136.88973612882918,-66.90497024894573],[-136.72931475647417,-66.94288865430327],[-136.62891973386732,-66.93819794355659],[-136.58213013973634,-66.91085551205856],[-136.45077218809848,-66.9000821324619],[-136.17435279042914,-66.89868290637222],[-135.94034524595145,-66.92234648620573],[-135.8494195516393,-66.91011245483521],[-135.77275480667043,-66.87834461552495],[-135.77612351558503,-66.82976893521888],[-135.84497843441292,-66.77793372736201],[-135.9366814783579,-66.72108319192617],[-135.98542752964465,-66.70759546623673],[-135.93147045428344,-66.6846873241117],[-135.88513893528645,-66.67958850939586],[-135.8533612692109,-66.65569083629566],[-135.71656840360814,-66.66010129922985],[-135.62413412596402,-66.70321853204072],[-135.51767028517298,-66.69656380490899],[-135.46250539165277,-66.66735194522947],[-135.377357885154,-66.66489825203479],[-135.25279453305959,-66.64506068119819],[-135.18735719208567,-66.66135766095387],[-135.2318712631685,-66.72279904683886],[-135.1708695335436,-66.75585118756202],[-135.09102492297626,-66.76354537922829],[-135.05484682157922,-66.78400220371935],[-135.13148928423337,-66.79609091804555],[-135.17135797276464,-66.82108440279944],[-135.1625892976349,-66.86323267132954],[-135.12410220313103,-66.88940011868418],[-135.0617231881913,-66.90851333149833],[-135.11943117147626,-66.94913720345176],[-135.10966692981899,-66.97636854303136],[-135.13019762490003,-67.02869169281945],[-135.0669512349976,-67.06447817019074],[-134.975603293806,-67.06897915157093],[-134.93327166602916,-67.06115673625449],[-134.89190787152214,-67.03581381663072],[-134.8867433080276,-67.00984618939346],[-134.82920642151595,-67.00292519773356],[-134.7652986949853,-66.97231149863165],[-134.75437711645742,-66.95115238007581],[-134.67929143798105,-66.9539489743055],[-134.64572324848353,-66.96843550389852],[-134.56749632235338,-66.98028805686876],[-134.50516649285262,-66.97648803110752],[-134.4587370526047,-66.95942992599839],[-134.44580669675128,-66.9243251438321],[-134.46522462475562,-66.90307050076564],[-134.40714268464293,-66.89173504747826],[-134.34052871334558,-66.89605367244404],[-134.3019331960167,-66.91716076121048],[-134.16917779552466,-66.93393470193166],[-134.07910808714118,-66.91619950581715],[-134.04863041821235,-66.90015950135876],[-133.9657735490129,-66.88947466375758],[-133.8658794422497,-66.8969786698091],[-133.76824585279945,-66.87718169210936],[-133.7409884102004,-66.84238092583215],[-133.86345019435515,-66.75201692561508],[-133.9214523136607,-66.74069508433115],[-133.84913450255064,-66.70774606829612],[-133.83075382028161,-66.68048257179368],[-133.7316669388396,-66.67775477289608],[-133.64180662285352,-66.6938364945904],[-133.56285407321903,-66.69922660350129],[-133.44936960902768,-66.68562891836665],[-133.39377650161296,-66.66524126992636],[-133.3845125133713,-66.62344976902672],[-133.45678071231782,-66.59208461277039],[-133.38932891617821,-66.56801904761129],[-133.30224781081114,-66.5743970524845],[-133.23989426184815,-66.56367731284212],[-133.19601933775985,-66.53871782329625],[-133.18083978499655,-66.47300008016124],[-133.0098099356667,-66.45967993762747],[-132.93707820798127,-66.46237252795292],[-132.87538267472578,-66.44985522962934],[-132.8362169095212,-66.42947485091996],[-132.83266876440686,-66.39621593092677],[-132.89495079999097,-66.36565628949964],[-132.9788396098,-66.36104226157074],[-133.0668037442618,-66.38504528312144],[-133.11265012366582,-66.37460861559201],[-132.94815083134353,-66.33208456788543],[-132.77363258083972,-66.30140980458859],[-132.63941517018299,-66.2675512795831],[-132.57066376890668,-66.27093470002097],[-132.62785559669788,-66.30813212995581],[-132.68868732840232,-66.32994032108378],[-132.72832551903346,-66.35966812235978],[-132.71620844711074,-66.39399460434242],[-132.73098169353216,-66.4214551446636],[-132.70715944443043,-66.44832145336856],[-132.7452500433012,-66.48255331999465],[-132.74145338421098,-66.50684941091798],[-132.70304222030165,-66.52723168724832],[-132.7120062506477,-66.59088282206787],[-132.62960767724462,-66.62138852315475],[-132.5572427163173,-66.62169063896499],[-132.4378409504078,-66.58542248538268],[-132.35551283683373,-66.60444116965787],[-132.2846050440553,-66.60948228165607],[-132.28492443222257,-66.65055948206505],[-132.2625373850636,-66.66714081062806],[-132.18885885058623,-66.68180691131842],[-132.102750203148,-66.6744999476348],[-132.05315380445634,-66.64757455156217],[-132.0654336482989,-66.60399222003345],[-132.00594539350945,-66.5683036676461],[-131.9522126642635,-66.55126169378198],[-131.91239991007717,-66.52458320723848],[-131.80300718131727,-66.51732758882926],[-131.7437621673959,-66.48357090993295],[-131.70249504590768,-66.43685828341977],[-131.6469455831575,-66.43956360792805],[-131.58499977911035,-66.49186957121225],[-131.62252194584116,-66.53276522619613],[-131.6826312508502,-66.5415490556686],[-131.7230631704537,-66.56522132295221],[-131.80807666274703,-66.58322284416832],[-131.83946866070815,-66.60918377949443],[-131.81189548654115,-66.64879744641127],[-131.8196179252254,-66.68590822862897],[-131.7975728051402,-66.69926040591338],[-131.87183956448678,-66.71819793316608],[-131.88440730907718,-66.74170273457678],[-131.93003515701528,-66.75660082930975],[-131.960497576378,-66.7839488402088],[-131.96440596456276,-66.83673628009967],[-131.92646339074088,-66.88499404840357],[-131.8454872498141,-66.91385625757854],[-131.79550900888574,-66.91188719076682],[-131.64890414693872,-66.87042707121837],[-131.62043309816687,-66.84141228805669],[-131.53087237781656,-66.85036660592728],[-131.4600635433218,-66.84144679865165],[-131.3983373767039,-66.80911394503234],[-131.34173076940942,-66.8276263425975],[-131.25631415034667,-66.83754788263995],[-131.211773801261,-66.83209387940823],[-131.14800126257632,-66.80636848022196],[-131.1311272321339,-66.77650169716561],[-131.07812915861822,-66.75698687685149],[-130.85833248923603,-66.76700933046166],[-130.80772611424914,-66.77977939987257],[-130.82142176773124,-66.82155312447166],[-130.76624330608104,-66.85570799759661],[-130.67566562725708,-66.86332583649698],[-130.5791156178023,-66.93292775743288],[-130.4939610039429,-66.95074838090925],[-130.43632764162666,-66.95268746793816],[-130.34751842379345,-66.93490410216978],[-130.23884068167558,-66.88730763219148],[-130.20311425637777,-66.85873341850312],[-130.22652113610513,-66.8183120266652],[-130.33537827201138,-66.78710647338949],[-130.38508251792027,-66.76339250766743],[-130.3253665183236,-66.74633138865582],[-130.30611180944342,-66.70717899204804],[-130.31939947733989,-66.6855310589885],[-130.36734830268213,-66.66545741631606],[-130.46338326410265,-66.65043592687694],[-130.50214700987792,-66.65451060329059],[-130.58880465003284,-66.64119679358856],[-130.57449479555936,-66.59104492286619],[-130.59601329587005,-66.56370341976083],[-130.57574456329831,-66.51573219494733],[-130.5449534614181,-66.50684959822847],[-130.53611755030462,-66.47357342297039],[-130.55606652498375,-66.45089568055809],[-130.6470010138441,-66.4269565371951],[-130.73596757833107,-66.43190096283469],[-130.779953842072,-66.44672261819052],[-130.840302909232,-66.48907335663874],[-130.9180654582856,-66.51130284621517],[-131.03939363562796,-66.51667718620453],[-131.0583032263362,-66.45629012886491],[-131.0894425896695,-66.4310877061397],[-131.156287885057,-66.40577369196424],[-131.21205909009151,-66.3986288514442],[-131.28318949923565,-66.40433792186779],[-131.36137072073882,-66.34803715656334],[-131.43657443312063,-66.33967393469538],[-131.4442772478459,-66.31881579073183],[-131.41128118508243,-66.28709001927425],[-131.35385483561365,-66.27889691020857],[-131.2712815670242,-66.23291028597914],[-130.8503513019879,-66.22623953859265],[-130.7519200140208,-66.23428094568392],[-130.68485839030728,-66.22872146962989],[-130.62946106721768,-66.19483690877576],[-130.64716048901775,-66.16027901141794],[-130.55218722840235,-66.1350923965519],[-129.88038275511047,-66.15500082516834],[-128.8368174556352,-66.17960406580241],[-128.4267327577807,-66.01025649720908],[-128.0513753326677,-65.85844034271489],[-127.47602533315106,-65.61492198338782],[-127.00844543008388,-65.41478745316435],[-126.64156603147023,-65.25822415444176],[-126.08732573776541,-65.00828065135957],[-126.0704811032427,-64.7802291007815],[-126.00374011683839,-64.7484198150009],[-126.00697383533209,-64.70819356749234],[-125.93194258545688,-64.67922720821684],[-125.88231034475865,-64.64902468218092],[-125.86161984823109,-64.6142132713128],[-125.9013834871558,-64.56141909948396],[-125.78551083085904,-64.45305717932291],[-125.6903673677268,-64.417637313044],[-125.52891071318442,-64.40242141860215],[-125.47514783303238,-64.38473547613326],[-125.3415336230792,-64.2945964806607],[-125.17404717458233,-64.29664810770117],[-125.07384609344487,-64.2706517074765],[-125.05258894456335,-64.20072174066145],[-124.99331974269046,-64.17794603224216],[-124.81935289915711,-64.14763125980865],[-124.68816253040995,-64.16262124918282],[-124.60662193049149,-64.14383078183091],[-124.57385571990979,-64.1078088783924],[-124.5320500890651,-64.10215954168115],[-124.48278121732734,-64.06462209994451],[-124.40168405949814,-64.02824940642842],[-124.41425555981361,-63.98363144532966],[-124.30528728952304,-63.97921751074918],[-124.2577130055673,-63.960814322628195],[-124.19017341941363,-63.90537160141676],[-124.15559055285303,-63.83427563738126],[-124.11566079355158,-63.782337138061386],[-124.05342177749787,-63.77334433473289],[-124.01632904035677,-63.79115514307927],[-123.93160439018247,-63.801014047674315],[-123.80957746393689,-63.776023594943304],[-123.65124415637055,-63.76842462132831],[-123.58591337875679,-63.770421031527476],[-123.38933970958449,-63.75873731241718],[-123.31563476896712,-63.72854940911809],[-123.22377996916595,-63.715964475866464],[-123.1526594070181,-63.66939669770883],[-123.07808992469967,-63.65760566672659],[-123.03112569967979,-63.632917885706384],[-123.01699512916547,-63.596443396062995],[-122.95131822415229,-63.563946122669535],[-122.92507983485369,-63.50238927894728],[-122.83475714741569,-63.410207717567644],[-122.7554450904461,-63.39180605928282],[-122.68934756604247,-63.34103624004756],[-122.72003711363378,-63.248505442074354],[-122.58584764343779,-63.19469325893983],[-122.4738555511576,-63.2063937016525],[-122.36688207639793,-63.17649511917866],[-122.34496074226153,-63.15098325058342],[-122.24440972260585,-63.14372610602891],[-122.18447631587229,-63.123022576057416],[-122.15699704950907,-63.086763759800256],[-122.19902328243685,-63.03779392720236],[-122.06096948617096,-63.001357389056516],[-121.99270365444912,-62.955879230145364],[-121.89793741061229,-62.96166845065145],[-121.82386493903083,-62.93634628050815],[-121.79870041805916,-62.90674539282767],[-121.8122501249859,-62.87315255982452],[-121.84609982522353,-62.854293339377115],[-121.78294715176489,-62.80101855114691],[-121.80083170010523,-62.74938940193048],[-121.82558212987222,-62.73459482734478],[-121.61985507414953,-62.62208050128506],[-120.66323580371258,-62.557373184144545],[-120.13268645658279,-62.51919243297577],[-119.60945111534143,-62.48509153665519],[-119.52187291010465,-62.470787346248684],[-119.37226790999433,-62.436566256671796],[-119.0417193226125,-62.379896711630735],[-118.80454043470124,-62.31539020973527],[-118.69230746691709,-62.29138643690191],[-118.37683314366637,-62.215531321531046],[-117.81880402440241,-62.06136879879023],[-117.3544591863764,-61.929665799174174],[-117.19613884003661,-61.67988463360904],[-116.96858293531845,-61.53895898621204],[-116.61708011044685,-61.468718946100324],[-116.14908458593473,-61.462472450086246],[-115.94160930694204,-61.44404348415363],[-115.66597823248225,-61.453405835546114],[-115.5062196959243,-61.518672178776875],[-115.41965007368991,-61.57090568261662],[-115.3101862795566,-61.59602112032214],[-115.13740558592568,-61.59736755659544],[-114.97368323071315,-61.58401931950239],[-114.77400710745542,-61.53598519434239],[-114.55723967376144,-61.45384840776778],[-114.50160664279196,-61.39712110079553],[-114.51087159281448,-61.2952490519499],[-114.48335118303501,-61.25166990152416],[-114.3982524396835,-61.18113488357509],[-114.33197572001939,-61.14337073562916],[-114.1372881403278,-61.10605903974417],[-113.89842392553274,-61.03221567867938],[-113.58457293356274,-60.94380770610033],[-113.25295697223099,-60.84216515779678],[-113.01519614766686,-60.78568493796284],[-112.82920404754738,-60.77398327431523],[-112.56284939585764,-60.83078817525545],[-112.16594658411101,-60.899471887456315],[-111.88533178521932,-60.92257912946196],[-111.48468871899281,-60.916150966953886],[-111.25961607836858,-60.96372600569426],[-111.15233556117605,-61.0399922020238],[-111.24774145679983,-61.15150514769482],[-111.38292717656117,-61.27578304611266],[-111.3654814806143,-61.337025583333556],[-111.22191187429111,-61.38941452565673],[-111.05196483010573,-61.38761694902415],[-110.89823473523059,-61.34427518174149],[-110.63209105835271,-61.11424304255302],[-110.38464857146666,-61.09569386254743],[-109.34932918139721,-60.94566592665598],[-109.08855839052988,-60.881858824975836],[-108.85567990571045,-60.86234944234151],[-108.51198779655893,-60.86615707435688],[-108.10608973934923,-60.885059057113786],[-107.53510498174765,-60.93292074500071],[-107.183384039956,-60.97558593790878],[-106.6774390511117,-61.022560502143506],[-106.66635860264496,-61.04960936260227],[-106.52728188619474,-61.05981553572455],[-106.43239290834197,-61.05082016031495],[-106.21979114122331,-61.06015016739544],[-106.05911329313109,-61.043255168027116],[-105.85682820230295,-61.028667603169055],[-105.39777279948596,-61.11405506092904],[-105.23512986110728,-61.15285926013316],[-104.96618440659158,-61.20311385106089],[-104.89142105579926,-61.22425928354677],[-104.67445713373677,-61.27263461277158],[-104.37664237192621,-61.31899677460321],[-104.10115918465809,-61.343709488862665],[-103.78227347325709,-61.39321410807694],[-103.60474129058194,-61.441193983213246],[-103.44908047384958,-61.47612226466842],[-103.20499974251165,-61.49867742198704],[-102.98608909369293,-61.5110759642857],[-102.93281914810517,-61.51916034013076],[-102.79784989609892,-61.55981156108865],[-102.69097947452688,-61.58563743908836],[-102.51444173422465,-61.65226774054889],[-102.49176738717154,-61.7258839876409],[-102.41548959059837,-61.7666048061195],[-102.22206304326895,-61.81471788368177],[-102.07381053704282,-61.85922855460408],[-102.02973778809981,-61.907890828711096],[-101.89470180852689,-61.942623594969476],[-101.79543218499866,-61.94678510455883],[-101.55611815795407,-62.01229919597464],[-101.44537656013931,-62.027441380125715],[-101.3525957808247,-62.053635431242434],[-101.25557580705474,-62.06286627995656],[-101.09948852939382,-62.10639926887654],[-100.99068236562036,-62.114580647769664],[-100.93063840847572,-62.08218240630561],[-100.84987239909889,-62.130753817434275],[-100.7855879331995,-62.156076793985],[-100.72908594435407,-62.16011922492316],[-100.65687133314978,-62.18589360946666],[-100.5782339922483,-62.227005722782465],[-100.50572491355975,-62.22937849511511],[-100.42645321160524,-62.24820989771087],[-100.30019212426143,-62.24269036579897],[-100.22058419620588,-62.21192671955263],[-100.16970456085906,-62.21881891232784],[-100.2815145029727,-62.272365776992174],[-100.31435791946339,-62.298256340133435],[-100.30792381740119,-62.37728747141372],[-100.29402721542286,-62.40202789570841],[-100.24508482347373,-62.42056852957781],[-100.19488244652307,-62.52496360862315],[-100.1204416664287,-62.554311780164184],[-99.88796599580172,-62.57140150022561],[-99.78220667255682,-62.64906590481622],[-99.42620277241521,-62.86118213966536],[-99.00593961411008,-63.11688345096679],[-98.5807885943273,-63.38706741047343],[-98.0359226922117,-63.57605664333033],[-97.3722835512238,-63.802554120860385],[-96.7385744455141,-64.02043048601253],[-96.65938515139845,-64.04336598103254],[-96.38922549557978,-64.09500087658184],[-96.13609848266724,-64.1914791430644],[-96.06386867737449,-64.21451666152647],[-96.04551253100296,-64.24587113323936],[-96.06808001467653,-64.27302218074269],[-96.15202203332363,-64.31302890011358],[-96.17412259829572,-64.3740416124004],[-96.13179497891602,-64.39999997080152],[-96.02322999028448,-64.43051065222333],[-95.78398027505918,-64.46166745811873],[-95.43570882094276,-64.47839447576175],[-95.35678024808024,-64.28986102210524],[-95.29499133447527,-64.285195519526],[-95.27132278313123,-64.32889422121288],[-95.19618889627058,-64.34738540645793],[-95.08958091470183,-64.33351636679576],[-95.05994353063286,-64.31516373934149],[-94.98819818127882,-64.36367933607684],[-95.0327940939377,-64.39921758075936],[-95.01997393391895,-64.43345239268513],[-94.9732598483991,-64.45687049411754],[-94.86222395572584,-64.46906884387141],[-94.8574383191496,-64.5386616190296],[-94.83671269193086,-64.57822663633878],[-94.84862713278986,-64.61535678899928],[-94.83321989598424,-64.6368293904567],[-94.86638698510497,-64.66543202578812],[-94.86028091855248,-64.69267892776617],[-94.7891556992852,-64.72250181965126],[-94.72620411711598,-64.72556815766359],[-94.64742567709044,-64.7085731856964],[-94.59758756569379,-64.7556018728048],[-94.60074101701632,-64.78576084946073],[-94.56892554073576,-64.82315573208136],[-94.43698831943449,-64.86434728081215],[-94.39151105622867,-64.89260604866978],[-94.26488140868598,-64.90695476568744],[-94.3146532572997,-65.01520313048375],[-94.37839333397196,-65.10978779634922],[-94.33494862962705,-65.15586961676743],[-94.17831025469843,-65.27844256021297],[-94.05141360138708,-65.35668402985273],[-93.90747354028487,-65.45103924194402],[-92.93324135702684,-65.29072295894083],[-92.3418416525802,-65.19618271873404],[-91.69109154913575,-65.08959540933438],[-90.75775964473587,-64.93879937486429],[-90.17303997078234,-64.84200620847122],[-89.8206562419962,-64.78197623687906],[-89.86354499160012,-64.75176819685525],[-89.9264456332827,-64.73466490205358],[-89.91232512347189,-64.71357769852227],[-89.83194129006766,-64.68684176029177],[-89.74482826622707,-64.6192820992379],[-89.63517111788512,-64.5065764801299],[-89.59419517185525,-64.44080706838999],[-89.55642641354908,-64.32898371032297],[-89.57497380704929,-64.29900264087802],[-89.570539934555,-64.2371127180477],[-89.55187502000473,-64.17379175699094],[-89.47285389811069,-64.06024194077975],[-89.49166854112593,-64.04250057265031],[-89.57835437429806,-64.0145417630248],[-89.70615178575237,-63.90243006054343],[-89.79539419557312,-63.87076311251755],[-89.92897160042708,-63.787244928618314],[-89.99771567114041,-63.67964041361112],[-90.07455889533826,-63.65338353004691],[-90.12358013901039,-63.59991190006375],[-90.12345557024062,-63.55236064008118],[-90.0869486041002,-63.52606583311597],[-90.08971538354616,-63.41688761498529],[-90.16902212865703,-63.30880699265028],[-90.3685116243165,-63.223222550080955],[-90.53698567053543,-63.27547292671502],[-90.69132023653623,-63.2650130303472],[-90.80970981141813,-63.26857599504938],[-90.94948648505512,-63.172838639075074],[-91.05315635396025,-63.180220770882556],[-91.1760309262415,-63.176675902192166],[-91.4689820811603,-63.1053224097556],[-91.89594729960854,-63.12827107967083],[-92.03793257070068,-63.101056140218695],[-92.50225133917623,-63.05616274489864],[-92.7111476260107,-63.01090229985863],[-93.03054447888722,-63.07513516414771],[-93.14913675965998,-62.97093761389281],[-93.2405475527517,-62.926303342243095],[-93.24416166815315,-62.86438972846676],[-93.6285023877933,-62.840527229615816],[-93.63302348917087,-62.821231744723384]]]]},\"properties\":{\"name\":\"Alaska\",\"ns_code\":\"01785533\",\"geoid\":\"02\",\"usps_abbrev\":\"AK\",\"fips_code\":\"02\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[32.031614119470305,-37.31145749031048],[31.834790477875785,-37.564176236215374],[31.783609821772767,-37.788684006827495],[31.884565480949565,-37.87161262368372],[32.08284982053173,-37.90511974244869],[32.26640418858472,-37.74192387115706],[32.224341468586,-37.44070844298501],[32.39041385525742,-37.38352511980817],[32.512558986050884,-37.450851749748],[32.56267274735416,-37.63720186581623],[32.661753806834135,-37.728364334234726],[32.641366449449336,-37.94379462537195],[32.44428416019176,-38.12312639929837],[32.22370183024281,-38.20919924067035],[32.12774615337138,-38.39503886527487],[32.123012924737054,-38.702195401096745],[32.22599503664792,-38.787260012105385],[32.37083034005361,-38.7743984426283],[32.75992359257443,-38.84664013274805],[32.750130516330444,-38.98439237853806],[32.392288162796675,-39.19831755220897],[32.24394769252949,-39.350896490794135],[32.31843646604245,-39.58010294268577],[32.45266942562949,-39.65278974726682],[32.87021508410606,-39.51221317618984],[32.99878994938308,-39.288377332058445],[33.14425263655427,-39.472956288527],[32.99135305077013,-39.639352125530614],[32.706374807682025,-39.79928816455226],[32.85039352979729,-39.91866913744455],[33.08951195948867,-39.97934687333774],[33.28199818147032,-40.09554378762899],[33.21379992784079,-40.18719997947957],[32.91371375283178,-40.11000322758213],[32.62949992474779,-39.84284476263585],[32.510416313764736,-40.11719011341619],[32.52294908808358,-40.21438794431068],[32.66483442748882,-40.34524098659139],[33.06879580330862,-40.457767264896624],[33.26297907930371,-40.35752021416425],[33.44109009866413,-40.51543542398537],[33.2794251437694,-40.613554552634206],[33.229660172506996,-40.8148736547427],[33.34343798181168,-40.894447649964576],[33.792931478599876,-40.943163843592195],[33.78397328217413,-41.13783284564212],[33.601916241586224,-41.25373521961374],[33.38613415024095,-41.28140300702086],[33.48395815408179,-41.42790212157608],[33.416098512699755,-41.64711195267731],[33.09961556704075,-41.49958762396302],[32.94566792349467,-41.628607415693594],[32.80906835039646,-41.592034967542716],[32.61513522942847,-41.70564004074387],[32.51480131642943,-41.67322716890666],[32.33360149673331,-41.7749143790677],[32.28942337306968,-41.982013427967054],[32.36894115303403,-42.1102392991357],[32.683032955957856,-42.141168714110016],[32.85581540998776,-42.08476852990535],[33.03220004122343,-42.121725504212456],[33.03870550448749,-41.9531185516379],[33.317756380881946,-41.917144111189],[33.31238793833236,-42.019847644069564],[33.16539379073121,-42.144352689699886],[33.12741694258222,-42.26443407215226],[32.885892541174385,-42.37509739882212],[32.78495255432622,-42.542049716198555],[32.52874867624489,-42.37490290231188],[32.39965413767137,-42.48518786366061],[32.53116054790444,-42.56593861182257],[32.85814710921028,-42.61483941027547],[32.93365523276877,-42.7029047915429],[32.75744234205274,-42.854302367838216],[32.414141474416844,-42.90295163347925],[32.27929908466076,-43.019300087099175],[32.22057876810456,-43.29471962654843],[32.033908508190294,-43.466103035179835],[31.846296549904228,-43.539250625794075],[31.979247728665015,-43.69907087388232],[31.88900715580514,-43.82566417036314],[31.758946567337798,-43.79720245579836],[31.673780797720585,-43.56834375342127],[31.375404026749326,-43.665207954031295],[31.28628533362601,-43.98148464178651],[31.387974334949785,-44.10784923048225],[31.74913940995634,-44.02391676138005],[31.804835043583182,-44.162136576063105],[31.5548897652602,-44.14366616315775],[31.263668369123373,-44.17017470173547],[31.10865601856145,-44.35812562389832],[31.14041708980422,-44.700617209539644],[31.11782422442552,-44.82208023484961],[30.9868581812678,-44.873342805463444],[30.737196617361736,-44.835572192788504],[30.524197555665125,-44.715467433007774],[30.41677615030726,-44.88145687746803],[30.611525359855666,-45.01142470844938],[30.993567621460443,-44.9452336199678],[31.09260347040591,-45.082272641228805],[30.884177839090693,-45.22537909826137],[30.694452159338667,-45.288294226868615],[30.452662787488997,-45.299069488974695],[30.49728144827107,-45.731308961574136],[30.58759278239539,-45.849777673429436],[30.763503871826327,-45.95881058609916],[30.769492960013174,-46.088060110707865],[30.57630510716313,-46.12468349365375],[30.400617135694066,-46.00462993052711],[30.288667634155512,-45.796809552052146],[30.118952773204782,-45.969999563919906],[30.182947474438844,-46.074632152239126],[30.31803408520602,-46.04471157613793],[30.319222005982883,-46.22685517577921],[30.560503846177163,-46.36392076006198],[30.522790753194126,-46.570202741701586],[30.283796129537798,-46.642448032300535],[29.781085381818777,-46.635111052588975],[29.772911487606184,-46.79506198523065],[30.08316887943337,-46.944223640987055],[30.127274575499875,-47.05709402028256],[29.928357319329994,-47.281844498145716],[29.928237987947554,-47.36812582233414],[30.315851688482105,-47.59386400998455],[30.323651318476198,-47.72806344593669],[30.172206850813353,-47.837227800710515],[29.89510558211698,-47.93157473174307],[33.02963784586749,-47.83173748398703],[35.39532164587088,-47.745341723635285],[38.57513098038582,-47.613755764820866],[41.51096024426994,-47.47925141599118],[41.48200327761686,-47.65403039111164],[41.36924138983298,-47.76736160666619],[41.419675961854836,-47.915221565760405],[41.3030432494887,-47.98537487529906],[41.13890206162125,-48.45523666693729],[40.99103226293645,-48.62723343131249],[41.056089895984854,-48.75470998676572],[40.94607138353791,-48.854449110355354],[40.96765349688798,-49.01064758786943],[41.1236336667782,-49.256066228953514],[41.15011236031368,-49.44131454425635],[41.35046911310887,-49.65698769217699],[41.41511862545073,-49.657154748649475],[41.59559408705114,-49.83959475051187],[41.79441373540688,-49.896147154481845],[42.04322888026133,-50.056428630066684],[42.064686095421,-50.26752189830786],[42.24963236683861,-50.48076148516227],[42.42252319560859,-50.563068415445414],[42.454942663856194,-50.92638494945708],[42.56067599074849,-51.06271418110913],[42.85824411478687,-51.25243160488693],[43.05528132750742,-51.176012390248545],[43.282015165324715,-51.3364621083425],[43.813135311787136,-51.225262667918706],[44.60091464084951,-51.186957774397065],[44.83291717222983,-51.144981857639046],[45.226891057578705,-51.00742633725492],[45.926228688955646,-51.235760884522705],[46.193217494366046,-51.11761926136067],[46.38103870410559,-51.184311306668384],[46.740760390257506,-51.17515151949074],[46.98866921780146,-51.11608657598193],[47.28337346366366,-51.001758147744184],[47.44790024065504,-50.88067124392227],[47.5833517551173,-50.94189306531213],[47.87793981062117,-50.99174818113146],[48.1006283404284,-50.96896744966577],[48.41530170008824,-51.01030444659545],[48.808610636528776,-51.02587820109279],[49.161471902799825,-50.98124329892551],[49.39341071106069,-51.05408889277907],[49.69758483541568,-51.08963108772201],[50.16270765860933,-51.0128586304241],[49.882096992570645,-49.57311972638721],[49.6735364425197,-48.42361093765877],[49.43300611764585,-47.10715832896062],[48.999732716326236,-44.717498063728094],[48.7921449426802,-43.57473554749848],[48.635251106449125,-42.59494673069236],[48.70202090482549,-40.67147754386478],[48.74705871514406,-39.02631852795543],[48.78443273652495,-37.17310802473811],[48.818302193000655,-35.62190957022789],[48.85108356095347,-34.29276220442066],[48.89622278911415,-32.164863835290184],[48.957799069871726,-30.251326210950456],[49.011877900519295,-28.01249452315756],[49.05084039235424,-26.522101032910896],[49.08372371399291,-25.817439323514556],[49.102318724017394,-24.72005334101876],[48.770717458289354,-24.564396155555613],[48.450529258033,-24.089261644065356],[46.024117971988375,-24.267531323959787],[44.26210350594579,-24.39394927370547],[41.01773218852917,-24.611012558355778],[40.258224286720186,-24.649799483237803],[38.75317263796547,-24.745703342726046],[36.260561391102065,-24.877537699148828],[36.34332758135578,-24.99947710295227],[36.64828855963235,-25.191865477716558],[36.641352147086906,-25.43327901254198],[36.289683562297895,-25.68398544639786],[35.71918466751438,-25.97295949784212],[35.63460265128983,-25.915484008120664],[35.56105932384703,-25.63920003832832],[35.39711821736398,-25.606216401324016],[35.30682062348506,-25.80349819181752],[35.47884115759642,-26.04045190057234],[35.39862699646865,-26.17996694568489],[35.543044929278025,-26.563358874762176],[35.42420462393373,-26.672242904079276],[35.150099313504185,-26.648196594256046],[35.12966948514558,-26.50268087678503],[35.24057713108985,-26.36699925733684],[35.152880187334574,-26.19727687439185],[34.969942517279385,-26.352553292515392],[35.05421011219985,-26.536142873580236],[34.87905914632875,-26.71755215778272],[35.01223144262989,-26.847173665289233],[35.41338959596093,-26.83227900721276],[35.50306696692393,-26.937502649986282],[35.41500542880794,-27.097007188620573],[35.20945945416986,-27.29346504411911],[34.98267029284965,-27.207029835502475],[35.06372864565275,-27.0446794708857],[34.92647540947744,-26.885726314309668],[34.778956262286584,-27.058867510227657],[34.8453762971154,-27.482811249854443],[35.07043338605405,-27.732913586568273],[35.08190242531556,-27.89483782854078],[34.89131276762164,-28.052146229945286],[34.846411848678535,-28.24329964023276],[35.00710648067387,-28.548182989946707],[34.93063082814083,-28.709108801241015],[34.602945331063424,-28.886399117222354],[34.4507917302791,-29.078010152685927],[34.517242395817455,-29.254151393253082],[34.31047079954046,-29.29389648355494],[34.38692302636397,-28.982954366501453],[34.18334130786145,-29.040803649941104],[33.92468961108111,-29.031618097280802],[33.88200209823276,-29.187426018311335],[34.038120364518385,-29.407730566989922],[33.906710388176464,-29.577094512884166],[33.68545932600704,-29.575510383912288],[33.514989432652406,-29.697502377004092],[33.45294462614141,-30.06084339605142],[33.24809658152999,-30.0053322069286],[33.09926795204111,-29.84650730067714],[32.92031598422559,-29.905996408569948],[33.057708752211845,-30.139111816025732],[33.22816099964971,-30.224181721709645],[33.64473408478863,-30.198188080468583],[33.706963817891925,-30.315134583302626],[33.62478264907049,-30.441303583338094],[33.360599735109666,-30.465507972856038],[33.10516275798062,-30.332344235953713],[32.85642056096659,-30.511435185029594],[32.85882354473401,-30.633192499926547],[33.06699728596216,-30.777668691666154],[33.29196574874568,-30.750805430796955],[33.366316815680506,-30.823620552655274],[33.2361795668234,-31.19765589088807],[32.704809769572876,-31.25625769586202],[32.87437697423823,-31.488820529867347],[32.84504855739626,-31.59210464660742],[32.65343697730249,-31.581697162984955],[32.529476395966064,-31.36183331855403],[32.36347212576189,-31.480241461791973],[32.13519996973751,-31.550125008445463],[32.13943948783211,-31.643460684622966],[32.547253628600416,-31.765911093874287],[32.60229844285172,-31.83925995462461],[32.328116941863605,-32.13967283289665],[32.26652181124474,-32.26450151143345],[32.4359038878898,-32.51277506759323],[32.768242144422445,-32.63614298266105],[32.72798800634388,-32.77979800518231],[32.57324248653316,-32.82003259811354],[32.4082630805434,-32.736177129211],[32.09716562559279,-32.798626755770755],[31.931639837641697,-32.73519765703889],[31.880106078299494,-33.03846503261657],[32.012893662115914,-33.175449579870396],[32.346782797250036,-33.11464890867297],[32.47712713437096,-33.17019820192299],[32.56148678758652,-33.366797725548395],[32.3083177206211,-33.46628216760103],[31.97824099432881,-33.36935154715778],[31.8125407342852,-33.20018592389597],[31.53192094528189,-33.24820631075088],[31.420072789352922,-33.35525980860059],[31.466996421670878,-33.473893022060174],[31.766616725547433,-33.5525537553956],[31.962922097649127,-33.676655991791336],[32.021761688521224,-33.79154590568082],[31.90243816249474,-33.93586595791077],[31.761905104173664,-33.94032940484002],[31.459040488201897,-34.06214544398206],[31.524893065964214,-34.25166880809522],[31.749197467018945,-34.44543811145125],[31.464837186683777,-34.807502020581126],[31.65748533946808,-34.82612587838855],[31.84806737204113,-34.713812709104516],[31.78938961436975,-34.522628963517086],[31.860062182217654,-34.44266601540599],[32.102250925940695,-34.580620870724225],[32.1513721387754,-34.69119251257627],[32.077994187380895,-34.83457589370279],[31.630008242526117,-35.001665091548354],[31.893142617998507,-35.13898959268901],[32.159469594071744,-35.075863612729265],[32.291384034969,-34.938083758869084],[32.33800306368329,-34.69646506334433],[32.51386418639424,-34.82410614355076],[32.1701756084808,-35.22086605249628],[32.046722422323,-35.31304840868492],[32.07121674772953,-35.60834799794082],[32.33670572047939,-35.94888115895373],[32.44758706035764,-35.68078168816333],[32.65698205948871,-35.750587727955036],[32.542656978843546,-35.970175641237205],[32.392783305813154,-36.04723827126362],[32.45109448765348,-36.276745611513945],[32.44523855039617,-36.48351709950182],[32.351126526585695,-36.56587324943336],[32.07853803687284,-36.58231389779677],[31.87715617846465,-36.52704660964304],[31.78579988472388,-36.7240674292944],[32.25551088218023,-36.95838464047205],[32.24036723910311,-37.13441267617159],[32.04795114406484,-37.221690939086976],[32.031614119470305,-37.31145749031048]]]},\"properties\":{\"name\":\"Mississippi\",\"ns_code\":\"01779790\",\"geoid\":\"28\",\"usps_abbrev\":\"MS\",\"fips_code\":\"28\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-36.43045257757133,-14.240447606823222],[-34.294765333962935,-14.389292832881416],[-32.000236620233004,-14.539887003833952],[-29.80954224042435,-14.67992666715688],[-27.896137357976812,-14.791064643822795],[-26.323341662896443,-14.876808649417603],[-24.081847126048533,-14.99025905169759],[-22.369321182613483,-15.073929843633497],[-19.40814628533552,-15.199315167710163],[-19.569269109165358,-18.857664709308587],[-19.652609384205274,-20.720216147423745],[-19.769547495861268,-23.294001419285724],[-19.833460713424607,-24.681852044358497],[-19.932079387687565,-26.79153932912127],[-19.994665640740436,-28.122836661714373],[-19.823678543397126,-28.116144103536115],[-19.718774309394774,-28.01821904176112],[-19.534875964218802,-28.07750727426012],[-19.348247569372877,-28.209941522417235],[-19.151750027118307,-28.463205557763594],[-18.973888061018243,-28.556395490907914],[-18.936278499470685,-28.694450333815457],[-18.597220714508143,-29.05508346912839],[-18.44603866059102,-29.12504846251198],[-18.26545581401648,-29.334260033441378],[-17.993598168916964,-29.31773659595825],[-17.714858559584773,-29.374824010925774],[-17.526018912556452,-29.106480087856433],[-17.22329180761269,-29.141010030690857],[-16.539571731709604,-29.405372531314406],[-16.46319979607748,-29.188075852164832],[-16.50190214852528,-28.990772531817306],[-16.383095275463464,-28.888949403251335],[-16.072042187648197,-29.21002112345091],[-15.883189849753498,-29.185839729174194],[-15.730380853322808,-29.256505191319913],[-15.785709934182428,-29.39701340648055],[-15.601018213802151,-29.491123044095424],[-15.588383586927216,-29.65574252077145],[-15.439965573961965,-29.668111179021142],[-15.47270841616905,-29.85884405301067],[-15.358781143224942,-30.447583876024016],[-14.959420791699836,-30.536532841131574],[-14.720638234928408,-30.48355259801761],[-14.581553049300771,-30.55848489936959],[-13.964059809419744,-30.499871438332555],[-13.791501469072989,-30.682485256604714],[-13.598208289809145,-30.781375909230356],[-13.271708813263999,-30.827532818450077],[-12.899713942043435,-30.993163767061276],[-12.451851239687656,-31.02686153440561],[-12.1711846545711,-30.84007650746522],[-11.937046165703226,-30.870504661442347],[-11.788558920250672,-30.944322247908946],[-11.374403805832259,-31.426322104243788],[-11.271760648721914,-31.488049508918273],[-11.012821701204206,-31.367720867622037],[-10.872225343634858,-31.37379381357863],[-10.748588142821722,-31.24186419717318],[-10.744559741614337,-31.094966317837528],[-10.537804893958327,-30.922817072852453],[-10.296441770294434,-30.973489595519258],[-10.168063757661145,-31.069466387084574],[-9.771292998303664,-31.077189601709946],[-9.584483661743569,-31.190827407304713],[-9.40159828530702,-31.209927955741833],[-9.173254757963948,-30.9910479758929],[-9.047623249880937,-30.971388259517333],[-8.941869248366238,-31.168169688868332],[-9.123700063165664,-31.474023160257293],[-9.012441832826982,-31.611805831915532],[-9.033263142187181,-31.738536878652273],[-8.924893916080329,-31.90131086323824],[-8.672169932202765,-31.963542672806287],[-8.262829710701908,-31.897797921405754],[-8.119051741530104,-31.989016818659696],[-8.1832078718515,-32.18517786918581],[-8.1911235305138,-32.45262049116131],[-8.353483089748424,-32.54938467254104],[-7.731629382419367,-32.84906952784877],[-7.472531887602555,-32.805848327170416],[-7.308141863314493,-32.69681573773403],[-7.048144309269941,-32.352005879622546],[-6.86680083347235,-32.33942295619654],[-6.598822067522163,-32.03629882230464],[-6.444562217904455,-32.01643223615143],[-6.139786691895077,-32.159273088191604],[-6.076106665233269,-32.238318703057665],[-6.031035659946524,-32.55045816070661],[-5.8497825044987435,-32.59431443862732],[-5.510277296761762,-32.46294231274821],[-5.26204136609087,-32.57317743015289],[-5.229453946697423,-32.772804477433745],[-5.2970980158048855,-32.92812848336546],[-5.0856651300784055,-33.07818146739987],[-4.779145523756367,-33.08250288203316],[-4.571204276231128,-32.836018723495336],[-4.41716966858048,-32.79135343105848],[-4.074537344595367,-32.81840084236963],[-4.008557108609522,-32.58015156237379],[-3.8167229484305376,-32.50539357400345],[-3.658612860264203,-32.599157383058035],[-3.554431488973217,-32.96461693228219],[-3.71907198940169,-33.01837436837566],[-3.7865074784643467,-33.18792957057369],[-3.715370333308204,-33.441641584098925],[-3.4913214315204217,-33.663663229542976],[-3.3105785990385934,-33.70761776061593],[-3.1450282511292724,-33.636402207947704],[-3.080551992781948,-33.424279627612584],[-3.121178358633581,-33.18690410728563],[-2.8593600380085684,-33.11072899140353],[-2.912385165427866,-32.99436471822645],[-2.698957461385117,-32.93679481028151],[-2.47385625603828,-32.66265649550315],[-2.5435764679755404,-32.35552188731753],[-2.4122668621236203,-32.39896022173239],[-2.119637487336145,-32.25894068628824],[-2.008794969795857,-32.33238301841312],[-1.95629459172393,-32.640800516631884],[-1.7869927312039589,-32.90355792471418],[-1.6409083230863606,-32.90619856354789],[-1.5840474241273395,-32.77413963098619],[-1.3617162999320243,-32.811678219733444],[-1.1720956287732474,-33.08121549638584],[-0.8902277929663149,-33.041366514826294],[-0.7538164963221266,-32.92990707384907],[-0.6491503811879846,-32.55383964928553],[-0.3953400204388487,-32.65735353642497],[-0.17231507967107795,-32.6580103259396],[-0.20366898019325666,-32.78126050490256],[-0.3569346213473919,-32.98323949117065],[-0.06384497287510292,-33.11918836508316],[0.1745741651607181,-33.09650773795267],[0.35895381106905416,-33.39987515289564],[0.7757192376770065,-33.36646187153344],[1.1234684336562055,-33.73092388075785],[1.1902759725389804,-33.9016475876117],[1.429789461981203,-33.85868808739268],[1.5056097585115893,-33.574690785458465],[1.59875623172296,-33.41600030200179],[1.7550523321403544,-33.43622452129111],[2.009474019352672,-33.55739563558723],[2.249159591901649,-33.4845674401022],[2.3356506694686234,-33.361684880998915],[2.4303044744418973,-33.01928498016705],[2.810329030673643,-32.96202545752462],[3.020783254827657,-33.02355455635802],[3.3177096231906726,-32.93571438544293],[3.6253569500896465,-32.885638134837706],[3.7501622279182607,-32.731517562969124],[4.305542376503763,-33.03300988938466],[4.391557036932706,-32.881953098179224],[4.659249434296152,-32.96960733613196],[4.769640159049032,-32.87599157666314],[4.738534906566522,-32.68667711591288],[4.876150270540764,-32.659634219125756],[5.1367379461497205,-32.70712050176881],[5.273747243463999,-32.582568379505574],[5.498017600880411,-32.58911964868686],[5.672056897637451,-32.379152427637486],[5.935355311916452,-32.487531840220356],[5.945829546130719,-32.69697858055599],[6.048508236219153,-32.748316195129554],[6.584354732022144,-32.83437680238695],[7.051966576710337,-32.83006087682136],[7.272498827883708,-32.71444357318791],[7.385557346210354,-32.779207652084665],[7.693599542453075,-32.66193762675159],[7.708430028574624,-32.406839984021545],[7.907776138023254,-32.24399864167019],[8.259017363358353,-32.398917523169615],[8.899326773285507,-32.83353160991025],[9.199480438356684,-32.85259246134763],[9.398979215387461,-32.99245602339075],[9.531949474318715,-33.1793329104714],[9.64307494859161,-33.16047128555067],[9.71534539400772,-33.34222629509246],[9.860306475818073,-33.39673195015044],[10.017143596745036,-33.55774346295809],[10.210019981927386,-33.56215418312257],[10.5341097108586,-33.64071223913361],[10.751297610731065,-33.84660469568585],[11.05858617991393,-33.890747592049436],[11.329400426360658,-34.01046377761611],[11.400318033364544,-33.90056085151786],[11.59980513038351,-33.99813994952377],[11.776249130963231,-34.22791810803691],[12.121386236041337,-34.25676356941846],[12.236049440867859,-34.15307230330095],[12.260564199495866,-31.43380849270457],[12.270375938695848,-29.812523047712347],[12.287363867055065,-27.943504782665443],[12.305100980201159,-26.309001822355476],[12.329989212212384,-24.47098857097598],[12.353277338634339,-23.10254991108338],[12.22636207276349,-22.37453686849098],[11.840133270364339,-19.96336506732185],[11.396540942671932,-17.223653031119962],[11.15817993632201,-15.607246340684972],[11.121876233715414,-13.398448797486239],[11.10561780019015,-12.115701625453237],[10.346174655638514,-12.128191751134366],[8.018159904386971,-12.145793365145732],[6.366511136529513,-12.160284396967521],[3.303442165004269,-12.17144365306711],[0.4852282462001948,-12.16570913646166],[-2.5581342682349137,-12.139373396106125],[-5.691256274698052,-12.09982731581384],[-7.942835536731039,-12.058517590987886],[-9.670877405710208,-12.026134544544073],[-12.471249895676253,-11.94014672676275],[-14.212078967739142,-11.887293093194],[-17.17393420968468,-11.780851881190827],[-19.86171980136749,-11.657117329803075],[-23.394491479033483,-11.518991482002313],[-25.39946404533427,-11.425704629877558],[-28.284934066932465,-11.286413585811227],[-30.763083521479004,-11.15169750347356],[-32.63945317059252,-11.020160389765309],[-34.45563211878416,-10.893299507143515],[-35.11137927281883,-10.814640988565417],[-36.16422165377581,-10.736986504068158],[-36.43045257757133,-14.240447606823222]]]},\"properties\":{\"name\":\"Oklahoma\",\"ns_code\":\"01102857\",\"geoid\":\"40\",\"usps_abbrev\":\"OK\",\"fips_code\":\"40\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[86.0203417031575,20.26334600887488],[85.70287244906785,20.056515597999976],[85.47282276757308,20.05169987625275],[85.3103149864385,19.825261813458084],[85.32989400454318,19.6858800083327],[85.56500083930595,19.486910376496308],[85.65453775840608,19.26860463645483],[85.81399123356661,19.08206063047437],[85.76593665990532,18.78548234131167],[85.7998409768074,18.60628250652424],[85.70478217325606,18.528707553270102],[85.86354646343358,18.38654942074748],[85.8612530061011,18.23464392961925],[85.96471685407607,18.042789962439887],[85.90919223223396,17.646110040584944],[85.7383087887109,17.397344822869055],[85.73827509208398,17.18760390565185],[85.59271068727955,16.847513049532846],[85.63383496672074,16.48548878964528],[85.5025370437139,16.28160865019699],[85.58172942467785,16.077470390481672],[85.56323358306324,15.826305108751882],[85.6176186208716,15.567408953133096],[85.51704402781576,15.378522197095213],[85.59818075129603,15.160791085044645],[85.37961332694384,15.171155490141043],[85.32097179449366,15.06305416808998],[85.4570058529785,14.788972465349172],[85.30108014883443,14.608812068704374],[85.3551189933532,14.313101030559633],[85.15998255073659,14.035023998874323],[85.43232554155162,13.727007053422183],[85.27011135769878,13.50634977024208],[85.30425878909529,13.271421640743881],[85.26697726386686,13.039002898331539],[84.98916017293159,12.953771755280922],[84.57264802543123,12.444466286258969],[84.26708037671752,11.996414089788939],[84.0980604601108,11.606354462913146],[83.83989932487786,11.46942896491929],[83.73993055854814,11.178853926618652],[83.4410715436981,11.085322933731263],[83.07193300698819,10.745996673921104],[82.86161537091422,10.689970844457719],[82.69294805945646,10.983810736577935],[82.40278190552384,11.091311905102014],[81.93242474072284,10.51577519814958],[81.95903074337154,10.071075872469708],[81.35370287030594,9.96582566633694],[81.2702849626114,9.82396666006472],[81.32930174643545,9.63039402975487],[81.15675069348917,9.518125252170096],[81.03263392074864,9.219980424417285],[81.17880106991406,8.769033844567373],[81.13945890606382,8.598274427441543],[80.8528808017183,8.360351496959382],[81.16325549485761,8.075036567240168],[81.12193951631471,7.705648954275659],[81.2952323846153,7.504942621334923],[81.14272788895197,7.429023024310303],[80.96595729399431,7.5468946980576295],[80.8201995731397,7.399377641746118],[80.82676066397269,7.2028594075589405],[80.6263311214322,6.974411057645834],[80.40263511236414,7.139219715013213],[80.50572092447621,7.417133592435823],[80.27549152796642,7.7439084517263925],[80.0078315703817,7.752710571957372],[79.83594130169178,7.980133078640402],[79.66933736028484,7.915871548956626],[79.62309079929533,7.693519252486719],[79.43575181960112,7.531168903044372],[79.29410014127322,7.025949311584218],[79.22851364519316,6.9434060788350305],[79.28187395732323,6.544907347231779],[79.06341999977161,6.324202301016678],[78.91737601565035,6.0961567947178],[79.06420817316067,5.913854432755951],[79.21223259414364,5.571268929439613],[79.1960293807988,5.363116942360239],[79.35659967510733,4.987791072464702],[79.33307077767876,4.798673935094019],[79.12175515325677,4.706220089044961],[78.84429841722034,4.695659762532754],[78.74479019499566,4.536143516970425],[78.76796845526846,3.985981670863565],[78.70819830129304,3.615258480727081],[78.36353128048276,3.4572049159510616],[78.27550625057162,3.480806274335099],[77.58387618422806,3.126209190092612],[77.39696573391997,3.1067565190891004],[77.26022528545836,3.2085467658203735],[77.31161973816478,2.664441818346023],[77.44666853843515,2.5570130125833703],[77.44466429864266,2.337566594415545],[77.52366385085777,2.102617704781861],[77.44082460585524,1.7480449281277697],[77.41425353391699,1.4095711698187934],[77.23848955724954,1.349934013084368],[77.3219607696849,1.1463042172516638],[77.62420198050121,0.9687010610430442],[77.65195141110199,0.8518666572573984],[77.86217163252795,0.7211380905707748],[78.0959064379731,0.27571981805381773],[78.26618038747748,0.2764788668365534],[78.40155513319608,0.10898318307738868],[78.25870186159383,-0.20383733616825053],[78.43107110483362,-0.34066034584217375],[78.7581913171688,-0.47863188466755924],[78.84700098400764,-0.7682565746250029],[79.08856476018497,-1.0380626912493833],[79.30158112184398,-1.123728121997314],[79.43982169343938,-1.321590213918799],[79.47128531844132,-1.4760154761008337],[79.65139955200942,-1.795512781024302],[79.89156540888139,-1.9777972224178],[80.07229497657448,-1.9803497480426042],[80.1654450578701,-2.1669137143981265],[80.3452507303561,-2.127927155313588],[80.52218562440052,-2.355044181841384],[80.65416124139733,-2.3286851065097527],[80.77286866880844,-2.5593016545933205],[81.0071095659011,-2.548906895304509],[81.13414467167324,-2.669177850074498],[81.6223371843472,-2.587927429285713],[81.80460992959269,-2.745920527857291],[81.52891763447363,-3.0478717123518395],[81.56950834207984,-3.15666437463107],[81.84940980071029,-3.265629321886453],[81.97443156428163,-3.4339744294449983],[81.96521784604107,-3.6653589258340404],[82.02970052420724,-3.8155342912020873],[82.2281913885714,-3.9836101404907174],[82.36722654421125,-4.006424744900486],[82.52383713520493,-4.273363444020781],[82.68679391465419,-4.309947195317347],[82.88451887809933,-4.243201755981756],[83.14754013738052,-4.356304508459577],[83.20803399304192,-4.533451321050716],[83.59141652004418,-4.738501356514239],[84.23994639207109,-4.602745423639396],[84.85250765118431,-4.019014795016701],[84.96730465743964,-4.0075044313646195],[85.04398452322059,-3.681519575777571],[85.198863118523,-3.5027713580828648],[85.47475673503547,-3.739062436408274],[86.07409784481197,-4.114410222775852],[86.36542666184498,-3.86506391958456],[86.69472696891604,-3.684289120770852],[87.31978638193326,-3.434244805486392],[87.5284866058359,-3.447539093962545],[87.79756746953558,-3.2457542367928363],[88.03270763593724,-2.9885880773082483],[87.803074099317,-2.75565373068083],[87.88376297055528,-2.541718437606635],[88.00787698519008,-2.418691707674528],[88.33980793412992,-2.5917208742452345],[88.4435042313688,-2.736968776787344],[88.77736378684925,-2.5207245334328183],[89.07401827773097,-2.2717914442330533],[89.53374861068022,-1.8216506989808705],[89.74913042826057,-1.727136942716769],[89.87895389657382,-1.8803410521982176],[89.8950870850492,-2.0431345828675482],[90.01587980751174,-2.111694851003894],[90.26083617076304,-1.8747511180689256],[90.88787594493938,-1.3445321475500915],[90.77341381978039,-1.0865021501949967],[90.66288801371131,-0.9728205749387263],[91.01018557122244,-0.6872003359789802],[91.18118732208349,-0.42292541482266144],[90.9026655870174,-0.3352232621154611],[90.69102740015576,-0.037713989689612766],[90.86588177783646,0.20239854299867527],[90.81936562423866,0.32406661816427645],[90.86302577366178,0.549832622475238],[91.03696149862323,0.9274684819353612],[91.20997241669842,1.388496928566473],[91.31816241750565,1.4710359816021426],[91.63551556913507,1.9738872705087],[91.91330570240768,2.3521712248644686],[92.02583427659216,2.6396705048767277],[92.1075054122022,3.046815420359259],[92.22536696603038,3.254170168887328],[92.17102028284279,3.482899500750651],[92.2794876295372,3.8923566955749966],[92.48806924181659,4.219184814758034],[92.78047672154786,4.527136266021934],[92.62182396960813,4.762025537572494],[92.9290262123249,5.169314120673114],[93.049652717933,5.643933706031471],[93.1228344719632,5.770985227763005],[93.0164259658284,6.065793423767053],[93.07771723316002,6.338644139586805],[93.09771411831886,6.776399592616325],[93.14679373698812,6.93800987528841],[93.80753809203428,6.757675551006282],[93.91247239561783,6.652463108093504],[94.25203597869555,6.154308939408794],[95.20447375493806,5.991618792049074],[95.3665047916681,6.109971044115402],[95.46382074559065,6.357872448893635],[95.65603840781847,6.660766536044776],[95.64177381229904,6.887897528114855],[95.81364810862391,7.416434803801922],[95.88505363554145,7.864998314899123],[96.0843981566309,7.952638331669311],[96.02976748945055,8.24421065054114],[96.16245804182759,8.917730671676216],[96.37822007120101,9.376825893819303],[97.16449576604509,8.888749016995671],[97.44633076144467,9.821856307387478],[97.58288433787857,9.957640986937298],[97.65609764636524,10.162694624441425],[97.78748856649054,10.034645003671882],[97.97572759166393,10.207439415975971],[98.17814196507074,10.644324054757904],[98.35012669273871,10.558510554460948],[98.5563120841372,10.980772621067437],[98.42233595914288,11.056526158799564],[98.81806170554457,11.685316556282132],[98.92108895121618,11.738341846504971],[99.04270900372383,12.036262373883801],[99.14875991714321,12.163215693155594],[98.92128041798566,12.338960961692365],[99.07062222555913,12.693686724257637],[98.94636038678883,12.76820103305163],[99.18293891446149,13.240601528285222],[99.24329563545716,13.508479102653537],[99.09372837426612,13.521501156574793],[99.1814734743431,13.747693175570177],[99.0649471447182,13.870312868715665],[99.1110546778112,14.01091338499088],[99.05801977191796,14.274032231640845],[99.57884463135807,14.002004346926869],[100.23537486010756,13.617789195945491],[102.29137242727693,12.497391979015086],[102.36316340811261,12.996981999255743],[102.45804469388976,13.270594470621978],[102.5055965226112,13.695780022089021],[102.61980388839608,13.906021594134234],[102.40817331438667,13.955949457763097],[102.3609298375404,14.267802876262053],[102.43599934025131,14.368304879957075],[102.2952866238383,14.574337596722664],[102.11376650336979,14.575491719086282],[102.01149778405231,14.751106669750895],[101.97220525428978,14.978664790026814],[101.80638225554546,14.97320920474158],[101.59603037242569,15.084213781594961],[101.45820612738939,15.410741292759333],[101.6602161966444,15.470174793749905],[101.59603748930043,15.731855724796016],[101.33259602777174,15.752858799000515],[101.3329572526846,15.623320520288347],[100.94539361241411,15.632031419811588],[100.69872070376707,15.534329906613175],[100.48650937987419,15.74625259841723],[100.06835609658876,15.98249627139758],[99.69066807364987,16.0135123272331],[99.3231871447232,15.554002956654282],[99.28497959123233,15.398575383274762],[98.78173448756891,15.460577177966648],[98.73092326589223,15.246762770128532],[98.43155457562918,15.283412376253954],[98.62547566908785,15.075842096191092],[98.58289757107053,14.986099117946777],[98.36468765497126,15.037362656058875],[98.36857104420092,14.890680378560528],[98.57085511814402,14.778422821158474],[98.33805948764545,14.501602958873002],[98.01407061026242,14.502164393732615],[97.81336026710756,14.427561427507927],[97.49900063696671,14.512039769206588],[97.31862494346862,14.446942450663377],[96.9111130336593,14.579040978560169],[96.4664696937268,14.834415457738492],[96.3311902987459,14.651101559622726],[96.29610529216453,14.495831870172832],[95.97607751532567,13.865824355578205],[95.88179962038565,13.848945352947316],[95.81768049025978,13.509207828753857],[95.25250011129002,13.644687905047013],[95.05763283574585,13.60646669732645],[95.02295724902235,13.443643487441271],[94.53356586050332,12.737826469704876],[94.31534184342591,12.643178471164024],[94.35467454769856,12.518951354838338],[93.90872113038101,12.115022647641243],[93.57950872697701,11.679273624129976],[93.57572575785841,11.595460617542976],[93.35890840852167,11.392495726190258],[93.26409869828908,11.391671411512743],[92.88273864707881,13.65144966384187],[92.90152752652904,13.654655399490924],[92.67748223760837,14.95807147531828],[90.1925781747983,14.539213121281538],[87.0892439286871,14.04077807509085],[86.89369984619013,15.206710156230184],[86.3576828522193,18.32655101657395],[86.0203417031575,20.26334600887488]]]},\"properties\":{\"name\":\"West Virginia\",\"ns_code\":\"01779805\",\"geoid\":\"54\",\"usps_abbrev\":\"WV\",\"fips_code\":\"54\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[50.17036245745686,28.272522495195947],[49.888893951519776,25.701324301721844],[49.679014135709465,23.478096303583097],[51.42906755473148,23.62565151479636],[53.697482910994104,23.82523516117235],[55.457284735048276,23.997960676607892],[57.33975709361291,24.16934378071564],[59.65944542550207,24.40966813658446],[61.37728620544562,24.585986895135516],[62.295177928639326,24.688403710600074],[62.34878730805479,24.272303645536002],[64.07441029768982,24.51176026584786],[65.55589907149322,24.725286921036016],[68.07424525413165,25.10989000970587],[69.60446643077707,25.348164558723276],[70.97395349209883,26.982017074388303],[70.70262537110939,27.470433620080257],[70.77765792628625,28.02483310567664],[70.65091231329454,28.32501286465014],[70.63078257129867,28.728854095159566],[70.720988630004,29.046062294043477],[70.8803344912959,29.25599988987408],[71.25143174444179,29.399172759548684],[71.46359987994134,29.518801728827984],[72.03550359569059,29.752252211686606],[72.71613640149602,30.833910768027806],[72.82407604411762,30.97892964549868],[73.01681880305824,30.96479988936291],[73.38293561444117,31.367311113124302],[73.42806205121492,31.5633913781782],[73.39524113061545,31.733443366784936],[73.51940847995435,32.34408955103559],[73.39504879916295,32.61486994448734],[73.41766230319234,32.92708106290112],[73.38129758868915,33.085863103712065],[73.41187623349015,33.325503304179435],[73.57892836887495,33.65806456332798],[73.5036433051249,33.74653357413037],[73.72089732892226,34.56384974587841],[73.90544803909407,35.32814991274429],[74.43047111081124,37.37794316562423],[74.08669160636937,38.24872642841035],[73.19150156322952,40.469754763810634],[72.18316299164616,42.84347803632628],[71.99718528616648,43.306189268405554],[71.37218179552465,44.713723680155596],[70.64217397000886,46.3375410274568],[69.39876532494723,46.74349855123749],[68.72570507823916,46.94772620715563],[68.04853634057903,47.17654212726326],[65.4505778080242,47.98600790134093],[64.69389598379128,47.928966339128095],[65.04036098931634,48.443674296599156],[65.48621825154191,49.069476399367204],[64.70830270168364,49.50424395143411],[64.28488296990452,49.543072993521626],[63.78699170919905,49.41290094199773],[63.44451602098009,49.459380246462935],[63.116632149593634,49.15824388705257],[62.86571240098894,49.12022798939889],[62.723091026675824,49.32108549997391],[62.56265624503241,49.38222600570691],[62.53572495069295,49.52502509962822],[62.151438814873195,49.670301127642084],[61.951253652632936,49.902684565014425],[61.8291756214382,50.2325161276575],[61.888581242108074,50.2691656120446],[61.68404876573248,50.482640448195546],[61.6074980165984,50.687326951986236],[61.702514676946976,51.08199202907624],[61.59299483089567,51.1902930339466],[61.35890790463598,51.157529597146265],[61.1148437800631,51.169891501393195],[61.00562751120714,51.01237549533405],[60.82404957201976,50.96202868645837],[60.57445023468691,51.009771017192364],[60.1947969443416,50.9505744528219],[60.0836019638425,50.88751964665941],[59.965846349400266,50.71471594807469],[59.56103537163164,50.718919256972356],[58.40753894397183,51.41366337827041],[57.72561959165896,52.4864078667236],[56.34100509915352,52.870656159114695],[55.69073238919994,53.0691546450735],[54.397822045483366,53.41716384374196],[53.107363143359834,53.76999607437035],[52.460967935690874,53.9598341733482],[51.177833514907434,54.30027165351571],[51.17289418613043,54.32793119190543],[50.211435096653396,54.58700799867355],[49.258143931522476,54.817615954241624],[48.587027072022096,55.00593611522529],[46.9711418243508,55.42373849333867],[46.3901193745058,55.59423423482898],[45.445339159115335,55.82688845194786],[44.18298929221999,56.15070875104915],[43.55177507338359,56.320426946520186],[42.29567764427769,56.63528972563572],[41.672843588598326,56.766553765247835],[41.66799408869815,56.79807308850195],[39.818270374569906,57.263171794062366],[38.38246905455713,56.968242011027655],[37.04095964245304,56.44354358062307],[35.36033273455381,55.78041479344863],[34.64072543401255,55.90944698264693],[34.06163583903006,55.02604228247535],[33.26081927492087,53.756756027819996],[32.6914593661213,52.84830092013629],[32.51653994158565,52.51347735517689],[31.993864356727865,51.644280612952706],[30.753345462133645,49.54308311812171],[30.881444575106286,49.398326873274925],[31.096770224557307,49.43701723176665],[31.294249835693943,49.425987896957935],[31.54303639493411,49.2797076636006],[31.774512751926572,49.282382032823826],[32.098777049409215,48.86788397956371],[32.31539065970811,48.640946694472156],[32.31666429901804,48.534931951492716],[33.32826966166799,48.391477980214034],[35.615865747462124,48.07607670354151],[37.48771800736876,47.8225520216253],[38.72788452904937,47.412761397930545],[39.082504549827696,47.313579215881816],[39.286146858687765,47.37850262996212],[39.59443819521896,47.32763213893424],[39.70778710053308,47.21575538972853],[39.91737994693348,47.219244392403915],[40.15585419097405,47.367550039423776],[40.43940436277576,47.396141250478614],[40.49914850221931,47.289328828979464],[40.66084595382664,47.32197579750872],[40.87542162942364,47.24308615694371],[41.08065169850329,47.301474210971065],[41.494366685987345,47.15257583974933],[41.719909890606466,47.19932657845954],[41.852028739693,47.12764778468816],[42.09739161667927,47.14640451005606],[42.37950049990607,47.03333352326877],[42.49942714902919,47.03893657627347],[42.525596454084194,46.8532145629638],[42.68578615729823,46.81900658276894],[42.40829929722763,46.55703556280667],[42.61797832862974,46.379889445397104],[42.83966315509113,46.36920619102547],[43.109209541002876,46.45702818225335],[43.285928053117615,46.282662789112806],[43.70304286545991,46.29052679206296],[43.92621181102633,46.12043214855423],[44.06457986277384,46.09640262560865],[44.24316163391212,45.92140092621145],[44.0428545453019,45.853382295929784],[44.31890531477694,45.56680599968289],[44.1865919264344,45.27053631307221],[44.29172733446243,45.05131402610465],[44.21340221742255,44.86595344811973],[43.98833184328351,44.700456374667475],[44.06356269690392,44.57789724119396],[43.91647320303387,44.33002114496851],[44.09772598824687,44.262715323998826],[44.393764136546935,44.311522229196605],[44.5773811944692,44.302406526390264],[44.85245435210603,44.52652607332497],[45.043732311613965,44.43416157466776],[45.113774765007484,44.29048998332916],[44.941117167023414,44.05828909577924],[44.79567992940876,43.58623765656174],[44.778512941090284,43.42198407920589],[45.055913811484075,43.270599531291644],[45.191957719334155,43.091901200568884],[46.30796150580435,42.997736239794285],[46.4115107009268,43.65923518491036],[46.83780180774498,43.88754472856755],[47.48470209481893,44.505341916201935],[47.77295834863797,44.991156904066706],[49.5013592685482,45.1049356440445],[51.99222072868171,44.29509974044662],[52.16340765499934,44.23448120604896],[51.32885988867803,43.600700277376305],[51.02615864677117,43.348438608511515],[50.23275586022013,42.23824805045567],[49.64587695484697,40.13809888865439],[49.6837123359882,40.140891669866754],[49.147608325043485,38.17578785493595],[49.09629566034854,38.04232376321015],[49.004177820789955,37.284631397999334],[48.849785474472924,35.73584214408787],[48.83621674825784,35.362100129047796],[48.91692561252267,33.64485185636286],[49.286773535881906,32.13290739541768],[49.78538958062279,29.952026531083973],[50.17036245745686,28.272522495195947]]]},\"properties\":{\"name\":\"Michigan\",\"ns_code\":\"01779789\",\"geoid\":\"26\",\"usps_abbrev\":\"MI\",\"fips_code\":\"26\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-66.14937212144588,20.85133770303588],[-66.51414029254477,18.28913267755052],[-66.70181901240139,17.103674779916705],[-66.91125085603706,15.644835375522801],[-67.26095392753037,13.186069034856116],[-67.51091439873119,11.44409119843858],[-67.76281911819255,9.632472520714792],[-68.00514988462709,7.929118542286623],[-68.26618779951605,6.161077689722425],[-68.44824695422317,5.064539343107781],[-68.88968021516625,1.899633559407581],[-68.89832582517238,1.0954021708186683],[-69.10350646213794,-0.3475001253983465],[-69.14812008412093,-0.7421638190247458],[-69.52407181961003,-3.427563605934836],[-69.69895376554851,-4.53168439623577],[-70.06615646820839,-7.199983140779321],[-69.11663339287611,-7.330990650637876],[-65.61542294127172,-7.791159036737113],[-62.70294771651034,-8.149849313805525],[-61.21849623307697,-8.32998402965444],[-59.21903468185595,-8.565909577948586],[-57.93017929093201,-8.712987944836321],[-57.89451012470379,-8.77233624792452],[-56.33192226014067,-8.94381983253033],[-54.14510436927682,-9.172408601251224],[-51.131652327134695,-9.471803807439555],[-48.648637511446495,-9.72092100147679],[-47.565332986345396,-9.841132605054584],[-45.4061527763659,-10.034806046172159],[-43.68534476498087,-10.185462161406354],[-40.75368422510607,-10.40416716489011],[-38.96509412679895,-10.53308834404615],[-36.16422165377581,-10.736986504068158],[-35.11137927281883,-10.814640988565417],[-34.45563211878416,-10.893299507143515],[-32.63945317059252,-11.020160389765309],[-30.763083521479004,-11.15169750347356],[-30.570701703559973,-8.1963047021963],[-30.43849538148796,-6.152684837351922],[-30.32281829261948,-4.090539137087066],[-30.14217736442976,-1.2198186228302819],[-30.012316451238505,0.8302801151070469],[-29.874707663042038,3.0733682855467483],[-29.71153210195261,5.884414175138174],[-29.54964031643803,8.565261677234583],[-29.438000494282,10.359026182719287],[-29.20939593005242,13.848449319910548],[-29.097518612734977,15.534579610933758],[-28.980312746595526,17.32193630084639],[-31.670678751960846,17.491372041018977],[-33.11141891629698,17.587866065381615],[-35.9763818856077,17.78999892346702],[-37.59322726934955,17.909202668195448],[-39.637481438049264,18.070139517390814],[-42.681102830587875,18.324642497628123],[-43.90485516831376,18.408579862527656],[-46.48869548711621,18.643306715049064],[-48.53497843202123,18.830570914072386],[-50.3124971612377,19.00736048014103],[-51.65431238331545,19.15696277903088],[-52.31716901737298,19.24645572814525],[-53.30418577750853,19.351015399427133],[-55.305687574094705,19.575618365847927],[-56.56858251529825,19.711234146408312],[-59.26929550047583,20.01429319404913],[-60.8367305258635,20.20016346781668],[-61.551479664287534,20.275157217139913],[-63.37771496347499,20.4930575128233],[-66.14937212144588,20.85133770303588]]]},\"properties\":{\"name\":\"Colorado\",\"ns_code\":\"01779779\",\"geoid\":\"08\",\"usps_abbrev\":\"CO\",\"fips_code\":\"08\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[115.53818983339688,30.238673686360286],[115.2416899437473,30.102906270853417],[115.18496969476226,29.95918116546346],[114.94967687349475,29.674189641624427],[114.8697965465033,29.413286155187475],[114.90369026318061,29.23698348118835],[114.84810740899236,28.970455127072242],[114.70248790905828,28.665911434985166],[114.44271391921411,28.42090216805526],[114.48660952538015,28.24094721803975],[114.34896809991949,28.131464269144356],[114.32385717444805,27.95272109693584],[114.03565067072951,27.734127350004282],[113.85444823060385,27.542018903386335],[113.98089511138157,27.317979291218435],[114.43990498793418,26.88869284694525],[114.42404578929323,26.71890916575502],[114.28316138773887,26.313119427628692],[114.15062318384118,26.170957171786206],[113.97433863518815,26.163071100443045],[113.88470336921932,25.97225878472674],[113.98462544637415,25.873063640049985],[113.93998181363963,25.58423452211511],[114.10642499437441,25.486398410292036],[114.02418104697068,25.31976241264672],[114.07351902104267,25.09900611363453],[114.30123990917106,24.809420774284337],[114.48226113009443,24.92120748603816],[114.67958892112331,24.905915485249754],[114.88664279725484,24.774575575199584],[115.01128353747316,24.3878052592151],[115.00755539825691,24.226893969462633],[115.11262780655908,24.016437626040386],[115.29233745699943,23.939193498348875],[115.47224516996847,24.013635809187974],[115.6394943860357,23.970012991151847],[115.88164717803328,23.609448351176596],[116.32546868724116,23.41173111710756],[116.53271748696623,23.151824014299617],[116.96848532124822,22.998303400779],[117.09999635255255,22.8266340606484],[117.30753352264205,22.70353262285802],[117.31963881777467,22.608556332272556],[117.02398570346605,22.370275085728114],[116.81842834159809,22.35097117727565],[116.7033770681652,22.06205575074993],[116.33366000659103,21.86505327977783],[115.9958939714613,21.46852661578317],[115.833389168443,21.363429550769293],[115.7328847126472,21.123117949045692],[115.52077468855103,20.991657909164207],[115.46054575482466,20.87786683943845],[115.55632026138787,20.639118897667363],[115.54483560748544,20.47650441955551],[115.26849250496733,20.34303913198919],[115.14415392161564,20.196292861405333],[114.89434386354408,20.057925048777648],[114.58593025771071,19.993365950532837],[114.21033256534885,19.588752782427065],[114.03072818528783,19.3005429810196],[113.99594774493909,19.065140862669],[113.88704537090452,18.794016708753702],[113.88778315142473,18.58139978688695],[113.70644726464364,18.28885591797057],[113.78361376110078,18.104656589614695],[114.03651441492316,17.992103847073],[113.9824477041137,17.726551396530862],[114.07957051855006,17.438305039440976],[113.91679586429542,17.387942229797382],[113.97079480848309,17.1165160505407],[114.74329480156902,16.56901213619873],[115.56469745619918,16.00870218207952],[116.09354034800272,15.465188193861561],[116.52528559714304,14.98866147580366],[116.68527317135417,14.860377313688904],[117.92824875280894,13.214670635245072],[118.35076623968659,13.92753447554447],[118.82280226714899,14.222363766941614],[118.97788624678365,14.549575357530742],[119.17159769519772,14.81692292270676],[119.18438698205458,15.069286337008068],[119.29799856722684,15.383709033587326],[119.45632677848018,15.665890430679623],[119.39650539143312,15.867799770003298],[119.70126071984564,16.678991610503196],[120.07119174520932,17.3346455696993],[120.42274694502825,17.491548925883112],[120.55890871674954,17.61671035644726],[120.91546488181305,18.37488808670896],[120.95298939289728,18.59483347137621],[121.17407762707487,19.2060162191185],[121.44159097956675,20.079559131527244],[121.58562761832192,20.8054685092312],[121.47593502287309,21.643198911268],[121.41530307452798,22.32728311721048],[121.37294265519013,23.216856410003082],[121.45608651657416,24.356631665948136],[121.39723856022526,24.819154299357645],[121.1592524382962,25.7364043746665],[120.76905083575932,25.891243646110137],[119.39170831154998,25.27872966646907],[119.1973251393997,25.37008378935204],[119.16886736103139,25.661777409463244],[119.31268875751685,25.7927850958454],[119.34513165471877,26.06899975016623],[119.27629641469133,26.27581934397854],[119.3598089971422,26.396416094220587],[119.56615648678113,26.411013774776578],[120.00755349656075,26.567263825209537],[120.0820270197065,26.900703189700813],[120.05374201698032,27.26789417079528],[120.20139676319339,27.759617542363213],[120.27289038605407,28.177875698876736],[120.29218983617032,28.786245451731943],[120.23410618546232,28.875251524958042],[118.57689714869815,29.336999906260125],[117.87303630019342,29.522150469521574],[116.94347315625474,29.820239250732623],[115.53818983339688,30.238673686360286]]]},\"properties\":{\"name\":\"New Jersey\",\"ns_code\":\"01779795\",\"geoid\":\"34\",\"usps_abbrev\":\"NJ\",\"fips_code\":\"34\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[112.34975158979492,18.650546356829768],[112.4627006754848,18.156026599618144],[113.01207230094438,16.28960854584161],[113.46694209900181,14.730132793450496],[114.23519404399333,12.070079948009354],[114.78134545014746,10.16153430152521],[116.28857264095097,10.430831770715029],[117.28892724536449,10.638673039837714],[118.61556646702499,10.932796329643827],[118.50065360503365,11.461773231194927],[118.32247876970006,12.032106329575942],[118.24669672995006,12.153927251458903],[118.15533158564351,12.525340868157953],[118.0019543708621,12.91711816126738],[117.92824875280894,13.214670635245072],[116.68527317135417,14.860377313688904],[116.52528559714304,14.98866147580366],[116.09354034800272,15.465188193861561],[115.56469745619918,16.00870218207952],[114.74329480156902,16.56901213619873],[113.97079480848309,17.1165160505407],[113.91679586429542,17.387942229797382],[114.07957051855006,17.438305039440976],[113.9824477041137,17.726551396530862],[114.03651441492316,17.992103847073],[113.78361376110078,18.104656589614695],[113.70644726464364,18.28885591797057],[113.88778315142473,18.58139978688695],[113.88704537090452,18.794016708753702],[113.99594774493909,19.065140862669],[114.03072818528783,19.3005429810196],[114.21033256534885,19.588752782427065],[113.81816964895312,19.697833632565867],[113.3624134230362,19.674483799978663],[112.97736474959864,19.52229975310984],[112.78408256181218,19.37837366103872],[112.61292384595062,19.179403594401926],[112.48155272207383,18.91447266458286],[112.42844151920987,18.666657043059057],[112.34975158979492,18.650546356829768]]]},\"properties\":{\"name\":\"Delaware\",\"ns_code\":\"01779781\",\"geoid\":\"10\",\"usps_abbrev\":\"DE\",\"fips_code\":\"10\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-90.99343519826625,63.834866645392054],[-91.41635087465394,62.951215068861096],[-92.12512112168136,61.414518536597],[-92.50396477001635,60.5394039234623],[-92.42654120740224,60.515035892910866],[-92.32081079622053,60.33939117682492],[-92.20940053753245,60.27195242513569],[-92.00817316476744,60.00722833460322],[-91.9046975748262,60.02002401825045],[-91.79857342357916,59.93025557513477],[-91.83749835970153,59.77555133528439],[-91.77893721381261,59.651948140825134],[-91.6421265967429,59.65701213145886],[-91.61821207997384,59.55113007753327],[-91.38014835456212,59.40011618879797],[-91.50369159991641,59.26422676665507],[-91.35051293426332,59.131679780430396],[-91.37974856645086,59.03427364236548],[-91.74483633320732,58.905974470944],[-91.73882883990369,58.86848022964928],[-91.350411674692,58.58605214349638],[-91.36613665358335,58.50819215059524],[-91.58643201014226,58.53439482408306],[-91.88079348768994,58.50291529840974],[-91.50139388001578,58.310380170017936],[-91.46911925870806,58.21873951307168],[-91.33550560925899,58.22300804040478],[-91.06924762680902,58.06946332140919],[-91.02221584417812,57.8691026280469],[-90.57616256121698,57.74601367557616],[-90.51684077755878,57.67687336365893],[-90.11883899634559,57.60393676864678],[-90.03250514708965,57.45589230805563],[-90.10669013409726,57.33618936893601],[-89.93147882930324,57.29169977353459],[-89.89746475748288,57.171158285264944],[-89.6675977639288,57.06724468915313],[-89.38061821946852,56.706702536752594],[-89.29312017220623,56.68290530647265],[-89.20931749210565,56.36984531970081],[-88.98692034693876,56.33750293568479],[-88.84282516685656,56.167498432487626],[-88.70020652278224,56.11145191582895],[-88.77891863338168,56.02962019227588],[-88.79798552505606,55.749480552514456],[-88.52902863952066,55.612440143117006],[-88.42830322502415,55.52712586776113],[-88.22661417140371,55.492786770335414],[-88.14841348308701,55.39044117976327],[-88.32339879225603,55.22868028898832],[-88.16591421273772,55.14179592502507],[-87.9816331718592,55.18953500487746],[-87.84367214442429,55.28367075464401],[-87.61662550571948,55.21756280462395],[-87.52476248489695,55.110340190637416],[-87.66266205060518,55.01023360186045],[-87.66337470678454,54.92709298476934],[-87.4977679728829,54.797797366038914],[-87.25191062609973,54.81959701627397],[-86.88719563415295,54.72257775579357],[-86.7993596920961,54.78482327351337],[-86.31494178946598,54.81813927243801],[-86.16106293923929,54.7042627986891],[-86.30210498968837,54.42077242248435],[-86.42265359423855,54.333115164372906],[-86.48191742915492,54.1646077516203],[-86.76109955960237,54.14753230242258],[-86.69928476341256,53.9766116718056],[-86.79164643111528,53.7805328330216],[-87.00792899386948,53.69975989154768],[-86.97742912027599,53.603939234872925],[-87.03588603672476,53.48287049077054],[-87.1731768270893,53.36737747391748],[-87.17327549752103,53.272605467364535],[-87.41024844148977,53.21754974173016],[-87.3523527634422,53.10263318284562],[-87.41895301524542,52.79674976784032],[-87.5850017645591,52.784664323680566],[-87.76432916776442,52.828219528883956],[-87.84721014767415,52.66062826731338],[-87.63777243259838,52.58227085494244],[-87.59014630882645,52.491443373485716],[-87.66497903625023,52.37555365818734],[-87.8123560895538,52.33437364803742],[-87.71113994624982,52.077011255899215],[-87.52654763442725,51.97421561208454],[-87.49837901563323,51.874647720223535],[-87.6693291883066,51.796402269181314],[-87.5403452327651,51.626505307349404],[-87.63951214833872,51.492722520400555],[-88.17455895298858,51.4514457301838],[-88.22610962207092,51.373152717426606],[-88.5512003725821,51.17250958788527],[-88.50375258753195,51.02855413381185],[-88.32089454601713,50.90571278322607],[-88.30282841642895,50.81903395110718],[-88.42618328592464,50.624204594370646],[-88.7340177938925,50.542759837208486],[-88.64797884799707,50.41295908894331],[-88.77363663006271,50.33242009030501],[-88.7647038306975,50.217392408825646],[-88.59871469024829,50.231482098753055],[-88.22257113620375,50.00305510236464],[-88.23474864696128,49.94529062236458],[-87.95952075057849,49.78772095106933],[-87.90015643438022,49.63685509297375],[-87.54882548081235,49.68987652954162],[-87.34594781187238,49.85221273591023],[-87.30997861405102,49.96368314574297],[-87.04860221410887,49.88975569313734],[-86.9658905735545,49.94883793620986],[-86.73801251215868,49.94867945758191],[-86.63838934975551,50.061408582562926],[-86.45382491235286,50.07082617803106],[-86.31162290754847,50.22326350885041],[-86.00679243496165,50.327515460044175],[-85.98725298872998,50.485677617049355],[-85.75030835582457,50.50237856587903],[-85.56913730826788,50.43529664262099],[-85.58394801764423,50.344100429635304],[-85.45327573366642,50.20635344818566],[-85.51642541948686,50.112036719578484],[-85.30986450528229,50.082228505722185],[-85.06678646632047,49.95165052788023],[-85.0458345314222,49.85498940129217],[-85.1870143858536,49.79712681303831],[-85.30840121452346,49.59911924078826],[-85.01665261539411,49.533962967809266],[-84.9993154210179,49.37007934846713],[-85.08962376600805,49.337981113763774],[-85.16585504224567,49.04208703217728],[-84.98963597106976,48.95207646778573],[-85.09383563419907,48.665584013478345],[-84.86974286658273,48.37953487184669],[-84.95384953397543,48.33313948787357],[-84.78231179104611,48.16854881197474],[-84.745929654667,48.05862268880722],[-84.56412767401085,47.898233345902405],[-84.58863707592522,47.75829072125636],[-84.4764194079906,47.54785162676585],[-84.26401638805181,47.521002199437994],[-84.32663983269494,47.41992722656075],[-84.0335909776272,47.21658257576567],[-84.12264141952404,46.73540451045286],[-84.38841243135218,46.71300403830784],[-84.40628757892017,46.5946370106507],[-84.3038021215638,46.393079880033326],[-84.30178812523009,46.28586528414839],[-84.169265178733,46.15406099229678],[-83.86960369688889,46.00067766969817],[-83.83769448096555,45.819022594583174],[-83.32460238549132,45.94687205979752],[-83.08346749201243,45.80840158973612],[-83.03375974824513,45.728560842729706],[-82.8129762086703,45.62550596824374],[-82.86130477156503,45.56923407915845],[-82.72412071875146,45.39342281698656],[-82.74388698226296,45.23728569391496],[-82.61637450871036,45.12621104425848],[-82.63924833171296,44.81654838166224],[-82.81898624987483,44.739566570961934],[-82.62089346172597,44.54646221051603],[-82.67837921053717,44.43513963746094],[-82.50937302597359,44.31944777958712],[-82.64822881649037,44.18046638688894],[-82.58749198793586,43.93804658520494],[-82.36890290625163,43.730717552634594],[-82.22028901093569,43.67396833298079],[-81.91109750292608,43.36304256488154],[-81.72641289912016,43.439142420649766],[-81.70267510852527,43.54672716294243],[-81.80401400972366,43.70077539154197],[-81.43373362060049,43.967093904978405],[-81.09281334991884,44.02471909461615],[-80.84800443552457,43.90027207400634],[-80.53474508691889,43.883849267668126],[-80.28039897702233,43.820052764126146],[-80.05621236419333,43.67685196894061],[-79.89971737779798,43.74612687599422],[-79.52236598434114,43.54418750890504],[-79.38129631598272,43.61418443147915],[-79.30158777261782,43.736796915286696],[-79.26836407687915,43.922320786583825],[-79.05730821308192,43.949572000077964],[-78.85653167907765,44.07780097022259],[-78.57573670033504,44.01962520032266],[-78.56465908033108,43.911276588818225],[-78.38864519691947,43.834689281585355],[-78.11799127036235,43.82597236060136],[-77.96231863029739,43.73036316694827],[-77.61617064853019,43.81044331775267],[-77.35944600602579,43.72260280667109],[-77.18025071348845,43.810054919437704],[-76.76401798617766,43.790887663761374],[-76.60425045804242,43.490552199130015],[-75.95746575036304,43.679547979267795],[-75.53545139233373,43.58052237151758],[-75.39349032218603,43.61855010792095],[-74.95709461329355,43.46346936110381],[-75.0128580238525,43.800352171404],[-74.68258494617295,44.07470141255043],[-74.72429126757649,44.259252097126875],[-74.5853633368475,44.31699547280972],[-74.23461802757296,44.33255893244024],[-74.09037864795337,44.447872949269666],[-73.92213571020076,44.33984153184105],[-73.74908754853588,44.147748418351085],[-73.60652899241313,43.875666678765185],[-73.4597138284,43.71009151775263],[-73.58064179607796,43.680285681644484],[-73.56422738941274,43.497389971920995],[-73.32828284565808,43.41355467461046],[-73.15939330000766,43.02899130161172],[-72.77560308200106,42.84692105176968],[-72.63669976978649,43.626570796806],[-72.37254577993053,44.849779228508325],[-72.21237114457928,45.5183957061764],[-70.86627676112258,45.3771927038873],[-70.4811571129134,45.28082894466711],[-68.97418089510765,45.124516625112186],[-68.76653618898702,45.13675348007],[-67.95841571959816,45.027894457441185],[-67.5048813366632,45.019192915399955],[-65.71647001771628,44.82048411587307],[-62.48386084323597,44.51548585059443],[-62.38038055042575,44.47219881571612],[-59.47341198213282,44.19242807378208],[-56.9654127029659,43.968087215455974],[-55.73300624460669,43.852502825925285],[-54.47932958715376,43.74507991483814],[-52.65069038701232,43.57951142612157],[-52.40725562614251,43.540504349825184],[-50.109743107180634,43.340412258518924],[-48.23345722196269,43.183261000037284],[-46.6177589120615,43.05738064153352],[-46.538175226511676,43.08955633160228],[-45.56072716424567,43.01233522835387],[-43.216763290047105,42.839625066021696],[-40.821491912220374,42.66703942418085],[-38.784869254483475,42.52617515068762],[-37.234577658947664,42.42305144937504],[-37.13811506639031,42.489656404850585],[-36.919407303376566,44.36501718168924],[-36.704904486512795,46.243773439609534],[-36.601311095276046,47.195627540121286],[-36.49208434197749,48.05504201486602],[-36.25964444908887,49.84649416289695],[-36.06701163775746,51.325402443626004],[-35.88879595881776,52.5738490171517],[-35.68648115023779,53.98664740695673],[-35.50695311231086,55.1753631206094],[-35.43052107845807,55.627674548321735],[-35.378820674899764,56.047480593928064],[-35.221144971110206,57.135444477530015],[-34.959251090885886,58.84084449641368],[-34.786523235013895,59.89079855553816],[-35.73933825884607,59.93041331386863],[-38.09962945578851,60.03459235907528],[-40.896495184149664,60.1687219542633],[-42.59432583426335,60.2548954116561],[-44.519947605207804,60.35254831025635],[-46.90038912834338,60.48307113177894],[-49.119221047724245,60.60973086816222],[-50.776506780400226,60.70415385802943],[-53.62606782199636,60.876965288466906],[-55.17094078297601,60.97349546713723],[-58.06724593180032,61.16145137854979],[-59.444082336838946,61.258037705936026],[-61.534686036425285,61.399462332904776],[-63.81493740006523,61.55829263735242],[-66.89341000218045,61.78011241832297],[-69.82297866775045,61.99874512942189],[-70.99049295183347,62.09086773176718],[-72.74188924390172,62.234472534264356],[-75.41972071352872,62.450553864043385],[-77.49342232101375,62.62159316374288],[-80.05228200454195,62.83650042599689],[-81.75542928860287,62.98810664097555],[-82.64495044574755,63.07278483319819],[-83.97899939064709,63.190550155371085],[-87.09407343863616,63.46689775253752],[-89.00351191560054,63.64741739578105],[-90.99343519826625,63.834866645392054]]]},\"properties\":{\"name\":\"Montana\",\"ns_code\":\"00767982\",\"geoid\":\"30\",\"usps_abbrev\":\"MT\",\"fips_code\":\"30\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-99.65519411180787,53.58499338642594],[-99.68223395417837,53.87178181175595],[-99.8216274627491,54.02710355393097],[-99.70466843345119,54.03817265798496],[-99.4303415629969,54.26579034899642],[-99.56445665148964,54.44882767903119],[-99.49003797461663,54.55054032013414],[-99.50396219063431,54.701235796161285],[-99.58089264166192,54.77482846109161],[-99.53167768988631,54.859615721032945],[-99.66690912449859,54.958591348229355],[-99.64548907739726,55.03325654414974],[-99.77708485935077,55.08610085813727],[-99.58267209149332,55.3973378692192],[-98.79056689214441,57.342171940348855],[-98.28299667879376,58.545182753007495],[-97.91977020108561,59.37618962122769],[-97.39248219999922,60.57634322079348],[-96.94192975715798,61.53062510842902],[-96.50988131802622,62.37151843638346],[-96.33364860617566,62.73591851668078],[-95.73753287537865,63.893285516375784],[-95.54646537387694,64.26681900007063],[-97.53837484037697,64.46731697993452],[-99.44110781310616,64.65662404910464],[-100.81930075012676,64.79459952919403],[-102.48345632099776,64.96360472093977],[-105.19987037229137,65.24393198186041],[-106.34934619948946,65.36378540905643],[-109.22100960173958,65.66430603328233],[-112.498952391197,66.01815244125093],[-114.37145969167103,66.22520014826537],[-117.23520842102238,66.52815045193634],[-118.8101982637074,66.7167483185535],[-121.72475789796964,67.04309622831882],[-124.3831769921858,67.34399506028396],[-123.293015419521,66.73008256547625],[-123.42139885363532,66.55846847782396],[-124.77972102985316,66.49967391478872],[-124.7786801004886,66.348188388889],[-124.8161829861873,66.07756970438797],[-124.73827983619687,65.78332723888803],[-124.5921596938622,65.67213281386542],[-125.4815907237206,65.35735607811593],[-126.94963676494348,65.35595255346975],[-127.57132394647111,65.48250213903567],[-128.96531151650922,65.828316660273],[-130.41651396640165,66.31063990319916],[-131.90937986713672,66.79284787352726],[-132.0400098708331,66.76907618856609],[-132.00748962202024,66.64610502305823],[-132.34704628360078,66.60631073256985],[-132.45696036919784,66.52557205543809],[-132.45115748665856,66.40263869970438],[-132.37482751893484,66.33198723403544],[-132.39207601343705,66.25126775644908],[-132.55580171524363,66.20205911466523],[-132.6209246654782,66.07937704885481],[-132.86800923428171,66.06641031592243],[-133.04762912897093,65.96936670299358],[-133.02981791224823,65.8318799390839],[-132.94066072170656,65.69210054386365],[-133.11797882391463,65.56420318966217],[-133.2056278317353,65.45875909750912],[-133.23270961889668,65.3447754607132],[-133.11849891292357,65.27498399363415],[-133.16884652003347,65.17764666001473],[-133.06410679046755,65.09467334284649],[-133.00416335882662,64.94818346274172],[-132.82307404119018,64.8649938746873],[-132.81434748603016,64.79979461264502],[-132.70468700149738,64.72573447091396],[-132.67103651083383,64.61451669474194],[-132.5623726389975,64.49299486960399],[-132.72581443908248,64.43064773909958],[-132.82046983957392,64.34304591700831],[-132.7908332623952,64.26577922702609],[-132.57539404747598,64.18116527213546],[-132.40456561016293,64.1763308604623],[-132.42035914853355,64.01257088349817],[-132.51371971451664,63.71826765285264],[-132.58334842752473,63.64370240456914],[-132.5766764600357,63.55058374165584],[-132.71968046466628,63.455294391152336],[-132.71124014016522,63.24268392044032],[-132.60645690298563,63.15979268613242],[-132.63633673622647,63.053358325329036],[-132.48622649633472,62.93703371944239],[-132.54457204730832,62.429468941574456],[-132.70036786204207,62.11712918060426],[-132.82903656840477,61.984117382952476],[-132.8927710506802,61.765373993108454],[-132.8101108497485,61.69578476869526],[-132.8041866279932,61.586527273023314],[-132.88773345216288,61.284438501949616],[-132.93549176337268,60.94939965739423],[-133.01654908896936,60.754429017093976],[-133.4854733035522,59.973862644384155],[-133.84253014526234,59.65504217364156],[-133.8503386459278,59.61639161524287],[-133.25962723946873,59.53678584280185],[-132.96557011394586,59.51311558933344],[-132.8130735313798,59.37203631028614],[-132.5436217350538,59.315883888556094],[-132.06021951024687,59.31475072226461],[-131.85607627982253,59.3399499769877],[-131.63991013813737,59.23492408744335],[-131.438507556662,59.23670100086436],[-131.15167507577817,59.18171382906721],[-130.61645853153436,59.150348000802964],[-130.4506287012739,58.97314197545696],[-130.5633518385455,58.80712350711431],[-130.3519770320266,58.63887025760216],[-129.9217127775598,58.566673406925126],[-129.29283171213697,58.640831928092545],[-129.06044777755628,58.590962904958424],[-128.63277003082533,58.325846621324],[-128.4930400450816,58.19120821410508],[-128.2564023464232,58.07226536102502],[-128.23678108583408,57.86289716690573],[-128.16569467432342,57.785499330742375],[-128.1442373113732,57.64887747695836],[-128.07088508519053,57.55864579906777],[-128.1546766830405,57.379205232086555],[-128.11754727567933,57.19287748416701],[-128.28247045232592,56.986235780373484],[-128.21611034477448,56.85661818570046],[-128.26248158058482,56.67620580550404],[-128.43838820341188,56.483604202493375],[-128.43329564803875,56.387177586015],[-128.08495646748935,56.170175568725135],[-127.50851628723443,55.98727378298148],[-127.05781411821225,55.77926232562888],[-126.75328065474334,55.781944972649086],[-126.63388274438591,55.73569302269658],[-126.57291380035855,55.63729795215453],[-126.27003378394186,55.57027739471496],[-125.91813747092297,55.598992048748016],[-125.80657094722989,55.637352204710666],[-125.39137765019946,55.59902176696066],[-125.07422158008288,55.654457113575255],[-124.86635000232852,55.64832095497419],[-124.33388029241551,55.72483157589509],[-124.0619081047129,55.8439500980345],[-123.76785491627473,55.85395637444821],[-123.42618967138792,55.74765202283844],[-123.08455027182,55.740330129388454],[-122.90667206011003,55.710849543479945],[-122.39407998112577,55.72236150057828],[-122.2417206172067,55.67501092615321],[-122.1040318858975,55.57234078073915],[-121.82622329232774,55.49375992049335],[-121.49595474081877,55.49310204314805],[-121.28698046540413,55.38559627782858],[-120.97229504164495,55.27054817928192],[-120.93958678014943,54.991759163272576],[-120.68341633007745,54.96597081747248],[-120.38590693802111,55.08132711416142],[-119.69111040966355,55.01025475758482],[-119.4828554453222,54.9209552061232],[-119.2378409957217,55.004477088485636],[-118.88897141877919,55.01322272836178],[-118.35537528474254,55.056934942157824],[-118.0385188401837,55.13541451574395],[-117.6885954438038,55.05075918873896],[-117.50002285414476,54.86001751133606],[-117.39691272592924,54.820595370292246],[-117.01022472268563,54.7823706570807],[-116.39418433179655,54.78198405922647],[-116.03047573944093,54.748089872545464],[-115.77156990824062,54.862099047682854],[-115.61123542291706,54.88559734276033],[-115.24660897778101,54.882056544138436],[-114.67361188527518,54.962905830954696],[-114.38608636701545,54.93634998022849],[-113.84673855409517,54.93887910026438],[-113.70692915921758,54.91008922985628],[-113.19042230943441,54.882120220924996],[-112.8808795520754,55.04283082528548],[-112.59665543746394,55.08498274966601],[-112.39993935305469,54.98549503910587],[-112.22662896903906,54.95068203534847],[-112.02635557843034,54.96830191126389],[-111.6052602764371,54.925441404957596],[-111.38336336352003,54.94234321142187],[-111.05676395917158,54.92387040992788],[-110.78055668840496,54.833317425101704],[-110.43737054220327,54.805259329873344],[-109.79125177413361,54.909244045088414],[-109.65285970941079,54.97637817018781],[-108.59426227063867,54.83187980576948],[-105.9817612815105,54.46445968345878],[-103.52732086629378,54.12519621948708],[-102.37941560477907,53.96033693072191],[-101.691203591329,53.87127990504971],[-99.65519411180787,53.58499338642594]]]},\"properties\":{\"name\":\"Washington\",\"ns_code\":\"01779804\",\"geoid\":\"53\",\"usps_abbrev\":\"WA\",\"fips_code\":\"53\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[129.30327410471773,37.03541946101093],[129.26805403031352,37.119929047734686],[127.87137523133403,36.854952247282085],[127.51089307782698,36.79880398548543],[125.54113438178682,36.41884398437745],[125.3183077228798,36.34888357361547],[124.65945060831648,36.25598685583092],[124.40779282543596,36.16280712114335],[124.37795074982209,35.99643131545959],[124.13194559515136,35.91445198693301],[124.08079089413489,36.137057889640005],[122.9704822745992,35.930828478882376],[120.89304614664769,35.58742343807646],[120.61600394280916,35.52724198182706],[120.68775435052373,35.24283992721372],[120.98504303618371,33.8325032988035],[121.16732624740258,33.01164532026735],[121.55903641091639,31.05854200775914],[122.0513799029547,30.63558854486694],[120.96999729152816,29.694488689396216],[121.49495941687137,29.231752746392697],[121.5328256871971,29.063050584249776],[121.81707867280363,28.905483575869155],[123.57606149475072,29.759976799319713],[124.7664030636971,30.405228390517564],[127.04730839080045,31.469680276885715],[129.1985508886449,32.466032060031544],[129.46992665613607,32.702295566453195],[129.8963861681831,32.91842215972009],[130.0212371708676,32.908748720381695],[130.05319408964203,33.076378641754495],[130.24804048899696,33.059689266235935],[130.35340574319244,33.2172399542754],[130.1648509582426,33.59971196562281],[130.3812406841101,33.684456639040995],[130.0017956483847,35.06950927592851],[129.44964621865003,36.60799814422786],[129.30327410471773,37.03541946101093]]]},\"properties\":{\"name\":\"Connecticut\",\"ns_code\":\"01779780\",\"geoid\":\"09\",\"usps_abbrev\":\"CT\",\"fips_code\":\"09\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-131.17850221701602,-20.382726770827713],[-131.6129192894161,-20.287188797149785],[-131.82505051014837,-20.34811913471495],[-131.94376163009161,-20.792489385199953],[-131.79604962231514,-21.084825131751277],[-131.54018141682553,-21.150256188811163],[-131.24599015086787,-21.109530449504064],[-131.06706603092516,-20.841897094575582],[-131.04001201925482,-20.66327185750264],[-131.17850221701602,-20.382726770827713]]],[[[-145.4387453198595,14.422684597889303],[-145.60139226178694,14.514638310381127],[-145.72603797097008,14.807357816584377],[-145.9632856783172,14.941502338513263],[-146.29770189823802,14.813698245069064],[-146.37659142862762,14.627242619078269],[-146.25018523432956,14.269893587945978],[-146.04862260272068,14.171079941179284],[-146.00078080360188,13.86147182983078],[-145.8346425588736,13.61143922921644],[-145.70443920278126,13.535867449864158],[-145.37878901513162,13.614552086305283],[-145.2381031642418,13.824310601739763],[-145.2450403308969,14.077986401890502],[-145.4387453198595,14.422684597889303]]],[[[-135.2844817668789,-21.53496218121574],[-135.22092918093222,-21.711704718405034],[-135.07825745117762,-21.88581990690057],[-134.74412071354453,-22.095112081153644],[-134.30463160452402,-22.200683823162205],[-133.92814839778035,-22.115543760494855],[-133.754664522866,-21.81349548361789],[-133.8180409241948,-21.62283484491228],[-134.0300697628802,-21.357540236757742],[-134.53616969635843,-20.990466729302057],[-134.93780115514062,-20.958080781522014],[-135.16633598332444,-21.04774092312906],[-135.2823427343995,-21.241369033290887],[-135.2844817668789,-21.53496218121574]]],[[[-128.42737515552815,-25.493535605051104],[-128.97632372910644,-24.794852849443853],[-129.13775869602162,-24.325734799173958],[-129.32646812987755,-23.96437117205674],[-129.5145990228086,-23.838653276106175],[-129.88536497929195,-23.785876408328733],[-130.1385215739895,-23.949414206974733],[-130.19080535605502,-24.27035756940463],[-130.04383878543644,-24.302918053926433],[-130.04894235478162,-24.53338879746007],[-129.9586251599906,-24.807749994448272],[-129.95289512508106,-25.058300391854466],[-129.74404972109227,-25.622489468326414],[-129.59386267858648,-25.760910377762595],[-129.44886581294784,-25.991976972564782],[-129.26041518445876,-26.144013414621732],[-129.09365962159978,-26.167581020357716],[-128.88934559319102,-26.09024842114154],[-128.5675263307078,-26.13773882300761],[-128.3091436607427,-25.947852365103653],[-128.2940544653923,-25.721549073459677],[-128.42737515552815,-25.493535605051104]]],[[[-127.4058040557432,-22.05855531399549],[-127.42079800424165,-21.935791426007587],[-127.61673285376818,-21.68113651555766],[-128.02055801652634,-21.422474338460063],[-128.16545639835968,-21.227858095987315],[-128.44814939106448,-21.04602827014757],[-128.9556195193511,-20.919648485212132],[-129.15658081605199,-20.94704447057632],[-129.3054258773444,-21.059991175620308],[-129.37357541087277,-21.2698971627499],[-129.34855194895735,-21.427187993568335],[-129.15336466308506,-21.79429811893915],[-128.87428913459644,-21.98089747448123],[-128.90744577341547,-22.245543079933928],[-128.8119425146086,-22.566250314739513],[-128.66469093135882,-22.733650655824082],[-128.4958620567543,-22.824861155459942],[-128.1017362887328,-22.937053801888],[-127.79875747978689,-23.121276180907348],[-127.64888927116846,-23.134403656191957],[-127.36141468093807,-23.051544953999834],[-127.18947357190163,-22.813782155218878],[-127.21874648321005,-22.41605985769362],[-127.40131161041181,-22.1489205459123],[-127.4058040557432,-22.05855531399549]]],[[[-132.92865969424352,-16.262650234514652],[-133.12573251575242,-15.998958627067168],[-133.40224282852373,-15.822277260306446],[-133.69577036101992,-15.817607487416339],[-133.97675425784107,-15.950231381721608],[-134.15122589594486,-15.738893032535472],[-134.4192910148245,-15.558365159252203],[-134.69642851315322,-15.522628448420347],[-134.88238034783012,-15.388607018177224],[-135.4275530384229,-15.17344714819922],[-135.66677373671166,-15.238237275506727],[-135.9323888809301,-15.42805699911121],[-136.01806051522837,-15.32872087687679],[-136.365435183373,-15.251506969399271],[-136.90462591680912,-15.210276350223793],[-137.06149359441972,-15.275511731296136],[-137.3960779858134,-15.196350367662532],[-137.509859521398,-14.904246832853774],[-137.76657069832302,-14.645808873902633],[-137.84993810185262,-14.429789290338167],[-138.1503001237076,-14.268549016208034],[-138.49821657711118,-14.370497638634557],[-138.6150328603649,-14.22693122686461],[-138.84496325392018,-14.164860737929743],[-138.99233059557253,-14.196891459906798],[-139.1737239927219,-14.389239134767724],[-139.18379536571922,-14.613776184640564],[-138.94349853985463,-14.853720947648196],[-138.98533768370223,-15.109130899822413],[-138.90776896328487,-15.292998685458858],[-138.75968984878554,-15.43871018789345],[-138.44563280642325,-15.511646733286865],[-138.2210033566794,-15.682990961360648],[-137.8206449137407,-15.730803781229236],[-137.74422413796253,-16.027789049484717],[-137.53104087918425,-16.404871037254978],[-137.14021555934286,-16.78255683726637],[-136.94052683963054,-16.84921651223538],[-136.40738418905107,-16.781171092504092],[-136.14918954715296,-16.690299325208386],[-135.82942063291668,-16.675672486314156],[-135.63144130089213,-16.481205790708334],[-135.43936366652213,-16.733503009746922],[-135.23922820674719,-16.86389214435104],[-135.02746011585378,-16.839490894227925],[-134.56079907417592,-16.951619446841118],[-134.10849323558412,-16.86381722757908],[-133.91390413308162,-16.921770709757894],[-133.49641924536826,-16.929934962047458],[-133.2069707108279,-16.763359888833538],[-132.9171336952313,-17.02692536826892],[-132.62210527329287,-17.114072026865585],[-132.2316833324512,-17.05687209373281],[-132.06624760104205,-16.938175221364443],[-132.00849155519242,-16.789549438775836],[-132.02940558732826,-16.590811967683404],[-132.2040959934733,-16.395999703180596],[-132.92865969424352,-16.262650234514652]]],[[[-143.6076597113233,41.56756840099671],[-143.50920934233739,41.51440644457659],[-143.4733481936462,41.3028385081228],[-143.57687472132207,41.11880446302514],[-143.54307094937292,41.014634747053286],[-143.67976474057775,40.798861036669564],[-143.81689698424026,40.826733588965716],[-144.02673054900848,41.03322399235193],[-144.26865270152584,41.06381637159915],[-144.44216191787757,40.95922036800607],[-144.52957589658294,40.79764697452787],[-144.449710111365,40.54305559490519],[-144.17484046477097,40.19593312336488],[-143.68475570391436,39.777876198224014],[-143.7663522901678,39.56854616040584],[-143.7289269593416,39.36534467663129],[-143.79176370044215,39.1812523105204],[-143.71494076434982,38.98458642702632],[-143.75332555162933,38.85074213370999],[-143.75932305209778,38.531292835731925],[-143.94989539561638,38.0906672804498],[-144.1812387483372,38.19024434208239],[-144.47122847946437,38.10961560303907],[-144.572840027539,37.86236581389353],[-144.44998410724745,37.69444192182736],[-144.2494377163788,37.61362268915546],[-144.64666105621703,37.08420894235834],[-144.95034217696332,37.00072168923759],[-145.04300473924133,36.87766039483368],[-145.10544705068,36.640788593370665],[-145.05430469439904,36.15240953053573],[-144.96641722615732,36.05581026609334],[-145.14941273123327,35.79179006444185],[-145.49930621642324,35.381837197428986],[-146.0532315792522,34.90043260505566],[-146.61831160935583,34.43178359508534],[-147.31928550012597,33.73769322541881],[-147.42208725567303,33.53447227340623],[-147.61275439106356,33.37421042508896],[-147.706788581836,33.1613352999037],[-147.57521337060717,32.76583511475176],[-147.6168390206271,32.48390205170366],[-147.78142518659604,32.196920219237484],[-147.8041028911657,32.01263865642813],[-147.7148937354862,31.814759123120005],[-147.3255240650342,31.24990768962291],[-147.25273510702738,31.20485641905979],[-147.12623534817348,30.91197472396211],[-146.79843185419676,30.64807692290766],[-146.76183304097685,30.55111434573867],[-146.8332573225932,30.30104206689884],[-146.73232434167915,30.06545024797837],[-146.5907495776314,29.985241663333206],[-146.45284825238804,29.680009588380553],[-146.39751307996002,29.421105585740715],[-146.29445761514737,29.282567990407586],[-146.29379012343037,29.129944859917615],[-146.19318391066406,28.849183448405476],[-146.07939427824198,28.71768911398382],[-146.18618057554554,28.162949799271978],[-146.06187346709666,27.886062356733632],[-146.22176656120095,27.286219542328052],[-146.2053934058732,27.087175683178685],[-146.4635666502573,26.882539628803393],[-146.6808446666129,26.569833438624624],[-146.95121682276107,25.8633682317699],[-146.94698331827993,25.14186011863735],[-146.98816156503855,24.825453992587803],[-146.92043907388828,24.517806105042705],[-146.8798114911395,23.82080999169841],[-146.95322840867976,23.61792866247386],[-147.17142859332222,23.548328372690186],[-147.29741322773242,23.385499809660278],[-147.30502475550728,22.920846212087145],[-147.13401885403113,22.621791571818505],[-147.08916948838223,22.4042687337109],[-146.7840225042944,21.833701302310075],[-146.60647230883583,21.65908311067193],[-146.5794447273825,21.493285972412597],[-146.29872851479237,21.15839978978843],[-146.1467035271445,20.65210298400313],[-145.9431914860022,20.16701372055273],[-145.85663935074754,19.83413465679333],[-145.68355334529048,19.704022286548025],[-145.63374686031713,19.549134202757376],[-145.47760349407702,19.3561502876163],[-145.24955187492756,19.181999913353764],[-145.1604628793801,18.879714052593712],[-145.06404058451184,18.749668113487267],[-144.9886184900421,18.42588792202618],[-145.09020643720936,18.101155763856966],[-144.8136063178617,17.449225295643714],[-144.75050674795173,16.953252353735298],[-145.2396059492182,16.280204414153367],[-145.3679963321691,16.19006865931451],[-145.45441073741821,15.957584959124585],[-145.4193559866626,15.769395657609296],[-145.20990529098444,15.59043279558545],[-144.8413560356427,15.479777988538455],[-144.4329924134451,15.603900410686997],[-144.2826271752045,15.355602217126952],[-144.27429609278946,15.160271899228723],[-144.05777521232423,14.917491942971632],[-143.93415893883855,14.621398069908782],[-143.7620690045412,14.485641982939208],[-143.54089294930404,14.44484878500503],[-143.26750064765997,14.126720633234996],[-143.18962416706097,13.916943330082225],[-143.2106213647515,13.710583441639281],[-143.37714812404994,12.874463321209245],[-143.57186036652132,12.587937800486802],[-143.6941030400248,11.944184270627813],[-143.6270196862661,11.616614085185532],[-143.45932584217093,11.449828705308873],[-143.483899418081,11.238896329112297],[-143.42057203768513,10.701378445480588],[-143.6996060233982,10.107147245125475],[-143.73459228260035,9.58484599680866],[-143.68957064875983,9.366663423866441],[-143.5050764515183,9.017823047191001],[-143.47951437159224,8.683237839766797],[-143.16375973605395,8.513823129462462],[-143.019084142355,8.251481479876352],[-142.95843841718477,7.945384717492991],[-142.6136559749819,7.528637876496541],[-142.1573552074682,7.15993911850388],[-142.18216272664492,6.905140681003101],[-142.25873752358248,5.236112488082884],[-142.58544551884043,4.946945723527341],[-142.62717838037094,4.688454305676642],[-142.562604886587,4.49906296465476],[-142.64292289542928,4.309027235514407],[-142.60234604119745,3.9809388100335013],[-142.63402546701846,3.7555870724254548],[-142.60467810500342,3.3615114876083214],[-142.66244471988236,3.187014163609684],[-142.72953925062177,2.6776184173656237],[-142.54176677068006,2.338632058297422],[-142.44564688121534,2.0246068546277987],[-142.31542737164833,1.8540035970773763],[-141.93732379864176,1.522210303927564],[-141.76512513568247,1.2278349987843808],[-141.59472575519067,0.6999110910655261],[-141.57734305221751,0.20175572111150783],[-141.48061900030416,-0.04539020912849284],[-141.20418910209875,-0.31055313430155945],[-141.1245781035513,-0.9329198977932821],[-141.0637772224993,-1.1038287511636387],[-140.86310567208284,-1.3457673899820812],[-140.65120213887224,-1.7945844168113085],[-140.61109789760582,-2.0918427664896364],[-140.62768298810454,-2.4343430806516904],[-140.55370974707358,-2.5696074379385294],[-140.49541560030588,-2.883737004259645],[-140.31893858109774,-3.054981284282814],[-139.89707412526596,-3.353857449518828],[-139.80729646928873,-3.545195563281301],[-139.74350468772724,-3.896153011795193],[-139.31638231637547,-4.728125083405137],[-139.04361962878443,-4.867103044890105],[-138.76441189325234,-5.078420092961597],[-138.79225023360766,-5.399950593121352],[-139.0561626022552,-5.761553191864652],[-139.19305707471048,-6.0697281896464474],[-139.06115270606693,-6.521468270068765],[-138.75969706432141,-6.967930961415329],[-138.53724454979067,-7.184305885469271],[-138.24832063107365,-7.314529386935683],[-138.12408861397543,-7.315741785913847],[-137.95065349289595,-7.470376568536901],[-138.10859188908233,-7.945103777879749],[-138.34364542941003,-8.371114787097596],[-138.43463736570342,-8.46595347684962],[-138.57328063304385,-8.800843568022996],[-138.5176593565305,-9.059166051684656],[-138.33987788275346,-9.284215381716237],[-138.66845904742837,-9.860790770350997],[-138.55428748634233,-10.271584495944083],[-138.7715787899509,-10.59042232647402],[-138.94129930003876,-10.756785010267913],[-139.06125553202872,-11.075567808131517],[-138.97833611328568,-11.397646001298922],[-138.7838211880377,-11.618602796230117],[-138.5125457175105,-11.686765558609475],[-138.34450653661833,-11.843923522486044],[-138.27610768235587,-12.121850647787742],[-138.26920207369415,-12.364849866262954],[-138.17137604565698,-12.508918834116452],[-137.91091894042515,-12.66260171676234],[-137.40682099490513,-12.666270279272075],[-136.93719210443638,-12.720017544908265],[-136.07041229485552,-12.90784488370695],[-135.84227806198805,-13.057956012552731],[-135.5773878935949,-13.101807565523595],[-134.98353340932636,-13.521509297909413],[-134.84341618491825,-13.662137716703016],[-134.5113744227421,-13.788442895076487],[-134.26051747775216,-13.771423390295393],[-133.9379090588976,-14.036055997794406],[-133.80378068955727,-14.086290884780226],[-133.43325849254532,-13.966249371057769],[-133.065285176017,-14.09316324112331],[-132.93059355677525,-14.245900514783362],[-132.6515589156588,-14.44424666348209],[-132.51756233917004,-14.715649595791817],[-132.1864899385638,-14.949580718198208],[-132.07783949261204,-15.136132128377795],[-131.8723931595238,-15.274238216886127],[-131.75618581249938,-15.992464549196944],[-131.61862190734692,-16.271771486346186],[-131.50722928873154,-16.60899710225056],[-131.20985478229588,-16.837645438363392],[-130.5044586981527,-17.18423471360945],[-130.11500757784253,-17.431174843915322],[-129.75362309192911,-17.55643296282492],[-129.46307714978755,-17.893426748263877],[-129.19212149743726,-17.91788106914574],[-128.85395555612976,-17.81491582551805],[-128.56798993976898,-17.888514263358207],[-128.26295912242517,-17.90297355859857],[-127.87106106950552,-17.990206445894543],[-127.73599736966281,-18.26453655050602],[-127.47635425353496,-19.054262953982764],[-127.4738790990379,-19.122599356213005],[-127.76601100987847,-19.306745019278143],[-127.85561744433451,-19.631260183663976],[-127.79641395282276,-19.84045211762505],[-127.61769092368593,-20.049403457101068],[-127.39743105780931,-20.200061264118172],[-127.20421855553481,-20.40485567010785],[-126.79615100015263,-20.49627707342782],[-126.53742059792036,-20.36608041458388],[-126.12776874827571,-20.450732702432365],[-125.88956943398547,-20.46503519409486],[-125.79285537345582,-20.787076258484884],[-125.50530828598716,-21.155905873589138],[-125.3441433200283,-21.30272056774727],[-125.03237984527907,-21.672534860598144],[-124.45134337947744,-22.198369310725035],[-124.15166844200539,-22.813175032084143],[-123.9934997562448,-22.85620611802308],[-123.59631832525224,-23.392992420553558],[-123.33105942097761,-23.650687502362032],[-123.10951463911302,-23.99023931453528],[-123.03636566376979,-24.185371817945832],[-122.76290922504691,-24.676174823739785],[-122.58158196283237,-25.39648066817884],[-122.55199234341154,-26.54214931533083],[-122.76084735765517,-26.911895188872805],[-122.74965666775647,-27.14719695966585],[-122.8787905921721,-27.871593500980424],[-122.86406392923139,-28.091927109588767],[-122.72833549464427,-28.33269331060219],[-122.59688124265321,-28.414778329138557],[-122.41960228757438,-28.40893841519962],[-122.45918250389954,-28.997310940419055],[-121.9908135848774,-29.055809814228862],[-120.01345769231398,-29.261203617565577],[-118.25068838784381,-29.444392329202294],[-115.55250575245378,-29.719356887975888],[-114.09612659449208,-29.871890438888332],[-112.3802743058536,-30.04435709601348],[-110.81497714666388,-30.205725782059705],[-107.65625525819341,-30.527220995306912],[-107.45342726200973,-30.439693640297815],[-107.03612592409301,-30.572204492764286],[-106.82704692134448,-30.567218912039817],[-106.70525560620338,-30.495942017588742],[-106.47484193028947,-30.487658045528992],[-106.45560084666384,-30.275814565294727],[-106.29565304804403,-30.143144685504986],[-106.01414127131207,-30.009686005078986],[-105.89183620365718,-29.632094698589025],[-105.95763684226517,-29.44050198817148],[-105.85173388914895,-29.328712198224157],[-105.96473051135462,-29.224935366958544],[-105.99344949425596,-28.91611513667504],[-106.05624658632837,-28.824651710959994],[-106.38777781080005,-28.732632046856512],[-106.63527333969179,-28.742061795176966],[-106.73100635589381,-28.6092375358991],[-106.92335850935629,-28.622611679133097],[-106.9649302963726,-28.319007403065843],[-107.05605677780636,-28.216640221935513],[-106.79869810575411,-27.841856091146855],[-106.68194673214319,-27.453416013101283],[-106.74111391946963,-27.31458114797644],[-106.6341676925809,-27.171301628829063],[-106.8775849797565,-27.011349947551807],[-106.90366089018833,-26.913022261894465],[-106.67922650634915,-26.7269869392927],[-106.63442046597224,-26.57701414382593],[-106.69064796344519,-26.26040020357106],[-106.13783122529694,-26.226114758204403],[-106.0591001279947,-26.053292792605014],[-105.80071527747941,-25.80135694306869],[-105.61602161562485,-25.73585588834],[-105.57689801677665,-25.638238062102875],[-105.35256685478998,-25.542971531910982],[-105.3941620086277,-25.28194519728655],[-105.2591734079388,-25.174469660072546],[-105.27192755719385,-24.925624649081506],[-105.16629910862203,-24.695707172571897],[-104.99216099820792,-24.653574052310994],[-105.03195256991361,-24.397834704462067],[-104.95468973188359,-24.18666220252007],[-105.01028448007489,-23.862005221636245],[-104.97313491234824,-23.64681200514262],[-104.8002033875719,-23.55934014036977],[-104.89198240208276,-23.143018946979378],[-104.77962905053668,-22.958271704406688],[-104.64361452636753,-22.941384961353325],[-104.36998815134828,-22.77437129575893],[-104.20432172007585,-22.61032394649825],[-104.091070927799,-22.192248165587262],[-103.95790980980371,-22.079012449575398],[-103.65871673699976,-22.059075109477053],[-103.39244674384689,-21.98386669559316],[-103.1280959619548,-21.80094498996343],[-102.93651511918974,-21.814384603846243],[-102.31473029701253,-21.396568343478034],[-102.13952547239167,-21.385704716305014],[-102.09395412659032,-21.08442754534635],[-102.25270525180579,-20.740557218582854],[-102.51215013541393,-20.579662843374955],[-102.67587344300597,-20.31335872368053],[-102.82481603371669,-20.221482690696146],[-102.85790668210599,-20.098360935575936],[-103.04514895508099,-19.894462957227315],[-103.23219403790233,-19.890677469962032],[-103.30576169149748,-19.80187196182959],[-103.17295291507531,-19.330548991058127],[-103.25723880032909,-19.038701139014822],[-103.39773526393817,-18.844836630533376],[-103.36620945100371,-18.501359495304982],[-103.4377197189206,-18.008986130004548],[-103.6613596052714,-17.801166109160025],[-103.82501294172411,-17.55992456888235],[-103.94723029595491,-17.01675045447402],[-104.12044429271214,-16.826419626193456],[-103.95926651835927,-16.015896682325312],[-103.95635518857927,-15.884671342375682],[-105.59283122160056,-13.504153237296105],[-106.07454348134796,-12.814172027768393],[-106.78608715069849,-11.75420317281139],[-108.51546033149725,-9.20248864588888],[-109.72552602132556,-7.4046970106968875],[-111.19030713605365,-5.211439664277684],[-111.76124887651667,-4.368210294186503],[-112.23134949852958,-3.647186700076669],[-113.37845213637786,-1.9269060996531262],[-114.46244223175184,-0.2882601819980282],[-116.07140275413252,2.1234382874526307],[-116.61261400100724,2.952157706071371],[-117.52375577896079,4.30281502810857],[-118.21692728970686,5.365692264819126],[-120.3226277158217,8.497234178853809],[-121.755031689671,10.636963455959537],[-121.99846930579847,10.984917540347595],[-123.35296097309259,12.986988234095776],[-124.861097177026,15.18652364784632],[-125.54145907271865,16.163419789919196],[-127.07792708247742,18.374909099066006],[-126.61506232664622,20.180854160109757],[-126.43105770798756,20.841499870134598],[-125.98430058518477,22.343457905028846],[-125.72438329085857,23.278240948122182],[-125.29479966882506,24.73503295443007],[-124.57452416607735,27.202039771017887],[-124.22591202346189,28.402504712360784],[-123.87509384606707,29.64423757941368],[-123.47449859770224,31.00449693500313],[-123.0495624179153,32.403491180404934],[-122.3545150195674,34.62316191888699],[-121.6844942768826,36.74731269520441],[-123.17607574590927,37.04791850023876],[-124.9871767849233,37.42802939101513],[-126.96017070981259,37.84735729797708],[-127.41698749832199,37.96051176421868],[-128.50296282223104,38.19501583694355],[-129.6822992376996,38.46147022410515],[-131.0608259078927,38.78668152979424],[-131.83767972922226,38.961069865163005],[-132.653014473519,39.16119510404131],[-133.74073824642883,39.41024770038051],[-135.04782212124658,39.672010804335066],[-136.3623234269235,39.951562193692624],[-137.12689704655835,40.1245999042852],[-137.62262820999211,40.270834995699744],[-138.6609792019808,40.4462493127599],[-139.0969448534485,40.55893143592608],[-140.05691335427923,40.76572425967242],[-140.23021105890652,40.77831344005613],[-142.4611204188709,41.295477701828375],[-143.6076597113233,41.56756840099671]]]]},\"properties\":{\"name\":\"California\",\"ns_code\":\"01779778\",\"geoid\":\"06\",\"usps_abbrev\":\"CA\",\"fips_code\":\"06\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[40.28382366690882,-14.451419584142306],[40.375637997522105,-14.23865509568735],[40.27963302351395,-13.95069272460474],[39.98635984307271,-13.881733254083315],[39.77113578197755,-14.064412374128374],[39.845752312227454,-14.292191020575443],[39.97868976983181,-14.468523305765057],[40.28382366690882,-14.451419584142306]]],[[[81.6223371843472,-2.587927429285713],[81.13414467167324,-2.669177850074498],[81.0071095659011,-2.548906895304509],[80.77286866880844,-2.5593016545933205],[80.65416124139733,-2.3286851065097527],[80.52218562440052,-2.355044181841384],[80.3452507303561,-2.127927155313588],[80.1654450578701,-2.1669137143981265],[80.07229497657448,-1.9803497480426042],[79.89156540888139,-1.9777972224178],[79.65139955200942,-1.795512781024302],[79.47128531844132,-1.4760154761008337],[79.43982169343938,-1.321590213918799],[79.30158112184398,-1.123728121997314],[79.08856476018497,-1.0380626912493833],[78.84700098400764,-0.7682565746250029],[78.7581913171688,-0.47863188466755924],[78.43107110483362,-0.34066034584217375],[78.25870186159383,-0.20383733616825053],[78.40155513319608,0.10898318307738868],[78.26618038747748,0.2764788668365534],[78.0959064379731,0.27571981805381773],[77.86217163252795,0.7211380905707748],[77.65195141110199,0.8518666572573984],[77.62420198050121,0.9687010610430442],[77.3219607696849,1.1463042172516638],[77.23848955724954,1.349934013084368],[77.41425353391699,1.4095711698187934],[77.44082460585524,1.7480449281277697],[77.52366385085777,2.102617704781861],[77.44466429864266,2.337566594415545],[77.44666853843515,2.5570130125833703],[77.31161973816478,2.664441818346023],[77.26022528545836,3.2085467658203735],[77.11457246558182,3.5520044022090453],[76.85909717228465,3.673813116165067],[76.56336488821118,3.9955741359172494],[76.39971280083411,4.077195227111696],[76.05484144310795,4.040159208891868],[75.86133986742134,4.111952932984528],[75.69486852627378,4.250532370458052],[75.59257705983369,4.611655470896041],[75.43409479810408,4.930682930774339],[75.39410006021647,5.304526354846226],[75.26467207358812,5.354524698109538],[75.04565878914997,5.243152934822119],[74.88708860860315,5.0942375473471255],[74.58446490122506,5.053484348152193],[74.42730292826704,4.756048679416471],[74.17413149668951,4.593930260222615],[74.08881365318514,4.293662480716848],[73.99184450260657,4.198997945965315],[73.73890455887198,4.135467861377105],[73.48790224311638,4.1884646013526785],[73.28287370483811,3.940782672632556],[73.10509980746232,4.005155395598175],[73.02844234885508,4.193050948001591],[72.73241572572621,4.327158591738465],[72.22428625143797,4.35874086165155],[71.91052725395694,4.517401728610043],[71.43224310071408,4.3212738789560765],[71.31711618691678,4.202985961628573],[71.29576750936221,3.958568603523413],[71.17306180304782,3.8662239827897453],[70.85421728668638,3.9736160552919637],[70.59068080724636,4.003156958842346],[70.47534959762376,4.283278870194467],[70.14430106608431,4.424613580389986],[70.00637124816502,4.662055377896051],[69.3375482133378,4.795542039709238],[68.98548859904783,4.628920610519897],[68.84242810328963,4.60987361669178],[68.35123009001207,4.726732915373601],[68.03296937064961,4.7649569383932455],[67.92159840963224,4.900085331597385],[67.84261916445017,5.36001133151638],[67.50194946256157,5.759523621104794],[67.29652347778972,6.196506491784181],[67.01976960764912,6.342369318915685],[66.73242858691985,6.346631939835574],[66.62369401008276,6.418295427073123],[66.56056678552913,6.714407787697023],[66.41508190832232,6.8451335556982045],[66.15508719583768,6.61321494282005],[65.91970361143774,6.619852436268781],[65.81492657679011,6.482701231871937],[65.50853889167,6.4122794049185785],[65.39034458960228,6.513383699916217],[65.17616693833833,6.564745365028389],[64.99118456090999,6.78358754433464],[64.80633040235348,6.840988125885624],[64.64592560618182,6.606695784008606],[64.46206799465298,6.496961030298087],[64.083708872641,6.104155967765344],[64.45929881664102,5.663112079623896],[64.50496054923201,5.437687523930299],[64.30504608194924,5.145323334381246],[64.36925419895066,5.0009905151169525],[64.66896337838023,5.006178396992976],[64.83381485859036,4.931003080186549],[64.83087768659324,4.777663974950748],[64.65758738299407,4.57929347941213],[64.77318570317941,4.232067633068364],[64.35761366969068,4.241424651756197],[64.1192418415272,4.088612207787986],[63.79884668706582,4.058824984960162],[63.3860967242109,3.7398368527043915],[63.09470784172625,3.430474231988406],[62.8820150841145,3.298340545894095],[62.60802182404086,3.3614615789067983],[62.370985003616205,3.6002008085723736],[62.111071290632204,3.5980697627756135],[61.75636228560986,3.4779896898354408],[61.55532666821855,3.5038650118728083],[61.335668271045584,3.2814989266169046],[61.34222492511285,3.075837139815188],[61.45091754872459,2.920310341803424],[61.48187602731784,2.586348194194126],[61.650472436912786,2.2587478396457437],[61.63522289157719,2.0266578885626307],[61.373506274100414,1.793549285191347],[61.27296555795242,1.5274297997554167],[61.137100138495306,1.433709338289772],[60.8063044338455,1.3425668041232788],[60.6383831392214,1.131200205051002],[60.523856678221755,0.3969143358111046],[60.38213933438672,0.17018128828573614],[60.083699968526986,-0.060839969849916305],[59.860471313822465,0.0676717709972911],[59.64022364272625,-0.008456194986501141],[59.57552710883738,-0.12011484717104175],[59.51992856113022,-0.4464403062293309],[59.35760988025214,-0.6295192202907314],[59.26838574545098,-0.8284131623667021],[59.263337871070654,-1.054881352285868],[59.32711631564137,-1.4277144387737712],[59.27440561292495,-1.9008912627121453],[59.17954640639905,-2.045810114955166],[58.753025619534064,-2.1692903114902653],[58.62868860199358,-2.4424154777011413],[58.42983350065045,-2.1682494236208667],[58.184614487371924,-2.082209978395786],[57.871166800421705,-2.152726449381178],[57.62310336382281,-2.0491318626847956],[57.34519925165786,-1.8714628857030597],[57.25045076485995,-1.56573364433626],[57.25553103355158,-1.2750646011034572],[57.111948105273584,-1.1215854519766941],[56.801804190095645,-0.9267121211977786],[56.64285388898636,-0.9920321297920588],[56.725287224993416,-1.1794235630781225],[56.95280124558824,-1.210547028673452],[56.936030116173285,-1.3867184105822843],[56.693160910597605,-1.4070293561663723],[56.55980923824359,-1.6085655096770577],[56.323369687171336,-1.4645585174928297],[56.21702197766335,-1.6450277186715374],[56.412373621780546,-1.7630976083735215],[56.41961435690005,-1.903945907642393],[56.30510568771615,-2.0361373922774955],[55.9962899809432,-2.103145303606134],[55.94185116445392,-2.218203528166697],[55.985095205321784,-2.6537891626341734],[56.11228179114467,-2.917030262654158],[55.928001748109615,-3.0285779977927234],[55.66713572412167,-3.023351395698949],[55.61434942492142,-3.1510762935473733],[55.62214247014544,-3.4847031058685496],[55.39143556354343,-3.6279501514820987],[55.30726553465606,-3.471561959601008],[55.35928235895188,-3.1559896751714804],[55.16184279071574,-3.1185461434103945],[54.96503196782403,-3.2969687920757647],[54.76059336032363,-3.155672675568776],[54.471570842101954,-2.6483079809626524],[54.35283359646039,-2.5911072353229354],[54.15920678409088,-2.6815927130609416],[53.819090824014154,-3.0947135871597657],[53.507970217395375,-3.1635877771496106],[53.1755740772926,-3.4099869991151097],[53.180389563857695,-3.6454194243004268],[53.07921475829909,-4.138882158576036],[52.97011142984537,-4.254107369326018],[52.76228661422612,-4.282164343404268],[52.67387865714531,-4.078692049601291],[52.52422012959665,-3.9032430046524627],[52.21261280183028,-3.877735214599786],[51.62986272129337,-3.526475151264524],[51.47839926362978,-3.4960509680754694],[51.28057604048659,-3.345871543433114],[50.99284608809306,-3.294979533990022],[50.804661757686496,-3.3849104336045044],[50.56622672131515,-3.6149992860642914],[50.29172784513932,-3.4764966266209876],[50.14604719893817,-3.1864686931589894],[50.01480167639618,-3.1927948500712486],[49.89851901964285,-3.5525501302877736],[50.11525701106813,-3.7730349839481963],[50.1574693844409,-3.971346343741792],[50.025593341307165,-4.198221752103228],[49.70525076954559,-4.2406232131837625],[49.642847162389664,-4.055685489103792],[49.712019581649514,-3.901585014753035],[49.59818699948032,-3.71704465363956],[49.45272618571144,-3.8010586168595255],[49.18866573318164,-3.83853548344641],[49.00595157545373,-3.973408549309207],[48.78920989116035,-3.979189145279691],[48.533374266091585,-3.6723365542115904],[48.40178971702981,-3.64724024073414],[48.194495037778985,-3.9316865695994796],[48.220952608129565,-4.094561396685453],[48.3822166244959,-4.260072791826448],[48.42102727920672,-4.512128941706204],[48.28854882332516,-4.591076732754291],[48.224601496069596,-4.773903413041127],[47.82079586899998,-4.60353711582711],[47.75277772066269,-4.629128611576485],[47.66159751815512,-4.954539060953149],[47.54581286475388,-5.11939235467411],[47.268208107772274,-5.3317217230174405],[47.101374447629226,-5.713062244149514],[47.299844757360155,-6.284107149744111],[47.66984517735192,-6.581061021255934],[47.74562385045744,-6.7509642364443385],[47.64687279764775,-6.985966998277556],[47.345863499888836,-7.019394247690165],[47.00909677537968,-7.128166600244739],[46.55767069569592,-7.214037336915845],[46.38167444919353,-7.316333257222748],[46.08425930247977,-7.619102111942188],[45.82743792920726,-7.474756343348786],[45.601781166226445,-7.6009021906805785],[45.50330533431432,-7.738202210418276],[45.45976024133149,-8.119175014291962],[45.34344807941107,-8.428668023050928],[45.39380542789744,-8.692004610082389],[45.60254003695765,-8.947257817623777],[45.75693544750846,-9.065370385249542],[45.91787353570153,-9.42098393972327],[45.815622275766025,-9.8859714142936],[45.640294894921276,-10.04456251436729],[45.413328951765784,-10.072663521000408],[45.12625419785735,-9.996845105429225],[44.85826250413755,-9.757867755673818],[44.35843058719464,-9.604471764723097],[44.17867863776386,-9.601607640386982],[43.77648829927588,-9.328209640392634],[43.032101013386864,-9.101703499874672],[42.79314180400813,-9.101957917229958],[42.48848369015388,-9.257722656335533],[42.1639978989253,-9.667966158783692],[42.07894873114586,-9.942875847622618],[41.853271116001636,-10.172474571702379],[41.73721541108819,-10.402125897914676],[41.78381827182473,-10.72787874851618],[42.02931500882346,-10.918906030870916],[42.16572717751512,-10.948943253185128],[42.233975489608056,-11.088561964883837],[42.16690913701174,-11.570441612573731],[42.07007779012503,-11.869760178374074],[41.89057696951949,-11.921175243514766],[41.86386144567858,-12.15769328918572],[42.22658643954535,-12.365078306716928],[42.188255866880425,-12.53550812300595],[41.93679483942295,-12.498028556626025],[41.78854133143369,-12.648446894578498],[41.78356035013613,-12.796840448530999],[41.97547115100682,-12.99441740928593],[42.04533329139144,-13.147180645074506],[41.84618419710384,-13.42380593300298],[41.831854727960966,-13.616491080523055],[41.710981374356464,-13.85021601700745],[41.470317377906795,-13.865587954717132],[41.17731742117705,-13.504626337927812],[41.084701675747475,-13.448921719646894],[40.8976275046259,-13.516115635813076],[40.66930026821994,-14.415031520710084],[41.32384165869534,-14.313157164605284],[43.259817647180355,-14.213390531012664],[44.325282615160006,-14.13112865220876],[47.46908842331646,-13.910085630879616],[48.39543920188501,-13.854921417724952],[48.488071769444275,-13.567056167330225],[48.37933805421208,-13.111243738217775],[48.189066083949264,-12.597049114876269],[48.51913038772314,-12.577523531127106],[49.44587290025221,-12.595743987691096],[49.443381790852385,-12.811263144609578],[51.57730213141181,-12.57704119334925],[53.85766250846292,-12.355659161535899],[55.75110072716155,-12.13438943665952],[56.56265647150839,-12.031084133202356],[56.72080896102133,-12.149167446352768],[57.02593371357225,-11.986327833361251],[58.01928238637695,-11.914084599832119],[59.719926572330074,-11.868852432167538],[60.85701629171012,-11.811740760233763],[62.72830165157747,-11.663149991115743],[63.98470689497909,-11.440150415789597],[64.9323617713904,-11.36207516026762],[65.59971978160024,-11.327897695426806],[66.52050520967916,-11.310982681544994],[68.15845717406785,-11.177381491510737],[69.67678437068923,-11.027483651302115],[71.20731257008325,-10.852074693557537],[72.95366851364929,-10.683262726635917],[73.02334235874751,-10.544509541001828],[73.33736989712013,-10.260780987662114],[73.58568582077532,-10.148619607842486],[73.76736951422563,-9.991753404861177],[73.98467257998134,-9.924132478057059],[74.4024765151628,-9.879927180112738],[74.77149942712623,-9.60600834392468],[75.59458640663296,-9.214712713517946],[75.9233191069052,-9.148304761430403],[75.92548730036826,-8.910013497625178],[76.05977310356245,-8.620840430461854],[76.0346115651762,-8.49751409811735],[76.16350725523861,-8.31245785922691],[76.74018704831747,-8.208409981691986],[77.2141028168155,-7.918575341000643],[77.29476761601256,-7.62218706946402],[77.18371868820515,-7.316707422239279],[77.38031982661391,-7.143730352815437],[77.37999883964847,-7.0554310901879544],[77.63420576456225,-7.005578398345154],[77.82324325606345,-6.726721255990658],[77.883418680728,-6.497131813362149],[77.89809284222356,-6.144913834565614],[78.31853561518327,-5.852009975600172],[78.7319608909541,-5.429389631035415],[79.30499090834273,-5.039648788852507],[79.7558280755132,-4.843389288419118],[79.94509049301482,-4.6007822120667345],[80.7816945854411,-3.6076724342930095],[81.6223371843472,-2.587927429285713]]]]},\"properties\":{\"name\":\"Kentucky\",\"ns_code\":\"01779786\",\"geoid\":\"21\",\"usps_abbrev\":\"KY\",\"fips_code\":\"21\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[133.9933694062156,34.56384024033656],[134.307237301078,34.5276291670292],[134.4248527302989,34.293341293792935],[134.6443298037935,34.21660047832026],[135.08054473227858,34.37833937582088],[135.16628509385723,34.186040526529254],[135.36537526758138,34.04351476572706],[135.29900562713408,33.848951567997304],[135.46491035985702,33.61462146518683],[135.65134956147227,33.5429161331395],[135.87941979397155,33.550555748154146],[136.06062205921089,33.63965529157697],[136.14459258186028,33.779792084576854],[136.13047257888675,33.98201769816828],[136.32193566221812,34.234692970661584],[136.7119431592376,34.361129414617196],[137.19841539183665,34.46444905518097],[137.529875485945,34.33625168963905],[137.7689679731673,34.46473978456809],[137.95964018240426,34.45995786362955],[138.10257153294492,34.533653152302165],[138.46657281885317,34.408867680992174],[139.08748893628345,34.31835635003039],[139.47066762581707,34.31582445577202],[139.97442971953714,34.41526429901682],[140.16329728644922,34.498956117495275],[140.42059330728267,34.763207189453745],[140.42661688631557,35.03709478133216],[140.2455194055976,35.27596650441579],[139.737289863565,35.71694812078608],[139.52588685059223,35.82012540672979],[139.20888990603132,35.80131256231467],[139.08248182734556,35.70066334440079],[139.05623610029463,35.42358906267324],[139.21439175268893,35.26123259944411],[138.97034327705262,35.154520619885844],[138.81291828113027,35.19231639358903],[138.6013951424721,35.16239562733999],[138.4336308656612,35.2823690168089],[138.27065127415696,35.28940359598327],[137.98603572862396,35.213882977640786],[137.72122082283553,35.02172118928885],[137.58670224887575,35.38013680058163],[137.4395768183997,35.48857977959327],[137.26075771191128,35.520709092467335],[136.9998481154308,35.426120665356635],[136.83455438225087,35.607524245279905],[137.03636557955292,35.69622136524351],[137.2106240319303,35.87171014166918],[137.30384336072413,36.061462268057554],[137.7047131623774,36.23735197865952],[137.9527633936068,36.216486231049345],[138.1468418330883,36.32123532825907],[138.20619054328276,36.4281727671134],[138.42003699832608,36.56734176237449],[138.81481734909266,36.73456632576917],[138.93245075451023,36.62714693245805],[138.89574990782222,36.464673012215975],[138.72356630445086,36.224069613481994],[138.87733361133672,35.976561674835914],[139.06005366137893,35.91764902600587],[139.29077675466095,35.96066612987608],[139.4517094162477,36.145435220365606],[139.67937509378137,36.3272592664439],[139.73032970115096,36.45901988794338],[139.68845300596487,36.791712786409555],[139.74140876766015,36.90435787751022],[139.7923384742891,37.315159796394724],[139.64009538113746,37.78640027044526],[139.4930638729874,38.098210007494394],[139.03283435255105,38.72804217603185],[138.86090052880527,38.902099861712564],[138.25869599879482,39.32031108415888],[137.9158141437711,39.43029384267224],[137.6063505221335,39.477378273419205],[137.32578743134735,39.471159738188945],[137.04556506368309,39.391767532057315],[136.83364268669098,39.24909376552791],[135.11125988942428,39.020407854273344],[134.86093095760856,39.19670612091915],[134.7802379964212,39.468675203524676],[134.50968141491344,39.557925103264644],[134.43008970991303,39.68548177661486],[134.37728834887582,41.10419010553143],[134.45476419885904,41.242204147454885],[134.6102669696198,41.36993074689378],[134.72427556603628,41.696830343320634],[134.5584490738332,41.97352477741904],[134.29793034127027,42.16114263980851],[133.8834661999237,42.162804726798846],[133.5278380812933,42.00481357257008],[133.45419778549024,42.03272300188781],[133.23346933051081,42.297124190546285],[133.1764299966707,42.4862427856795],[133.04166874218075,42.64737208167325],[133.08113474723828,42.74191225455039],[132.65525667769688,42.64258016485941],[132.5370425505697,42.55804447336694],[132.31106363347598,42.63095823449987],[132.08044996870242,42.59652398347294],[131.9304623232988,42.48128836477813],[131.6241714665097,42.36609085118271],[131.556750164767,42.06213602375717],[131.18550194704338,42.07149972584161],[130.97305747690726,41.85961794277567],[131.09301949146953,41.58925773277427],[130.76325481059618,41.5511971055586],[130.60229296226674,41.26506978079449],[128.3079787364996,40.86702867882627],[127.10913044492565,40.66509529319471],[124.67748134534378,40.26871438221356],[122.88898419709365,39.98061450879279],[120.56803459168982,39.59428954639799],[120.52224526442674,38.16192502168683],[120.4478370553605,35.71422906664168],[120.61600394280916,35.52724198182706],[120.89304614664769,35.58742343807646],[122.9704822745992,35.930828478882376],[124.08079089413489,36.137057889640005],[124.13194559515136,35.91445198693301],[124.37795074982209,35.99643131545959],[124.40779282543596,36.16280712114335],[124.65945060831648,36.25598685583092],[125.3183077228798,36.34888357361547],[125.54113438178682,36.41884398437745],[127.51089307782698,36.79880398548543],[127.87137523133403,36.854952247282085],[129.26805403031352,37.119929047734686],[129.30327410471773,37.03541946101093],[131.410467021794,37.53913536682372],[131.64007796643645,36.8439469635909],[131.8496066738795,36.91931555016701],[131.94012025331722,36.72372002114902],[131.9450790801495,36.490836301138984],[132.1305324599862,36.27128638186928],[132.5160374603242,36.18793688400782],[132.99269650816493,35.82767331505208],[133.3422904506853,35.81422141069382],[133.4040807229222,35.49478515177352],[133.47009238906404,35.43926711892609],[133.71219476376652,34.87794423648536],[133.9933694062156,34.56384024033656]]]},\"properties\":{\"name\":\"Massachusetts\",\"ns_code\":\"00606926\",\"geoid\":\"25\",\"usps_abbrev\":\"MA\",\"fips_code\":\"25\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[89.02981898113399,-68.08822748501443],[88.85894916066646,-68.17912774543194],[88.66947062077378,-68.22598382255461],[88.55895423730286,-68.3108466557909],[88.58300173946638,-68.38783056295209],[88.67760502807171,-68.43803945415111],[88.9920131870871,-68.4520099022973],[89.1691828947423,-68.42958627823673],[89.50912454248655,-68.42925776807164],[89.77303873524022,-68.34359490989668],[89.95649292996124,-68.16650866085521],[89.92456533892599,-68.07858371680618],[89.6817240688863,-68.026141879943],[89.25628611871916,-68.04847893235485],[89.02981898113399,-68.08822748501443]]],[[[55.432677152303704,-50.40741284955484],[56.17546701693948,-50.269926396553245],[56.76433348535094,-50.13223487116816],[57.007145188462445,-50.15714550442654],[57.72622566812671,-50.04833103131353],[58.16986666106797,-49.946775683024036],[59.10523290665591,-49.77131145220814],[60.28530031504438,-49.58969384035597],[60.6961772512211,-49.550477820370325],[61.04858977523648,-49.547647355425134],[61.455671592006844,-49.57535311321357],[61.67829366571119,-49.55448981314567],[62.22974605317829,-49.547927357569115],[62.966334078395455,-49.597186753950325],[63.56547436659088,-49.67975654693468],[64.74446578172734,-49.90091360082285],[65.08380219921932,-49.977944474433734],[65.87516290126847,-50.22904915666567],[66.26931146200798,-50.42565183698061],[66.65166006090864,-50.55112866775194],[67.14764377239611,-50.648682261970094],[67.46330843586469,-50.861891539697616],[67.78262050336845,-50.99597426670007],[68.00993870307713,-51.053443610569616],[68.37941222922518,-51.07614862336945],[68.26057550864557,-51.23200556076544],[68.2579624752788,-51.397142173476844],[68.31672893825532,-51.58319361803412],[68.44231679627875,-51.81398351050621],[68.66250044448175,-52.087690288058745],[69.005409158802,-52.31550079285715],[69.35006694309712,-52.3221705255529],[69.54228299916488,-52.19311191578445],[69.90026494924486,-52.200236424358195],[70.3068066086552,-52.32168128303401],[70.71850769781136,-52.38736747595735],[70.93488648090397,-52.4948438727847],[71.22523375700864,-52.48421047734058],[71.83905885844167,-52.30673353235077],[72.42155652639548,-52.02673289630432],[72.73290365840431,-51.94079286921642],[72.94700865523137,-51.84328234302305],[73.47397212964461,-51.45258992904894],[73.788656299307,-51.370470687802424],[74.03996584714255,-51.25468953997657],[74.13984525855231,-51.136435992508645],[74.16726038294708,-50.964001044438774],[74.33997791447678,-50.94459133617973],[74.54918876693318,-50.81357331001602],[74.94620344808418,-50.84506040720837],[75.22111742636642,-50.814213420910185],[75.45026217822854,-50.70530020088564],[75.64053419752771,-50.34397648036165],[75.55746543258549,-50.18407817982469],[75.62805844004286,-50.10041374209954],[76.0479777088418,-50.126389156007455],[76.30412403575684,-50.05781774076685],[76.46879490159951,-49.88426074165842],[76.86179995423358,-49.79811808913898],[77.0453895442429,-49.891542533889286],[77.30744178291489,-49.91290159173027],[77.73142398951177,-50.102381100658405],[77.9126798547796,-50.102047142153424],[78.06651401676775,-50.205588941898384],[78.42272425628319,-50.29603395183941],[78.82020549675757,-50.311602205503554],[79.14956433247549,-50.44570626437402],[79.3798501271691,-50.64102800069579],[79.50961784114274,-50.69718641192756],[79.61345976796328,-50.94823236554867],[79.83432260269748,-51.118472896236156],[80.25821390176979,-51.20523210255761],[80.48688545113755,-51.35214799205351],[80.6843926625868,-51.35515605366625],[80.80330724354307,-51.42172062914736],[80.81988245526352,-51.61567123789907],[81.01791306357909,-51.98372383629641],[81.19167222921371,-52.09158689308016],[81.61662464799815,-52.165838106537535],[81.6893307165693,-52.261144870413034],[81.98090998619486,-52.347653694551674],[82.08250753124071,-52.50142319982361],[82.48202824734916,-52.632545486616145],[82.53102416741089,-52.88268599576486],[82.58157301772435,-52.941524527673906],[83.01208201710322,-53.121925880783444],[83.10188239860722,-53.375777121581706],[83.29200921512424,-53.58469738631258],[83.51819456404685,-53.66440334200672],[83.7429340547859,-53.68050629987968],[84.20526611989656,-53.6046581157016],[84.32187133484629,-53.51632292448012],[84.48832859779225,-53.47341645037282],[84.90247954746711,-53.566954434254455],[85.03430478269001,-53.559035998513544],[85.15668266373387,-53.66901085391469],[85.24149125472027,-53.85469388661052],[85.19020091232245,-53.98631443479164],[85.38573205686998,-54.150016256641656],[85.5481841399124,-54.175517956538485],[85.66360302217038,-54.302687615677456],[85.59250391961595,-54.502027637340504],[85.61595199955877,-54.58438686681078],[85.82126663702515,-54.76667701548134],[85.85207436497939,-54.90562477828536],[86.04610428586028,-55.10782120370491],[86.3410059125618,-55.33390808626148],[86.28552656256528,-55.406754329308434],[86.27117877108253,-55.59834222415666],[86.35870241542712,-55.77865698417471],[86.36884638496899,-55.9251850974116],[86.22428206414678,-56.12152078463189],[86.12531644344207,-56.32294264679699],[86.21058746226295,-56.53468003194648],[85.98362801355408,-56.69713887029232],[85.65957255168676,-56.79202305051023],[85.59639720929832,-56.94836674105731],[85.79278430852361,-57.42503454134774],[85.94533952769892,-57.55819874430904],[85.92098969087495,-57.67209616776461],[85.95947465245928,-57.86041355182867],[85.90162477146285,-58.14458667501748],[85.99885419893025,-58.3299755013712],[86.20783879912904,-58.482983653790406],[86.72625193580656,-58.762623992510385],[86.71924714908106,-58.96619365883412],[86.76324536159271,-59.03044724133767],[86.74517587860305,-59.25709537094043],[86.83575060979823,-59.3937072519657],[87.20933130878765,-59.580366670191324],[87.45930509040713,-59.78541217590106],[87.67248123230006,-59.86450530112555],[87.88882312098248,-60.0126546436566],[88.23084949500111,-60.163800067124896],[88.32128216723657,-60.27958614883411],[88.45219203541279,-60.35048149738413],[88.74338647546298,-60.44542467464311],[89.06485592264715,-60.72687224636353],[89.31577704839613,-60.97217146695938],[90.15102370516801,-61.49529658087529],[90.5818715350745,-61.71092873594292],[90.7183273124862,-61.81556429638382],[90.78126159807289,-61.93025337594515],[90.82641271970726,-62.15976711894703],[91.13688997212496,-62.32172175543862],[91.2084543280942,-62.43288887496748],[91.35433364588668,-62.52033470484135],[91.56125320123219,-62.73183133978165],[91.71154289579256,-62.79000140680416],[92.00448278001942,-62.83193778602849],[92.22480104744052,-62.93900931697887],[92.45476956215225,-62.99689062200224],[92.73752483388425,-62.99887218219099],[93.09956194766275,-62.89629175368094],[93.28649218242879,-62.86951295386728],[93.54232318151287,-62.931473691243255],[93.81635728478919,-63.05563376357315],[94.01086521079044,-63.218907658912144],[94.18295429598433,-63.407998902504445],[94.34949233493737,-63.734710421452604],[94.45384088788323,-63.871000275205105],[94.86272850880485,-64.12312458812178],[94.85075285958911,-64.18659816090772],[95.02923993450293,-64.29838282260572],[95.12542941165513,-64.3942301822336],[95.37639099318413,-64.49209721226674],[95.58450960822844,-64.63467542422305],[95.80369196247813,-64.68809661645328],[96.18135234845066,-64.66246673519512],[96.35762662038844,-64.5917845355418],[96.40029387188085,-64.52961613071078],[96.80562874357821,-64.58302603509796],[97.10556640774465,-64.65993141801576],[97.3944389490372,-64.68585470680173],[97.77376959471233,-64.89925687474089],[97.96211936355023,-64.94341436380958],[98.13172580676498,-64.94394291765339],[98.33251277840213,-65.06256278107317],[98.69966347775919,-65.21257528329797],[98.86833323058086,-65.36628844843453],[99.1503482000532,-65.46221795250965],[99.28747463836342,-65.54435448185757],[99.32287706091049,-65.64160433782416],[99.40822427810264,-65.69482034580679],[99.29748666304504,-66.05946295622165],[99.4663130247355,-66.1791340214404],[99.61898119248073,-66.22868277042564],[99.73742492826793,-66.34700577990921],[99.98194968740383,-66.38198302060749],[100.20901119760933,-66.4642659758252],[100.35649292013981,-66.4750978113227],[100.54249908404147,-66.55809429134955],[100.74157983480079,-66.72026846719329],[101.01254648000668,-66.8173030789211],[101.20433657467231,-66.86266872576704],[101.38844876520061,-66.97427296587009],[101.63915484466615,-67.02340815023457],[101.49690187750078,-67.09699361899477],[100.87981927763919,-67.21478708552361],[100.65133747547384,-67.17867561813094],[100.42333923197009,-67.20208864759925],[100.21657826861072,-67.13122442387161],[100.05433753304878,-67.12104899788467],[99.90222230594787,-67.0607324389105],[99.56050068153831,-67.04604636892917],[99.31075945833302,-67.09733679426903],[99.18251025242228,-67.0897038826245],[98.81863374817668,-67.11837835238408],[98.59154437726878,-67.10288711371825],[98.24162591608372,-67.16371547290125],[97.82693992682022,-67.27660794651672],[97.75262712819078,-67.31978607842085],[97.38165404632565,-67.40148700202272],[96.67658450600558,-67.64405387210869],[96.16916175096041,-67.75160985016875],[95.81387672089707,-67.86094211749075],[95.64784377840898,-67.86346742009142],[95.36408253220615,-67.90447850545164],[95.05441287051865,-67.97985862204209],[94.8301976707305,-68.064476269087],[94.4432490665845,-68.01144597815299],[94.07705856293975,-68.02341779153241],[93.91017408211934,-68.05716237304566],[93.65331153583881,-68.17359366881875],[93.64599836944919,-68.30221824600571],[93.75703185125514,-68.35719619716187],[93.97496038502061,-68.39620981212505],[94.46140503537286,-68.36888665461397],[94.65294773807757,-68.29618073382564],[94.78536973548975,-68.35953081195993],[94.98459822088574,-68.39840394130582],[95.31101247775764,-68.39799670211377],[95.31700840502626,-68.50099062536201],[95.56760334333464,-68.5714129426806],[96.08148443347794,-68.53231870758806],[96.41460735128379,-68.47738014723028],[96.55155743830764,-68.4127256157764],[96.71095842751457,-68.37726105128941],[97.0642600037786,-68.40974617724336],[97.69959564364326,-68.30964742266629],[97.8407982341054,-68.2574709063689],[98.12358697565577,-68.20198073616051],[98.22583494420624,-68.11348298645379],[98.13036692443714,-68.02315657118932],[98.20416214503777,-67.98831023189989],[98.47550832784098,-68.00282090081764],[98.80876081729654,-67.93781720131025],[99.03823189949769,-67.92618077785974],[99.60685574098521,-67.82101926110707],[99.74782551232049,-67.81170475712754],[100.14859450186114,-67.69600785998725],[100.27981153792898,-67.68813404028049],[100.49627658113297,-67.62681231035728],[100.65851223850379,-67.65085562941043],[100.97539921572968,-67.64055983663863],[101.15522949141952,-67.61108899461966],[101.29023957984624,-67.53484024742428],[101.43407534817085,-67.51618649702078],[101.90961894894761,-67.36631834131515],[102.07191304500033,-67.34701381291364],[102.3018341753992,-67.24337576220239],[102.54571616236846,-67.19426405797338],[102.73412093790337,-67.18234441487326],[102.95088496406775,-67.09218605487858],[103.3645919629119,-66.96370150827657],[103.50345473490877,-66.93867059453322],[103.68711676517125,-66.86201220947466],[103.87043447347666,-66.75255448753336],[104.14950289800925,-66.66841669962854],[104.27160754812206,-66.58144312409136],[104.54863206940779,-66.53168921804432],[104.63915783578602,-66.49174557939266],[104.69191327650314,-66.3903791295905],[104.8672556473692,-66.33669168923387],[104.93792214826793,-66.20402101677216],[105.13046452170563,-66.14576897327922],[105.25781629516206,-66.06106795432966],[105.40099317378399,-65.91841254296199],[105.70316827864421,-65.79674968218082],[105.78167494006233,-65.65608592786265],[105.65739289145425,-65.5764874857733],[105.773774957516,-65.45022808142757],[105.96674335349272,-65.44860499395786],[106.18775438058545,-65.36422064653148],[106.35669151354699,-65.16981364732024],[106.3323073114046,-65.09484928123324],[106.09989777140534,-65.00926558415847],[106.15211643723906,-64.88480797936847],[106.14216046635762,-64.6816888390338],[106.37213217493975,-64.66464466456777],[106.50406732960391,-64.61313580727929],[106.5381040589545,-64.47882767804013],[106.3986877709308,-64.4045761198513],[106.13417132318199,-64.33914737734857],[106.08661296479215,-64.1273498731785],[106.20142215397428,-64.00975549160876],[106.17511313084673,-63.756386677857094],[106.02450483715391,-63.561944540988335],[106.03739154031949,-63.32845355660974],[105.9708752945103,-62.95290501384751],[105.92801283370082,-62.41136483692987],[105.82069890441855,-62.071386570620824],[105.78922044518545,-61.83570968275134],[105.78885844396281,-61.51395444780599],[105.75792901788164,-61.282050285622866],[105.68865341908163,-61.09794240936237],[105.66135367842142,-60.89831083469738],[105.58021157982735,-60.757554601360866],[105.54759474019596,-60.58459117250539],[105.3177885516334,-60.31475406619705],[104.87317007322635,-59.82134210126055],[104.49648407193762,-59.506771278395824],[104.34011954585598,-59.31778192588891],[104.07146176569978,-59.12478124277345],[103.17494359936043,-58.36814359697988],[103.14003004345247,-58.28571546012809],[102.86629903977561,-58.06882758237096],[102.65510165895653,-57.81231862275222],[102.52286863726408,-57.70025818197696],[102.2117139563658,-57.35350412190086],[101.60175799372719,-56.85638151562216],[101.35914383347216,-56.68095416468494],[100.76081977738127,-56.194753587405636],[100.26206237666864,-55.45619668041568],[100.24871602217044,-55.19246683410811],[100.27641062435835,-54.961284872973344],[100.4738624145326,-54.79978250074319],[100.51527085704957,-54.680997501576975],[100.4159916625931,-54.52607163099385],[100.20233449034407,-54.34554020015621],[100.003852864793,-54.07829357734162],[99.1437622280363,-53.55648147140276],[98.46817637043983,-53.08449289921616],[97.26264548662576,-52.190965911438646],[97.02175312809602,-52.04297812365192],[96.60177169041567,-51.613561504010235],[96.23420464279268,-51.26035162387049],[95.49542141566151,-50.45911809509716],[95.20765135103316,-50.17514723773424],[94.95091215124317,-49.879039819530036],[94.77597724844273,-49.63099403304506],[94.61881662541042,-49.47370148976953],[94.43500501833994,-49.1989434711595],[94.35157434231154,-48.957535156386825],[93.93361535321368,-48.40431103254903],[93.61194488565333,-47.83466522421612],[93.14471253387212,-47.03174577559057],[93.04794940193926,-46.73651620871579],[93.1128371471095,-46.643853425784464],[93.04198587785413,-46.42175062455737],[92.84385152932069,-46.33359346595599],[92.78534525248362,-46.1091930142769],[92.61956603434008,-45.926096800535625],[92.48042581991217,-45.463181189856805],[92.48678673993896,-45.28237369216555],[92.56442275886788,-45.2066518804026],[92.59183171420038,-45.01739862811755],[91.8861189045048,-45.1020206280613],[91.7131102964824,-45.04330432389546],[91.21896785333067,-45.11859381218923],[90.8930629974791,-45.08143856741177],[90.759016593618,-45.14004793742913],[90.65884827512433,-45.02888182129786],[90.31329735646354,-45.09891295413994],[90.1533977487059,-45.01209020107904],[89.80942789705898,-44.935735317304584],[89.26474782360171,-44.928481945633514],[89.1855423288073,-44.82234863155122],[88.7496316735033,-44.89001132484405],[88.73823661694664,-45.08644059365175],[88.54693575265951,-45.056092964685256],[88.52049238371494,-45.20128387885285],[88.36525660145234,-45.29169136872919],[88.43167479164337,-45.49340464619388],[88.37829035278807,-45.65916190610519],[88.761686095956,-46.168700074523514],[88.72253568955561,-46.35441835660682],[88.78899250221109,-46.61573231053122],[88.68899999509495,-46.924003873336446],[88.77375306799121,-47.12732571292057],[88.61562583133015,-47.21066698321153],[88.36487507816017,-47.18325716762123],[87.96926693996579,-47.26637393868941],[87.82220310765028,-47.18823141756077],[87.66360204903384,-46.97379060552429],[87.6597657180251,-46.68022913733847],[87.49179585341936,-46.58508756798463],[87.36773644212734,-46.44800238766053],[87.49198765598551,-46.282005258916236],[85.18833479026303,-46.393266899224514],[82.28112074345374,-46.53408809832304],[79.61860515051032,-46.653280753265],[76.70790269482727,-46.79008610197094],[74.2662686138261,-46.90319657353128],[73.65923920063081,-46.939180411621834],[72.6162468225409,-46.98006281445921],[71.27240879618165,-47.04208629375494],[71.03912872515367,-46.86493515083746],[70.95731145290854,-46.872479718732926],[70.74532090150586,-46.55722541125091],[70.78280328808448,-46.4158626848595],[70.6526340487566,-46.21696207426428],[70.36512158559299,-46.00554183064941],[70.3618910502851,-45.872563319718104],[70.20953297766985,-45.82780414208346],[70.19629910032872,-45.69210057800601],[68.7358723309614,-45.80823604802087],[66.7738477694868,-45.98432513267169],[64.91639136121721,-46.13737095218202],[63.42185456928227,-46.250299454582354],[60.74015587389449,-46.42746089623557],[57.234163634396964,-46.639014860660225],[56.64790107899711,-46.69189861924351],[56.1957565027828,-46.71038249153672],[54.45464744590852,-46.822713026245864],[54.535192510557934,-46.9732492266617],[54.39378139464519,-47.36450033915733],[54.31851777109372,-47.46957187593697],[54.38322082321151,-47.56292172989796],[54.92200575903599,-47.86059407145466],[54.9993239741839,-47.99524438580866],[55.22214012086684,-48.11800456589026],[55.83190072065795,-48.304599446178024],[55.94204980927267,-48.5229907728421],[55.88034000699768,-48.73318016311412],[55.68925075241795,-49.003267434986505],[55.778556812210724,-49.22543362955025],[55.92121943207875,-49.32564678943361],[56.19632780302312,-49.35814661357701],[56.214461496466434,-49.42675432034854],[55.80460403066488,-49.63927232208758],[55.771391925849905,-49.84754572731648],[55.70505917138437,-49.91149978837416],[55.481780446955916,-49.94770803691703],[55.45888789818493,-50.031394843161834],[55.79163391958566,-50.06239902874279],[55.39783258270929,-50.160076470000924],[55.432677152303704,-50.40741284955484]]]]},\"properties\":{\"name\":\"Florida\",\"ns_code\":\"00294478\",\"geoid\":\"12\",\"usps_abbrev\":\"FL\",\"fips_code\":\"12\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-72.77560308200106,42.84692105176968],[-73.15939330000766,43.02899130161172],[-73.32828284565808,43.41355467461046],[-73.56422738941274,43.497389971920995],[-73.58064179607796,43.680285681644484],[-73.4597138284,43.71009151775263],[-73.60652899241313,43.875666678765185],[-73.74908754853588,44.147748418351085],[-73.92213571020076,44.33984153184105],[-74.09037864795337,44.447872949269666],[-74.23461802757296,44.33255893244024],[-74.5853633368475,44.31699547280972],[-74.72429126757649,44.259252097126875],[-74.68258494617295,44.07470141255043],[-75.0128580238525,43.800352171404],[-74.95709461329355,43.46346936110381],[-75.39349032218603,43.61855010792095],[-75.53545139233373,43.58052237151758],[-75.95746575036304,43.679547979267795],[-76.60425045804242,43.490552199130015],[-76.76401798617766,43.790887663761374],[-77.18025071348845,43.810054919437704],[-77.35944600602579,43.72260280667109],[-77.61617064853019,43.81044331775267],[-77.96231863029739,43.73036316694827],[-78.11799127036235,43.82597236060136],[-78.38864519691947,43.834689281585355],[-78.56465908033108,43.911276588818225],[-78.57573670033504,44.01962520032266],[-78.85653167907765,44.07780097022259],[-79.05730821308192,43.949572000077964],[-79.26836407687915,43.922320786583825],[-79.30158777261782,43.736796915286696],[-79.38129631598272,43.61418443147915],[-79.52236598434114,43.54418750890504],[-79.89971737779798,43.74612687599422],[-80.05621236419333,43.67685196894061],[-80.28039897702233,43.820052764126146],[-80.53474508691889,43.883849267668126],[-80.84800443552457,43.90027207400634],[-81.09281334991884,44.02471909461615],[-81.43373362060049,43.967093904978405],[-81.80401400972366,43.70077539154197],[-81.70267510852527,43.54672716294243],[-81.72641289912016,43.439142420649766],[-81.91109750292608,43.36304256488154],[-82.22028901093569,43.67396833298079],[-82.36890290625163,43.730717552634594],[-82.58749198793586,43.93804658520494],[-82.64822881649037,44.18046638688894],[-82.50937302597359,44.31944777958712],[-82.67837921053717,44.43513963746094],[-82.62089346172597,44.54646221051603],[-82.81898624987483,44.739566570961934],[-82.63924833171296,44.81654838166224],[-82.61637450871036,45.12621104425848],[-82.74388698226296,45.23728569391496],[-82.72412071875146,45.39342281698656],[-82.86130477156503,45.56923407915845],[-82.8129762086703,45.62550596824374],[-83.03375974824513,45.728560842729706],[-83.08346749201243,45.80840158973612],[-83.32460238549132,45.94687205979752],[-83.83769448096555,45.819022594583174],[-83.86960369688889,46.00067766969817],[-84.169265178733,46.15406099229678],[-84.30178812523009,46.28586528414839],[-84.3038021215638,46.393079880033326],[-84.40628757892017,46.5946370106507],[-84.38841243135218,46.71300403830784],[-84.12264141952404,46.73540451045286],[-84.0335909776272,47.21658257576567],[-84.32663983269494,47.41992722656075],[-84.26401638805181,47.521002199437994],[-84.4764194079906,47.54785162676585],[-84.58863707592522,47.75829072125636],[-84.56412767401085,47.898233345902405],[-84.745929654667,48.05862268880722],[-84.78231179104611,48.16854881197474],[-84.95384953397543,48.33313948787357],[-84.86974286658273,48.37953487184669],[-85.09383563419907,48.665584013478345],[-84.98963597106976,48.95207646778573],[-85.16585504224567,49.04208703217728],[-85.08962376600805,49.337981113763774],[-84.9993154210179,49.37007934846713],[-85.01665261539411,49.533962967809266],[-85.30840121452346,49.59911924078826],[-85.1870143858536,49.79712681303831],[-85.0458345314222,49.85498940129217],[-85.06678646632047,49.95165052788023],[-85.30986450528229,50.082228505722185],[-85.51642541948686,50.112036719578484],[-85.45327573366642,50.20635344818566],[-85.58394801764423,50.344100429635304],[-85.56913730826788,50.43529664262099],[-85.75030835582457,50.50237856587903],[-85.98725298872998,50.485677617049355],[-86.00679243496165,50.327515460044175],[-86.31162290754847,50.22326350885041],[-86.45382491235286,50.07082617803106],[-86.63838934975551,50.061408582562926],[-86.73801251215868,49.94867945758191],[-86.9658905735545,49.94883793620986],[-87.04860221410887,49.88975569313734],[-87.30997861405102,49.96368314574297],[-87.34594781187238,49.85221273591023],[-87.54882548081235,49.68987652954162],[-87.90015643438022,49.63685509297375],[-87.95952075057849,49.78772095106933],[-88.23474864696128,49.94529062236458],[-88.22257113620375,50.00305510236464],[-88.59871469024829,50.231482098753055],[-88.7647038306975,50.217392408825646],[-88.77363663006271,50.33242009030501],[-88.64797884799707,50.41295908894331],[-88.7340177938925,50.542759837208486],[-88.42618328592464,50.624204594370646],[-88.30282841642895,50.81903395110718],[-88.32089454601713,50.90571278322607],[-88.50375258753195,51.02855413381185],[-88.5512003725821,51.17250958788527],[-88.22610962207092,51.373152717426606],[-88.17455895298858,51.4514457301838],[-87.63951214833872,51.492722520400555],[-87.5403452327651,51.626505307349404],[-87.6693291883066,51.796402269181314],[-87.49837901563323,51.874647720223535],[-87.52654763442725,51.97421561208454],[-87.71113994624982,52.077011255899215],[-87.8123560895538,52.33437364803742],[-87.66497903625023,52.37555365818734],[-87.59014630882645,52.491443373485716],[-87.63777243259838,52.58227085494244],[-87.84721014767415,52.66062826731338],[-87.76432916776442,52.828219528883956],[-87.5850017645591,52.784664323680566],[-87.41895301524542,52.79674976784032],[-87.3523527634422,53.10263318284562],[-87.41024844148977,53.21754974173016],[-87.17327549752103,53.272605467364535],[-87.1731768270893,53.36737747391748],[-87.03588603672476,53.48287049077054],[-86.97742912027599,53.603939234872925],[-87.00792899386948,53.69975989154768],[-86.79164643111528,53.7805328330216],[-86.69928476341256,53.9766116718056],[-86.76109955960237,54.14753230242258],[-86.48191742915492,54.1646077516203],[-86.42265359423855,54.333115164372906],[-86.30210498968837,54.42077242248435],[-86.16106293923929,54.7042627986891],[-86.31494178946598,54.81813927243801],[-86.7993596920961,54.78482327351337],[-86.88719563415295,54.72257775579357],[-87.25191062609973,54.81959701627397],[-87.4977679728829,54.797797366038914],[-87.66337470678454,54.92709298476934],[-87.66266205060518,55.01023360186045],[-87.52476248489695,55.110340190637416],[-87.61662550571948,55.21756280462395],[-87.84367214442429,55.28367075464401],[-87.9816331718592,55.18953500487746],[-88.16591421273772,55.14179592502507],[-88.32339879225603,55.22868028898832],[-88.14841348308701,55.39044117976327],[-88.22661417140371,55.492786770335414],[-88.42830322502415,55.52712586776113],[-88.52902863952066,55.612440143117006],[-88.79798552505606,55.749480552514456],[-88.77891863338168,56.02962019227588],[-88.70020652278224,56.11145191582895],[-88.84282516685656,56.167498432487626],[-88.98692034693876,56.33750293568479],[-89.20931749210565,56.36984531970081],[-89.29312017220623,56.68290530647265],[-89.38061821946852,56.706702536752594],[-89.6675977639288,57.06724468915313],[-89.89746475748288,57.171158285264944],[-89.93147882930324,57.29169977353459],[-90.10669013409726,57.33618936893601],[-90.03250514708965,57.45589230805563],[-90.11883899634559,57.60393676864678],[-90.51684077755878,57.67687336365893],[-90.57616256121698,57.74601367557616],[-91.02221584417812,57.8691026280469],[-91.06924762680902,58.06946332140919],[-91.33550560925899,58.22300804040478],[-91.46911925870806,58.21873951307168],[-91.50139388001578,58.310380170017936],[-91.88079348768994,58.50291529840974],[-91.58643201014226,58.53439482408306],[-91.36613665358335,58.50819215059524],[-91.350411674692,58.58605214349638],[-91.73882883990369,58.86848022964928],[-91.74483633320732,58.905974470944],[-91.37974856645086,59.03427364236548],[-91.35051293426332,59.131679780430396],[-91.50369159991641,59.26422676665507],[-91.38014835456212,59.40011618879797],[-91.61821207997384,59.55113007753327],[-91.6421265967429,59.65701213145886],[-91.77893721381261,59.651948140825134],[-91.83749835970153,59.77555133528439],[-91.79857342357916,59.93025557513477],[-91.9046975748262,60.02002401825045],[-92.00817316476744,60.00722833460322],[-92.20940053753245,60.27195242513569],[-92.32081079622053,60.33939117682492],[-92.42654120740224,60.515035892910866],[-92.50396477001635,60.5394039234623],[-92.12512112168136,61.414518536597],[-91.41635087465394,62.951215068861096],[-90.99343519826625,63.834866645392054],[-93.73645525892604,64.09354888508248],[-95.54646537387694,64.26681900007063],[-95.73753287537865,63.893285516375784],[-96.33364860617566,62.73591851668078],[-96.50988131802622,62.37151843638346],[-96.94192975715798,61.53062510842902],[-97.39248219999922,60.57634322079348],[-97.91977020108561,59.37618962122769],[-98.28299667879376,58.545182753007495],[-98.79056689214441,57.342171940348855],[-99.58267209149332,55.3973378692192],[-99.77708485935077,55.08610085813727],[-99.64548907739726,55.03325654414974],[-99.66690912449859,54.958591348229355],[-99.53167768988631,54.859615721032945],[-99.58089264166192,54.77482846109161],[-99.50396219063431,54.701235796161285],[-99.49003797461663,54.55054032013414],[-99.56445665148964,54.44882767903119],[-99.4303415629969,54.26579034899642],[-99.70466843345119,54.03817265798496],[-99.8216274627491,54.02710355393097],[-99.68223395417837,53.87178181175595],[-99.65519411180787,53.58499338642594],[-99.52449807345116,53.39112308053666],[-99.53744474609252,53.22790843557864],[-99.34412523996022,53.02984336899676],[-99.19224283465542,52.74049345960149],[-99.03670384983627,52.763559423652126],[-98.86831457518072,52.71589909271222],[-98.7685494626916,52.53061182406527],[-98.42764197495399,52.471609788781855],[-98.21164919802338,52.24270393227402],[-98.27447080090356,52.05879795486116],[-98.1107380550883,51.854598741904375],[-98.06116547174709,51.63086620374217],[-98.43923014808503,51.43613338372843],[-98.67145286637954,51.26241006201507],[-98.7199203114458,51.08332781583287],[-98.91362005671911,51.01821752020097],[-99.00508976444887,50.8921515332837],[-99.16079383182831,50.82706870572649],[-99.53295748137555,50.513788979317546],[-99.59379917245694,50.33963863116914],[-99.790828269545,50.20046963810494],[-100.06149525014901,49.789067262171194],[-100.3478143330297,49.64644448747876],[-100.52836886882972,49.4576329441944],[-100.83736685505268,49.31886443069326],[-100.94501140556109,49.147402915918356],[-100.89629100378279,48.90243473293274],[-101.06751324847971,48.70856197244175],[-101.34893674241998,48.55154108639806],[-101.59271300657448,48.37520081311834],[-101.63455390654505,48.28941128702149],[-102.00159328835987,48.20863981774457],[-102.23537551250205,48.19025137408067],[-102.39732548417328,48.01147082328849],[-102.61667841818345,47.853694677211976],[-102.66614846514551,47.74280670020851],[-102.8173313553911,47.6373603267232],[-102.8868260584878,47.49550584459218],[-103.01605181510736,47.439780228893675],[-103.043016885453,47.32996334835393],[-103.26397524479476,47.25107314587208],[-103.40520390237555,47.11867658257322],[-103.53647238091946,47.09756135015471],[-103.58132936949927,46.821330115410376],[-103.76661774417218,46.696326136256204],[-103.76512834538075,46.58178693664673],[-103.60208381061173,46.36003822942989],[-103.82341391563706,46.20877291339343],[-103.62563466229177,45.977012701257124],[-103.48178606578387,45.95247379623876],[-103.34430514928228,46.031574474262435],[-103.10417573634216,45.75188259694662],[-102.95375974158272,45.81911546605488],[-102.68283247813967,45.74473741557345],[-102.71732103215253,45.5049289144849],[-102.56001716755374,45.47703635859386],[-102.42345519130927,45.383663589225854],[-102.42535492595087,45.25148721250877],[-102.69820928179095,45.013837403699775],[-102.93661451640374,44.977842512500246],[-102.97420866281541,44.794643366406284],[-102.82546862415712,44.623885331530225],[-103.00338620127158,44.45502489515333],[-103.12137095499337,44.13200145191199],[-103.22189303587803,44.129443622808296],[-103.30301538936111,43.89980017688626],[-103.61561042528409,43.73824615623293],[-103.61979656954036,43.62926353878937],[-104.30105399909925,41.39720865640458],[-104.6921617607558,40.08372732755261],[-105.1545324548961,38.48082692622877],[-105.87478092560248,35.93645644097883],[-106.46404420226641,33.78026638811077],[-104.31192778132018,33.36581054392819],[-103.0893826266503,33.13511965910285],[-102.44677704993042,33.02674323248517],[-100.5242642068822,32.67530139609796],[-97.61678214331693,32.15818197719285],[-96.20430334539437,31.912657858899273],[-95.03593678533751,31.748734799760364],[-93.96900094680741,31.52191471915314],[-93.29275078279323,31.41412303255641],[-91.0947051057272,31.037517701670165],[-90.33473517882668,30.877303989026007],[-88.27544927309998,30.57685599057522],[-85.59393538827628,30.18499100633823],[-83.75030608011934,29.911971823374394],[-81.5113417713196,29.578137009654668],[-81.41513541661094,29.534585688355065],[-80.5767476663345,29.421118773069484],[-77.87014524621921,29.033486328164564],[-75.55878313374993,28.718537400695144],[-75.02552976760673,31.61537016081338],[-74.72989914360193,33.082599456695036],[-74.44256184642381,34.59230711509369],[-74.13631972475936,36.1576828777831],[-73.87439037819183,37.50407454855722],[-73.40151502879577,39.84505711229115],[-73.28828807352137,40.45063917558032],[-72.77560308200106,42.84692105176968]]]},\"properties\":{\"name\":\"Idaho\",\"ns_code\":\"01779783\",\"geoid\":\"16\",\"usps_abbrev\":\"ID\",\"fips_code\":\"16\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[11.15817993632201,-15.607246340684972],[14.019792351059385,-15.565278568712136],[15.376344051405509,-15.53707531736063],[17.924495149820324,-15.47489464770237],[19.570358621570605,-15.436279173323959],[21.74441641874613,-15.368783805472427],[23.40600110467133,-15.306723188525936],[23.8933140913726,-15.299232000271587],[25.911999109283276,-15.213349504714875],[28.635107754361403,-15.090305286856136],[29.302143987639354,-15.074262151981163],[31.98889446653313,-14.933454502205953],[34.45372225390078,-14.801411529299058],[36.50370562805858,-14.686484268208192],[36.68404460859383,-15.281283543551083],[36.96408022082457,-15.345955190989583],[37.05761161146479,-15.445518532191814],[36.978684343590245,-15.882952555307314],[37.08986949611283,-16.01391345003748],[36.9932152069695,-16.228813581290115],[36.81816172698048,-16.285359005391065],[36.77424071301694,-16.523612154069223],[36.43233941631891,-16.720545319438113],[36.25227202860081,-16.879639932275566],[36.189061565608185,-17.19137000872146],[35.8719449394597,-17.38171628712585],[35.83580246162693,-17.50058992458322],[35.58811204977749,-17.899151070892714],[35.438314094355995,-18.22226757141799],[36.90761359457927,-18.122665884942],[39.115908061472275,-17.969574673603287],[39.26500269064257,-17.957629768053884],[39.361472033225105,-17.72441427532786],[39.379498527778956,-17.38998800438465],[39.85218016980386,-17.03433569605153],[39.752771828483624,-16.754333657747686],[39.239661444137276,-16.41529190435378],[39.211571148892276,-16.22450657564741],[39.46178823807437,-16.22292334920965],[39.696246084332905,-16.28688155082066],[40.12450608924756,-16.165485762720365],[40.104657518915964,-16.027712840387068],[39.85809927161027,-15.936332912593821],[39.65864933137292,-15.805091721972541],[39.649903738017244,-15.587816549931453],[40.09839005908344,-15.566865293651219],[40.20700537518413,-15.320538326404073],[39.999127539218755,-15.01045492484409],[40.093492288278085,-14.595557770587407],[39.97868976983181,-14.468523305765057],[39.845752312227454,-14.292191020575443],[39.77113578197755,-14.064412374128374],[39.98635984307271,-13.881733254083315],[40.27963302351395,-13.95069272460474],[40.375637997522105,-14.23865509568735],[40.28382366690882,-14.451419584142306],[40.24501954933335,-14.602289103261224],[40.352911368285525,-14.719945412565714],[40.48236603709288,-14.687612295964634],[40.66930026821994,-14.415031520710084],[40.8976275046259,-13.516115635813076],[41.084701675747475,-13.448921719646894],[41.17731742117705,-13.504626337927812],[41.470317377906795,-13.865587954717132],[41.710981374356464,-13.85021601700745],[41.831854727960966,-13.616491080523055],[41.84618419710384,-13.42380593300298],[42.04533329139144,-13.147180645074506],[41.97547115100682,-12.99441740928593],[41.78356035013613,-12.796840448530999],[41.78854133143369,-12.648446894578498],[41.93679483942295,-12.498028556626025],[42.188255866880425,-12.53550812300595],[42.22658643954535,-12.365078306716928],[41.86386144567858,-12.15769328918572],[41.89057696951949,-11.921175243514766],[42.07007779012503,-11.869760178374074],[42.16690913701174,-11.570441612573731],[42.233975489608056,-11.088561964883837],[42.16572717751512,-10.948943253185128],[42.02931500882346,-10.918906030870916],[41.826595902865876,-11.016634258560076],[41.697242880876125,-10.957021532433703],[41.61357150026111,-10.687290100840675],[41.27244728804197,-10.390236243229479],[41.00087425362418,-10.369483913465693],[41.04913914505725,-10.541321363028842],[41.31033057384324,-10.732387597091643],[41.27153438386131,-10.871231135183885],[41.12791514279512,-10.910383754522526],[40.97656780970497,-10.774943523072466],[40.611191166619314,-10.60214862159497],[40.589221667866525,-10.220778506300196],[40.30401248043611,-9.930013777625792],[40.0698265933911,-9.509859894345103],[40.07110951686211,-9.202895966082833],[39.88680553721259,-9.153249812396679],[39.70946597166495,-8.916100974138994],[39.74487955957955,-8.713913951717359],[39.92997923735485,-8.530721060461332],[40.14274888220047,-8.47139187412875],[40.2026884640639,-8.113536167920964],[40.075774599597864,-7.812982268303306],[39.88541418979997,-7.616545556390664],[39.598398839270466,-7.141389640626316],[39.55645754554342,-6.806046530677747],[39.77242892103263,-6.789738004523492],[39.779763474599996,-6.675255212690824],[39.53942683775235,-6.387838600831967],[39.54951614780897,-6.069131216250661],[39.466657423953066,-5.982018687893724],[39.13833014171012,-5.897773457139166],[38.9269055789515,-5.649350235074406],[38.75364570624236,-5.6919807547863055],[38.644363669994426,-5.5931973659844205],[38.67358633195593,-5.404133406871772],[38.20202567718062,-4.999442158902918],[37.873904100224195,-4.9115369140054685],[37.85485439361129,-4.773318841766313],[37.590733140532635,-4.617383825116188],[37.308884765662164,-4.8847145614090985],[36.996435236268844,-4.806225486318696],[36.86460671966089,-4.563551212570213],[36.93649962721861,-4.410408406325724],[37.09009000177457,-4.342206807635736],[37.027353517918435,-4.183547281274452],[36.767969849940776,-4.265681565519528],[36.31554701140561,-3.897209060466638],[36.16100221498926,-3.9040548175269496],[35.96299347922159,-3.740568875963845],[35.93016980460167,-3.59367799353469],[35.69695273325673,-3.5554730215201245],[35.287899869794096,-3.2742998765646374],[35.08917524183904,-2.958909486586947],[34.74636276096354,-2.746667764471987],[34.59709503409951,-2.5239826747154055],[34.494358875300264,-2.084892075500455],[34.48198564050713,-1.7262621055774032],[34.62099596384176,-1.323111163499892],[34.88649868374687,-0.983727517106635],[34.991072511469454,-0.3432457992380856],[35.193303330257585,0.06379486462909392],[35.352025252055086,0.23864002110531032],[35.41165728591256,0.4446227590576482],[35.347069733752335,0.799636529382131],[35.18914948216547,1.0467375572229356],[35.418591025902316,1.4984102384763298],[35.64533639356983,1.695607378241656],[35.676229943629075,2.0672172845215946],[35.19652614924403,2.3141805420177732],[34.893793192301665,2.524281529736155],[34.56688244587193,2.5390291894029957],[34.09517875377449,2.762893490631699],[33.77292937954032,2.806660024422795],[33.65981466020491,2.7357113002479063],[33.59166649872845,2.494096261741557],[33.285100258949356,2.1026185380300904],[33.118583329741185,2.052533789477337],[32.85952912852419,2.173091014162095],[32.62594701233995,2.4419998236286844],[32.51085924620584,2.9225356272140925],[32.301808411628,3.3660482612222036],[32.45586847733624,3.6122456103341194],[32.41429428168557,3.8247399590854143],[32.28111764542598,4.070624550043868],[32.14519068565356,4.753900822266205],[32.00502900057115,4.848644563872421],[31.75899223967253,5.151757289241015],[31.45002942670855,5.391884044049794],[30.96001931804774,5.701955121835456],[30.9285860356661,5.776465569528098],[30.62232227643527,5.905330144447368],[30.37027904986839,6.0748731676913845],[30.22910675029438,6.251705051955103],[30.2111355749887,6.397841751130604],[29.99532700333498,6.70448369864561],[29.735044152758707,6.739881809472998],[29.618416495330436,6.870929779026695],[29.50853919258213,7.152986882366935],[29.30060879305578,7.231864118550506],[28.996240447887615,7.561040970806828],[28.594084773934274,7.860833623062233],[28.476256380166095,7.987561279216836],[28.47725925636208,8.442597133076251],[28.400474966662724,8.561717614446039],[28.0786803185042,8.773145257066087],[27.990125695463256,9.025905324044874],[28.123404112927346,9.294891704550018],[27.681209506801867,10.137747949000373],[27.702545280998812,10.287555540040804],[27.56444267771727,10.767870397305137],[27.534549053524504,11.13719401739156],[27.550389469814935,11.546046008571679],[27.610344031279425,11.844557638254571],[27.75308706531505,12.303522114402103],[27.970598476146584,12.565960353874365],[27.850443626729838,12.616508059920148],[27.626274986859638,12.579595961996988],[27.39365223646377,12.767584519632887],[27.369148043238127,13.102507700364185],[27.14441381440257,13.117960011395798],[26.988940517459294,13.340282195207541],[26.86588092483485,13.403403820850327],[26.826848184063138,13.664267844433354],[26.502253928920393,13.72501393808454],[26.454202244841692,13.935333932747854],[26.228238394896653,14.136673232518838],[25.482973306531253,14.065696092489317],[24.260676105732653,13.972042952077866],[22.177515221359698,13.845938600037732],[21.243935730963845,13.780059873168897],[19.34436744481307,13.690335991123066],[18.01997462416407,13.613200800964595],[16.478373506186482,13.56941524191641],[13.662808845956164,13.454665993667406],[12.530616694410385,13.422649718617294],[10.8592379111042,13.392374568848469],[8.724009035483911,13.399118468582895],[6.05511071764217,13.420658791476566],[4.566633702986271,13.439284734745812],[4.524304813228201,13.390169844207072],[4.613560418195268,13.024601980758263],[4.935511477066024,13.042923323227733],[4.951272460836428,13.242447693604879],[5.071323642050462,13.282660578188313],[5.176765771937176,13.11124695257141],[5.126717302863511,12.96528448192216],[4.9211662714740925,12.880188203929183],[4.958770487169235,12.628488777917802],[5.139060004098508,12.454092709302369],[5.130611975215265,12.253243833874672],[5.234283699761395,11.929296083622926],[5.34135207230563,11.735951728911141],[5.174570297093842,11.596189706233424],[5.717030731083257,11.376775147162574],[5.7016294100410345,11.232749965879616],[5.919099224807783,11.081449431966108],[6.12119598177792,11.043464083788667],[6.169493007748666,10.895615014473329],[6.1277377114886695,10.620629472667467],[6.3297043336144885,10.46917956119775],[6.381175599284466,10.305796793936675],[6.569915340338304,10.20450602868001],[6.446421997339522,9.67722108834969],[6.601560663029592,9.512215114572014],[6.750435022901872,9.544697304392711],[7.0535168363995515,9.323093145878019],[7.370682879678943,8.960469713921997],[7.6149905139732414,8.899177902438877],[7.612336436514287,8.702164050110822],[7.702531242337706,8.61810780735034],[7.938170505738833,8.648889248662297],[7.974278041169988,8.481264381095682],[8.272121094067826,8.350770932637694],[8.541762298081405,8.382171638633482],[8.648344165534755,8.623366743268518],[8.973301239533571,8.638506393383208],[9.083827596009659,8.586303334472309],[9.060353726570169,8.288783444107393],[9.324274489846369,8.167776811344714],[9.40928593788372,7.974511003403423],[9.33306771250659,7.859742984913162],[9.175572240030853,7.8498572565853095],[9.217531150903955,7.629497559782909],[9.448011815026327,7.728928455231902],[9.507319937495396,7.595597377163614],[9.419147423627242,7.425915916967337],[9.273046529912655,7.37930997171756],[9.027152727370488,7.5274437317141665],[8.90385132772932,7.431756453996362],[8.913001504006026,7.136232482805796],[8.614129389818752,6.974141840157881],[8.451259382710589,6.637699039205099],[8.444696583367827,6.343655602116607],[8.169694740714364,6.292176606901212],[8.168761145218173,6.05001968621848],[8.48522792944248,5.749330359525648],[8.5813917339368,5.487728014115429],[8.818386583707644,5.384913836765333],[8.914253495274922,5.210351132585056],[9.210506495934165,4.9515808857691725],[9.375110441683805,5.005543384641402],[9.430503177647187,4.886164805076974],[9.261929297114474,4.7090496118149],[9.330838183573544,4.330449262237731],[9.61884764167137,4.1186096616912415],[9.723557172824412,3.9863732123435405],[9.703583741090133,3.7421152173382177],[9.971123403995515,3.682391955438755],[10.083669546426995,3.4885326859336088],[10.233200588046849,3.4128526976295266],[10.488994171881494,3.5172271696255835],[10.652157957630978,3.4540645010576663],[10.700842176267143,3.3107095067228953],[10.942232904280978,3.3626137350808376],[11.040756064804082,3.294577136152732],[10.944250743197683,2.9653427391317586],[10.969712175675513,0.8458103818709393],[10.978285250285774,-1.697595599832909],[11.007986447680219,-3.40698894608187],[11.024645641602872,-5.17660369154813],[11.025854269945661,-6.594127954948864],[11.060071699086222,-8.564991764529054],[11.083149581423152,-10.733698997627359],[11.10561780019015,-12.115701625453237],[11.121876233715414,-13.398448797486239],[11.15817993632201,-15.607246340684972]]]},\"properties\":{\"name\":\"Missouri\",\"ns_code\":\"01779791\",\"geoid\":\"29\",\"usps_abbrev\":\"MO\",\"fips_code\":\"29\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[]],[[[-86.976421716419,-59.19176371285572],[-87.1870067956047,-59.13596570244509],[-87.45787156432733,-59.51437913507164],[-87.12145673897376,-59.52297205920951],[-86.88193246459696,-59.42576789603772],[-86.86722683644939,-59.30773724682937],[-86.976421716419,-59.19176371285572]]],[[]],[[]],[[]],[[]],[[]],[[]],[[[-85.41955731276964,-59.00984014245544],[-85.09160214402313,-59.08914045903313],[-84.77007735803029,-59.05209340049514],[-84.64613068745078,-58.967616864461334],[-84.57539567333286,-58.836251827116975],[-84.44010098500468,-58.74203896572752],[-84.02030240720235,-58.6688555413924],[-83.82883379485013,-58.58250592012526],[-83.76784285030078,-58.42691134756018],[-83.80010083339025,-58.35391444087984],[-83.65575231988856,-58.26058698152107],[-83.5928031793589,-58.12705344405945],[-83.73036933741342,-57.985916857945305],[-83.98353819080964,-57.8403559310283],[-84.27308662246789,-57.78520785581053],[-84.54142776238963,-57.81802266073612],[-84.67176304026619,-57.93744646283592],[-84.7726794375977,-58.12338342567573],[-84.94366411700341,-58.160330390432556],[-85.23503373664384,-58.297682122477646],[-85.48599005919844,-58.38223480239169],[-85.6410661301469,-58.599829795243686],[-85.621900734722,-58.86063686638093],[-85.41955731276964,-59.00984014245544]]],[[[-64.33967725567578,-62.12145231925232],[-64.09644759807428,-62.279271290233666],[-63.91448193761405,-62.30156641191102],[-63.89519201033444,-62.46622349518716],[-63.77513285548172,-62.602784163966795],[-63.554801182975964,-62.71733842741465],[-63.34281437223167,-62.7437134607258],[-63.162073409088954,-62.72171378026632],[-62.753480775944006,-62.72573165728349],[-62.55507463953584,-62.65826635538436],[-62.21942283470392,-62.58124372642653],[-61.915626007132225,-62.34383120505626],[-61.96594373980692,-62.201410007427306],[-62.40797944887831,-61.935719881712664],[-62.710693381381745,-61.82361532847959],[-63.01129180101466,-61.77816566123782],[-63.523940462868715,-61.75441517884006],[-63.82848284792668,-61.76029934944814],[-64.18747403168645,-61.84369406277611],[-64.33768024307285,-61.93442828245809],[-64.37809170755988,-62.06603948927598],[-64.33967725567578,-62.12145231925232]]],[[]],[[[-63.692935539235066,-60.81421142663795],[-63.82237581616583,-60.867659005111264],[-64.6563722551029,-60.825443459410366],[-64.82331485551406,-60.778489969979276],[-65.06602811914908,-60.75407369682826],[-65.41390558904655,-60.76059626240902],[-65.63541895992194,-60.84232562747241],[-65.67883384379712,-61.01015533671955],[-65.93515575517154,-61.17114647899671],[-66.04106945196263,-61.2805277605679],[-66.04726762990231,-61.362302355393034],[-65.9308072501743,-61.45977690384293],[-65.7442654621445,-61.532837553076995],[-65.14267394456778,-61.55873805702447],[-64.46001106440656,-61.54054268324979],[-64.07548129352176,-61.51005672585461],[-63.92873051264152,-61.56039002356043],[-63.4172310854918,-61.63082154282622],[-63.01946033761302,-61.71231476951189],[-62.499616000995665,-61.70407689736096],[-62.15357514981829,-61.62137873948033],[-61.39909813448873,-61.38026406738089],[-61.25167184276372,-61.24899192288205],[-61.31266226148663,-61.08625678258212],[-61.49333961549092,-60.98821042090315],[-61.731617045805265,-60.93487063704647],[-62.218772862424,-60.90720518756784],[-62.61148456345145,-60.94677824800706],[-62.75108903864681,-60.91105163860422],[-62.99151867453179,-60.923230399118815],[-63.10618742106225,-60.830122636029685],[-63.271985402320645,-60.78608503829523],[-63.52750898439984,-60.77155258479336],[-63.692935539235066,-60.81421142663795]]],[[[-80.31050275576641,-58.7344055540255],[-79.91313881593763,-58.79389270774227],[-79.64422464908843,-58.732250345279034],[-79.33425478529215,-58.620714997065235],[-78.99901514716677,-58.463667009670694],[-78.83929820283134,-58.345989110759966],[-78.803490694058,-58.25452984289787],[-78.83008249286378,-58.05592029148471],[-78.60107643764137,-57.81062343043394],[-78.54402689769239,-57.656732317859046],[-78.56778891222976,-57.49820070910068],[-78.69710204610286,-57.35641657590664],[-78.98270249341016,-57.17896717901529],[-79.60741900303844,-57.020528381186004],[-79.80706367309676,-57.06730122404922],[-80.14837348544923,-57.03523486764882],[-80.41978571572447,-57.0738304621579],[-80.75403670948165,-57.04337471367897],[-81.00853577509535,-57.08312881330657],[-81.44044439421081,-57.25356943025507],[-81.92751328542211,-57.356347485779],[-82.18940430428141,-57.50449341577804],[-82.22591367752445,-57.60090773643917],[-82.52060772046909,-57.75184302440606],[-82.59140535466973,-57.819828762887155],[-82.60894151934706,-58.04338228075502],[-82.56729566190087,-58.105267854290204],[-82.23826243950208,-58.33145268725227],[-81.85483629906321,-58.43600130922413],[-81.69413854750071,-58.45395554870303],[-81.16151241428793,-58.681373039562864],[-80.87214084397618,-58.68738619139291],[-80.48392281866145,-58.73368111769999],[-80.31050275576641,-58.7344055540255]]],[[[-72.40158477567908,-59.82839959084251],[-72.27516201345674,-59.880028451773164],[-72.29421990906151,-59.978955940988705],[-72.12797755927828,-60.147216111831106],[-71.88723585772289,-60.280736253719006],[-71.73965075957514,-60.445687084238834],[-71.50944260389221,-60.511125718431224],[-71.38536213970836,-60.76703565228537],[-71.2849251519387,-60.838444936827],[-71.02316410310976,-60.88959970324487],[-70.11422157106414,-60.839771380268125],[-69.60759356751635,-60.84792403405864],[-69.34346327699527,-60.975138825406034],[-69.01679477882287,-61.0233962310969],[-68.75669055655628,-60.994268438681836],[-68.58168664227885,-60.94694084040569],[-68.33434572931242,-60.993593238453634],[-68.11636060967882,-60.97463627646145],[-67.65958040929651,-60.7844112058279],[-67.59368957379517,-60.694457868908614],[-67.66171368595107,-60.49293484872323],[-67.79666111683989,-60.4104745234764],[-67.9540986619144,-60.19101773832563],[-67.8774172269996,-60.10797792270926],[-68.01189537564008,-59.98727954895879],[-68.3539539063704,-59.901628905521946],[-68.61313788507337,-59.91671913510031],[-68.81329501997953,-59.81637559051981],[-68.93072618438093,-59.67140592180754],[-69.23402182840557,-59.53982810339619],[-69.48128117525258,-59.23894240837319],[-69.88306830599107,-59.05897799645939],[-70.25336267045759,-59.032738575719115],[-70.60731716631562,-59.11095642581813],[-70.91173597647061,-59.24403214543778],[-71.04112583684592,-59.37300089135012],[-71.26980359672267,-59.46326131651071],[-72.13446748387804,-59.51204993358236],[-72.44295159452676,-59.55491756948937],[-72.58052665378425,-59.670911996538685],[-72.56452407818664,-59.738412863544795],[-72.40158477567908,-59.82839959084251]]],[[[-60.675441827476064,-62.568385572931405],[-60.31773173153872,-62.57961627180863],[-60.237603358429524,-62.6979028278054],[-60.44974776855176,-62.7626703685019],[-60.570028095797866,-62.83150102500272],[-60.89284824973543,-62.832236022408125],[-61.2771769242162,-62.93882479494697],[-61.608445764168636,-63.00482746021856],[-61.804279533575844,-63.078107595351455],[-61.9416195217055,-63.19856479751408],[-61.9131748566001,-63.48506658024812],[-61.61173113378597,-63.48553483895925],[-60.45900460005693,-63.45674516345832],[-60.163464232957345,-63.33881694938499],[-60.09796086800145,-63.24441381123246],[-60.15762816532769,-63.15174518685729],[-59.9662348077913,-63.154387976134736],[-59.70155170534978,-63.22153885004073],[-59.314681661503364,-63.23934938687383],[-58.82867170213299,-63.219632848437215],[-58.55724030349906,-63.17626442555673],[-58.19269548558662,-63.08976120064793],[-57.78750513582752,-63.112627473097824],[-57.372063413658495,-63.02014060617052],[-57.244501494183595,-63.02364709593945],[-56.990064783503456,-62.97449683129629],[-56.623381685308814,-62.81840393932649],[-56.40146658771252,-62.60871096280656],[-56.39433402954744,-62.42227116337405],[-56.47874310683207,-62.241761908660564],[-56.61302448799613,-62.11488414462133],[-56.887955114047024,-62.11539737679368],[-56.88732055508226,-62.19118126072103],[-57.10401920316344,-62.16596748472209],[-57.400809719067205,-62.097219595627706],[-57.633347904711854,-62.00591827943162],[-57.80313778157321,-61.97589136468849],[-58.19943217922935,-61.79585605733232],[-58.72952281895469,-61.68673055444849],[-59.164511494890796,-61.68742780097657],[-59.60416547629174,-61.788695718951466],[-59.913568802942834,-61.80719321524067],[-60.02413356054153,-61.742952136578126],[-60.07710446568842,-61.63791308376758],[-60.1801480707115,-61.58331839984096],[-60.641542796511025,-61.42510921726305],[-60.85279208321749,-61.39110921272044],[-61.38444306964383,-61.44477611730483],[-61.63916188142711,-61.55809639461491],[-61.816604984750256,-61.68454701445292],[-61.928640423870874,-61.87629697162352],[-61.92936566810392,-61.992615277055364],[-61.83244711168577,-62.14458206885236],[-61.72636114094341,-62.22885232541607],[-61.310865283634065,-62.43944678766379],[-61.07676778678766,-62.532425908151815],[-60.82142033829599,-62.53224439435537],[-60.675441827476064,-62.568385572931405]]],[[[-51.80612007410345,-64.69200994635712],[-53.08993923203773,-64.42391382351752],[-53.78094014296531,-64.32910985880329],[-54.036545836728834,-64.33004747816486],[-54.39473353745406,-64.22574957956607],[-54.77899172019775,-64.13219668454953],[-54.869479723059854,-64.08020948868726],[-55.20161608812185,-63.97871267690463],[-55.77584518009442,-63.896422084522065],[-56.24635552141322,-63.94465993441407],[-56.52117320228471,-64.08337180675368],[-56.58562969376138,-64.1661849884019],[-56.58402877073641,-64.37389613280803],[-56.410451544485014,-64.63099927021156],[-56.08582073697759,-64.82793381728995],[-56.45130192414271,-64.99976339499734],[-56.608231239265756,-65.15686839522702],[-56.944005050841916,-65.21529493526214],[-57.21978410819566,-65.35310387522414],[-57.394364805298046,-65.39011406246877],[-57.580016986553595,-65.5106940371674],[-57.65797545849665,-65.71952735180756],[-57.452702568025295,-65.88796947465812],[-57.36049878920629,-66.0050781972822],[-57.09436546350464,-66.07111925963365],[-57.0065383101974,-66.1698314476408],[-56.9364883107857,-66.3519486336408],[-56.84297391781593,-66.44812680190239],[-56.70058985316326,-66.50575076322873],[-56.60116128972976,-66.67320584006632],[-56.46229550865785,-66.79186427249233],[-56.48263159100951,-66.94226698214673],[-56.66344914173132,-67.3513401641101],[-56.66352913557869,-67.44650378523103],[-56.527014349831944,-67.5937562113423],[-56.335649198466335,-67.71654299944896],[-55.990563450919595,-67.80149021877374],[-55.67424009444388,-67.82754380051419],[-55.35888667019029,-67.91585140924869],[-55.076095055251734,-67.95954660279388],[-54.95946322926337,-68.05100707373805],[-54.76531186243995,-68.09250094720778],[-54.55395904635189,-68.09375969420691],[-54.336520439219676,-68.05735157967992],[-53.89830463527998,-67.91005658606815],[-53.76256176262195,-67.83644705742456],[-53.709720024460324,-67.76312411156827],[-53.58496155079681,-67.71155440728197],[-53.42610854112635,-67.53904097761307],[-53.23273068497241,-67.47386309162982],[-52.98226762228646,-67.44089966539818],[-52.7306859851316,-67.3386266124636],[-52.39870150770125,-67.29125712680901],[-52.101442866569734,-67.18546070982927],[-51.83359320776795,-67.1219730810074],[-51.64437749922236,-67.14724648194401],[-51.32053694318809,-67.15437175720353],[-50.80482727545191,-67.08561976062548],[-50.42623050514047,-66.99293222392838],[-50.07821389290976,-66.94369690435762],[-49.68993207381188,-66.8635314208252],[-49.35107218638497,-66.72909179838874],[-49.07519102167938,-66.65847098384856],[-48.823435291222246,-66.57033576576247],[-48.59288683871604,-66.45311388857397],[-48.46688731166826,-66.31073045348273],[-48.48712162664866,-66.22619620156281],[-48.61636175974756,-66.15936404060125],[-48.91450274101888,-66.06415065105398],[-49.271561803869744,-65.99932781206306],[-49.51986792013009,-65.86712522668867],[-49.63833589578237,-65.83385867422263],[-49.634553148257844,-65.74364393303361],[-49.801366881571205,-65.59111068724306],[-49.97394959785095,-65.51923868131237],[-50.34810195233998,-65.4850202504788],[-50.34118622446916,-65.24324169963707],[-50.71114171469799,-65.04500912967372],[-51.07891720409698,-64.90263015932388],[-51.80612007410345,-64.69200994635712]]]]},\"properties\":{\"name\":\"Hawaii\",\"ns_code\":\"01779782\",\"geoid\":\"15\",\"usps_abbrev\":\"HI\",\"fips_code\":\"15\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[63.42482494774179,-22.843137506469024],[62.134048965493356,-22.949685482998014],[59.71624178541565,-23.168053705646],[58.44842101369036,-23.28622260041496],[56.32328994947061,-23.469323197838985],[54.886398371047115,-23.564773831332076],[52.891321993088674,-23.691488716424097],[51.55040943357218,-23.796020507964997],[49.59928982564007,-23.933875606776798],[48.42650634592789,-24.00937717466316],[48.450529258033,-24.089261644065356],[48.770717458289354,-24.564396155555613],[49.102318724017394,-24.72005334101876],[49.08372371399291,-25.817439323514556],[49.05084039235424,-26.522101032910896],[49.011877900519295,-28.01249452315756],[48.957799069871726,-30.251326210950456],[48.89622278911415,-32.164863835290184],[48.85108356095347,-34.29276220442066],[48.818302193000655,-35.62190957022789],[48.78443273652495,-37.17310802473811],[48.74705871514406,-39.02631852795543],[48.70202090482549,-40.67147754386478],[48.635251106449125,-42.59494673069236],[48.7921449426802,-43.57473554749848],[48.999732716326236,-44.717498063728094],[49.43300611764585,-47.10715832896062],[49.6735364425197,-48.42361093765877],[49.882096992570645,-49.57311972638721],[50.16270765860933,-51.0128586304241],[50.369966964285055,-50.85987729679268],[50.606667737576686,-50.90329047019945],[50.803978899168534,-50.882983053347104],[51.50356684428442,-50.75605560442921],[51.70578104218372,-50.86537884234699],[52.06973326949439,-50.96590372848481],[52.3552727930551,-50.93622923055497],[52.49132742407167,-50.82480865890757],[52.965361691688365,-50.77157699973285],[53.694329304773625,-50.74191771840816],[54.23228888573714,-50.64698124170037],[54.65802045976985,-50.58937999156783],[55.432677152303704,-50.40741284955484],[55.39783258270929,-50.160076470000924],[55.79163391958566,-50.06239902874279],[55.45888789818493,-50.031394843161834],[55.481780446955916,-49.94770803691703],[55.70505917138437,-49.91149978837416],[55.771391925849905,-49.84754572731648],[55.80460403066488,-49.63927232208758],[56.214461496466434,-49.42675432034854],[56.19632780302312,-49.35814661357701],[55.92121943207875,-49.32564678943361],[55.778556812210724,-49.22543362955025],[55.68925075241795,-49.003267434986505],[55.88034000699768,-48.73318016311412],[55.94204980927267,-48.5229907728421],[55.83190072065795,-48.304599446178024],[55.22214012086684,-48.11800456589026],[54.9993239741839,-47.99524438580866],[54.92200575903599,-47.86059407145466],[54.38322082321151,-47.56292172989796],[54.31851777109372,-47.46957187593697],[54.39378139464519,-47.36450033915733],[54.535192510557934,-46.9732492266617],[54.45464744590852,-46.822713026245864],[56.1957565027828,-46.71038249153672],[56.64790107899711,-46.69189861924351],[57.234163634396964,-46.639014860660225],[60.74015587389449,-46.42746089623557],[63.42185456928227,-46.250299454582354],[64.91639136121721,-46.13737095218202],[66.7738477694868,-45.98432513267169],[68.7358723309614,-45.80823604802087],[70.19629910032872,-45.69210057800601],[70.0988287079811,-45.43342027703474],[69.9775134221617,-45.33687212833131],[69.89719048375117,-45.165049048518654],[69.79360794514375,-45.12403413274162],[69.61133003072719,-44.94340793318844],[69.47351938350666,-44.9186831221116],[69.39840475097725,-44.731904836499524],[69.4410624763717,-44.61288591342831],[69.30992185918646,-44.422739972031636],[69.42431500046223,-44.26250647022132],[69.43619531077319,-44.08907089212956],[69.35505109869972,-43.900827273710576],[69.45338635393999,-43.50208746311834],[69.39364556014804,-43.3559976366992],[69.5169954666728,-42.97271795561201],[69.39159978990268,-42.81671227629591],[69.35520905914618,-42.566557615849966],[69.15926418407966,-42.46448759337489],[69.11123676274981,-42.36742702417077],[68.88032911038079,-42.19342573483923],[68.89275736397595,-42.00670145172606],[68.71454245174245,-41.745937058577255],[68.7512169315937,-41.565507746882034],[68.65979026224501,-41.34525545176124],[68.93928072988811,-40.84698734010655],[68.90039470203128,-40.77885141860553],[69.04679878793986,-40.47603173541186],[68.99475225613098,-40.28305254478265],[69.04290122177706,-40.05045850423539],[68.9047927631641,-39.816885494550206],[69.17958296188097,-39.51925946743698],[69.43923174763418,-39.416767262986596],[69.36387342240704,-39.30189926695326],[69.61684113609299,-39.27177345283864],[69.76003231829094,-39.038060405353754],[69.53244999377507,-38.83479967797245],[69.10142218738754,-38.73796241118222],[69.07190754408029,-38.61230975807568],[69.18099863135441,-38.50533539445314],[69.18583399420908,-38.04883118728757],[69.05024616124861,-38.01633300778022],[68.94965907399772,-37.66058724153553],[68.7949487424158,-37.513362231384036],[68.50631592508438,-37.33141230299216],[68.38662244849968,-37.1299010688702],[68.22542965486726,-36.979482333880725],[68.31546943926574,-36.897161194283164],[68.1530836744863,-36.76485713830245],[68.06453344133321,-36.583028957532534],[68.10750119120276,-36.481665679220754],[67.90723494684319,-36.332523181840656],[68.01915815585218,-36.248052991669496],[67.73501045815965,-36.07702956021999],[67.77312322829931,-35.889565216242666],[67.57114866541467,-35.72772811820122],[67.08924550741122,-34.3459528649659],[66.36916397872132,-32.17132347335651],[65.8480041532393,-30.583753467405295],[65.35334285155002,-29.03775810334111],[64.72273234144755,-27.019045293040904],[64.18677820422954,-25.381793440605563],[63.86179930676535,-24.354809416419766],[63.42482494774179,-22.843137506469024]]]},\"properties\":{\"name\":\"Alabama\",\"ns_code\":\"01779775\",\"geoid\":\"01\",\"usps_abbrev\":\"AL\",\"fips_code\":\"01\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[77.76766199990318,-21.098173501567533],[77.75426678073293,-21.49643645198443],[77.54791349928631,-21.60968097318748],[77.33032459103508,-21.966121160718938],[77.11672510368311,-22.009876248129572],[77.11556541184892,-22.15312488188986],[76.73411842652578,-22.66393653547174],[76.75672547064481,-22.906298569503296],[76.639129636322,-23.015735619307264],[76.6371147865837,-23.14769190365468],[76.75182460645489,-23.3793444266158],[76.95940695825877,-23.423561869813213],[77.04011803258982,-23.515221311738983],[77.2746902825852,-23.596685756997413],[77.43078635522446,-23.750849203340668],[77.82177403898275,-23.839166248091946],[78.1136466922666,-24.00773603428736],[78.36350915757598,-24.258524235978335],[78.69850806944197,-24.44143728805723],[78.95560673489317,-24.433062219187676],[79.46391638633034,-24.324134103619112],[79.70526867777363,-24.4719794905524],[79.97843933083279,-25.068233410018525],[80.20265040996036,-25.186563923361813],[80.34166689680922,-25.454440223914848],[80.52559629335536,-25.578534506970765],[80.613888942852,-25.72566469617386],[80.6731931799653,-26.007748184526584],[80.8640482578189,-26.35319798280792],[81.12993022274708,-26.454673951298332],[81.47054139918431,-26.83721108104131],[81.70453535561428,-27.041139123912714],[81.83677864326374,-27.33709887357899],[82.00404776599267,-27.53737923222012],[82.19356703797618,-27.526062978684735],[82.39849400596847,-27.70369808025753],[82.86635233341497,-27.95223614877518],[82.94786322160105,-27.925433886045525],[83.33822096256898,-28.08097105081662],[83.64493880498972,-28.257316065012073],[83.66580648074446,-28.354311930873195],[84.00182986169833,-28.518504117575663],[84.13316778584606,-28.837770240274658],[84.3716238178001,-29.047630165050258],[84.42518358382762,-29.240098318215292],[84.83496748281011,-29.441211270368196],[84.98889392174904,-29.38921873185103],[85.08634800252754,-29.463836030743344],[85.37197039720114,-29.541099279981037],[85.6204247736154,-29.720686413790276],[85.80617471545726,-29.97251229967371],[86.12817336669214,-30.045464553832065],[86.28279909046586,-30.213966550997192],[86.18645107083925,-30.28823171157283],[86.23458334202361,-30.572055265510965],[86.17656638879782,-30.692677885647612],[86.3641722502691,-30.863276565938108],[86.80478811815317,-30.966188902942143],[86.73194593937228,-31.083816545608787],[86.91364636041432,-31.177336547281563],[86.83751715053404,-31.326792236681978],[87.25325486803582,-31.523147428252198],[87.45378102439234,-31.56172920799068],[87.41954835574735,-31.750934624123147],[87.59110465954325,-31.8924187165502],[87.84801022129442,-32.01345872800421],[88.21964827675845,-32.10347634741017],[88.39670504425347,-32.071392612611085],[88.73897829881193,-32.320061325671496],[89.08552792165587,-32.41064131922269],[89.21617313781691,-32.510808708425884],[89.2267965352167,-32.924966943635724],[89.36001092819406,-33.010656508890214],[89.59185600721314,-33.41465024140462],[89.78244170684594,-33.44837168183225],[89.86920971060275,-33.81365018277697],[90.01136666853141,-34.01767950660334],[89.92820325653882,-34.26499710309212],[90.07156204023303,-34.327747134342424],[90.18718227520947,-34.535663771153594],[90.0602427445512,-34.68418854893035],[90.27143018564671,-34.85995107103623],[90.72479118694099,-35.015411381539444],[90.83512668319813,-34.980791834283586],[90.98459396710524,-35.12113169563251],[91.17309004982907,-35.206388787129384],[91.52751470020566,-35.45423676089371],[91.54637862100248,-35.7139078415734],[91.74224586756941,-35.87117690611482],[91.93362878346059,-36.13252524052419],[92.16772844688722,-36.32149849381005],[92.19746016398612,-36.493092772968595],[92.09548022583003,-36.53481882489895],[92.03966933392867,-36.694800704287246],[92.28277850119764,-36.92257869808115],[92.28916397734528,-37.108529147294924],[92.41151986042901,-37.390655847245235],[92.74048511987056,-37.5061963827762],[92.94991339899079,-37.50810545681085],[93.0560889171512,-37.410001956262995],[93.43311280856832,-37.5441518906878],[93.62824435365563,-37.674450500474606],[93.8367392871202,-37.68659212747877],[94.67930476941575,-37.58765270040925],[94.70185314509477,-37.40670139069302],[94.6400351786453,-37.298535897605774],[94.87142722707114,-37.148981939445896],[95.06868141887692,-36.90640423909922],[95.23147735402291,-36.875519823794065],[95.37967397482822,-36.75134366025392],[95.51847310542121,-36.73144094614094],[95.69803900795424,-36.536056153928016],[95.7974044463416,-36.274159609441725],[96.14714094395838,-36.06559356624418],[96.31263768277934,-36.04841759220517],[96.52261512417503,-35.90893700299182],[96.5991665605456,-35.703567528315425],[96.5532749638714,-35.53812335995404],[96.64647084908015,-35.47770868075721],[96.733566751582,-35.28678634296193],[96.89488425976126,-35.16777773552788],[96.94101705353509,-35.03403667797931],[97.10771636309926,-34.945361909231494],[97.17846397358504,-34.67968397412105],[97.41131863543254,-34.50511479471061],[97.6055999765721,-34.50912118341234],[97.8643478313952,-34.424585326461155],[97.99096880404718,-34.25872894378713],[97.99698824405787,-34.106443729053126],[98.32642069684948,-33.99432916105886],[98.63379805112109,-33.962409616443374],[98.93186317470722,-33.62266491950732],[99.26917928362579,-33.39775963444977],[99.39364809154719,-33.20717288084966],[99.55579436601501,-33.11767892960492],[99.6982708000617,-32.87783373485644],[99.70399402820202,-32.71345103674165],[100.07901834136884,-32.530131683382585],[100.22386611825047,-32.362162211382156],[100.32646605053479,-32.14221578432499],[100.46831147645078,-32.08062355498314],[100.59173660062643,-31.890075680857795],[100.86551800118784,-31.730823666943355],[101.0164286325808,-31.325933756915372],[101.21964965479955,-31.239804895931062],[101.30920024724287,-31.11151679637032],[101.72918863449453,-31.130454397475827],[102.00348019487421,-31.024178974846787],[102.17605983822094,-30.43078496215162],[102.49204761698108,-30.270899462307916],[102.64006513030252,-29.975018483681644],[102.83522076458704,-29.74171307121525],[103.09618084910917,-29.577797005342628],[103.14034521143928,-29.382918270210553],[103.05405849017187,-29.226863790315768],[102.81768031610052,-29.062464519482994],[102.90089157096752,-28.678674733089377],[102.85251233005795,-28.273678637298058],[103.04999868843937,-27.678015607980146],[103.25004786785694,-27.4378838814433],[103.39583487969439,-27.091723086756065],[103.705807722573,-26.564206651289656],[104.0098252329385,-26.133235261070368],[104.2487976440452,-25.846225756327105],[104.6567648314145,-25.467055320795023],[104.9511165996769,-25.27677729654171],[105.69307225187308,-24.91272972026431],[105.40117888217257,-24.708122819895326],[103.10652524368457,-23.202153899823763],[100.87552246584264,-21.72263667713465],[99.80313947923015,-20.968647948800808],[97.66448117592878,-19.54485992163963],[96.52140585755068,-19.72587535823846],[94.85485068809201,-19.949101301421816],[92.59327679921813,-20.262130757251967],[91.21852622673208,-20.455934849124137],[91.17402040784998,-19.668496158960654],[90.10370166259662,-18.64964472751929],[89.56569638481284,-19.1629070117072],[89.45331332283322,-19.057711426268757],[89.4289751055779,-18.89812580740435],[89.54355730230722,-18.716074856125555],[89.42041877728808,-18.49208438271667],[89.28791200555077,-18.469962457072576],[88.16212841965874,-18.57010609902441],[85.36681253852011,-18.84628112577993],[82.88665366675997,-19.098512787368833],[82.34405023738721,-19.132073993844468],[82.11956506308252,-19.227365294115078],[81.84968370378266,-19.26177769357911],[81.66858445601012,-19.118222140377366],[81.51728719606,-19.25502285294858],[81.47618306968162,-19.43499789034963],[81.15918553585999,-19.469127537095684],[80.95475387493408,-19.619568609007953],[80.55575609541307,-19.801196089617928],[80.31430482487133,-19.874198937540864],[80.02934145350153,-20.16091274715415],[79.8640165473195,-20.19105000032777],[79.67959964279204,-20.39205010459256],[79.54104542573003,-20.291097505830503],[77.76766199990318,-21.098173501567533]]]},\"properties\":{\"name\":\"South Carolina\",\"ns_code\":\"01779799\",\"geoid\":\"45\",\"usps_abbrev\":\"SC\",\"fips_code\":\"45\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[125.33173354769006,52.03404327888572],[125.18900699574655,51.882384098377756],[125.3636186834371,51.78973446362172],[125.40831832817523,51.67403230509751],[125.548214198375,51.587469675014354],[125.38135667103712,51.36720553646322],[125.40867600817954,51.237889262660836],[125.32245036898077,51.122853272093636],[125.367813188991,50.992165806777436],[125.17162546282515,50.809161344406526],[125.23901951910437,50.70536142152174],[125.45043604702781,50.59289225179665],[125.57212419656433,50.424495215287685],[125.70063048091207,50.40036000998462],[125.81297086674402,50.23856321728435],[125.93783421550377,50.15071012731711],[125.90221522830652,50.06459919136289],[125.69348470452633,49.96112771919476],[125.85257381875628,49.91032726264964],[125.7891253550237,49.80351297573415],[125.8152231575149,49.658401375318405],[125.59671806579415,49.49615124240602],[125.59631771035129,49.40880507686415],[125.3840327610332,49.213121809186894],[125.02064189162161,49.10591180913796],[124.94206544110659,49.00575882882672],[124.9931357948419,48.89895302154721],[124.73706182178657,48.748829688101324],[124.54656576542033,48.7730592594782],[124.45644099816184,48.70548718658891],[124.20041581004547,48.66141201360286],[124.04393421779352,48.55046273255042],[123.90867908538934,48.36132182830789],[124.04852155777398,48.1499572164835],[124.0469273540822,47.90634342237279],[124.21070332745423,47.78167677619729],[124.40252637776258,47.43647531789678],[124.25422819574872,47.10094029802606],[124.14151819876415,46.94883354139368],[124.29636003882914,46.78801907807668],[124.22590761390973,46.726351074009436],[124.25411853016107,46.583846520969026],[124.13944046153603,46.480856998586674],[124.04043490842754,46.22318448337693],[124.14390919066214,46.01198017862321],[124.0075191849875,45.66873673062816],[123.83581189871776,45.56200979008499],[123.73745637493076,45.34504496844419],[123.79235453696997,45.21296816653346],[123.73067640800826,45.03909139773924],[123.7886275140211,44.86132427101542],[123.6124206649462,44.72298518283338],[123.63571065571405,44.51873948611975],[123.5988694690055,44.35342773422813],[123.7277710469728,44.25881595828186],[123.70714929443511,44.150409314464284],[123.78470343005151,43.59935995824878],[123.96235460675688,43.362995052711675],[123.95295584720955,43.20129271477746],[123.86230677666292,42.981052646423045],[123.94050151765924,42.75611956547159],[123.89897043240951,42.701232446064296],[123.97879115123585,42.42791772353158],[124.12712459109257,42.30838041292245],[124.17060954647097,42.15868028858966],[124.05750805661215,41.974904520746655],[124.16453907085881,41.79942874411229],[124.16944201792201,41.54436905567323],[123.90492970718502,41.39787591760065],[124.0107694644384,41.21076467185411],[123.92450323100823,41.0119087849881],[123.95902767134878,40.83735235619776],[124.13260561355408,40.602273179286264],[124.53485878686337,40.41529462394155],[124.67748134534378,40.26871438221356],[127.10913044492565,40.66509529319471],[128.3079787364996,40.86702867882627],[130.60229296226674,41.26506978079449],[130.76325481059618,41.5511971055586],[131.09301949146953,41.58925773277427],[130.97305747690726,41.85961794277567],[131.18550194704338,42.07149972584161],[131.556750164767,42.06213602375717],[131.6241714665097,42.36609085118271],[131.9304623232988,42.48128836477813],[132.08044996870242,42.59652398347294],[132.31106363347598,42.63095823449987],[132.5370425505697,42.55804447336694],[132.65525667769688,42.64258016485941],[133.08113474723828,42.74191225455039],[133.02466486664383,43.023981480278195],[133.1298601613813,43.19814942237109],[133.28746236752278,43.08663736832078],[133.6580433061861,43.06235463890404],[133.8024057151763,43.117289299673445],[133.50461598872863,43.39301706060676],[133.38528186482586,43.40949505195338],[132.92882946984196,43.63620547690788],[132.84647749066417,43.781724400112964],[132.6912137113554,43.74252848759796],[132.16161201109176,43.91407521243357],[132.07699371185493,44.01008335854328],[132.02892051401375,44.21624948199154],[132.0497060639114,44.42067874957083],[131.9804553054598,44.48234578019346],[131.73619200591207,44.53917494309881],[131.50089949387365,44.63843816821173],[131.23340351555717,44.85712999545492],[130.99778022049136,44.88233574425018],[130.81190298194667,45.182143361930876],[130.89384260381516,45.33108190998469],[130.75014295628958,45.50390283419896],[130.7793998108097,45.724626696686386],[130.73445505052513,45.883425870280774],[130.59020296993518,45.953526631356866],[130.09957912864883,46.98578778838331],[129.7541540183064,47.77752620369343],[129.07559413754984,49.249425366936755],[128.50456482599188,50.41392642612356],[128.19306372375905,51.05609949177426],[127.52772604687412,52.29229693861835],[126.95585557464001,53.3574094667694],[126.8216338942304,53.577974960451336],[126.76366101518362,53.55458597675502],[126.69984000649968,53.29427489844497],[126.4812266921463,53.241469130106594],[126.21110186908608,53.23511197901603],[125.80078983961458,53.38727871751414],[125.65778203398038,53.250236300748355],[125.53954704398605,53.20736824286408],[125.51956022248943,53.10617683675783],[125.32435790978728,53.01847804800431],[125.48920293391402,52.91229906338089],[125.41969117048744,52.617213956959674],[125.48155070773068,52.55023903501866],[125.35729036726057,52.356514868204286],[125.28279590105147,52.32054163291843],[125.27465452654269,52.168676749761715],[125.33173354769006,52.03404327888572]]]},\"properties\":{\"name\":\"New Hampshire\",\"ns_code\":\"01779794\",\"geoid\":\"33\",\"usps_abbrev\":\"NH\",\"fips_code\":\"33\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-38.42089975162439,31.046166356157094],[-35.435470206022416,30.835870855677726],[-32.40168420170831,30.63817936807173],[-31.038509520669585,30.550494434731803],[-28.449178181411146,30.39699461505357],[-27.089082874793526,30.323986620631644],[-25.900603997596086,30.242488249781623],[-23.802568877144278,30.150390325038412],[-22.038078009167002,30.068388709065175],[-20.164288154664945,29.99437226334395],[-17.6157680736938,29.889274176661395],[-15.55126541776475,29.8150479264762],[-13.268451237170234,29.745366861077578],[-11.660156498285353,29.701273966984807],[-9.653284017760964,29.654230937140433],[-9.50099789307368,29.332200761821905],[-8.8607365743802,29.025387582130133],[-8.777326799110496,28.933523733913088],[-8.42826650254188,28.85005942414416],[-8.232035974124473,28.712446506538623],[-7.766264756361203,28.50082381434388],[-7.4281209911331345,28.241304006588752],[-7.17118774488943,28.117616105344137],[-6.827029462407007,28.165026422850907],[-6.515735936773009,28.453114904710162],[-6.441649665071563,28.71322255187798],[-6.210809280007701,28.774107298356764],[-5.9170938643394235,28.64825178890919],[-5.461083639749054,28.593541454411834],[-5.029074572726973,28.688483048409903],[-4.812902988400563,28.617458701532655],[-4.514906571215436,28.682037559255566],[-4.174435585525769,28.605824498781104],[-4.0578175763258555,28.722707899585146],[-3.664414575101721,28.65452570246484],[-3.4838930889274944,28.7261048816832],[-3.3976597693743695,28.64887365070716],[-3.182958437002049,28.645645775429898],[-3.0252969807448653,28.57913684441256],[-3.000313508323079,28.370422582929802],[-2.679776646177394,28.26265765809505],[-2.5751325356643417,28.109584928693156],[-2.27106218092595,28.11131377219759],[-1.7893199040689165,28.029378719726797],[-1.634919889410364,27.770330691146093],[-1.4204706910430704,27.859832217640847],[-0.8925890481803961,27.666987242256493],[-0.873494259763609,27.463761649225187],[-0.6754029460769706,27.397003133666814],[-0.4872750187000615,27.426893516824006],[-0.2775917097526164,27.33785558119672],[-0.40166750811082036,27.051386278565555],[-0.2779742136903237,26.86564308418313],[-0.023655380726697754,26.689321546245615],[0.03755105341655823,26.442517699830056],[0.15464266536529156,26.38321370115526],[0.39407977125824806,26.484244925053765],[0.5644421654734105,26.417835325608497],[0.6875945186650881,26.240717939910198],[0.9797148313671948,26.290151267204052],[0.8140146479040917,26.293324685638794],[0.7574131046799324,26.42399264068566],[0.8175569541155933,26.71064917228056],[0.7189188335162018,26.867517212648057],[0.6161682349632844,27.18702234673124],[0.15256911964913314,27.646545803127932],[-0.0016972328994087971,27.893991437966044],[0.08213229121287764,28.165535481577717],[0.16691332051285304,28.1663513037544],[0.30278406873266805,28.450337450210387],[0.4752751199019332,28.58263631255613],[0.4672843283971302,28.735863160017722],[0.561890842501714,28.880881392118077],[0.535985504287191,29.074477491339405],[0.7161814669670364,29.263133762233068],[0.6052142084093701,29.39610236293982],[0.7559970892605764,29.58443231871838],[0.6428110839582256,29.708872668579083],[0.6596407663446835,29.831417809932784],[0.9199219472441345,29.920861497313965],[0.9556703928478859,30.12560147114146],[1.0470511928489967,30.264623530931733],[0.8775692426131096,30.503717755218386],[0.8427305450413489,30.896224917036143],[0.6315264133443401,30.871561020887267],[0.43671930562883743,30.92690109817087],[0.4673359030500558,31.06016526543672],[0.2772439854721544,31.216665675980906],[0.5991479647471554,31.44992268497563],[0.545914904565765,31.600549581931844],[0.6165576081197521,31.90549134608896],[0.24916933435598632,32.189203503642396],[0.3070273418123105,32.39241010350416],[0.22204198333311617,32.596672999517864],[0.9736110818548098,32.59276864128454],[0.9866839303716597,34.51691591908545],[0.9908102533559819,35.70438669139522],[1.0060031697166245,37.29603995997629],[1.0254307593061216,39.62595555897181],[1.0300071007365206,40.95710531814725],[1.0358402860510656,42.755734607790174],[0.8575392048559706,43.05080426826421],[0.20514760875891433,43.32315258388948],[-0.09867698255165118,43.33304691900658],[-0.3511788475970739,43.57535599220275],[-0.5078223321922518,43.90089280205305],[-0.9691692084629064,44.34082289250518],[-0.8757390167105714,44.55470908712366],[-0.47761668139843577,44.76146543799582],[-0.4323716392161333,44.81200458148526],[-0.04343724809158762,44.97939948706171],[0.12509091839790584,45.178714668518644],[0.4067097066126685,45.41773359298238],[0.45037641880835183,45.537580066223924],[0.5081726085095676,45.99068655467205],[-2.7805528354880926,46.01281168094331],[-4.055748394564249,46.024048986448896],[-6.767051825159562,46.06220706721373],[-8.683204064720615,46.09681846028056],[-10.381834019830912,46.14020928244068],[-13.062862931282208,46.205610516292694],[-15.365269208643562,46.268471281033364],[-18.08146365896906,46.36020675461896],[-20.27619857183955,46.434380822887896],[-21.717346212991128,46.48454771410048],[-23.59819515891553,46.55756959667688],[-25.410848982078694,46.633709601812456],[-27.09753913918916,46.70807056729681],[-29.19599669078322,46.80477536701994],[-31.343564572368198,46.91069195222916],[-33.25309958547422,47.009638280102415],[-35.1141936051611,47.11021496440881],[-36.601311095276046,47.195627540121286],[-36.704904486512795,46.243773439609534],[-36.919407303376566,44.36501718168924],[-37.13811506639031,42.489656404850585],[-37.234577658947664,42.42305144937504],[-37.29964610900672,41.77605794596337],[-37.51612977447475,39.848442779957004],[-37.67532564637129,38.3695839830172],[-37.823232677634316,36.95800419534266],[-37.982732639546064,35.464426137922366],[-38.1776121869848,33.557716725082756],[-38.42089975162439,31.046166356157094]]]},\"properties\":{\"name\":\"South Dakota\",\"ns_code\":\"01785534\",\"geoid\":\"46\",\"usps_abbrev\":\"SD\",\"fips_code\":\"46\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[27.970598476146584,12.565960353874365],[27.75308706531505,12.303522114402103],[27.610344031279425,11.844557638254571],[27.550389469814935,11.546046008571679],[27.534549053524504,11.13719401739156],[27.56444267771727,10.767870397305137],[27.702545280998812,10.287555540040804],[27.681209506801867,10.137747949000373],[28.123404112927346,9.294891704550018],[27.990125695463256,9.025905324044874],[28.0786803185042,8.773145257066087],[28.400474966662724,8.561717614446039],[28.47725925636208,8.442597133076251],[28.476256380166095,7.987561279216836],[28.594084773934274,7.860833623062233],[28.996240447887615,7.561040970806828],[29.30060879305578,7.231864118550506],[29.50853919258213,7.152986882366935],[29.618416495330436,6.870929779026695],[29.735044152758707,6.739881809472998],[29.99532700333498,6.70448369864561],[30.2111355749887,6.397841751130604],[30.22910675029438,6.251705051955103],[30.37027904986839,6.0748731676913845],[30.62232227643527,5.905330144447368],[30.9285860356661,5.776465569528098],[30.96001931804774,5.701955121835456],[31.45002942670855,5.391884044049794],[31.75899223967253,5.151757289241015],[32.00502900057115,4.848644563872421],[32.14519068565356,4.753900822266205],[32.28111764542598,4.070624550043868],[32.41429428168557,3.8247399590854143],[32.45586847733624,3.6122456103341194],[32.301808411628,3.3660482612222036],[32.51085924620584,2.9225356272140925],[32.62594701233995,2.4419998236286844],[32.85952912852419,2.173091014162095],[33.118583329741185,2.052533789477337],[33.285100258949356,2.1026185380300904],[33.59166649872845,2.494096261741557],[33.65981466020491,2.7357113002479063],[33.77292937954032,2.806660024422795],[34.09517875377449,2.762893490631699],[34.56688244587193,2.5390291894029957],[34.893793192301665,2.524281529736155],[35.19652614924403,2.3141805420177732],[35.676229943629075,2.0672172845215946],[35.64533639356983,1.695607378241656],[35.418591025902316,1.4984102384763298],[35.18914948216547,1.0467375572229356],[35.347069733752335,0.799636529382131],[35.41165728591256,0.4446227590576482],[35.352025252055086,0.23864002110531032],[35.193303330257585,0.06379486462909392],[34.991072511469454,-0.3432457992380856],[34.88649868374687,-0.983727517106635],[34.62099596384176,-1.323111163499892],[34.48198564050713,-1.7262621055774032],[34.494358875300264,-2.084892075500455],[34.59709503409951,-2.5239826747154055],[34.74636276096354,-2.746667764471987],[35.08917524183904,-2.958909486586947],[35.287899869794096,-3.2742998765646374],[35.69695273325673,-3.5554730215201245],[35.93016980460167,-3.59367799353469],[35.96299347922159,-3.740568875963845],[36.16100221498926,-3.9040548175269496],[36.31554701140561,-3.897209060466638],[36.767969849940776,-4.265681565519528],[37.027353517918435,-4.183547281274452],[37.09009000177457,-4.342206807635736],[36.93649962721861,-4.410408406325724],[36.86460671966089,-4.563551212570213],[36.996435236268844,-4.806225486318696],[37.308884765662164,-4.8847145614090985],[37.590733140532635,-4.617383825116188],[37.85485439361129,-4.773318841766313],[37.873904100224195,-4.9115369140054685],[38.20202567718062,-4.999442158902918],[38.67358633195593,-5.404133406871772],[38.644363669994426,-5.5931973659844205],[38.75364570624236,-5.6919807547863055],[38.9269055789515,-5.649350235074406],[39.13833014171012,-5.897773457139166],[39.466657423953066,-5.982018687893724],[39.54951614780897,-6.069131216250661],[39.53942683775235,-6.387838600831967],[39.779763474599996,-6.675255212690824],[39.77242892103263,-6.789738004523492],[39.55645754554342,-6.806046530677747],[39.598398839270466,-7.141389640626316],[39.88541418979997,-7.616545556390664],[40.075774599597864,-7.812982268303306],[40.2026884640639,-8.113536167920964],[40.14274888220047,-8.47139187412875],[39.92997923735485,-8.530721060461332],[39.74487955957955,-8.713913951717359],[39.70946597166495,-8.916100974138994],[39.88680553721259,-9.153249812396679],[40.07110951686211,-9.202895966082833],[40.0698265933911,-9.509859894345103],[40.30401248043611,-9.930013777625792],[40.589221667866525,-10.220778506300196],[40.611191166619314,-10.60214862159497],[40.97656780970497,-10.774943523072466],[41.12791514279512,-10.910383754522526],[41.27153438386131,-10.871231135183885],[41.31033057384324,-10.732387597091643],[41.04913914505725,-10.541321363028842],[41.00087425362418,-10.369483913465693],[41.27244728804197,-10.390236243229479],[41.61357150026111,-10.687290100840675],[41.697242880876125,-10.957021532433703],[41.826595902865876,-11.016634258560076],[42.02931500882346,-10.918906030870916],[41.78381827182473,-10.72787874851618],[41.73721541108819,-10.402125897914676],[41.853271116001636,-10.172474571702379],[42.07894873114586,-9.942875847622618],[42.1639978989253,-9.667966158783692],[42.48848369015388,-9.257722656335533],[42.79314180400813,-9.101957917229958],[43.032101013386864,-9.101703499874672],[43.77648829927588,-9.328209640392634],[44.17867863776386,-9.601607640386982],[44.35843058719464,-9.604471764723097],[44.85826250413755,-9.757867755673818],[45.12625419785735,-9.996845105429225],[45.413328951765784,-10.072663521000408],[45.640294894921276,-10.04456251436729],[45.815622275766025,-9.8859714142936],[45.91787353570153,-9.42098393972327],[45.75693544750846,-9.065370385249542],[45.60254003695765,-8.947257817623777],[45.39380542789744,-8.692004610082389],[45.34344807941107,-8.428668023050928],[45.45976024133149,-8.119175014291962],[45.50330533431432,-7.738202210418276],[45.601781166226445,-7.6009021906805785],[45.82743792920726,-7.474756343348786],[46.08425930247977,-7.619102111942188],[46.38167444919353,-7.316333257222748],[46.55767069569592,-7.214037336915845],[47.00909677537968,-7.128166600244739],[47.345863499888836,-7.019394247690165],[47.64687279764775,-6.985966998277556],[47.74562385045744,-6.7509642364443385],[47.66984517735192,-6.581061021255934],[47.299844757360155,-6.284107149744111],[47.101374447629226,-5.713062244149514],[47.268208107772274,-5.3317217230174405],[47.54581286475388,-5.11939235467411],[47.66159751815512,-4.954539060953149],[47.75277772066269,-4.629128611576485],[47.32389073339251,-3.9837531181143886],[47.77648238850776,-3.9375163695114326],[47.60804075973208,-3.508579672171381],[47.73111260799551,-3.3482564858463886],[47.578339694295,-2.964227259776972],[47.92796839297162,-2.681625695645843],[47.94412416275669,-2.4413104366661917],[47.710114569224636,-2.578265115288926],[47.6516309981103,-2.441912300830186],[47.859730003712386,-2.3738850027284304],[48.01541404721793,-2.2396517702458247],[48.18150978297955,-1.9706020803951665],[47.83755281030384,-1.7886308528143078],[47.732338310116866,-1.530974854153102],[47.95611549451001,-1.0512157947154845],[48.4429679600332,-1.134729721943118],[48.53945704874203,-0.9511673082285726],[48.51392337114073,-0.7691104487419872],[48.6373061639245,-0.48164446652757226],[48.75061028503341,-0.4366443897877624],[48.94987654741273,-0.09322169384806234],[48.9986931962876,0.17214657945662074],[48.905006547623934,0.366742378437696],[49.21932993198439,0.49957058674164023],[49.36450431088716,0.7279712901985141],[49.27296701199219,0.8903286762231071],[49.5149113042863,1.2072036819606522],[49.468832503800726,1.3558069404046516],[49.597404031999794,1.7869763284620956],[49.93197964942109,1.935548542491193],[50.00349912242538,2.0535533630211606],[50.076888261593616,2.6976547013144785],[49.88686364083716,2.91131688302663],[49.87176969302376,3.136902377793636],[49.721174090986125,3.2066776512288575],[49.86151511581071,3.668762472073883],[49.871676246471466,3.9106386773825412],[49.77309266998525,4.016720332057655],[49.523909232635894,4.092053700206378],[49.48289164043679,4.606396898628047],[49.20283314946597,4.920284680090491],[48.96235906947449,5.127554262332134],[49.14430997059769,5.503714755310231],[49.31997509983352,5.622563050220832],[49.36556383157906,5.795905272948319],[49.31270356480771,6.016371282395683],[49.17713718055547,6.040183769990792],[49.13952651722871,6.402431572243366],[49.21672851797439,6.572684821286265],[49.520403689753394,6.7004872988201685],[49.31070145507213,8.961518549061534],[49.173895663005894,10.437359082423045],[49.011790864184945,12.3200491362822],[48.80804737440423,14.851532353645092],[48.59910520831439,17.127534813646722],[48.27943715286203,20.454793349799054],[48.018170751464694,23.332301666346563],[49.679014135709465,23.478096303583097],[49.888893951519776,25.701324301721844],[50.17036245745686,28.272522495195947],[48.322146643868074,28.12324540306667],[46.094012221873825,27.940797548044877],[44.64527192640692,27.85150127751966],[43.0368092951476,27.727890656163375],[41.79131926350792,27.64416347903055],[40.88024096510782,27.563380049999616],[39.90115985006474,27.523315905967053],[38.48718337071383,27.446401710559925],[36.36120355580179,27.360233404879427],[34.872672187369986,27.27789428285057],[33.817156066254555,27.23623740197722],[32.52155476900662,27.157648369977238],[31.295089013593266,27.103713553000162],[31.238774894436055,26.909980971738158],[31.401730562651206,26.792452384559123],[31.68855525640139,26.69655185609229],[31.781137603626053,26.536936515264184],[32.1970778043148,26.352465551919153],[32.546995621867126,25.974108173277305],[32.48493503664864,25.710261660954345],[32.55802522288108,25.532606542320565],[32.71803436183088,25.35276129240763],[32.90539276372555,25.23251880593415],[33.0575809473437,25.21209721546635],[33.308682924858275,25.081131280528012],[33.70107261395784,24.932052726100277],[33.752926162562744,24.79295944761995],[33.96112884442261,24.711622059268127],[33.985677059097995,24.224232994914562],[34.13111147047373,23.97713974016284],[34.03362203816671,23.69538498894794],[34.10275297853448,23.338374571032507],[34.029469247333004,23.134628476581568],[33.96729280159735,22.645126958357146],[33.50508952601178,22.38105540643515],[33.3246283480023,22.15160764260727],[33.35275185214712,21.920954904563114],[33.2480365337831,21.78100888762084],[33.21485293037075,21.561036595438722],[33.241314928432814,21.16602232186575],[32.88296702432376,20.995021118956103],[32.64346330930041,20.700649369313776],[32.43835120290535,20.652440553917316],[32.2217631840786,20.69531402022547],[31.94063593612805,20.578506094628665],[31.89086235531989,20.462564750467866],[31.63968313260905,20.232535487571933],[31.211773733602158,20.128967829312874],[30.627603492029152,20.131956465701492],[30.1996188546565,19.882655087882544],[29.95910527223696,19.956037810144462],[29.68416366348127,19.872156259781267],[29.58086724190003,19.77324281077238],[29.506982764331266,19.493999419117767],[29.483761505554654,19.09451675296415],[29.29037097519139,18.673228141666993],[29.304233847993412,18.598668856072756],[29.565797507109487,18.34182757968159],[29.70591686535596,18.122829400017558],[29.98773533035041,18.063118753208347],[30.241938213207177,17.657959488272958],[30.286237237464704,17.24776928766923],[30.222515092504867,16.866250512266525],[30.263668521415465,16.719882916430684],[30.217938814064023,16.487821539057304],[30.03438274878378,16.362182304230387],[29.802887576427242,16.07549491878837],[29.544667099410546,15.667984324675128],[29.581399112496577,15.429929983949672],[29.476082300047278,15.06556800098195],[29.468204816394206,14.700403323410459],[29.13555562691834,14.440108723499003],[28.80005664167075,14.427838018828597],[28.471422315392395,14.318855468603386],[28.21246455185492,14.144271893315615],[27.98455107858742,13.720238274759327],[28.19448744364804,13.518934926923027],[28.226689371041754,13.321694288390757],[28.15251370108082,13.067913543927235],[28.20097398703783,12.67290571879121],[27.970598476146584,12.565960353874365]]]},\"properties\":{\"name\":\"Illinois\",\"ns_code\":\"01779784\",\"geoid\":\"17\",\"usps_abbrev\":\"IL\",\"fips_code\":\"17\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[36.260561391102065,-24.877537699148828],[38.75317263796547,-24.745703342726046],[40.258224286720186,-24.649799483237803],[41.01773218852917,-24.611012558355778],[44.26210350594579,-24.39394927370547],[46.024117971988375,-24.267531323959787],[48.450529258033,-24.089261644065356],[48.42650634592789,-24.00937717466316],[49.59928982564007,-23.933875606776798],[51.55040943357218,-23.796020507964997],[52.891321993088674,-23.691488716424097],[54.886398371047115,-23.564773831332076],[56.32328994947061,-23.469323197838985],[58.44842101369036,-23.28622260041496],[59.71624178541565,-23.168053705646],[62.134048965493356,-22.949685482998014],[63.42482494774179,-22.843137506469024],[64.22481316338741,-22.773006512299627],[67.17860657418238,-22.428840514878786],[68.0052751159567,-22.341512169884833],[70.80794660775955,-22.022139403687262],[70.78013527605904,-20.42262712792637],[71.19363587985673,-20.09888826287473],[71.28636332833409,-20.240498857757924],[71.80726018014595,-20.126623929961557],[72.08601286085427,-19.979337277642035],[72.25281681177728,-19.73571687261687],[72.15691262548265,-19.632411882319726],[72.11366212200812,-19.432278014661847],[72.26903446098035,-19.250599834492935],[72.19072706567347,-18.983479212667564],[72.2940167715854,-18.776010950734474],[72.5806237804252,-18.527596585806677],[72.72067271591253,-18.479552543278935],[72.84983267637566,-18.18039793382296],[73.16890276569633,-18.09560005392199],[73.44087375362479,-17.797854600626632],[73.80299737484795,-17.708775711240595],[74.18336181424016,-17.681023212207137],[74.3946220840787,-17.568061733975682],[74.60999847004605,-17.62575119429795],[74.99914504694023,-17.597663380125425],[75.25236438737383,-17.227391972746734],[75.39097166446678,-17.214063894240613],[75.67595312733447,-16.986568470449114],[75.83246731109061,-16.780678290474853],[75.98234483070084,-16.83803252128131],[76.26281747738909,-16.51413955696742],[76.31015168279156,-16.292342169264085],[76.61976364791266,-16.230292010259646],[76.73643134638608,-15.970761853424147],[76.9569200441073,-15.932526071412093],[77.16981227974762,-15.74213215653247],[77.4134027908399,-15.724460954450267],[77.65695412693587,-15.79207944113862],[77.76582550017623,-15.680847128324512],[77.90047033017278,-15.392192080113109],[78.0411421049874,-15.261513535172467],[77.98999758402479,-15.079884072286097],[78.1164215516622,-14.992777311721785],[77.96485535282152,-14.65327606904387],[78.03960105961819,-14.534811458817813],[78.30111178615611,-14.469000711002598],[78.50511882901215,-14.634074815624682],[78.66187057697778,-14.401153539948341],[78.67902925519556,-14.056082927897332],[78.99854096265719,-13.85027273728808],[79.1504335599222,-13.67978143611453],[79.40313409862831,-13.497373159775439],[79.69593975882711,-13.695893519371419],[79.61081120537588,-13.980288460825545],[79.64688316068334,-14.159268265762535],[79.99671370673715,-14.133149139246257],[80.23393131635598,-14.005751668953037],[80.45662713460545,-13.761453568622331],[80.66532261114435,-13.203138569276398],[80.93984846889012,-12.933558718633298],[81.05091234975818,-12.920026699228305],[81.29561465644734,-12.738581505535397],[81.54846287407379,-12.736043618159655],[81.69544942474501,-12.5155902329007],[81.92199610680164,-12.603263486939836],[82.07293971992274,-12.529094547143306],[82.22461971189247,-12.827543935784893],[82.50781494620364,-12.772666077619327],[82.63105675792296,-12.610773477836428],[82.75639459559704,-12.63813099633056],[83.2691216057478,-11.270372895921893],[83.41721245083406,-11.165519230563826],[83.53031592193645,-10.98074478288423],[83.8315144804208,-10.784412401161104],[84.0362864302466,-10.895395765013722],[84.29394183684099,-10.792006350522591],[84.08839031245184,-10.35813774402632],[84.1941169828441,-10.272177327029064],[84.19184742824443,-10.039917846102876],[84.291105376638,-9.935082742902816],[84.23882940208316,-9.665401598348202],[84.14376369207609,-9.467066392202401],[84.27023539876669,-9.208502100457075],[84.25923764886333,-9.07272836103646],[84.40452888412263,-8.879944664676708],[82.85350448747016,-9.079908288255261],[82.81238206534643,-9.244105793510057],[81.2580462916417,-9.458633999196023],[78.94780690658617,-9.801192376388617],[77.4574389103767,-10.009527111662164],[75.35966547147387,-10.28904611852714],[75.2700694672899,-10.270181350235596],[74.01365918116866,-10.441966429495706],[73.02334235874751,-10.544509541001828],[72.95366851364929,-10.683262726635917],[71.20731257008325,-10.852074693557537],[69.67678437068923,-11.027483651302115],[68.15845717406785,-11.177381491510737],[66.52050520967916,-11.310982681544994],[65.59971978160024,-11.327897695426806],[64.9323617713904,-11.36207516026762],[63.98470689497909,-11.440150415789597],[62.72830165157747,-11.663149991115743],[60.85701629171012,-11.811740760233763],[59.719926572330074,-11.868852432167538],[58.01928238637695,-11.914084599832119],[57.02593371357225,-11.986327833361251],[56.72080896102133,-12.149167446352768],[56.56265647150839,-12.031084133202356],[55.75110072716155,-12.13438943665952],[53.85766250846292,-12.355659161535899],[51.57730213141181,-12.57704119334925],[49.443381790852385,-12.811263144609578],[49.44587290025221,-12.595743987691096],[48.51913038772314,-12.577523531127106],[48.189066083949264,-12.597049114876269],[48.37933805421208,-13.111243738217775],[48.488071769444275,-13.567056167330225],[48.39543920188501,-13.854921417724952],[47.46908842331646,-13.910085630879616],[44.325282615160006,-14.13112865220876],[43.259817647180355,-14.213390531012664],[41.32384165869534,-14.313157164605284],[40.66930026821994,-14.415031520710084],[40.48236603709288,-14.687612295964634],[40.352911368285525,-14.719945412565714],[40.24501954933335,-14.602289103261224],[40.28382366690882,-14.451419584142306],[39.97868976983181,-14.468523305765057],[40.093492288278085,-14.595557770587407],[39.999127539218755,-15.01045492484409],[40.20700537518413,-15.320538326404073],[40.09839005908344,-15.566865293651219],[39.649903738017244,-15.587816549931453],[39.65864933137292,-15.805091721972541],[39.85809927161027,-15.936332912593821],[40.104657518915964,-16.027712840387068],[40.12450608924756,-16.165485762720365],[39.696246084332905,-16.28688155082066],[39.46178823807437,-16.22292334920965],[39.211571148892276,-16.22450657564741],[39.239661444137276,-16.41529190435378],[39.752771828483624,-16.754333657747686],[39.85218016980386,-17.03433569605153],[39.379498527778956,-17.38998800438465],[39.361472033225105,-17.72441427532786],[39.26500269064257,-17.957629768053884],[39.115908061472275,-17.969574673603287],[39.24887367351738,-18.22843424928867],[39.59084101201864,-18.452247484392093],[39.67288079515994,-18.65927749986791],[39.55521562762062,-18.748149145098164],[39.26441551619333,-18.606670578351885],[39.106143041893446,-18.61380612200213],[38.9476715714111,-18.831455263700974],[39.021402722705794,-18.956595843834705],[39.32888383148892,-19.062421925099763],[39.365949756733215,-19.191680430608812],[39.15951272632336,-19.29975046608843],[39.061378365848924,-19.272920323047632],[38.8527203735984,-19.406053892008003],[38.729052934177005,-19.65799362889604],[38.41349327656696,-19.78248915193974],[38.23560969059252,-19.668804638554544],[37.996704094847125,-19.827145578212868],[37.98724728476727,-20.15220621789405],[38.152583174630315,-20.356451297675484],[38.39841144349679,-20.377747533512476],[38.55803872871316,-20.268823195331972],[38.58136726845251,-20.496400586696577],[38.358878696042865,-20.64193958495011],[38.07419559280895,-20.758770204982476],[38.02303755334256,-20.860374185479493],[38.10426561595151,-21.01663468287733],[38.31243911465389,-21.090338856633778],[38.28478865066546,-21.32479806979053],[38.127175717141576,-21.29889929278476],[38.04842054868656,-21.16624025531676],[37.85324140468391,-21.041098520946136],[37.571176129617825,-21.135606060059928],[37.52338128652198,-21.357937952445372],[37.57405907457836,-21.513092302783054],[37.7263183897461,-21.68704483404015],[37.671510792708254,-21.940665300707508],[37.46262166930792,-22.083158969773432],[37.443660476723956,-21.6920482950006],[37.34945703098811,-21.62032547379543],[37.15117138324114,-21.698587389498243],[37.102443208103175,-21.8781377905487],[36.88442095620468,-22.022511217501044],[36.841011578323325,-22.272152299898227],[37.04586882916092,-22.24442241769457],[37.041870610807564,-22.110666064024144],[37.20283488117142,-22.078464961862007],[37.44141941587227,-22.244726330510037],[37.27375185695396,-22.527782607500786],[37.28050706059203,-22.781276677289313],[36.9692624583171,-22.857956826279775],[36.948076629050334,-22.972226200968645],[37.054517774448634,-23.11944261569013],[37.35974313717196,-23.132544482233914],[37.4859820298953,-23.27478038661021],[37.39701957203101,-23.5051376545552],[37.284821433880644,-23.55715780858012],[37.60116227583746,-23.881683823435],[37.413525895566686,-24.019520503721356],[37.183942084686805,-23.918530655142664],[36.99134808483628,-24.03423759721473],[36.824770001777395,-24.64463115256318],[36.34067143957052,-24.57137231989583],[36.260561391102065,-24.877537699148828]]]},\"properties\":{\"name\":\"Tennessee\",\"ns_code\":\"01325873\",\"geoid\":\"47\",\"usps_abbrev\":\"TN\",\"fips_code\":\"47\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[64.46206799465298,6.496961030298087],[64.33903817453199,7.532787558595136],[64.17054497401708,9.143401607471407],[63.89578134364314,11.501048129000212],[63.758149871900734,12.77329354099837],[63.55954251603414,14.718873077449544],[63.2692092386263,17.161307055451946],[62.986553126148976,19.39005762118276],[62.71681757976824,21.506162424885872],[62.34878730805479,24.272303645536002],[62.295177928639326,24.688403710600074],[61.37728620544562,24.585986895135516],[59.65944542550207,24.40966813658446],[57.33975709361291,24.16934378071564],[55.457284735048276,23.997960676607892],[53.697482910994104,23.82523516117235],[51.42906755473148,23.62565151479636],[49.679014135709465,23.478096303583097],[48.018170751464694,23.332301666346563],[48.27943715286203,20.454793349799054],[48.59910520831439,17.127534813646722],[48.80804737440423,14.851532353645092],[49.011790864184945,12.3200491362822],[49.173895663005894,10.437359082423045],[49.31070145507213,8.961518549061534],[49.520403689753394,6.7004872988201685],[49.21672851797439,6.572684821286265],[49.13952651722871,6.402431572243366],[49.17713718055547,6.040183769990792],[49.31270356480771,6.016371282395683],[49.36556383157906,5.795905272948319],[49.31997509983352,5.622563050220832],[49.14430997059769,5.503714755310231],[48.96235906947449,5.127554262332134],[49.20283314946597,4.920284680090491],[49.48289164043679,4.606396898628047],[49.523909232635894,4.092053700206378],[49.77309266998525,4.016720332057655],[49.871676246471466,3.9106386773825412],[49.86151511581071,3.668762472073883],[49.721174090986125,3.2066776512288575],[49.87176969302376,3.136902377793636],[49.88686364083716,2.91131688302663],[50.076888261593616,2.6976547013144785],[50.00349912242538,2.0535533630211606],[49.93197964942109,1.935548542491193],[49.597404031999794,1.7869763284620956],[49.468832503800726,1.3558069404046516],[49.5149113042863,1.2072036819606522],[49.27296701199219,0.8903286762231071],[49.36450431088716,0.7279712901985141],[49.21932993198439,0.49957058674164023],[48.905006547623934,0.366742378437696],[48.9986931962876,0.17214657945662074],[48.94987654741273,-0.09322169384806234],[48.75061028503341,-0.4366443897877624],[48.6373061639245,-0.48164446652757226],[48.51392337114073,-0.7691104487419872],[48.53945704874203,-0.9511673082285726],[48.4429679600332,-1.134729721943118],[47.95611549451001,-1.0512157947154845],[47.732338310116866,-1.530974854153102],[47.83755281030384,-1.7886308528143078],[48.18150978297955,-1.9706020803951665],[48.01541404721793,-2.2396517702458247],[47.859730003712386,-2.3738850027284304],[47.6516309981103,-2.441912300830186],[47.710114569224636,-2.578265115288926],[47.94412416275669,-2.4413104366661917],[47.92796839297162,-2.681625695645843],[47.578339694295,-2.964227259776972],[47.73111260799551,-3.3482564858463886],[47.60804075973208,-3.508579672171381],[47.77648238850776,-3.9375163695114326],[47.32389073339251,-3.9837531181143886],[47.75277772066269,-4.629128611576485],[47.82079586899998,-4.60353711582711],[48.224601496069596,-4.773903413041127],[48.28854882332516,-4.591076732754291],[48.42102727920672,-4.512128941706204],[48.3822166244959,-4.260072791826448],[48.220952608129565,-4.094561396685453],[48.194495037778985,-3.9316865695994796],[48.40178971702981,-3.64724024073414],[48.533374266091585,-3.6723365542115904],[48.78920989116035,-3.979189145279691],[49.00595157545373,-3.973408549309207],[49.18866573318164,-3.83853548344641],[49.45272618571144,-3.8010586168595255],[49.59818699948032,-3.71704465363956],[49.712019581649514,-3.901585014753035],[49.642847162389664,-4.055685489103792],[49.70525076954559,-4.2406232131837625],[50.025593341307165,-4.198221752103228],[50.1574693844409,-3.971346343741792],[50.11525701106813,-3.7730349839481963],[49.89851901964285,-3.5525501302877736],[50.01480167639618,-3.1927948500712486],[50.14604719893817,-3.1864686931589894],[50.29172784513932,-3.4764966266209876],[50.56622672131515,-3.6149992860642914],[50.804661757686496,-3.3849104336045044],[50.99284608809306,-3.294979533990022],[51.28057604048659,-3.345871543433114],[51.47839926362978,-3.4960509680754694],[51.62986272129337,-3.526475151264524],[52.21261280183028,-3.877735214599786],[52.52422012959665,-3.9032430046524627],[52.67387865714531,-4.078692049601291],[52.76228661422612,-4.282164343404268],[52.97011142984537,-4.254107369326018],[53.07921475829909,-4.138882158576036],[53.180389563857695,-3.6454194243004268],[53.1755740772926,-3.4099869991151097],[53.507970217395375,-3.1635877771496106],[53.819090824014154,-3.0947135871597657],[54.15920678409088,-2.6815927130609416],[54.35283359646039,-2.5911072353229354],[54.471570842101954,-2.6483079809626524],[54.76059336032363,-3.155672675568776],[54.96503196782403,-3.2969687920757647],[55.16184279071574,-3.1185461434103945],[55.35928235895188,-3.1559896751714804],[55.30726553465606,-3.471561959601008],[55.39143556354343,-3.6279501514820987],[55.62214247014544,-3.4847031058685496],[55.61434942492142,-3.1510762935473733],[55.66713572412167,-3.023351395698949],[55.928001748109615,-3.0285779977927234],[56.11228179114467,-2.917030262654158],[55.985095205321784,-2.6537891626341734],[55.94185116445392,-2.218203528166697],[55.9962899809432,-2.103145303606134],[56.30510568771615,-2.0361373922774955],[56.41961435690005,-1.903945907642393],[56.412373621780546,-1.7630976083735215],[56.21702197766335,-1.6450277186715374],[56.323369687171336,-1.4645585174928297],[56.55980923824359,-1.6085655096770577],[56.693160910597605,-1.4070293561663723],[56.936030116173285,-1.3867184105822843],[56.95280124558824,-1.210547028673452],[56.725287224993416,-1.1794235630781225],[56.64285388898636,-0.9920321297920588],[56.801804190095645,-0.9267121211977786],[57.111948105273584,-1.1215854519766941],[57.25553103355158,-1.2750646011034572],[57.25045076485995,-1.56573364433626],[57.34519925165786,-1.8714628857030597],[57.62310336382281,-2.0491318626847956],[57.871166800421705,-2.152726449381178],[58.184614487371924,-2.082209978395786],[58.42983350065045,-2.1682494236208667],[58.62868860199358,-2.4424154777011413],[58.753025619534064,-2.1692903114902653],[59.17954640639905,-2.045810114955166],[59.27440561292495,-1.9008912627121453],[59.32711631564137,-1.4277144387737712],[59.263337871070654,-1.054881352285868],[59.26838574545098,-0.8284131623667021],[59.35760988025214,-0.6295192202907314],[59.51992856113022,-0.4464403062293309],[59.57552710883738,-0.12011484717104175],[59.64022364272625,-0.008456194986501141],[59.860471313822465,0.0676717709972911],[60.083699968526986,-0.060839969849916305],[60.38213933438672,0.17018128828573614],[60.523856678221755,0.3969143358111046],[60.6383831392214,1.131200205051002],[60.8063044338455,1.3425668041232788],[61.137100138495306,1.433709338289772],[61.27296555795242,1.5274297997554167],[61.373506274100414,1.793549285191347],[61.63522289157719,2.0266578885626307],[61.650472436912786,2.2587478396457437],[61.48187602731784,2.586348194194126],[61.45091754872459,2.920310341803424],[61.34222492511285,3.075837139815188],[61.335668271045584,3.2814989266169046],[61.55532666821855,3.5038650118728083],[61.75636228560986,3.4779896898354408],[62.111071290632204,3.5980697627756135],[62.370985003616205,3.6002008085723736],[62.60802182404086,3.3614615789067983],[62.8820150841145,3.298340545894095],[63.09470784172625,3.430474231988406],[63.3860967242109,3.7398368527043915],[63.79884668706582,4.058824984960162],[64.1192418415272,4.088612207787986],[64.35761366969068,4.241424651756197],[64.77318570317941,4.232067633068364],[64.65758738299407,4.57929347941213],[64.83087768659324,4.777663974950748],[64.83381485859036,4.931003080186549],[64.66896337838023,5.006178396992976],[64.36925419895066,5.0009905151169525],[64.30504608194924,5.145323334381246],[64.50496054923201,5.437687523930299],[64.45929881664102,5.663112079623896],[64.083708872641,6.104155967765344],[64.46206799465298,6.496961030298087]]]},\"properties\":{\"name\":\"Indiana\",\"ns_code\":\"00448508\",\"geoid\":\"18\",\"usps_abbrev\":\"IN\",\"fips_code\":\"18\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[4.566633702986271,13.439284734745812],[6.05511071764217,13.420658791476566],[8.724009035483911,13.399118468582895],[10.8592379111042,13.392374568848469],[12.530616694410385,13.422649718617294],[13.662808845956164,13.454665993667406],[16.478373506186482,13.56941524191641],[18.01997462416407,13.613200800964595],[19.34436744481307,13.690335991123066],[21.243935730963845,13.780059873168897],[22.177515221359698,13.845938600037732],[24.260676105732653,13.972042952077866],[25.482973306531253,14.065696092489317],[26.228238394896653,14.136673232518838],[26.454202244841692,13.935333932747854],[26.502253928920393,13.72501393808454],[26.826848184063138,13.664267844433354],[26.86588092483485,13.403403820850327],[26.988940517459294,13.340282195207541],[27.14441381440257,13.117960011395798],[27.369148043238127,13.102507700364185],[27.39365223646377,12.767584519632887],[27.626274986859638,12.579595961996988],[27.850443626729838,12.616508059920148],[27.970598476146584,12.565960353874365],[28.20097398703783,12.67290571879121],[28.15251370108082,13.067913543927235],[28.226689371041754,13.321694288390757],[28.19448744364804,13.518934926923027],[27.98455107858742,13.720238274759327],[28.21246455185492,14.144271893315615],[28.471422315392395,14.318855468603386],[28.80005664167075,14.427838018828597],[29.13555562691834,14.440108723499003],[29.468204816394206,14.700403323410459],[29.476082300047278,15.06556800098195],[29.581399112496577,15.429929983949672],[29.544667099410546,15.667984324675128],[29.802887576427242,16.07549491878837],[30.03438274878378,16.362182304230387],[30.217938814064023,16.487821539057304],[30.263668521415465,16.719882916430684],[30.222515092504867,16.866250512266525],[30.286237237464704,17.24776928766923],[30.241938213207177,17.657959488272958],[29.98773533035041,18.063118753208347],[29.70591686535596,18.122829400017558],[29.565797507109487,18.34182757968159],[29.304233847993412,18.598668856072756],[29.29037097519139,18.673228141666993],[29.483761505554654,19.09451675296415],[29.506982764331266,19.493999419117767],[29.58086724190003,19.77324281077238],[29.68416366348127,19.872156259781267],[29.95910527223696,19.956037810144462],[30.1996188546565,19.882655087882544],[30.627603492029152,20.131956465701492],[31.211773733602158,20.128967829312874],[31.63968313260905,20.232535487571933],[31.89086235531989,20.462564750467866],[31.94063593612805,20.578506094628665],[32.2217631840786,20.69531402022547],[32.43835120290535,20.652440553917316],[32.64346330930041,20.700649369313776],[32.88296702432376,20.995021118956103],[33.241314928432814,21.16602232186575],[33.21485293037075,21.561036595438722],[33.2480365337831,21.78100888762084],[33.35275185214712,21.920954904563114],[33.3246283480023,22.15160764260727],[33.50508952601178,22.38105540643515],[33.96729280159735,22.645126958357146],[34.029469247333004,23.134628476581568],[34.10275297853448,23.338374571032507],[34.03362203816671,23.69538498894794],[34.13111147047373,23.97713974016284],[33.985677059097995,24.224232994914562],[33.96112884442261,24.711622059268127],[33.752926162562744,24.79295944761995],[33.70107261395784,24.932052726100277],[33.308682924858275,25.081131280528012],[33.0575809473437,25.21209721546635],[32.90539276372555,25.23251880593415],[32.71803436183088,25.35276129240763],[32.55802522288108,25.532606542320565],[32.48493503664864,25.710261660954345],[32.546995621867126,25.974108173277305],[32.1970778043148,26.352465551919153],[31.781137603626053,26.536936515264184],[31.68855525640139,26.69655185609229],[31.401730562651206,26.792452384559123],[31.238774894436055,26.909980971738158],[31.295089013593266,27.103713553000162],[31.25610002559266,27.330651569250037],[31.021382339683505,27.623045359959598],[30.924008011619627,27.86625156164734],[30.828687323935846,27.9251049521726],[29.601955225602257,28.158172964660938],[29.06868413057472,28.451236250748636],[28.98679048074195,28.57229579890704],[28.758349955817714,29.355971423831484],[28.52761571336651,29.480936563693],[28.490268703657076,29.84534944197953],[28.31694350556365,30.306149322949565],[28.281780060491307,30.489891890014377],[28.279119178409097,30.8975852878614],[28.37462033920655,30.952759809144972],[28.545230058703137,31.31315753621773],[28.740678527468997,31.492703540701573],[28.841438503458434,31.65442181186268],[28.684192232114384,31.878831947987972],[28.447266232070852,32.0784367147982],[28.064008006431965,32.200210342274445],[27.991337963016516,32.29891579759119],[28.068803052300847,32.480767332930995],[28.031754147211803,32.62722424294913],[27.86745746231414,32.86299366831677],[27.92765601723543,33.10950255608115],[25.795529580444896,33.0227700159463],[23.74935733630682,32.948519147876816],[21.512749406205497,32.87234651601961],[19.630204831331824,32.81210273978479],[17.217850635880215,32.748863196080414],[14.021186236851864,32.683048610381796],[12.192648926057934,32.6544919179649],[8.898085636581447,32.61787935435676],[6.223887571918285,32.59372341408471],[3.4497999803169686,32.58612149136622],[0.9736110818548098,32.59276864128454],[0.22204198333311617,32.596672999517864],[0.3070273418123105,32.39241010350416],[0.24916933435598632,32.189203503642396],[0.6165576081197521,31.90549134608896],[0.545914904565765,31.600549581931844],[0.5991479647471554,31.44992268497563],[0.2772439854721544,31.216665675980906],[0.4673359030500558,31.06016526543672],[0.43671930562883743,30.92690109817087],[0.6315264133443401,30.871561020887267],[0.8427305450413489,30.896224917036143],[0.8775692426131096,30.503717755218386],[1.0470511928489967,30.264623530931733],[0.9556703928478859,30.12560147114146],[0.9199219472441345,29.920861497313965],[0.6596407663446835,29.831417809932784],[0.6428110839582256,29.708872668579083],[0.7559970892605764,29.58443231871838],[0.6052142084093701,29.39610236293982],[0.7161814669670364,29.263133762233068],[0.535985504287191,29.074477491339405],[0.561890842501714,28.880881392118077],[0.4672843283971302,28.735863160017722],[0.4752751199019332,28.58263631255613],[0.30278406873266805,28.450337450210387],[0.16691332051285304,28.1663513037544],[0.08213229121287764,28.165535481577717],[-0.0016972328994087971,27.893991437966044],[0.15256911964913314,27.646545803127932],[0.6161682349632844,27.18702234673124],[0.7189188335162018,26.867517212648057],[0.8175569541155933,26.71064917228056],[0.7574131046799324,26.42399264068566],[0.8140146479040917,26.293324685638794],[0.9797148313671948,26.290151267204052],[1.2070468058815351,26.262269786758782],[1.3170001323383436,26.00373940607817],[1.1611366076195317,25.788414424950464],[1.1709047408934936,25.29887387407281],[1.3693593237953312,25.12577642688133],[1.3792358968876897,24.994355771899674],[1.5706342938662072,24.78999406610143],[1.6097082259552298,24.597245422633755],[1.4195502037299796,24.47154674011529],[1.4875224015211121,24.171119171262383],[1.6806816491878092,23.9568001436344],[1.9023080765035387,23.809580592865178],[1.8399784153668197,23.571384414228366],[1.897896580573708,23.3614268798015],[2.0751905634456413,23.354447743388793],[2.135574860918952,23.242839131993424],[2.33584705934872,23.11798969340588],[2.2973517195739834,22.988327656418125],[2.627695344919608,22.854073475946016],[2.558283877426512,22.524337321987396],[2.453777453250886,22.442620226298253],[2.5880045961504554,22.183871221014627],[2.7211726037835264,22.09104717485961],[2.728898856892638,21.916238428320103],[2.9654018379238956,21.700412395240086],[2.752180995823043,21.29620497689652],[2.9070574489596264,21.186143874575272],[2.667914510051546,20.93296388311348],[2.8047524049215204,20.736435784720225],[2.6941523375848706,20.56340798512874],[2.706442810628245,20.428273498465643],[2.8780622447406214,20.24168603368916],[2.7961448642923474,20.04827833548479],[3.0041283583182303,19.814464239922643],[3.126262769132797,19.827832968952553],[3.166775590995487,20.01252868113025],[3.3081925861373076,20.006525582231475],[3.3462961594941834,19.838352609917347],[3.2158645466870652,19.68642998332438],[3.286549328898408,19.551038287006893],[3.6456637837208516,19.50478642640128],[3.7324659969125578,19.418763207557575],[3.6388146224709006,19.026087460007872],[3.68630673415468,18.858217384764597],[3.5357692942157293,18.718680921955404],[3.705644335830788,18.526376403012577],[3.9163060171285817,18.50436202152423],[3.9595308712422734,18.275741007372854],[3.687465630835406,18.253278768302415],[3.7800122386440322,17.967056330525764],[3.7013042738169726,17.77657203595921],[3.758373037296556,17.595235773106445],[4.01290474689483,17.617933465820226],[3.93162669061964,17.385712980164325],[4.041606104449733,16.93999603471849],[3.9362684316088865,16.759875147245435],[4.0504897477605795,16.5878308986067],[4.03688106433218,16.303554571863984],[4.204941017650553,16.191944222820375],[4.182269651757157,15.780487194215638],[4.312981658312283,15.521642598406116],[4.122279003520577,15.383260267491366],[4.179334910070468,15.182242769362636],[4.144606822544939,14.962929195059871],[4.181576179115136,14.773123460110572],[3.942786992715526,14.592770582134653],[3.934243662055091,14.362658551457494],[4.098249197017881,14.256097910467863],[4.151163760908274,14.08341555974057],[4.437417203622507,13.957548473062506],[4.543882487588355,13.702752981410557],[4.643381104437475,13.606319591367356],[4.566633702986271,13.439284734745812]]]},\"properties\":{\"name\":\"Iowa\",\"ns_code\":\"01779785\",\"geoid\":\"19\",\"usps_abbrev\":\"IA\",\"fips_code\":\"19\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-75.66672067756258,-42.87870827077865],[-75.20584300687074,-40.41503614082921],[-74.8431870617263,-38.422973340792886],[-74.57934636526043,-36.9299058055318],[-74.23162123045276,-34.909192966754944],[-73.94859456101388,-33.19871589178014],[-73.64139271229433,-31.35232237658829],[-73.28603790761778,-29.108330784372782],[-72.85718263319062,-26.375565755857558],[-72.5130751974101,-24.145700147117658],[-72.0890521622258,-21.327039620607557],[-71.7941053760312,-19.316171511465754],[-71.52733612066665,-17.466140110725757],[-71.29605605667153,-15.869301677173693],[-70.9382265162427,-13.388186587584192],[-70.68518534223044,-11.590768436308707],[-70.41696558431377,-9.6984640341206],[-70.06615646820839,-7.199983140779321],[-72.58110158247436,-6.84904083779033],[-74.00796249026585,-6.651367327837256],[-75.5279546486134,-6.425952144565376],[-78.02327518136048,-6.0556952853108355],[-78.2331802207451,-5.980228143496496],[-79.61788028712324,-5.771627496170111],[-82.52391577881168,-5.331130099729538],[-85.15728377563966,-4.893552579475372],[-86.54481122636447,-4.662414955192292],[-88.22023969126292,-4.37513457277261],[-90.71450887810519,-3.9423124359646518],[-92.39305975068585,-3.6449368696579887],[-95.27385736116857,-3.11524807527112],[-97.92594531658744,-2.60993149468631],[-98.31772662452275,-4.66365116894885],[-98.72704270042878,-6.985690707365453],[-98.8651226862623,-7.625865259013503],[-98.98583520622175,-8.296261402047778],[-99.13323876452229,-8.371689721075727],[-99.83142468409413,-9.368214141490043],[-99.94607923288198,-9.321000752720265],[-100.17133335995302,-9.363574645632715],[-100.34994986157949,-9.326226524304776],[-100.4598297410693,-9.086070582045924],[-100.68350572585013,-8.934350232686866],[-100.6052440877174,-8.840309554367726],[-100.67805508822282,-8.640615713462662],[-100.882596119104,-8.300390081587215],[-101.11417285451137,-8.227259794377257],[-101.31818712678053,-8.339501774782002],[-101.37232622862969,-8.238648138257332],[-101.65750766980236,-8.261820556021087],[-101.64510524407184,-8.100620501857588],[-101.99579047134034,-8.021946356483408],[-102.30006598242925,-8.102790903406197],[-102.32369107142355,-8.032723044651947],[-102.67465901916233,-8.193062385748888],[-102.9735374573064,-8.166913643827721],[-103.11400788372124,-8.273575264538827],[-103.02331386679813,-8.724140783996043],[-103.14090900309125,-8.800049750619609],[-103.19178349896153,-9.061135973897656],[-103.07406471020224,-9.429193623378897],[-103.10228816489881,-9.554091849588364],[-102.901109344497,-9.855803619162337],[-103.18103684900105,-10.002027053177896],[-103.14062016110769,-10.100205604872817],[-103.27127962472096,-10.275829481659759],[-103.24022250530544,-10.634863281789784],[-103.3662841596472,-10.933746558594963],[-103.27059135226342,-11.119497926026362],[-103.36016213553556,-11.37763323152724],[-103.2166531358894,-11.682760161572633],[-103.34804405645531,-11.860260106499497],[-103.34045560298607,-12.209879355546278],[-103.51262577600201,-12.417935558765425],[-103.50106367800551,-12.80315085154365],[-103.34916792857719,-13.101469253839477],[-103.29091778912795,-13.742908130845827],[-103.36875359761396,-14.86217129094591],[-103.46511409694874,-15.081169407746138],[-103.74949530950785,-15.086130292789543],[-103.87800329065054,-15.170740895650214],[-103.68926941971598,-15.428193693979512],[-103.9547468422213,-15.74233503596342],[-103.95635518857927,-15.884671342375682],[-103.95926651835927,-16.015896682325312],[-104.12044429271214,-16.826419626193456],[-103.94723029595491,-17.01675045447402],[-103.82501294172411,-17.55992456888235],[-103.6613596052714,-17.801166109160025],[-103.4377197189206,-18.008986130004548],[-103.36620945100371,-18.501359495304982],[-103.39773526393817,-18.844836630533376],[-103.25723880032909,-19.038701139014822],[-103.17295291507531,-19.330548991058127],[-103.30576169149748,-19.80187196182959],[-103.23219403790233,-19.890677469962032],[-103.04514895508099,-19.894462957227315],[-102.85790668210599,-20.098360935575936],[-102.82481603371669,-20.221482690696146],[-102.67587344300597,-20.31335872368053],[-102.51215013541393,-20.579662843374955],[-102.25270525180579,-20.740557218582854],[-102.09395412659032,-21.08442754534635],[-102.13952547239167,-21.385704716305014],[-102.31473029701253,-21.396568343478034],[-102.93651511918974,-21.814384603846243],[-103.1280959619548,-21.80094498996343],[-103.39244674384689,-21.98386669559316],[-103.65871673699976,-22.059075109477053],[-103.95790980980371,-22.079012449575398],[-104.091070927799,-22.192248165587262],[-104.20432172007585,-22.61032394649825],[-104.36998815134828,-22.77437129575893],[-104.64361452636753,-22.941384961353325],[-104.77962905053668,-22.958271704406688],[-104.89198240208276,-23.143018946979378],[-104.8002033875719,-23.55934014036977],[-104.97313491234824,-23.64681200514262],[-105.01028448007489,-23.862005221636245],[-104.95468973188359,-24.18666220252007],[-105.03195256991361,-24.397834704462067],[-104.99216099820792,-24.653574052310994],[-105.16629910862203,-24.695707172571897],[-105.27192755719385,-24.925624649081506],[-105.2591734079388,-25.174469660072546],[-105.3941620086277,-25.28194519728655],[-105.35256685478998,-25.542971531910982],[-105.57689801677665,-25.638238062102875],[-105.61602161562485,-25.73585588834],[-105.80071527747941,-25.80135694306869],[-106.0591001279947,-26.053292792605014],[-106.13783122529694,-26.226114758204403],[-106.69064796344519,-26.26040020357106],[-106.63442046597224,-26.57701414382593],[-106.67922650634915,-26.7269869392927],[-106.90366089018833,-26.913022261894465],[-106.8775849797565,-27.011349947551807],[-106.6341676925809,-27.171301628829063],[-106.74111391946963,-27.31458114797644],[-106.68194673214319,-27.453416013101283],[-106.79869810575411,-27.841856091146855],[-107.05605677780636,-28.216640221935513],[-106.9649302963726,-28.319007403065843],[-106.92335850935629,-28.622611679133097],[-106.73100635589381,-28.6092375358991],[-106.63527333969179,-28.742061795176966],[-106.38777781080005,-28.732632046856512],[-106.05624658632837,-28.824651710959994],[-105.99344949425596,-28.91611513667504],[-105.96473051135462,-29.224935366958544],[-105.85173388914895,-29.328712198224157],[-105.95763684226517,-29.44050198817148],[-105.89183620365718,-29.632094698589025],[-106.01414127131207,-30.009686005078986],[-106.29565304804403,-30.143144685504986],[-106.45560084666384,-30.275814565294727],[-106.47484193028947,-30.487658045528992],[-106.70525560620338,-30.495942017588742],[-106.82704692134448,-30.567218912039817],[-107.03612592409301,-30.572204492764286],[-107.45342726200973,-30.439693640297815],[-107.65625525819341,-30.527220995306912],[-107.92317803055228,-30.85245162523779],[-108.15302459951818,-31.033204893888367],[-108.32700868977825,-31.056616723860664],[-108.30102200255203,-31.202517648110796],[-108.41965148604548,-31.345448071588702],[-108.39979317809889,-31.548550234053547],[-108.52279076752237,-31.784389864462977],[-106.18000856696423,-32.97796757382814],[-104.76889054298347,-33.690429488155544],[-102.71401600255241,-34.71728018932968],[-101.12221535212252,-35.49933689584529],[-99.2755491086671,-36.40008670760769],[-97.8636461831872,-37.04798457219635],[-95.82100425504446,-37.987240739894986],[-94.1324128261763,-38.75251214101569],[-91.10707938387083,-40.105654071035744],[-89.00762970360155,-41.02449611558609],[-87.8244091734755,-41.53798920566837],[-85.8789786000625,-41.760401011040074],[-84.1373173558697,-41.96567414624672],[-80.62680241685676,-42.349932079690205],[-78.37398694144532,-42.59166262177287],[-75.66672067756258,-42.87870827077865]]]},\"properties\":{\"name\":\"Arizona\",\"ns_code\":\"01779777\",\"geoid\":\"04\",\"usps_abbrev\":\"AZ\",\"fips_code\":\"04\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[0.5081726085095676,45.99068655467205],[0.45037641880835183,45.537580066223924],[0.4067097066126685,45.41773359298238],[0.12509091839790584,45.178714668518644],[-0.04343724809158762,44.97939948706171],[-0.4323716392161333,44.81200458148526],[-0.47761668139843577,44.76146543799582],[-0.8757390167105714,44.55470908712366],[-0.9691692084629064,44.34082289250518],[-0.5078223321922518,43.90089280205305],[-0.3511788475970739,43.57535599220275],[-0.09867698255165118,43.33304691900658],[0.20514760875891433,43.32315258388948],[0.8575392048559706,43.05080426826421],[1.0358402860510656,42.755734607790174],[1.0300071007365206,40.95710531814725],[1.0254307593061216,39.62595555897181],[1.0060031697166245,37.29603995997629],[0.9908102533559819,35.70438669139522],[0.9866839303716597,34.51691591908545],[0.9736110818548098,32.59276864128454],[3.4497999803169686,32.58612149136622],[6.223887571918285,32.59372341408471],[8.898085636581447,32.61787935435676],[12.192648926057934,32.6544919179649],[14.021186236851864,32.683048610381796],[17.217850635880215,32.748863196080414],[19.630204831331824,32.81210273978479],[21.512749406205497,32.87234651601961],[23.74935733630682,32.948519147876816],[25.795529580444896,33.0227700159463],[27.92765601723543,33.10950255608115],[27.775917242334216,33.39463238009364],[27.798859940322433,33.62605902574001],[27.624172763088477,33.784066266728296],[27.592020915847815,34.344148490399995],[27.693477771231322,34.731418821946434],[27.593487037896985,34.830411365483],[27.46281560667962,35.147584136670744],[27.066939268098132,35.5381345462518],[27.01974535358221,35.643039186050544],[26.7986372085802,35.803376375599974],[26.609304021172644,36.014216538096115],[26.263392262703803,36.10206832409163],[25.875234665281862,36.135996570683986],[25.53750746930195,36.3312598210325],[25.19171931768627,36.647795142262716],[24.6339096498412,36.87899371100886],[24.327828310206485,37.07626099859232],[24.226464388232632,37.270087039750784],[24.250479892848016,37.36981714746444],[24.016236705115162,37.83252096920267],[23.847353995940544,37.96426904200924],[23.441642124888098,38.11396959552893],[23.35966179447737,38.18247452092611],[22.997566838176596,38.28584329418501],[22.49779998121391,38.3741895719447],[22.17632974747136,38.57339438444454],[22.08984672213719,38.66533760608337],[21.980864911348554,38.92915141614688],[21.823739612981118,39.00587169433496],[21.390950327792172,39.04065846964225],[20.846437026952565,39.03474663073056],[20.690050796617772,39.22967519557695],[20.4154476013018,39.29230560150185],[20.41310697043716,39.419266437448265],[20.190026663056774,39.54768049351847],[19.83684806485278,39.81877742450687],[19.44177589089301,40.01284938875452],[19.636044565350506,40.5057229632988],[19.582391992073003,40.77010122625181],[19.6883409535947,41.0284142550249],[19.585144240458398,41.21341992223126],[19.610725114412837,41.48207051147134],[19.394189412195566,41.70800665874334],[19.675276408484045,41.94357163953732],[19.546614042800005,42.349010687206714],[19.614324944588898,42.525738370298185],[19.545749977782517,42.88902064644142],[19.667483141129534,42.95611118951166],[19.84980309176973,43.148126283572694],[19.82004486906774,43.23736252648631],[20.077968999392972,43.47810141718307],[20.08560606920014,43.699958064219715],[19.9117507666094,43.812639096692045],[19.66724900070536,44.06412877524926],[19.66367488568345,44.20055431622517],[19.420751723970092,44.33321160593779],[19.173412714758495,44.28996672178723],[18.86826391024103,44.35701056384629],[18.834128041079076,44.62543756863332],[18.907552461208276,45.07821379836368],[19.196824723546275,45.2188308378387],[19.317558169737833,45.322219191497894],[19.438869054620966,45.5603781275989],[19.396514930918876,45.63937726865175],[19.645050449654573,45.96570291549164],[20.005149933736654,46.185940821887954],[20.43071592285467,46.284122842929875],[20.55150959647546,46.44271761167195],[20.81442967785834,46.395783574686185],[20.954224052637464,46.61145070616144],[21.111103243425756,46.66969554510653],[21.409531881692356,46.624249084334146],[21.47242950743686,46.83624910416229],[21.67289354373347,46.91442579726936],[21.57774130407355,48.667519612102595],[21.517825264401516,49.711284745741324],[21.650616327806333,49.64559653245087],[21.914094236219544,49.64728351899211],[22.07990907749717,49.83790964985987],[22.009234864868407,49.96010232247501],[22.219582083847467,49.95865656715873],[22.254004120099754,50.06700468475312],[22.48845306324833,50.11740607791418],[22.84111265036028,49.92025395360462],[23.026464445947774,50.01244049188273],[23.534616701029805,50.20984114160884],[24.88483981933787,50.75361399862922],[25.321749939675996,50.93888994579757],[26.17401552421586,51.351030829823245],[27.677328601024563,52.05845971490952],[29.295942850893024,52.80560583108209],[31.196055531530874,52.83160876694376],[32.6914593661213,52.84830092013629],[33.26081927492087,53.756756027819996],[34.06163583903006,55.02604228247535],[34.64072543401255,55.90944698264693],[34.178263930757,55.82097291086161],[33.996415644414725,55.87388287658544],[33.52648393594915,55.84951616071847],[33.433597517472606,55.89628472012353],[33.02489665340869,55.854319168321176],[32.791172606994856,55.72960400744421],[32.65649469510067,55.73314913196335],[32.187031540406835,55.877055205158136],[32.17411289224769,55.95850486342298],[32.015353199825995,56.094203793556126],[31.480403487320284,56.18243804697098],[31.04053828423699,56.153140492023354],[30.734367504611463,56.106671521805325],[30.653613713773645,56.12815343940606],[30.32278989830236,56.062212333184085],[30.185536799286687,56.11262417499849],[30.01917801674518,56.082013690705175],[29.45231339777327,56.052843538414976],[29.330427537648188,56.159209832130095],[29.042656659680755,56.06951942578727],[28.566737294292867,56.0193218363259],[28.468802985996266,56.0309289118973],[28.282803229912403,56.1870509043518],[28.23376374931908,56.33725060136462],[28.099304659221605,56.31720944147874],[28.042511574164525,56.57275414271496],[27.818419061042555,56.59052908169911],[27.390365089828247,56.475064068737865],[27.140043087909714,56.346483256214356],[26.897742646569288,56.309301292898645],[26.12960397682485,55.9058265594774],[25.940657310378924,55.86027643489623],[25.554894983090815,55.83161922182033],[25.279763230329984,55.741275343370184],[25.17354270694174,55.81883326819127],[24.989767656718623,55.810995373977434],[24.578254080480686,55.72330442978355],[24.59202632987051,55.90091224237677],[24.497982357460177,55.93365582062629],[24.25270239309571,55.90634092935234],[24.184336296048865,55.95469872125279],[23.902992272611638,55.96749804898821],[23.960007991376717,56.07590242231759],[23.85974206550114,56.300413891333314],[23.543311502346466,56.29701430181704],[23.371857399501824,56.3372166369751],[23.14243594141446,56.31313257373336],[22.993654214817724,56.42971640983849],[22.748228563600453,56.395482762328704],[22.697181006589737,56.47795730874958],[22.443556472162253,56.5250686495595],[22.403438110653806,56.64865069344821],[22.457808028873906,56.742951350841714],[22.04946478311343,56.859517480822106],[21.67136669684229,56.887549372942075],[21.462027514882244,56.814484320093946],[21.19408694321225,56.84478301565032],[21.069587071397706,56.79591056992282],[21.02494347227104,56.58379306850935],[21.136408835680992,56.41312986601179],[20.97484949706818,56.39497735073616],[20.714678794542323,56.31015181909071],[20.479352385164777,56.59266031279572],[20.20159001543067,56.81204241856487],[20.16384180492995,56.888289073074795],[20.253468600921217,57.00335173841601],[19.998072155661735,57.17768620198516],[19.290377147093768,57.121068610089324],[19.01385594432377,57.21660309999972],[19.071696333188157,57.33940770643459],[19.367113128454537,57.362335090366976],[19.364629569659424,57.527405423886876],[18.921374877422494,57.50549498023431],[17.94447720819711,57.74363546200607],[17.84141196298853,57.83044147897063],[17.676924961736162,57.80249611076423],[17.177570360153616,57.80914771850102],[16.755093876129425,57.785705399793336],[16.613165294084997,57.85542986368866],[16.3871162501403,57.85315312408396],[15.948946218396943,57.785895548336505],[15.840590655548933,57.70309777576116],[15.682137816578933,57.716866355916586],[15.391959575154116,57.62943514593457],[15.392540466853147,57.47174760348384],[15.015495359727263,57.401862368687304],[14.608952064152804,57.40169382284562],[14.548832320547922,57.352098530696615],[13.837553584157899,57.33798203880514],[13.721602657561691,57.390012478535354],[13.77685916972731,57.543602951428355],[13.695410515167465,57.67869947096091],[13.582844862381005,57.764379657802145],[13.187255143954491,57.76653239094065],[12.804556348205608,57.807017212940075],[12.08740428507245,57.80903687010475],[11.67183081870335,57.833819051163424],[11.570206500004794,58.00311958810756],[11.358947082283908,58.043377693187615],[11.197113673984267,58.015984888621844],[10.846755295060516,58.04147309865928],[10.741441019837875,57.98060544989573],[10.2650656471637,58.00561843743851],[10.008628239910031,58.065112615694304],[9.515921973867218,58.29592611763606],[9.464177222620398,58.45115129009745],[9.553533397196428,58.67130304699626],[9.37830222154924,59.09266266028181],[9.230104915998075,59.09165271160189],[9.220626232742887,59.44893496020902],[9.110078642507956,59.52492998295117],[8.985688258367452,59.79844428177478],[8.987163324756398,59.856763605571956],[8.847528651595042,60.13753458066811],[8.887900678636429,60.230864123799684],[8.709513395054328,60.24064404351848],[8.219657397214101,60.39792964275663],[7.745330839213352,60.33658228939616],[7.4735665031813046,60.38181776138634],[7.295995785583051,60.443016902013525],[7.318284072673496,59.07687923127186],[6.299560518089154,59.07217951839052],[3.675059221808148,59.07078891942554],[0.34491155549295627,59.07661952539853],[-1.0987695809648461,59.08269655479607],[-2.514240910251004,59.0930468110162],[-2.544673563402833,58.902113712374515],[-2.3881284110987364,58.662507587206754],[-2.2632713371150275,58.54777201638002],[-2.316896744599685,58.48374481396241],[-2.1624871910125267,58.29231907744561],[-2.099975593618694,58.08063180043035],[-1.9556035746368639,57.94488349710897],[-1.9318050619431526,57.827994891256864],[-2.049753243730531,57.736378843672206],[-2.247808155049092,57.4466774184207],[-2.1945631719553176,57.34453033667036],[-2.1492408897379183,57.088322870864694],[-2.1851397845439573,57.011779704636474],[-2.1033599343086897,56.86956957419518],[-2.228207458939015,56.78979725872343],[-2.221822286042145,56.70467562745023],[-2.0861169756899858,56.61980585484494],[-2.1095831523296584,56.532014110957846],[-2.0425939107751665,56.375839617675794],[-2.1385172378549897,56.20882817600163],[-2.1209589695903497,56.09134275865352],[-2.209631775009159,56.007898773766755],[-2.1977227705035807,55.84235907306892],[-1.9745977363039544,55.55161718226401],[-1.844247497902176,55.44789184773539],[-1.7763993577440162,55.057482997341396],[-1.5980046121266376,54.939232373046174],[-1.6359576402967417,54.79980401487811],[-1.5407438272238536,54.73512908626708],[-1.0406657886228197,53.986868488402884],[-0.9900753432214491,53.77810229127356],[-0.8083793507302905,53.571913097176996],[-0.8689036138645797,53.364039054254796],[-0.8140340832208064,53.17687471062925],[-0.8466217106203335,52.931677881676585],[-0.8966908700908015,52.84319291859724],[-0.7635805970742475,52.68696413109471],[-0.8364522968376281,52.65415623314273],[-0.7306109846913699,52.49414143897698],[-0.797818284450651,52.299420065234706],[-0.7297482677395977,52.04577196863621],[-0.7819846000218355,51.87831742768142],[-0.6983888140141886,51.82885560349115],[-0.7618711897790037,51.70277920807086],[-0.6744457126057841,51.49108510077976],[-0.7250013154828518,51.349778436675855],[-0.6854971699036456,51.23689978917899],[-0.7717499678844988,51.04196960471558],[-0.5658838668028204,50.6877211044601],[-0.40926454098402076,50.62947149412579],[-0.4941801352770946,50.539307336182105],[-0.4892844369879443,50.28915062887876],[-0.6256705711949769,50.15877111102839],[-0.5516720301971151,50.04046274260632],[-0.5257899500516574,49.74915319872003],[-0.6189462640742759,49.53423099981933],[-0.6045297695882347,49.33056397200346],[-0.3604512997842686,49.04961701283427],[-0.3853381571626031,48.923847399841506],[-0.31306909234893515,48.62595422798637],[-0.2085683228377623,48.552228245276886],[-0.22254336620976925,48.418711108950724],[-0.11350729882314771,48.349203689777454],[0.014881392192430341,48.13079467249016],[0.34717285356843175,47.91211802256283],[0.3518479852870505,47.42514239548147],[0.417072108190286,47.30268025106105],[0.3961619133026314,47.184574911065475],[0.5602373225255404,46.72266782933081],[0.4416006544472208,46.443393739325835],[0.5081726085095676,45.99068655467205]]]},\"properties\":{\"name\":\"Minnesota\",\"ns_code\":\"00662849\",\"geoid\":\"27\",\"usps_abbrev\":\"MN\",\"fips_code\":\"27\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[16.860657166854118,-54.481653916024285],[17.075335502220355,-54.41604715563201],[17.150859091996058,-54.32098031020782],[17.122276291410124,-54.16905888361166],[17.63480307492702,-54.06151300460846],[18.86249540972926,-53.94669517551689],[19.485425919335842,-53.9537827232053],[19.723079237300663,-54.026996845738445],[20.004586205976654,-53.9774595186271],[20.101612614074405,-53.89922182012277],[20.49843660184829,-53.88377777990059],[21.10940793630116,-53.95795685716853],[22.03704914054961,-54.13650398399134],[23.108949449410733,-54.4211171470023],[23.461587674960708,-54.50338886066344],[24.203142343928338,-54.63394242986352],[24.65714199878478,-54.672184462688904],[25.62218019113803,-54.72533325866301],[26.318214719422258,-54.7873117751785],[26.750118266973317,-54.72304754264806],[27.21576383386842,-54.558020138112326],[27.641408909241864,-54.5501866229403],[27.85014774036835,-54.65139440753393],[27.95682986216184,-54.77191527502721],[28.246723794756154,-54.82763972904648],[28.37133267576049,-54.912187390431214],[28.697666108515072,-54.98283225470791],[28.808087476825484,-55.163967425605954],[28.95269717797689,-55.21428983999575],[29.16510982046854,-55.216343418233215],[29.54791792924455,-55.06023256068348],[29.894252336333675,-55.18471431197974],[30.040790704855752,-55.19450319461544],[30.38332585576637,-55.122509929651414],[30.86280162475231,-55.22190697025331],[31.103715083249227,-55.23017789366717],[31.792833395756126,-55.380633957070025],[32.46269357492662,-55.74350100853682],[32.62889126209163,-55.78778527054424],[33.1658605180907,-55.872987069508795],[33.532156029809684,-55.88687617376646],[33.73313755423474,-55.97032663610871],[34.441152364595254,-56.040627630996426],[34.48057136091389,-56.1171856987862],[34.35503750336242,-56.252246390803634],[34.45912787979764,-56.40942250686285],[34.70725913991961,-56.48845247477884],[35.10677196231915,-56.53140953505975],[35.63544973941866,-56.53618632763662],[36.2683023725282,-56.4905054466885],[37.299387822944425,-56.28373145410467],[37.76418095941743,-56.38040786399215],[38.209083129237776,-56.41842298146813],[38.61144726888814,-56.37426991744331],[39.25475925980547,-56.234294227286625],[39.492518535704356,-56.21402143825606],[39.791799004443405,-56.12366158951729],[40.14145304122473,-55.96942019165645],[40.497492450653226,-55.78445588219308],[43.092404238029985,-55.432468303879475],[43.45150579781013,-55.46998740441804],[43.78409733274765,-55.568781525436584],[44.01827773386855,-55.764868760656086],[44.063036568081145,-55.862044945963206],[44.10167363245096,-56.292579044668464],[43.965761284526,-56.515198788052864],[44.00760775121856,-56.63961060834176],[44.21485864673582,-56.736720778557],[44.40998597557661,-56.7507888609514],[44.65760212553397,-56.677172607460776],[45.26010823313545,-56.23437252267043],[45.53393709048051,-56.186332095507176],[45.70035659752948,-56.24705758800264],[45.79705093111482,-56.363023623896396],[45.97720198839418,-56.42460586023197],[46.20181972816909,-56.4268806662082],[46.42217186607035,-56.32798775348537],[46.45758102676362,-56.25260468066639],[46.70288997027767,-56.060419160228044],[46.897272928745835,-55.96961372357042],[46.96534363590772,-55.73567061176172],[47.37833922081092,-55.589511700046245],[47.4491536229552,-55.435571284531115],[47.45035450450124,-55.25021750852145],[47.374277634821524,-55.10603802219321],[47.217562896970385,-55.041236396348836],[46.706439608747445,-54.97797053848251],[46.40549648236659,-54.909196635295096],[46.23174878110985,-54.773905879612514],[46.11680319899074,-54.73441575591583],[45.93740377050288,-54.56920999894269],[45.88504204407791,-54.371807396979555],[46.01048981661404,-54.247189359460535],[46.29600062816112,-54.18162425420842],[46.491850084736456,-54.08358584938931],[46.93419019387404,-53.706592368651116],[47.20986633794782,-53.5472915226827],[47.44975989523687,-53.34344939416615],[47.705970305810915,-53.0837775676751],[47.82041319281179,-52.92693544900483],[47.98457575462698,-52.526708582554974],[47.99712540837824,-52.18722829758727],[47.93036408293247,-51.95410452132763],[47.822963903683956,-51.772871339511155],[47.588571193358874,-51.54655615929419],[47.40516912419355,-51.44965529854659],[47.18016991529713,-51.40958939239812],[46.98866921780146,-51.11608657598193],[46.740760390257506,-51.17515151949074],[46.38103870410559,-51.184311306668384],[46.193217494366046,-51.11761926136067],[45.926228688955646,-51.235760884522705],[45.226891057578705,-51.00742633725492],[44.83291717222983,-51.144981857639046],[44.60091464084951,-51.186957774397065],[43.813135311787136,-51.225262667918706],[43.282015165324715,-51.3364621083425],[43.05528132750742,-51.176012390248545],[42.85824411478687,-51.25243160488693],[42.56067599074849,-51.06271418110913],[42.454942663856194,-50.92638494945708],[42.42252319560859,-50.563068415445414],[42.24963236683861,-50.48076148516227],[42.064686095421,-50.26752189830786],[42.04322888026133,-50.056428630066684],[41.79441373540688,-49.896147154481845],[41.59559408705114,-49.83959475051187],[41.41511862545073,-49.657154748649475],[41.35046911310887,-49.65698769217699],[41.15011236031368,-49.44131454425635],[41.1236336667782,-49.256066228953514],[40.96765349688798,-49.01064758786943],[40.94607138353791,-48.854449110355354],[41.056089895984854,-48.75470998676572],[40.99103226293645,-48.62723343131249],[41.13890206162125,-48.45523666693729],[41.3030432494887,-47.98537487529906],[41.419675961854836,-47.915221565760405],[41.36924138983298,-47.76736160666619],[41.48200327761686,-47.65403039111164],[41.51096024426994,-47.47925141599118],[38.57513098038582,-47.613755764820866],[35.39532164587088,-47.745341723635285],[33.02963784586749,-47.83173748398703],[29.89510558211698,-47.93157473174307],[30.172206850813353,-47.837227800710515],[30.323651318476198,-47.72806344593669],[30.315851688482105,-47.59386400998455],[29.928237987947554,-47.36812582233414],[29.928357319329994,-47.281844498145716],[30.127274575499875,-47.05709402028256],[30.08316887943337,-46.944223640987055],[29.772911487606184,-46.79506198523065],[29.781085381818777,-46.635111052588975],[30.283796129537798,-46.642448032300535],[30.522790753194126,-46.570202741701586],[30.560503846177163,-46.36392076006198],[30.319222005982883,-46.22685517577921],[30.31803408520602,-46.04471157613793],[30.182947474438844,-46.074632152239126],[30.118952773204782,-45.969999563919906],[30.288667634155512,-45.796809552052146],[30.400617135694066,-46.00462993052711],[30.57630510716313,-46.12468349365375],[30.769492960013174,-46.088060110707865],[30.763503871826327,-45.95881058609916],[30.58759278239539,-45.849777673429436],[30.49728144827107,-45.731308961574136],[30.452662787488997,-45.299069488974695],[30.694452159338667,-45.288294226868615],[30.884177839090693,-45.22537909826137],[31.09260347040591,-45.082272641228805],[30.993567621460443,-44.9452336199678],[30.611525359855666,-45.01142470844938],[30.41677615030726,-44.88145687746803],[30.524197555665125,-44.715467433007774],[30.737196617361736,-44.835572192788504],[30.9868581812678,-44.873342805463444],[31.11782422442552,-44.82208023484961],[31.14041708980422,-44.700617209539644],[31.10865601856145,-44.35812562389832],[31.263668369123373,-44.17017470173547],[31.5548897652602,-44.14366616315775],[31.804835043583182,-44.162136576063105],[31.74913940995634,-44.02391676138005],[31.387974334949785,-44.10784923048225],[31.28628533362601,-43.98148464178651],[31.375404026749326,-43.665207954031295],[31.673780797720585,-43.56834375342127],[31.758946567337798,-43.79720245579836],[31.88900715580514,-43.82566417036314],[31.979247728665015,-43.69907087388232],[31.846296549904228,-43.539250625794075],[32.033908508190294,-43.466103035179835],[32.22057876810456,-43.29471962654843],[32.27929908466076,-43.019300087099175],[32.414141474416844,-42.90295163347925],[32.75744234205274,-42.854302367838216],[32.93365523276877,-42.7029047915429],[32.85814710921028,-42.61483941027547],[32.53116054790444,-42.56593861182257],[32.39965413767137,-42.48518786366061],[32.52874867624489,-42.37490290231188],[32.78495255432622,-42.542049716198555],[32.885892541174385,-42.37509739882212],[33.12741694258222,-42.26443407215226],[33.16539379073121,-42.144352689699886],[33.31238793833236,-42.019847644069564],[33.317756380881946,-41.917144111189],[33.03870550448749,-41.9531185516379],[33.03220004122343,-42.121725504212456],[32.85581540998776,-42.08476852990535],[32.683032955957856,-42.141168714110016],[32.36894115303403,-42.1102392991357],[32.28942337306968,-41.982013427967054],[32.33360149673331,-41.7749143790677],[32.51480131642943,-41.67322716890666],[32.61513522942847,-41.70564004074387],[32.80906835039646,-41.592034967542716],[32.94566792349467,-41.628607415693594],[33.09961556704075,-41.49958762396302],[33.416098512699755,-41.64711195267731],[33.48395815408179,-41.42790212157608],[33.38613415024095,-41.28140300702086],[33.601916241586224,-41.25373521961374],[33.78397328217413,-41.13783284564212],[33.792931478599876,-40.943163843592195],[33.34343798181168,-40.894447649964576],[33.229660172506996,-40.8148736547427],[33.2794251437694,-40.613554552634206],[33.44109009866413,-40.51543542398537],[33.26297907930371,-40.35752021416425],[33.06879580330862,-40.457767264896624],[32.66483442748882,-40.34524098659139],[32.52294908808358,-40.21438794431068],[32.510416313764736,-40.11719011341619],[32.62949992474779,-39.84284476263585],[32.91371375283178,-40.11000322758213],[33.21379992784079,-40.18719997947957],[33.28199818147032,-40.09554378762899],[33.08951195948867,-39.97934687333774],[32.85039352979729,-39.91866913744455],[32.706374807682025,-39.79928816455226],[32.99135305077013,-39.639352125530614],[33.14425263655427,-39.472956288527],[32.99878994938308,-39.288377332058445],[32.87021508410606,-39.51221317618984],[32.45266942562949,-39.65278974726682],[32.31843646604245,-39.58010294268577],[32.24394769252949,-39.350896490794135],[32.392288162796675,-39.19831755220897],[32.750130516330444,-38.98439237853806],[32.75992359257443,-38.84664013274805],[32.37083034005361,-38.7743984426283],[32.22599503664792,-38.787260012105385],[32.123012924737054,-38.702195401096745],[32.12774615337138,-38.39503886527487],[32.22370183024281,-38.20919924067035],[32.44428416019176,-38.12312639929837],[32.641366449449336,-37.94379462537195],[32.661753806834135,-37.728364334234726],[32.56267274735416,-37.63720186581623],[32.512558986050884,-37.450851749748],[32.39041385525742,-37.38352511980817],[32.224341468586,-37.44070844298501],[32.26640418858472,-37.74192387115706],[32.08284982053173,-37.90511974244869],[31.884565480949565,-37.87161262368372],[31.783609821772767,-37.788684006827495],[31.834790477875785,-37.564176236215374],[32.031614119470305,-37.31145749031048],[29.530089104121256,-37.39567088628889],[26.834115214817892,-37.48159696708304],[25.77272847394905,-37.508023486561044],[23.754396494801465,-37.54852914091689],[21.134675563728955,-37.59106294839715],[18.221534641938934,-37.65330840414843],[14.938844508169291,-37.70994947256987],[14.973190192831003,-39.073327638286536],[15.039210349022449,-41.46029194166757],[15.090954695099137,-43.26776292788101],[15.3110766766616,-43.39914298465211],[15.535829002264233,-43.673526769679086],[15.948119608194819,-43.80195610973413],[15.958300498573687,-43.89873835155138],[16.138658999991637,-44.094228900393304],[16.12981742183856,-44.16835784709262],[16.34750240095119,-44.277755930611676],[16.452934364447202,-44.413247174041125],[16.37051854504374,-44.53419580385294],[16.63412438448574,-44.77506388525528],[16.466004656392784,-44.9260682615788],[16.507486285743227,-45.18483605192617],[16.38679814027283,-45.26168565804616],[16.499371457087996,-45.504309064390384],[16.721033849602073,-45.66246643859487],[16.977953042322017,-45.656903949080025],[17.119969101035867,-45.75653741462817],[16.945994427507745,-45.94479022141172],[17.295878746272127,-46.09563746981775],[17.22619186077742,-46.22733914200992],[17.415016003408724,-46.289534335143514],[17.462188197644466,-46.44820746404799],[17.429631415860452,-46.65344048510427],[17.348925726434185,-46.71886355520514],[17.525334094201675,-46.8566652233134],[17.804771424618284,-46.96311708848324],[17.783284799407987,-47.09170997370907],[17.972526553080915,-47.41513656402056],[18.213105211538483,-47.31051957512991],[18.284479016936842,-47.654713908139335],[18.142992710084574,-47.76129376128045],[18.36651502856814,-47.85551031397793],[18.38768507393181,-48.019067452225855],[18.30540826762213,-48.16772265822539],[18.14871916219485,-48.25451656577226],[18.369425040808842,-48.56519565562267],[18.21348424943591,-48.83462902695043],[18.249277929216845,-48.98811165854063],[18.027290473743555,-49.32621373889332],[17.890050877357627,-49.36394352501423],[17.932790365780814,-49.53508084167189],[17.802736100754238,-49.728286895585875],[17.492558368881348,-49.90448713939489],[17.527871827095694,-50.11841840330249],[17.313898458053192,-50.1515997762754],[17.168662092746597,-50.3669133025027],[17.371856537395686,-50.50529931632715],[17.31262456375672,-50.576578627740126],[17.435479426365497,-50.826230951793846],[17.07986477695998,-51.04814010416887],[17.04803295732638,-51.32006816270609],[17.27007744975628,-51.46629762517567],[17.4216090849371,-51.49410315795654],[17.433289504682293,-51.63001257127399],[17.335076797081747,-51.85653410553063],[17.520580849091207,-52.175901030015076],[17.335316217115032,-52.36315658967231],[17.36247149626637,-52.536777673755076],[17.250703873429924,-52.664467576626215],[16.946365879243835,-52.83061407780028],[16.849575534911306,-52.95744128308013],[16.712808131528906,-53.229887410981405],[16.53597613829947,-53.37923266027618],[16.15398994441837,-53.55902210209862],[16.119862695571317,-53.6515120285313],[16.311532463824996,-53.75680294002934],[16.375878899593452,-53.87962853880653],[16.49331460174898,-53.90648904440363],[16.70253829026303,-54.09502765283123],[16.860657166854118,-54.481653916024285]]]},\"properties\":{\"name\":\"Louisiana\",\"ns_code\":\"01629543\",\"geoid\":\"22\",\"usps_abbrev\":\"LA\",\"fips_code\":\"22\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[107.02450930933394,10.962213468970118],[107.57852947899858,11.801574062023647],[106.7226389247445,12.369760716237415],[106.38580292615627,11.863649052603156],[106.56947347409032,11.694342116262495],[106.71564066176406,11.678377301226295],[106.97781571304941,11.37570967472036],[107.02450930933394,10.962213468970118]]]},\"properties\":{\"name\":\"District of Columbia\",\"ns_code\":\"01702382\",\"geoid\":\"11\",\"usps_abbrev\":\"DC\",\"fips_code\":\"11\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[118.3067932714886,7.778400879750187],[117.85676156328286,7.671075057818993],[115.86945854147275,6.999974523535072],[115.78208881922629,6.78039113526925],[115.81570007043307,6.643084432114052],[115.56479776441456,6.650970782676073],[115.2035100220737,6.469764396734147],[115.03224053131896,6.219182814020738],[114.21520455624739,6.005390282154803],[114.20483340053009,6.291074745864971],[113.92333528821048,6.2850607463882975],[113.60353019997696,6.217904783329489],[112.71322020741007,5.547140766699677],[112.55480126611815,5.646505444821286],[112.05400179117531,5.8313624280248995],[111.61778313716428,5.894552790988313],[111.26497024178423,6.164431845021574],[110.96392881820609,6.201334789340535],[110.79292178299136,6.508536736465467],[110.41368495631839,6.67352154417733],[110.27534463995715,6.949119177250992],[109.76423944970331,6.9381762491140595],[109.21310937642242,6.880729891049097],[109.00759593996705,6.800503754111768],[108.8144942263293,6.820315367767952],[108.53957556350498,6.968444504341259],[108.34658141031935,6.993806006983769],[108.20592913913912,7.319514231146407],[107.9603678037902,7.439302838248477],[107.7549448201775,7.627205271568506],[107.8116028498248,7.7896504098989094],[107.72065366460163,8.132241548419186],[107.50704304545368,8.212217099466644],[107.37053886973963,7.960236779041609],[107.09319283008956,7.905609080569285],[106.98629573448682,7.722731291221307],[106.61401146600438,7.552027785342525],[106.33544775141952,7.572001970673087],[106.17493884982565,7.776248319513229],[106.0066345139295,8.179100664374609],[105.96155155630765,8.4552122797489],[106.17692253658599,9.142806962729404],[106.18498372412752,9.362784532874336],[106.38098125190368,9.580670824623118],[106.75802681570752,9.775026681914209],[106.70447042222509,9.855101870874512],[106.72432058583891,10.134171508578872],[106.91377004148188,10.335655864502204],[107.10555832374921,10.449941629139829],[107.02450930933394,10.962213468970118],[106.97781571304941,11.37570967472036],[106.71564066176406,11.678377301226295],[106.56947347409032,11.694342116262495],[106.38580292615627,11.863649052603156],[106.20849115641765,12.03577091393761],[105.91959900414291,12.004873330505683],[105.64329033725602,12.064808452571778],[105.55752398012893,12.187190189758573],[105.58649122524825,12.337500818983386],[105.0175638232132,12.519051529224463],[104.76735822375218,12.465739464204814],[104.35269871072487,12.475921282043297],[104.19984253622071,12.667473167947952],[103.9788795583627,12.72589643248786],[103.89334227214505,12.904001535121626],[103.95169115562369,13.16123559173858],[104.08419125701731,13.233427759504695],[104.1586527947449,13.512474715193989],[103.98417499624469,13.629353967900819],[103.65622653354558,13.703633358692551],[103.44924718779949,13.957582019946177],[103.34854898444803,13.903809242564225],[103.04188544235926,13.896497218025093],[102.85195172971495,13.971644326188752],[102.61980388839608,13.906021594134234],[102.5055965226112,13.695780022089021],[102.45804469388976,13.270594470621978],[102.36316340811261,12.996981999255743],[102.29137242727693,12.497391979015086],[100.23537486010756,13.617789195945491],[99.57884463135807,14.002004346926869],[99.05801977191796,14.274032231640845],[99.1110546778112,14.01091338499088],[99.0649471447182,13.870312868715665],[99.1814734743431,13.747693175570177],[99.09372837426612,13.521501156574793],[99.24329563545716,13.508479102653537],[99.18293891446149,13.240601528285222],[98.94636038678883,12.76820103305163],[99.07062222555913,12.693686724257637],[98.92128041798566,12.338960961692365],[99.14875991714321,12.163215693155594],[99.04270900372383,12.036262373883801],[98.92108895121618,11.738341846504971],[98.81806170554457,11.685316556282132],[98.42233595914288,11.056526158799564],[98.5563120841372,10.980772621067437],[98.35012669273871,10.558510554460948],[98.17814196507074,10.644324054757904],[97.97572759166393,10.207439415975971],[97.78748856649054,10.034645003671882],[97.65609764636524,10.162694624441425],[97.58288433787857,9.957640986937298],[97.44633076144467,9.821856307387478],[97.16449576604509,8.888749016995671],[96.37822007120101,9.376825893819303],[96.16245804182759,8.917730671676216],[96.02976748945055,8.24421065054114],[96.0843981566309,7.952638331669311],[95.88505363554145,7.864998314899123],[95.81364810862391,7.416434803801922],[95.64177381229904,6.887897528114855],[95.65603840781847,6.660766536044776],[95.46382074559065,6.357872448893635],[95.3665047916681,6.109971044115402],[95.20447375493806,5.991618792049074],[94.25203597869555,6.154308939408794],[93.91247239561783,6.652463108093504],[93.80753809203428,6.757675551006282],[93.14679373698812,6.93800987528841],[93.09771411831886,6.776399592616325],[93.07771723316002,6.338644139586805],[93.0164259658284,6.065793423767053],[93.1228344719632,5.770985227763005],[93.049652717933,5.643933706031471],[92.9290262123249,5.169314120673114],[92.62182396960813,4.762025537572494],[92.78047672154786,4.527136266021934],[92.48806924181659,4.219184814758034],[92.2794876295372,3.8923566955749966],[92.17102028284279,3.482899500750651],[92.22536696603038,3.254170168887328],[92.1075054122022,3.046815420359259],[92.02583427659216,2.6396705048767277],[91.91330570240768,2.3521712248644686],[91.63551556913507,1.9738872705087],[91.31816241750565,1.4710359816021426],[91.20997241669842,1.388496928566473],[91.03696149862323,0.9274684819353612],[90.86302577366178,0.549832622475238],[90.81936562423866,0.32406661816427645],[90.86588177783646,0.20239854299867527],[90.69102740015576,-0.037713989689612766],[90.9026655870174,-0.3352232621154611],[91.18118732208349,-0.42292541482266144],[91.01018557122244,-0.6872003359789802],[90.66288801371131,-0.9728205749387263],[90.77341381978039,-1.0865021501949967],[90.88787594493938,-1.3445321475500915],[90.26083617076304,-1.8747511180689256],[90.01587980751174,-2.111694851003894],[89.8950870850492,-2.0431345828675482],[89.87895389657382,-1.8803410521982176],[89.74913042826057,-1.727136942716769],[89.53374861068022,-1.8216506989808705],[89.07401827773097,-2.2717914442330533],[88.77736378684925,-2.5207245334328183],[88.4435042313688,-2.736968776787344],[88.33980793412992,-2.5917208742452345],[88.00787698519008,-2.418691707674528],[87.88376297055528,-2.541718437606635],[87.803074099317,-2.75565373068083],[88.03270763593724,-2.9885880773082483],[87.79756746953558,-3.2457542367928363],[87.5284866058359,-3.447539093962545],[87.31978638193326,-3.434244805486392],[86.69472696891604,-3.684289120770852],[86.36542666184498,-3.86506391958456],[86.07409784481197,-4.114410222775852],[85.47475673503547,-3.739062436408274],[85.198863118523,-3.5027713580828648],[85.04398452322059,-3.681519575777571],[84.96730465743964,-4.0075044313646195],[84.85250765118431,-4.019014795016701],[84.23994639207109,-4.602745423639396],[83.59141652004418,-4.738501356514239],[83.20803399304192,-4.533451321050716],[83.14754013738052,-4.356304508459577],[82.88451887809933,-4.243201755981756],[82.68679391465419,-4.309947195317347],[82.52383713520493,-4.273363444020781],[82.36722654421125,-4.006424744900486],[82.2281913885714,-3.9836101404907174],[82.02970052420724,-3.8155342912020873],[81.96521784604107,-3.6653589258340404],[81.97443156428163,-3.4339744294449983],[81.84940980071029,-3.265629321886453],[81.56950834207984,-3.15666437463107],[81.52891763447363,-3.0478717123518395],[81.80460992959269,-2.745920527857291],[81.6223371843472,-2.587927429285713],[80.7816945854411,-3.6076724342930095],[79.94509049301482,-4.6007822120667345],[79.7558280755132,-4.843389288419118],[79.30499090834273,-5.039648788852507],[78.7319608909541,-5.429389631035415],[78.31853561518327,-5.852009975600172],[77.89809284222356,-6.144913834565614],[77.883418680728,-6.497131813362149],[77.82324325606345,-6.726721255990658],[77.63420576456225,-7.005578398345154],[77.37999883964847,-7.0554310901879544],[77.38031982661391,-7.143730352815437],[77.18371868820515,-7.316707422239279],[77.29476761601256,-7.62218706946402],[77.2141028168155,-7.918575341000643],[76.74018704831747,-8.208409981691986],[76.16350725523861,-8.31245785922691],[76.0346115651762,-8.49751409811735],[76.05977310356245,-8.620840430461854],[75.92548730036826,-8.910013497625178],[75.9233191069052,-9.148304761430403],[75.59458640663296,-9.214712713517946],[74.77149942712623,-9.60600834392468],[74.4024765151628,-9.879927180112738],[73.98467257998134,-9.924132478057059],[73.76736951422563,-9.991753404861177],[73.58568582077532,-10.148619607842486],[73.33736989712013,-10.260780987662114],[73.02334235874751,-10.544509541001828],[74.01365918116866,-10.441966429495706],[75.2700694672899,-10.270181350235596],[75.35966547147387,-10.28904611852714],[77.4574389103767,-10.009527111662164],[78.94780690658617,-9.801192376388617],[81.2580462916417,-9.458633999196023],[82.81238206534643,-9.244105793510057],[82.85350448747016,-9.079908288255261],[84.40452888412263,-8.879944664676708],[84.25923764886333,-9.07272836103646],[84.69417097697749,-9.016715872885204],[85.14972146071612,-8.993892905179065],[85.97107549461133,-8.9098053559797],[86.46370052650293,-8.831530805045887],[88.03633737522944,-8.666843901529807],[88.98433986405772,-8.55123222421043],[89.59676369120042,-8.430713614571244],[92.05795966983088,-8.163121246954093],[93.07544938449347,-8.003906209399869],[94.34049032550311,-7.789078313311727],[96.15390941767419,-7.49171742734163],[97.90725979563915,-7.184047487190085],[98.91049332540015,-6.999285202839742],[102.03220849372849,-6.439587074293968],[103.48674873875919,-6.142903433833908],[104.08416045667515,-6.035991800944563],[106.27229445365657,-5.604034942250461],[108.37437941961872,-5.1992883374297065],[110.89817971942948,-4.699539299186722],[110.90620142859268,-4.6376306239069915],[111.5383541465546,-4.520289151029984],[114.37096307218879,-3.9339119830992018],[117.12108169757701,-3.346380359213608],[116.88452963506711,-2.851218740888135],[116.40026891774137,-2.078427094631183],[115.82118807210932,-0.7877393623412244],[115.84786004236004,0.08762989253020201],[116.12466780503196,0.5568651268613205],[116.31800284729766,0.7974669529557522],[116.4524792246928,1.1598182584124188],[116.48434059712677,1.4212873750070152],[116.42388263774896,1.6203250333932682],[116.47028892937581,1.8513209139187012],[116.75909286242991,2.404412768211719],[116.71735741369577,2.6206502938536507],[116.85824096635464,3.013251942369583],[116.83735264452206,3.329352573422831],[117.05819370898787,4.081784227924687],[116.91614631577552,4.380509033653408],[116.95655004309509,4.869937704798775],[117.10292182454671,5.634898013679003],[117.26519984923561,6.034986968821054],[117.55631632646725,6.0161590785589345],[117.70523555835028,6.088774455480712],[117.88643698026789,6.352438328213558],[118.06199361496972,7.04794170277495],[118.3067932714886,7.778400879750187]]]},\"properties\":{\"name\":\"Virginia\",\"ns_code\":\"01779803\",\"geoid\":\"51\",\"usps_abbrev\":\"VA\",\"fips_code\":\"51\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-36.43045257757133,-14.240447606823222],[-36.65416668109671,-14.224725853685417],[-36.91456613305394,-17.678785956695073],[-37.119183063057974,-20.224301378073704],[-37.24548351756329,-21.769812299292997],[-37.39917350316916,-23.65205673675902],[-37.59656998612709,-26.060917930965182],[-37.83021059438795,-28.779162187074043],[-38.01262163867694,-30.881984496177594],[-38.17054164269268,-32.32517493592396],[-38.31871434071027,-33.71004562798746],[-38.65224228164232,-36.67179220592704],[-38.785012211063425,-38.067669528922025],[-38.91890577599681,-39.48755482895051],[-39.06499942679885,-40.97873789125463],[-39.183416535501486,-42.18978778747151],[-41.05067528277907,-42.085377021019234],[-43.28841465475426,-41.953560297268346],[-45.380024749776325,-41.82453533742719],[-47.59593062478979,-41.679483766598096],[-50.7976674670213,-41.457111895520065],[-53.367431728254395,-41.26867749734346],[-55.17545508789623,-41.125723278127246],[-57.53493890768654,-40.93432655062959],[-60.486531990786126,-40.7002276832302],[-60.628605077616854,-40.79825282043941],[-60.560079919540605,-41.04568132475225],[-60.731545970681054,-41.25462960466579],[-60.6983005555485,-41.41241564196411],[-60.391074635530565,-41.73932804647357],[-60.21299139256785,-41.77086392694232],[-60.11877282891669,-41.90839834598665],[-59.91962872779291,-42.108150913884806],[-59.67261750099027,-42.04548349896354],[-59.27560477380557,-42.25072559688692],[-59.11394035939658,-42.4521959021443],[-59.05010514517801,-42.63044510995069],[-58.91599839928484,-42.824283715571895],[-58.80145155757729,-43.19196470704422],[-58.60718917596187,-43.33059986726018],[-58.49862310426012,-43.63389600132568],[-58.39582730153575,-43.741653922907915],[-58.235861746594196,-43.80262616101478],[-58.01551843536743,-43.96497556182682],[-57.883361207806495,-43.98583840498384],[-57.692740997034655,-44.13189204805771],[-57.28385895133181,-44.1877460844211],[-57.077105712543684,-44.343232353824526],[-56.98302676427084,-44.35645107310257],[-56.88912589395674,-44.62909119124586],[-56.75051728439277,-44.63960806344209],[-56.684820349118674,-44.754173234673836],[-56.530611622548136,-44.7776282691324],[-56.141273763636484,-45.24715928858406],[-56.043584313869694,-45.42528132464498],[-55.84857516402006,-45.46093405777244],[-55.67020907731852,-45.616247134184086],[-55.324170462246315,-45.74174664907451],[-55.298707840105244,-45.82529597368043],[-55.08949205763127,-45.9117484750017],[-54.97997595459568,-46.06096337434372],[-54.96072389987036,-46.24810683346395],[-54.86002074487828,-46.40411225600299],[-54.71466934805146,-46.438876190021766],[-54.534435844013004,-46.618898054732675],[-54.264361190969,-46.76636781413029],[-54.22117711466183,-46.84292648418048],[-53.97430213504034,-46.96490109038332],[-53.92189823927155,-47.13985772683705],[-53.641931387310045,-47.221693330868575],[-53.12997388514514,-47.45724996760292],[-52.92794642817866,-47.436944548086124],[-52.356901658216096,-47.77884878279583],[-52.064714605202894,-48.069761887660185],[-51.85103544662412,-48.117468770936966],[-51.73176768057509,-48.092463640302114],[-51.615489891259955,-48.21222292821927],[-51.56158107024156,-48.46826702368632],[-51.33836184256619,-48.5311317317143],[-51.14645007207857,-48.67984050107292],[-51.043278785314655,-49.041416445679694],[-51.012600658477204,-49.364359312570905],[-50.93689837736119,-49.43036756120161],[-51.024023969786015,-49.53166072221147],[-50.74597905962235,-49.67848517628454],[-50.77082123583404,-49.812495318315676],[-50.48441449175136,-49.982781482527436],[-50.497394075206316,-50.093470637024915],[-50.230983447329685,-50.28755704255872],[-50.10832361918673,-50.56193988223815],[-50.203657108211274,-50.908038975150006],[-50.16409078647587,-50.99989733548304],[-50.31645095108647,-51.135013901234366],[-50.31344174311925,-51.253774342098],[-50.21476094625068,-51.49174448445844],[-50.211295692068475,-51.74264492971583],[-49.982052582242645,-51.95190920663388],[-49.96465352422183,-52.03760028094824],[-49.69925876010882,-52.35722723042778],[-49.65992138426516,-52.49471746902375],[-49.547903050710985,-52.54890160606767],[-49.55142734108448,-52.80663924604674],[-49.442630907059645,-52.87875604944297],[-49.392517876638784,-52.9995354724816],[-49.17228669108151,-53.054541522003255],[-49.11920810173434,-53.1401157422997],[-48.72389454343701,-53.32191038197654],[-48.70875863888986,-53.389461146712556],[-48.38102448100134,-53.560549885561294],[-48.12250873846204,-53.55723754174872],[-47.91506557954622,-53.61316570900587],[-47.60687678664225,-53.764839449742766],[-47.63884890117427,-53.897202341154795],[-47.465508080350496,-54.00668059865191],[-47.4068610585228,-54.13450610691889],[-47.24943570365673,-54.21915150697572],[-47.03970899066944,-54.24799092625822],[-46.903769323166145,-54.36907202221405],[-46.53971479511181,-54.52858188094093],[-46.25065225816966,-54.6090562835707],[-45.96425329717322,-54.62992979462354],[-45.586237591619955,-54.71081815382761],[-45.09982557830361,-54.79135101017075],[-44.95701993172958,-54.98481580439277],[-44.74493360467757,-55.15489068941814],[-44.65076337997382,-55.14459883475439],[-44.23021796310958,-55.26942628564728],[-44.00175066790639,-55.3070121024209],[-43.75477398064208,-55.30364313667931],[-43.56605692067789,-55.386900120014175],[-43.140809436754004,-55.6946677090676],[-42.771902991297374,-55.887699686377026],[-42.47346057872314,-55.90653087066791],[-42.032246173102926,-56.023965146211374],[-41.94511238875259,-56.088496983920216],[-41.651263097064565,-56.073875419464],[-41.34856093923259,-56.14889504548519],[-41.107706759375006,-56.0547207225765],[-40.9698797118997,-55.94357166921712],[-40.96622307105088,-55.80887607039149],[-40.65719622478635,-55.66061448983774],[-40.53260166183712,-55.66493365313059],[-40.39792164281943,-55.57368563512137],[-40.14486633719501,-55.33519952991228],[-39.9760646994234,-55.37585074340834],[-39.7598051077504,-55.329468677123536],[-39.43401524457774,-55.21226499008347],[-39.44745831767723,-55.14304476140679],[-39.65598954052229,-55.047057718736994],[-39.52678129835877,-54.924046443711624],[-39.54837572140662,-54.85821432064348],[-39.430222641554494,-54.67487934545038],[-39.22760759083148,-54.67136713278361],[-39.08537871876717,-54.514723763590204],[-39.109784313478016,-54.366144238549296],[-38.92173717217269,-54.11654203404293],[-38.91297961530434,-53.99489326557843],[-38.7179992792156,-53.91693289600905],[-38.63177204850429,-53.70481028209713],[-38.45379140817187,-53.69751541399656],[-38.396668625874135,-53.51330518816206],[-38.12588787900349,-53.376385027026494],[-38.14752029511003,-53.296069673160545],[-37.99292204906135,-53.10757677776698],[-37.80222199215052,-53.146425748047186],[-37.335640109923595,-53.06456978375467],[-37.19303413632815,-53.12560823062165],[-36.966310221972726,-53.040763995870506],[-36.96961378141359,-52.96429308322684],[-36.71619539925852,-53.0103983518395],[-36.474947817271456,-53.01627482263486],[-36.297300651537334,-53.074098029286915],[-35.8593091743314,-52.643181853243284],[-35.59874113867558,-52.61758816156744],[-35.37264998630426,-52.73133134179629],[-35.134123765894515,-52.796563326157624],[-34.92714678778861,-52.76821129481724],[-34.76311247680832,-52.92209077758011],[-34.50720263254393,-53.02529920281612],[-34.30659220353553,-53.024338674318635],[-34.09257560435469,-53.07294353938904],[-33.66287956689007,-52.96305311321688],[-33.51065223948199,-53.05353483311276],[-33.01918215787093,-53.07854495384272],[-32.853059381195706,-53.02675295542544],[-32.78363845954687,-53.089129704217],[-32.486793263747344,-53.160509958054696],[-32.44683272906489,-53.118864322325784],[-32.02153607919557,-53.24803830303038],[-31.714923750145648,-53.2235851320837],[-31.622720505286768,-53.29742000168309],[-31.365572049830597,-53.222930656146666],[-31.187874077056804,-53.25075882089766],[-31.114996271967946,-53.12628313028295],[-30.913250002576337,-53.137856928093],[-30.95399818408758,-53.290161156081766],[-30.71257182719266,-53.281054416100254],[-30.427998328712846,-53.195971929795846],[-30.40054282356302,-53.356584598241824],[-30.196473280547966,-53.381317013213156],[-30.102006832529547,-53.51026312476579],[-29.966091240177583,-53.57937527654981],[-29.910639373545454,-53.78936389691177],[-29.811793387465585,-53.75223054168542],[-29.60438933663708,-53.77244419194372],[-29.52440274970297,-53.85252382432415],[-29.606771810613946,-53.95645691911072],[-29.618392227463854,-54.08305420005241],[-29.40709449229023,-54.06614092139602],[-29.41363727415469,-53.963598360574856],[-29.294504142054056,-53.902591954346626],[-29.189061401077016,-54.181181737942524],[-29.325000946933628,-54.2947598048604],[-29.169180112422698,-54.349462052445354],[-28.79235040751544,-54.40429544580283],[-28.670833648952403,-54.56402141117131],[-28.272335504149023,-54.607894143920916],[-28.10897583179292,-54.65740428093805],[-27.98277011187157,-54.87781238667992],[-27.795647187259572,-55.05323947792493],[-27.471413772807825,-55.132399975648674],[-27.41774829708328,-55.190227438738674],[-27.08919196305096,-55.306056088974266],[-27.044896182705603,-55.416869329135835],[-26.57110664192068,-55.560231834798074],[-26.381789057048078,-55.81888072979654],[-26.44789197916311,-55.86711887538201],[-26.222817803102195,-56.010395092393146],[-25.846413407619224,-56.17342061693508],[-25.786048366373635,-56.28048934844292],[-25.77718432649865,-56.44934647836222],[-25.716081806966628,-56.57402490329993],[-25.722969064110245,-56.74941742685186],[-25.643498976645432,-56.956749426306516],[-25.462482048045374,-56.96397795551231],[-25.324769149883096,-57.227372522224364],[-25.138906086458697,-57.271680079788325],[-25.098868941472343,-57.44250083045115],[-24.92377697021662,-57.600787339784496],[-24.95824829138327,-57.78252570210978],[-24.904849731281455,-57.90038005506035],[-24.671983918791767,-57.98509334272168],[-24.591645263368502,-58.10417464075011],[-24.29183669962513,-58.205153091350454],[-24.383855182166776,-58.327370994147316],[-24.243098407197824,-58.47242041985603],[-23.919820469965774,-58.53140145237123],[-24.135367453597052,-58.620007938881976],[-23.97893802073235,-58.7179997724983],[-23.95164755597807,-58.79028946595417],[-24.04343190519548,-58.89419099477537],[-23.840736785159848,-59.102681848885304],[-23.68926835649378,-59.21164685518388],[-23.735327937240402,-59.32918345785736],[-23.519607313760698,-59.493683880169016],[-23.30612983997891,-59.517006885410034],[-23.24159209127263,-59.66735397823425],[-22.4721291698901,-59.85317405974845],[-22.27800864145192,-60.078360771015824],[-22.06792416967971,-60.1476590137674],[-22.00151784625983,-60.3186834422505],[-21.905676696768708,-60.410534608369304],[-21.543160531293793,-60.4597901940394],[-21.59359643857824,-60.55299955331471],[-21.470953875775617,-60.68142165534095],[-21.324668523543203,-60.749645305582256],[-21.40075566086806,-60.831151854547464],[-21.262392629834704,-60.92297277409391],[-21.220940304354375,-61.110854248432204],[-21.078449316126942,-61.12326081562001],[-21.04294937821407,-61.206050068819124],[-20.852364647911937,-61.19098376901057],[-20.78888848372251,-61.30241317862456],[-20.59577627770044,-61.33898369147564],[-20.302254961110762,-61.56861924048734],[-20.198077059514418,-61.60843328944713],[-19.970553996281637,-61.59566524067656],[-19.959123128953046,-61.676518116114856],[-19.78925088186257,-61.71105455741937],[-19.560590556148,-61.663844160953055],[-19.402573894662826,-61.74547945585151],[-19.17909231541283,-61.791778864093125],[-18.994262750039326,-61.93404588855055],[-19.06864680307727,-62.000909254053745],[-19.12175447098137,-62.151498888876404],[-18.920776055578994,-62.14980694606917],[-18.81468251671963,-62.22278662876755],[-18.922514352757645,-62.31057459890803],[-18.885781556133672,-62.43673697070662],[-19.010796293910836,-62.6751728453172],[-19.230953840907688,-62.74567335922344],[-18.960489372450983,-62.79298470578916],[-18.977905229501633,-62.891681388299745],[-18.783852921305062,-62.91244377963854],[-18.6337920839556,-62.97322582103175],[-18.652724818933653,-63.07114271540272],[-18.555703163347008,-63.20460759848665],[-18.648189604297116,-63.28841958012525],[-18.596395209133597,-63.37294591198261],[-18.67393158482712,-63.43313166597344],[-18.6326211654143,-63.52077510655399],[-18.74142288556556,-63.55617256217367],[-18.721288509525447,-63.69427900855557],[-18.526941985286147,-63.715640317430726],[-18.29398750019937,-63.855374145408014],[-18.37143021256612,-63.94652625833289],[-18.050866898308374,-64.01825791045339],[-17.955788227408043,-64.06910897459719],[-18.007704681100567,-64.15075897648438],[-17.795287583281215,-64.19963863409609],[-17.630843544811594,-64.27061188509688],[-17.596526680978148,-64.35577118583397],[-17.476549383992992,-64.44149870843293],[-17.471276917234242,-64.57097092691684],[-17.276839417180025,-64.63790862647011],[-17.290887018336655,-64.73111554846864],[-17.237699982866232,-64.84692812452327],[-17.10373418094907,-64.95486687403663],[-17.050967067215836,-65.20773856350347],[-16.80688553448148,-65.24252649629307],[-16.66146669021632,-65.31891924907315],[-16.585136272541988,-65.40265331760914],[-16.717201087027103,-65.53638736883636],[-16.538386684217603,-65.62548878771712],[-16.245216591306907,-65.57985439191697],[-16.080000761837468,-65.64479008087403],[-15.84318299054087,-65.62153849614735],[-15.730585852044765,-65.69351215073979],[-15.38303554986089,-65.71467835653212],[-15.348331748410422,-65.76670816222831],[-15.12807196188412,-65.73310655920052],[-14.998522398297943,-65.75519857518435],[-14.860018222300836,-65.72181557224438],[-14.719063637517925,-65.7550490681124],[-14.589752763981465,-65.85650952809152],[-14.411314974205151,-65.84629915806532],[-14.306655354534538,-65.93681146787749],[-13.970961162938929,-66.05311454523446],[-13.883491273521335,-66.12499470430404],[-13.45683904313034,-66.06595016907569],[-13.339143807875082,-66.07549210818566],[-12.850814740916269,-66.21225116232938],[-12.582817061847171,-66.16651211637847],[-12.455881402860545,-66.24459760856338],[-12.104461052483925,-66.36486394571236],[-11.972806638460991,-66.33522419120251],[-11.756866296141915,-66.35865845602774],[-11.711441763798344,-66.4694173844846],[-11.452956215823058,-66.53174979525804],[-11.228110248450339,-66.61555887550281],[-11.049495163534369,-66.60439888153067],[-10.905932535812653,-66.66522086579114],[-10.727668545221565,-66.6268026950105],[-10.509684829296319,-66.65353828854434],[-10.292222675351892,-66.63794389334508],[-9.87300952770901,-66.7115236603331],[-9.768979124909109,-66.66115308237588],[-9.539357791642388,-66.64821540116658],[-9.404127743005947,-66.68793676547143],[-8.837788125693587,-66.65514073738366],[-8.651102164368158,-66.69343749683283],[-8.356997988080778,-66.67455871650043],[-8.269912329166324,-66.7487182831448],[-7.858879077673099,-66.7815723043386],[-7.476197839397394,-66.76637494728215],[-7.192985275174612,-66.87836672880051],[-6.938151290596029,-67.02302985435006],[-6.606183770908571,-67.07338463502795],[-6.544248305772442,-67.16817617929983],[-6.385216975894405,-67.14225387173512],[-6.220148058052443,-67.16369994183309],[-6.050884916353971,-67.27344673069977],[-5.592661164641258,-67.2996914513733],[-5.4951919188909635,-67.19405090170673],[-5.592312669129176,-67.11911780020472],[-5.436370696576375,-67.06685591724202],[-5.279182972230066,-67.08849560807288],[-4.9566453163889745,-66.9952239479772],[-4.764822396625515,-67.00702257986545],[-4.523619717631566,-66.96617271137045],[-3.753605381349953,-66.94109352533526],[-3.791915293518343,-66.76089216284454],[-3.738057446993473,-66.65848578401766],[-3.8540157354009827,-66.57609475202605],[-3.862964995966607,-66.4744369023861],[-3.956945567011979,-66.16214718946165],[-4.142133794009065,-65.80125798795267],[-4.494704170062871,-65.20828179745621],[-4.72872359683358,-64.95307136883655],[-4.918171351427403,-64.6456778854319],[-5.0247616014831715,-64.42818788297768],[-5.114958955364765,-64.1364805542689],[-5.153492410013288,-63.8847267585146],[-5.121961506578552,-63.57179424784041],[-5.046738772232266,-63.299208398535285],[-4.959849555601238,-63.126417851044856],[-4.65135322985969,-62.66931450251192],[-4.320533789252213,-62.317194004154075],[-3.8347627738774213,-61.85368557075757],[-3.3498208077112017,-61.486533722873986],[-3.1674781629627518,-61.375183980188545],[-2.927878351910208,-61.30682348267275],[-2.765722862274162,-61.062695354768614],[-2.2263527533677183,-60.73820837360813],[-2.0279948867187985,-60.63337805157614],[-1.4044886656040119,-60.358227736355374],[-0.5954045465810478,-60.043452415099985],[-0.03959404800034868,-59.861245496714],[0.9442556627360507,-59.61414849459644],[1.0869740638830794,-59.53458690506933],[1.1562189576246682,-59.428298427531246],[1.2892136104572702,-59.35432323713474],[1.5979836396829858,-59.28196775398488],[1.6838891576160382,-59.19133471474822],[2.1622401940124916,-59.03057477572506],[2.8158192944523766,-58.85494465292852],[4.091168792892375,-58.548683155054995],[4.874802214945355,-58.34063056002168],[6.239789327924319,-57.91746510804551],[6.478095768145959,-57.85971650898401],[6.938700042425697,-57.70535756043063],[7.3550551792548085,-57.657322185462746],[7.550499717966691,-57.59336459353309],[7.636801192809975,-57.499814543885776],[8.13991770332969,-57.29988338670837],[8.429655468997439,-57.14868048902558],[8.683388119050951,-56.983007646502436],[8.894270064776409,-56.88727733160195],[9.009647656207623,-56.68293868328645],[9.544848603647628,-56.53794313072147],[10.089495719385482,-56.31837796421796],[10.952329846796577,-56.02519269161623],[11.317221345160936,-55.8195264132587],[11.610475652800458,-55.823159664864384],[11.756664763818712,-55.74545521676766],[11.867491490180146,-55.57154358469973],[11.699193528201965,-55.41260184241921],[11.853819593940802,-55.35120443475241],[12.629200995280865,-55.122146960731236],[13.42522515367665,-54.918760993276564],[14.93370069516882,-54.50977794896729],[15.497376524300984,-54.3809730507564],[16.287724819195688,-54.37114235346836],[16.480650857380112,-54.38350893630903],[16.647803989359,-54.46919941216256],[16.860657166854118,-54.481653916024285],[16.70253829026303,-54.09502765283123],[16.49331460174898,-53.90648904440363],[16.375878899593452,-53.87962853880653],[16.311532463824996,-53.75680294002934],[16.119862695571317,-53.6515120285313],[16.15398994441837,-53.55902210209862],[16.53597613829947,-53.37923266027618],[16.712808131528906,-53.229887410981405],[16.849575534911306,-52.95744128308013],[16.946365879243835,-52.83061407780028],[17.250703873429924,-52.664467576626215],[17.36247149626637,-52.536777673755076],[17.335316217115032,-52.36315658967231],[17.520580849091207,-52.175901030015076],[17.335076797081747,-51.85653410553063],[17.433289504682293,-51.63001257127399],[17.4216090849371,-51.49410315795654],[17.27007744975628,-51.46629762517567],[17.04803295732638,-51.32006816270609],[17.07986477695998,-51.04814010416887],[17.435479426365497,-50.826230951793846],[17.31262456375672,-50.576578627740126],[17.371856537395686,-50.50529931632715],[17.168662092746597,-50.3669133025027],[17.313898458053192,-50.1515997762754],[17.527871827095694,-50.11841840330249],[17.492558368881348,-49.90448713939489],[17.802736100754238,-49.728286895585875],[17.932790365780814,-49.53508084167189],[17.890050877357627,-49.36394352501423],[18.027290473743555,-49.32621373889332],[18.249277929216845,-48.98811165854063],[18.21348424943591,-48.83462902695043],[18.369425040808842,-48.56519565562267],[18.14871916219485,-48.25451656577226],[18.30540826762213,-48.16772265822539],[18.38768507393181,-48.019067452225855],[18.36651502856814,-47.85551031397793],[18.142992710084574,-47.76129376128045],[18.284479016936842,-47.654713908139335],[18.213105211538483,-47.31051957512991],[17.972526553080915,-47.41513656402056],[17.783284799407987,-47.09170997370907],[17.804771424618284,-46.96311708848324],[17.525334094201675,-46.8566652233134],[17.348925726434185,-46.71886355520514],[17.429631415860452,-46.65344048510427],[17.462188197644466,-46.44820746404799],[17.415016003408724,-46.289534335143514],[17.22619186077742,-46.22733914200992],[17.295878746272127,-46.09563746981775],[16.945994427507745,-45.94479022141172],[17.119969101035867,-45.75653741462817],[16.977953042322017,-45.656903949080025],[16.721033849602073,-45.66246643859487],[16.499371457087996,-45.504309064390384],[16.38679814027283,-45.26168565804616],[16.507486285743227,-45.18483605192617],[16.466004656392784,-44.9260682615788],[16.63412438448574,-44.77506388525528],[16.37051854504374,-44.53419580385294],[16.452934364447202,-44.413247174041125],[16.34750240095119,-44.277755930611676],[16.12981742183856,-44.16835784709262],[16.138658999991637,-44.094228900393304],[15.958300498573687,-43.89873835155138],[15.948119608194819,-43.80195610973413],[15.535829002264233,-43.673526769679086],[15.3110766766616,-43.39914298465211],[15.090954695099137,-43.26776292788101],[15.039210349022449,-41.46029194166757],[14.973190192831003,-39.073327638286536],[14.938844508169291,-37.70994947256987],[14.907324639973321,-36.43508032501178],[14.857164167706863,-34.62120304262815],[14.677297365436807,-34.60203397363033],[14.624163250942505,-34.48624522177896],[14.241670868305176,-34.55487560428714],[14.15081472709045,-34.415830488832846],[14.020002408710457,-34.38427806374433],[13.91004251918931,-34.55212470935728],[13.77853041803739,-34.634348772460854],[13.700538736215915,-34.494892082941746],[13.391065686219665,-34.47153566152299],[13.286489126546305,-34.649429159486786],[13.031212193208116,-34.60016236719403],[13.027691958054028,-34.69847325250416],[12.815205790911714,-34.68531639130821],[12.570809975542652,-34.42605222609139],[12.434106754820833,-34.424013469734014],[12.434057490177294,-34.27219432958021],[12.236049440867859,-34.15307230330095],[12.121386236041337,-34.25676356941846],[11.776249130963231,-34.22791810803691],[11.59980513038351,-33.99813994952377],[11.400318033364544,-33.90056085151786],[11.329400426360658,-34.01046377761611],[11.05858617991393,-33.890747592049436],[10.751297610731065,-33.84660469568585],[10.5341097108586,-33.64071223913361],[10.210019981927386,-33.56215418312257],[10.017143596745036,-33.55774346295809],[9.860306475818073,-33.39673195015044],[9.71534539400772,-33.34222629509246],[9.64307494859161,-33.16047128555067],[9.531949474318715,-33.1793329104714],[9.398979215387461,-32.99245602339075],[9.199480438356684,-32.85259246134763],[8.899326773285507,-32.83353160991025],[8.259017363358353,-32.398917523169615],[7.907776138023254,-32.24399864167019],[7.708430028574624,-32.406839984021545],[7.693599542453075,-32.66193762675159],[7.385557346210354,-32.779207652084665],[7.272498827883708,-32.71444357318791],[7.051966576710337,-32.83006087682136],[6.584354732022144,-32.83437680238695],[6.048508236219153,-32.748316195129554],[5.945829546130719,-32.69697858055599],[5.935355311916452,-32.487531840220356],[5.672056897637451,-32.379152427637486],[5.498017600880411,-32.58911964868686],[5.273747243463999,-32.582568379505574],[5.1367379461497205,-32.70712050176881],[4.876150270540764,-32.659634219125756],[4.738534906566522,-32.68667711591288],[4.769640159049032,-32.87599157666314],[4.659249434296152,-32.96960733613196],[4.391557036932706,-32.881953098179224],[4.305542376503763,-33.03300988938466],[3.7501622279182607,-32.731517562969124],[3.6253569500896465,-32.885638134837706],[3.3177096231906726,-32.93571438544293],[3.020783254827657,-33.02355455635802],[2.810329030673643,-32.96202545752462],[2.4303044744418973,-33.01928498016705],[2.3356506694686234,-33.361684880998915],[2.249159591901649,-33.4845674401022],[2.009474019352672,-33.55739563558723],[1.7550523321403544,-33.43622452129111],[1.59875623172296,-33.41600030200179],[1.5056097585115893,-33.574690785458465],[1.429789461981203,-33.85868808739268],[1.1902759725389804,-33.9016475876117],[1.1234684336562055,-33.73092388075785],[0.7757192376770065,-33.36646187153344],[0.35895381106905416,-33.39987515289564],[0.1745741651607181,-33.09650773795267],[-0.06384497287510292,-33.11918836508316],[-0.3569346213473919,-32.98323949117065],[-0.20366898019325666,-32.78126050490256],[-0.17231507967107795,-32.6580103259396],[-0.3953400204388487,-32.65735353642497],[-0.6491503811879846,-32.55383964928553],[-0.7538164963221266,-32.92990707384907],[-0.8902277929663149,-33.041366514826294],[-1.1720956287732474,-33.08121549638584],[-1.3617162999320243,-32.811678219733444],[-1.5840474241273395,-32.77413963098619],[-1.6409083230863606,-32.90619856354789],[-1.7869927312039589,-32.90355792471418],[-1.95629459172393,-32.640800516631884],[-2.008794969795857,-32.33238301841312],[-2.119637487336145,-32.25894068628824],[-2.4122668621236203,-32.39896022173239],[-2.5435764679755404,-32.35552188731753],[-2.47385625603828,-32.66265649550315],[-2.698957461385117,-32.93679481028151],[-2.912385165427866,-32.99436471822645],[-2.8593600380085684,-33.11072899140353],[-3.121178358633581,-33.18690410728563],[-3.080551992781948,-33.424279627612584],[-3.1450282511292724,-33.636402207947704],[-3.3105785990385934,-33.70761776061593],[-3.4913214315204217,-33.663663229542976],[-3.715370333308204,-33.441641584098925],[-3.7865074784643467,-33.18792957057369],[-3.71907198940169,-33.01837436837566],[-3.554431488973217,-32.96461693228219],[-3.658612860264203,-32.599157383058035],[-3.8167229484305376,-32.50539357400345],[-4.008557108609522,-32.58015156237379],[-4.074537344595367,-32.81840084236963],[-4.41716966858048,-32.79135343105848],[-4.571204276231128,-32.836018723495336],[-4.779145523756367,-33.08250288203316],[-5.0856651300784055,-33.07818146739987],[-5.2970980158048855,-32.92812848336546],[-5.229453946697423,-32.772804477433745],[-5.26204136609087,-32.57317743015289],[-5.510277296761762,-32.46294231274821],[-5.8497825044987435,-32.59431443862732],[-6.031035659946524,-32.55045816070661],[-6.076106665233269,-32.238318703057665],[-6.139786691895077,-32.159273088191604],[-6.444562217904455,-32.01643223615143],[-6.598822067522163,-32.03629882230464],[-6.86680083347235,-32.33942295619654],[-7.048144309269941,-32.352005879622546],[-7.308141863314493,-32.69681573773403],[-7.472531887602555,-32.805848327170416],[-7.731629382419367,-32.84906952784877],[-8.353483089748424,-32.54938467254104],[-8.1911235305138,-32.45262049116131],[-8.1832078718515,-32.18517786918581],[-8.119051741530104,-31.989016818659696],[-8.262829710701908,-31.897797921405754],[-8.672169932202765,-31.963542672806287],[-8.924893916080329,-31.90131086323824],[-9.033263142187181,-31.738536878652273],[-9.012441832826982,-31.611805831915532],[-9.123700063165664,-31.474023160257293],[-8.941869248366238,-31.168169688868332],[-9.047623249880937,-30.971388259517333],[-9.173254757963948,-30.9910479758929],[-9.40159828530702,-31.209927955741833],[-9.584483661743569,-31.190827407304713],[-9.771292998303664,-31.077189601709946],[-10.168063757661145,-31.069466387084574],[-10.296441770294434,-30.973489595519258],[-10.537804893958327,-30.922817072852453],[-10.744559741614337,-31.094966317837528],[-10.748588142821722,-31.24186419717318],[-10.872225343634858,-31.37379381357863],[-11.012821701204206,-31.367720867622037],[-11.271760648721914,-31.488049508918273],[-11.374403805832259,-31.426322104243788],[-11.788558920250672,-30.944322247908946],[-11.937046165703226,-30.870504661442347],[-12.1711846545711,-30.84007650746522],[-12.451851239687656,-31.02686153440561],[-12.899713942043435,-30.993163767061276],[-13.271708813263999,-30.827532818450077],[-13.598208289809145,-30.781375909230356],[-13.791501469072989,-30.682485256604714],[-13.964059809419744,-30.499871438332555],[-14.581553049300771,-30.55848489936959],[-14.720638234928408,-30.48355259801761],[-14.959420791699836,-30.536532841131574],[-15.358781143224942,-30.447583876024016],[-15.47270841616905,-29.85884405301067],[-15.439965573961965,-29.668111179021142],[-15.588383586927216,-29.65574252077145],[-15.601018213802151,-29.491123044095424],[-15.785709934182428,-29.39701340648055],[-15.730380853322808,-29.256505191319913],[-15.883189849753498,-29.185839729174194],[-16.072042187648197,-29.21002112345091],[-16.383095275463464,-28.888949403251335],[-16.50190214852528,-28.990772531817306],[-16.46319979607748,-29.188075852164832],[-16.539571731709604,-29.405372531314406],[-17.22329180761269,-29.141010030690857],[-17.526018912556452,-29.106480087856433],[-17.714858559584773,-29.374824010925774],[-17.993598168916964,-29.31773659595825],[-18.26545581401648,-29.334260033441378],[-18.44603866059102,-29.12504846251198],[-18.597220714508143,-29.05508346912839],[-18.936278499470685,-28.694450333815457],[-18.973888061018243,-28.556395490907914],[-19.151750027118307,-28.463205557763594],[-19.348247569372877,-28.209941522417235],[-19.534875964218802,-28.07750727426012],[-19.718774309394774,-28.01821904176112],[-19.823678543397126,-28.116144103536115],[-19.994665640740436,-28.122836661714373],[-19.932079387687565,-26.79153932912127],[-19.833460713424607,-24.681852044358497],[-19.769547495861268,-23.294001419285724],[-19.652609384205274,-20.720216147423745],[-19.569269109165358,-18.857664709308587],[-19.40814628533552,-15.199315167710163],[-22.369321182613483,-15.073929843633497],[-24.081847126048533,-14.99025905169759],[-26.323341662896443,-14.876808649417603],[-27.896137357976812,-14.791064643822795],[-29.80954224042435,-14.67992666715688],[-32.000236620233004,-14.539887003833952],[-34.294765333962935,-14.389292832881416],[-36.43045257757133,-14.240447606823222]]]},\"properties\":{\"name\":\"Texas\",\"ns_code\":\"01779801\",\"geoid\":\"48\",\"usps_abbrev\":\"TX\",\"fips_code\":\"48\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[125.33173354769006,52.03404327888572],[123.88642134807479,51.7889334425857],[122.41149917331867,51.52848639269733],[121.59512730255712,51.391823812341315],[120.56085843921541,51.249339028933505],[120.02164577776038,51.18436694331614],[118.76613596765914,51.003072262606054],[118.17193263105521,50.89838362080273],[117.59311951426727,50.8148163291382],[116.34293700742873,50.59961959981198],[116.32620581312236,50.48731913607965],[116.44457455353606,50.39983009845968],[116.51929756848914,50.198295830139465],[116.45814042267364,50.11871563433572],[116.45623355392952,49.80396486007932],[116.73136472269425,49.68604733518393],[116.7651674384062,49.622228344181025],[116.68946590371982,49.39856877403645],[116.79214086674855,49.0344040438406],[116.77726082614879,48.79582044824158],[116.86352103953985,48.67426540624245],[117.1183511146703,48.533162097456994],[117.45772862630004,48.22010393066117],[117.55845718499167,47.998996732756765],[117.53204753173225,47.79820675385528],[117.46772813678066,47.70737607172842],[117.61953293999632,47.42295044009229],[117.74566593504751,47.26544481234301],[117.74392587857683,47.143137828444914],[117.64606012903432,47.06820255070011],[117.59837412999993,46.90732460871907],[117.4915969486154,46.805669205843984],[117.51978285710966,46.41932059852067],[117.5029783646203,46.066873166367756],[117.67102364855317,45.999893524315915],[117.83369564480829,45.55470047781071],[118.10653648915725,45.294979054186854],[118.16464022327919,45.14945046907818],[118.1183569900502,45.001304549319826],[118.39852141703776,44.79579766601065],[118.37724426818609,44.558907606083125],[118.26683473113651,44.34530847400301],[118.24460345360545,44.05639705248496],[118.31123684645961,44.00915055485067],[118.32598560502586,43.80281651683977],[118.5054101971334,43.7493989720351],[118.57462732408288,44.05567868275201],[118.88554932575377,44.12198157351217],[119.05242934596345,43.90492540858018],[119.20810196313921,43.856143209039345],[119.34874981027826,43.70875770181161],[119.3480702372152,43.62662886005533],[119.7368023475769,42.307679364297016],[120.17631542950036,40.66796950637492],[120.3500205077386,40.04965065815525],[120.34149340337619,39.8697994227833],[120.5099039490052,39.58381706460384],[120.56803459168982,39.59428954639799],[122.88898419709365,39.98061450879279],[124.67748134534378,40.26871438221356],[124.53485878686337,40.41529462394155],[124.13260561355408,40.602273179286264],[123.95902767134878,40.83735235619776],[123.92450323100823,41.0119087849881],[124.0107694644384,41.21076467185411],[123.90492970718502,41.39787591760065],[124.16944201792201,41.54436905567323],[124.16453907085881,41.79942874411229],[124.05750805661215,41.974904520746655],[124.17060954647097,42.15868028858966],[124.12712459109257,42.30838041292245],[123.97879115123585,42.42791772353158],[123.89897043240951,42.701232446064296],[123.94050151765924,42.75611956547159],[123.86230677666292,42.981052646423045],[123.95295584720955,43.20129271477746],[123.96235460675688,43.362995052711675],[123.78470343005151,43.59935995824878],[123.70714929443511,44.150409314464284],[123.7277710469728,44.25881595828186],[123.5988694690055,44.35342773422813],[123.63571065571405,44.51873948611975],[123.6124206649462,44.72298518283338],[123.7886275140211,44.86132427101542],[123.73067640800826,45.03909139773924],[123.79235453696997,45.21296816653346],[123.73745637493076,45.34504496844419],[123.83581189871776,45.56200979008499],[124.0075191849875,45.66873673062816],[124.14390919066214,46.01198017862321],[124.04043490842754,46.22318448337693],[124.13944046153603,46.480856998586674],[124.25411853016107,46.583846520969026],[124.22590761390973,46.726351074009436],[124.29636003882914,46.78801907807668],[124.14151819876415,46.94883354139368],[124.25422819574872,47.10094029802606],[124.40252637776258,47.43647531789678],[124.21070332745423,47.78167677619729],[124.0469273540822,47.90634342237279],[124.04852155777398,48.1499572164835],[123.90867908538934,48.36132182830789],[124.04393421779352,48.55046273255042],[124.20041581004547,48.66141201360286],[124.45644099816184,48.70548718658891],[124.54656576542033,48.7730592594782],[124.73706182178657,48.748829688101324],[124.9931357948419,48.89895302154721],[124.94206544110659,49.00575882882672],[125.02064189162161,49.10591180913796],[125.3840327610332,49.213121809186894],[125.59631771035129,49.40880507686415],[125.59671806579415,49.49615124240602],[125.8152231575149,49.658401375318405],[125.7891253550237,49.80351297573415],[125.85257381875628,49.91032726264964],[125.69348470452633,49.96112771919476],[125.90221522830652,50.06459919136289],[125.93783421550377,50.15071012731711],[125.81297086674402,50.23856321728435],[125.70063048091207,50.40036000998462],[125.57212419656433,50.424495215287685],[125.45043604702781,50.59289225179665],[125.23901951910437,50.70536142152174],[125.17162546282515,50.809161344406526],[125.367813188991,50.992165806777436],[125.32245036898077,51.122853272093636],[125.40867600817954,51.237889262660836],[125.38135667103712,51.36720553646322],[125.548214198375,51.587469675014354],[125.40831832817523,51.67403230509751],[125.3636186834371,51.78973446362172],[125.18900699574655,51.882384098377756],[125.33173354769006,52.03404327888572]]]},\"properties\":{\"name\":\"Vermont\",\"ns_code\":\"01779802\",\"geoid\":\"50\",\"usps_abbrev\":\"VT\",\"fips_code\":\"50\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[133.8024057151763,43.117289299673445],[133.92736497186866,43.32818054529733],[133.86550730028725,43.609255951243945],[133.63935055029123,43.78752742006674],[133.41449550505808,43.79498781275054],[133.42890269492173,44.004383495689076],[133.5114547332058,44.159626588158396],[133.72589607897908,44.02976531609657],[133.91697453902853,43.980678887377294],[134.21529544568727,44.078116397982974],[134.29508636677014,44.21069096666324],[134.26541857370603,44.3273490192184],[134.1319421296945,44.447023596092706],[133.96965592035022,44.4978940770469],[133.7329586692134,44.45672151128182],[133.55968177739888,44.5387292078726],[133.5733815457332,44.6886510657581],[133.44512899010945,44.83675121012756],[133.50939624132312,45.034584246537825],[133.8448120367201,45.15578706573646],[134.05944136008165,45.32699561402909],[134.1796653899654,45.70428224986699],[134.3098584373615,45.79892573960227],[134.3613584191233,45.92713032794144],[134.73108967461508,46.50322748233811],[135.9836238965159,47.36565179310913],[136.2122670304954,47.41136289219072],[136.30124235490027,47.48272055972273],[136.4927020058441,47.49850145734127],[136.6272147204988,47.56327797258695],[136.7984110762072,47.846610476896494],[136.98371440858935,47.82769898741222],[137.3004790309887,47.8980195497399],[137.47743009317946,48.077928132900276],[137.4698919706159,48.312714739540354],[137.86363397883204,48.5415674798973],[137.995350208528,48.50839669417293],[137.99880213719496,48.38023590001047],[138.14288820104608,48.20347679602613],[138.32229435351852,48.13412252663614],[138.6173477820658,48.17501395439816],[138.7588239385493,48.29029791007427],[138.8266511596839,48.485993791887275],[138.70701702546347,48.6478531214086],[138.76091833894145,48.71939486272772],[138.9981771402709,48.67410678124802],[139.20809593609158,48.737107632514245],[139.4408364437522,48.915339072798496],[139.50762444143302,49.09086969336034],[139.70337531380528,49.04808695187349],[139.74139773193022,48.944422688940904],[139.97471827463502,48.8275418940734],[140.18755847218046,48.835482442384],[140.36782150027568,48.7223032818149],[140.65124008959612,48.698760114975535],[140.8823717912441,48.75108358371173],[140.99112164918418,48.856593840809836],[141.01770095896458,48.98270630275162],[140.94832554178674,49.100495548834836],[141.0675501027287,49.26105312223572],[141.1870440757305,49.29408925986358],[141.34124264336737,49.42129818589825],[141.36399477405539,49.61609398886591],[141.22808729624313,49.73277842214406],[141.007738685593,49.780098342964976],[140.9428385366027,49.86384710147842],[141.27479747772136,49.859958625495864],[141.62744835453813,49.957649541258945],[141.78364922433533,50.05614114420169],[142.1518002014355,50.52996751626116],[142.50131134456677,50.55829018431054],[142.7653890221283,50.64742841320697],[142.92491996220392,50.85357026533664],[143.19867987455626,50.94778034708817],[143.2804480614235,51.08318113921482],[143.17106617794508,51.36696361626645],[143.3316223339662,51.56444671627885],[143.22500272754982,51.72171287013599],[143.52414703501594,51.84242320996574],[143.73915326857,51.86811688630735],[143.9144328099467,51.993366885526825],[143.92696146329033,52.10655809161554],[144.05411254495698,52.23194588582765],[144.22588586003292,52.175503586899424],[144.43032579134922,52.19031689036551],[144.62633651315826,52.31618672918026],[144.64495215511607,52.43226032746803],[144.53240841334903,52.547269941164075],[144.63972740830832,52.65942071194587],[144.84302494699583,52.60420206579571],[145.16112841415853,52.680684622492656],[145.36945223080227,52.66940885012401],[145.6757824371133,52.78826997595442],[145.96586776283175,53.00043870286901],[146.07815628406175,53.1681516282445],[146.06780133464727,53.34767415621557],[146.2855472549678,53.45496877187292],[146.44269017768528,53.47375537185432],[146.60272919341276,53.56324254845818],[146.64518495986852,53.65127970686117],[146.89095188841677,53.720199383704575],[147.12497463062832,53.882321854266706],[147.44287890490125,54.193407969771734],[147.69039977954307,54.57962691146528],[147.82088808992273,54.66381819375455],[147.9190148328967,54.83041522013452],[148.08668773857747,54.907488338106994],[148.1292543414645,54.99174796558147],[147.83488970602565,55.07287489061368],[147.66983293829594,55.05762582151397],[147.5063887122122,55.192301495975585],[147.47770132519676,55.371464469961744],[147.1303860306747,55.49427386272205],[146.5210215388017,55.896346854778436],[146.3539157449718,56.02526127709895],[146.0262695587167,56.17443016705739],[145.79683810291985,56.17348780892835],[145.69554551759754,56.1227637963778],[145.42130260951345,56.19073219155161],[145.33839773153318,56.160729096609856],[145.39895052497428,56.006421965976386],[145.2312937989161,55.8813959142237],[144.98378187195704,55.95148930051337],[144.40213998295644,56.22849504603623],[144.26368474059726,56.34034332483073],[144.3410450416266,56.6246270385903],[144.2549554115373,56.81353322205005],[143.96047082379445,56.90258347630283],[143.69592084681844,57.1268059473276],[143.92806533225706,57.31677508807284],[143.95058615596056,57.407041756848834],[143.82864361451956,57.52492597453653],[143.66387247226052,57.57843515163908],[143.49918008344198,57.480270049755106],[143.0238552055269,57.4821294061914],[142.56587936270037,57.5033102467476],[142.280048064921,57.65732628105917],[142.11178877974962,57.57523572357791],[141.85605834789638,57.57374364516885],[141.71878002878233,57.75815236307567],[141.84814771916186,57.78538255428766],[141.6422445404327,57.89571772643609],[141.60200226451292,58.03031396828913],[141.73205301589647,58.152232623802206],[141.45305637449394,58.26851002479784],[141.427500713783,58.31529257786734],[141.60693144656764,58.48013655897623],[141.40794720628148,58.54949707620975],[140.74719211659504,59.65285684547039],[140.00831798449528,60.79876522974486],[139.57260712675637,61.47340003896162],[139.02752312499496,62.30918239351374],[138.5184584830112,62.364911434127286],[138.436312843005,62.42921475592511],[138.20600385929745,62.49475183815141],[138.0095212052551,62.602854196769734],[137.80164235059615,62.62622459686165],[137.57508627675918,62.69638789156732],[137.30969036890122,62.724834081451384],[136.83982994633072,62.83110276448414],[136.81379117334163,62.86952783918828],[136.43530704267556,62.89422360316384],[136.36347119576314,62.920015467680884],[135.9206730324865,62.875369692345195],[135.7169607505462,62.77843568193177],[135.7775456958124,62.65641521186554],[135.60386951331805,62.56484819717229],[135.36219763274207,62.585744966299444],[135.16468274418696,62.554495169142164],[135.04232273670976,62.49045824781527],[134.90108005878292,62.489978052840634],[134.84473841461758,62.34323360792631],[134.60591087857046,62.27310663534683],[134.47741426892296,62.28368968876367],[133.9555682026708,62.110835812131505],[133.79065323464926,62.002160889302],[133.58799481252058,61.93986205044981],[133.3682292581883,61.99879213280863],[132.8238960851988,62.05897939447187],[132.61884730899834,62.27368107037903],[132.4992888713992,62.467393372560615],[132.52565317174088,62.57551213950456],[132.35197138635164,62.63846497306539],[132.2804196046381,62.59869655706915],[132.05438078126102,62.6324954219696],[131.77379750037463,62.64314668435445],[131.54185644282265,62.62271909061834],[130.898024053177,61.74926846479438],[130.03601353089553,60.53421717466529],[129.4015150787609,59.6049739515324],[129.65506979952514,58.58487833498558],[129.49118790103046,58.527922567633595],[129.33079237113643,58.31398927030428],[129.20331995223117,58.29201195367039],[129.09746298633289,58.16518337589236],[129.16551194938958,58.05660561044756],[129.0628589136518,58.00976290440022],[129.04119252573528,57.76581183330881],[128.98484059923643,57.58114473986175],[129.3157462906078,57.47095322114154],[129.30764903524843,57.288956983303486],[129.1836621778385,57.27425626087361],[129.14078681430976,57.11018246440474],[129.2753536942488,57.129403024042716],[129.18342186681588,56.960170063622826],[129.27982528587788,56.88108444760827],[129.29900037231423,56.75269241422306],[129.52742592414765,56.77240491423418],[129.69531846937576,56.70410416876484],[129.61264508386697,56.627940408041205],[129.6969597694869,56.55225322667149],[129.60973051802813,56.41396456046447],[129.37131051212734,56.297966281786366],[129.12723431114736,56.02718524261903],[129.2290581200358,55.90589904496193],[129.34754545893216,55.864904507610305],[129.3419617581723,55.74556728936427],[129.1861677417444,55.65528067514689],[129.05452527319466,55.645605379716194],[128.83422371107773,55.444431316673075],[128.7076849952474,55.43029391816296],[128.49335792881698,55.16185035719381],[128.3789018292098,55.11955193176529],[128.25111052465851,54.91222201215575],[128.2926087159265,54.87119783154958],[128.1806521690174,54.695632600633616],[128.24733919312257,54.60477573807409],[128.4955444601205,54.4810681291669],[128.65433600740755,54.47565711850276],[128.85555916615226,54.34018671817996],[128.78469572844844,54.20415295588825],[128.61863581109083,54.25119098816415],[128.46168267862635,54.21013639653132],[128.25025279361995,54.30846468113445],[127.9946137426165,54.299235244120354],[127.89515352675168,54.16184479280468],[128.07009505907692,54.04172765051252],[128.03520599227448,53.92290809762636],[128.13711349862416,53.85644369335638],[128.03907703629963,53.58727473099442],[128.0572515855574,53.4406581653095],[127.9178787307706,53.44308503209912],[127.65879236505606,53.595164704861794],[127.61365460279582,53.731716542126954],[127.22719755322399,53.76829073990569],[127.16135849278794,53.692544604580114],[126.8216338942304,53.577974960451336],[126.95585557464001,53.3574094667694],[127.52772604687412,52.29229693861835],[128.19306372375905,51.05609949177426],[128.50456482599188,50.41392642612356],[129.07559413754984,49.249425366936755],[129.7541540183064,47.77752620369343],[130.09957912864883,46.98578778838331],[130.59020296993518,45.953526631356866],[130.73445505052513,45.883425870280774],[130.7793998108097,45.724626696686386],[130.75014295628958,45.50390283419896],[130.89384260381516,45.33108190998469],[130.81190298194667,45.182143361930876],[130.99778022049136,44.88233574425018],[131.23340351555717,44.85712999545492],[131.50089949387365,44.63843816821173],[131.73619200591207,44.53917494309881],[131.9804553054598,44.48234578019346],[132.0497060639114,44.42067874957083],[132.02892051401375,44.21624948199154],[132.07699371185493,44.01008335854328],[132.16161201109176,43.91407521243357],[132.6912137113554,43.74252848759796],[132.84647749066417,43.781724400112964],[132.92882946984196,43.63620547690788],[133.38528186482586,43.40949505195338],[133.50461598872863,43.39301706060676],[133.8024057151763,43.117289299673445]]]},\"properties\":{\"name\":\"Maine\",\"ns_code\":\"01779787\",\"geoid\":\"23\",\"usps_abbrev\":\"ME\",\"fips_code\":\"23\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[84.25923764886333,-9.07272836103646],[84.27023539876669,-9.208502100457075],[84.14376369207609,-9.467066392202401],[84.23882940208316,-9.665401598348202],[84.291105376638,-9.935082742902816],[84.19184742824443,-10.039917846102876],[84.1941169828441,-10.272177327029064],[84.08839031245184,-10.35813774402632],[84.29394183684099,-10.792006350522591],[84.0362864302466,-10.895395765013722],[83.8315144804208,-10.784412401161104],[83.53031592193645,-10.98074478288423],[83.41721245083406,-11.165519230563826],[83.2691216057478,-11.270372895921893],[82.75639459559704,-12.63813099633056],[82.63105675792296,-12.610773477836428],[82.50781494620364,-12.772666077619327],[82.22461971189247,-12.827543935784893],[82.07293971992274,-12.529094547143306],[81.92199610680164,-12.603263486939836],[81.69544942474501,-12.5155902329007],[81.54846287407379,-12.736043618159655],[81.29561465644734,-12.738581505535397],[81.05091234975818,-12.920026699228305],[80.93984846889012,-12.933558718633298],[80.66532261114435,-13.203138569276398],[80.45662713460545,-13.761453568622331],[80.23393131635598,-14.005751668953037],[79.99671370673715,-14.133149139246257],[79.64688316068334,-14.159268265762535],[79.61081120537588,-13.980288460825545],[79.69593975882711,-13.695893519371419],[79.40313409862831,-13.497373159775439],[79.1504335599222,-13.67978143611453],[78.99854096265719,-13.85027273728808],[78.67902925519556,-14.056082927897332],[78.66187057697778,-14.401153539948341],[78.50511882901215,-14.634074815624682],[78.30111178615611,-14.469000711002598],[78.03960105961819,-14.534811458817813],[77.96485535282152,-14.65327606904387],[78.1164215516622,-14.992777311721785],[77.98999758402479,-15.079884072286097],[78.0411421049874,-15.261513535172467],[77.90047033017278,-15.392192080113109],[77.76582550017623,-15.680847128324512],[77.65695412693587,-15.79207944113862],[77.4134027908399,-15.724460954450267],[77.16981227974762,-15.74213215653247],[76.9569200441073,-15.932526071412093],[76.73643134638608,-15.970761853424147],[76.61976364791266,-16.230292010259646],[76.31015168279156,-16.292342169264085],[76.26281747738909,-16.51413955696742],[75.98234483070084,-16.83803252128131],[75.83246731109061,-16.780678290474853],[75.67595312733447,-16.986568470449114],[75.39097166446678,-17.214063894240613],[75.25236438737383,-17.227391972746734],[74.99914504694023,-17.597663380125425],[74.60999847004605,-17.62575119429795],[74.3946220840787,-17.568061733975682],[74.18336181424016,-17.681023212207137],[73.80299737484795,-17.708775711240595],[73.44087375362479,-17.797854600626632],[73.16890276569633,-18.09560005392199],[72.84983267637566,-18.18039793382296],[72.72067271591253,-18.479552543278935],[72.5806237804252,-18.527596585806677],[72.2940167715854,-18.776010950734474],[72.19072706567347,-18.983479212667564],[72.26903446098035,-19.250599834492935],[72.11366212200812,-19.432278014661847],[72.15691262548265,-19.632411882319726],[72.25281681177728,-19.73571687261687],[72.08601286085427,-19.979337277642035],[71.80726018014595,-20.126623929961557],[71.28636332833409,-20.240498857757924],[71.19363587985673,-20.09888826287473],[70.78013527605904,-20.42262712792637],[70.80794660775955,-22.022139403687262],[72.43353989249063,-21.843294573020348],[74.09674523107819,-21.64061966151776],[74.84423927188125,-21.557471776139266],[74.83795166476989,-21.521124841791174],[75.5188348739196,-21.43251655954602],[77.76766199990318,-21.098173501567533],[79.54104542573003,-20.291097505830503],[79.67959964279204,-20.39205010459256],[79.8640165473195,-20.19105000032777],[80.02934145350153,-20.16091274715415],[80.31430482487133,-19.874198937540864],[80.55575609541307,-19.801196089617928],[80.95475387493408,-19.619568609007953],[81.15918553585999,-19.469127537095684],[81.47618306968162,-19.43499789034963],[81.51728719606,-19.25502285294858],[81.66858445601012,-19.118222140377366],[81.84968370378266,-19.26177769357911],[82.11956506308252,-19.227365294115078],[82.34405023738721,-19.132073993844468],[82.88665366675997,-19.098512787368833],[85.36681253852011,-18.84628112577993],[88.16212841965874,-18.57010609902441],[89.28791200555077,-18.469962457072576],[89.42041877728808,-18.49208438271667],[89.54355730230722,-18.716074856125555],[89.4289751055779,-18.89812580740435],[89.45331332283322,-19.057711426268757],[89.56569638481284,-19.1629070117072],[90.10370166259662,-18.64964472751929],[91.17402040784998,-19.668496158960654],[91.21852622673208,-20.455934849124137],[92.59327679921813,-20.262130757251967],[94.85485068809201,-19.949101301421816],[96.52140585755068,-19.72587535823846],[97.66448117592878,-19.54485992163963],[99.80313947923015,-20.968647948800808],[100.87552246584264,-21.72263667713465],[103.10652524368457,-23.202153899823763],[105.40117888217257,-24.708122819895326],[105.69307225187308,-24.91272972026431],[106.2329145823347,-24.619310750922462],[106.55101274665812,-24.485005366081612],[106.99630004243346,-24.3562196246981],[107.6726758747216,-24.242284998162514],[108.14753201158445,-24.258169181323794],[108.25431170989722,-24.376604539525065],[108.51748038294105,-24.487755646725994],[108.64316584612855,-24.710216332085178],[108.86861129185685,-24.765492457784944],[109.12291079282105,-24.648466233131742],[109.20498041371745,-24.361858002269955],[109.10652155017141,-24.045846673386023],[109.12320780905175,-23.714777526836286],[109.20203852404812,-23.492914975607995],[109.26368450236244,-22.838479343908883],[109.39096036663055,-22.173875080166006],[109.53273754887313,-21.945763503386615],[109.54114615015295,-21.80444783274068],[109.7683718431734,-21.285408185641405],[109.8810750773899,-21.179161609281543],[110.07673086676974,-20.836659910255435],[110.5828090849737,-20.19952376560722],[111.06578711751823,-19.741471119433843],[111.69963651725145,-19.256155275312537],[112.06217606456393,-18.836297172661183],[112.54053012355179,-18.443069392326187],[113.20594977460986,-18.079535663463567],[114.19929079771094,-17.70376153823475],[114.58796485963425,-17.596975886474606],[114.91893985954336,-17.56508720451762],[115.39996996914323,-17.64314378795827],[115.43997161663857,-17.762244408711677],[115.67252102791767,-18.04840288856857],[115.81650531779307,-18.129727839428575],[116.04108215233255,-18.118619666892908],[116.20484140500884,-17.985828625231484],[116.35025452931954,-17.350611345725415],[116.62446477564436,-16.713827061496122],[116.87092240716807,-16.241120392562756],[117.15980144162315,-15.768610159181634],[117.72287893158312,-14.95574260952223],[118.18268222755859,-14.346724667975142],[118.32705387087086,-14.302430625477278],[118.73906770285033,-13.759060934870138],[119.24250291160484,-13.336202325259086],[119.76387829748201,-13.009914700180127],[120.18611397913618,-12.707181168052923],[120.3681239193746,-12.611482078208185],[120.63044088692102,-12.667510753245194],[120.88998742197893,-12.547475825094965],[120.99277678948272,-12.346667804479715],[120.97888865480752,-10.993354758860457],[120.86756747018683,-10.219458314730243],[120.87063207870608,-9.899301977773707],[120.80573861084413,-9.555258661625848],[120.56471319290962,-8.934232053845552],[120.35962769956534,-8.535329408178132],[120.015618553242,-8.119680056501217],[119.28870125097922,-7.0983794709914925],[118.43135247194328,-5.9628266832780685],[118.05799568142162,-5.353601008177359],[117.68707547494174,-4.674424825810796],[117.2367237601711,-3.6947161528059413],[117.12108169757701,-3.346380359213608],[114.37096307218879,-3.9339119830992018],[111.5383541465546,-4.520289151029984],[110.90620142859268,-4.6376306239069915],[110.89817971942948,-4.699539299186722],[108.37437941961872,-5.1992883374297065],[106.27229445365657,-5.604034942250461],[104.08416045667515,-6.035991800944563],[103.48674873875919,-6.142903433833908],[102.03220849372849,-6.439587074293968],[98.91049332540015,-6.999285202839742],[97.90725979563915,-7.184047487190085],[96.15390941767419,-7.49171742734163],[94.34049032550311,-7.789078313311727],[93.07544938449347,-8.003906209399869],[92.05795966983088,-8.163121246954093],[89.59676369120042,-8.430713614571244],[88.98433986405772,-8.55123222421043],[88.03633737522944,-8.666843901529807],[86.46370052650293,-8.831530805045887],[85.97107549461133,-8.9098053559797],[85.14972146071612,-8.993892905179065],[84.69417097697749,-9.016715872885204],[84.25923764886333,-9.07272836103646]]]},\"properties\":{\"name\":\"North Carolina\",\"ns_code\":\"01027616\",\"geoid\":\"37\",\"usps_abbrev\":\"NC\",\"fips_code\":\"37\"}}]}\n"
  },
  {
    "path": "viz-lib/src/visualizations/choropleth/maps/usa.geo.json",
    "content": "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-104.057697,44.99738],[-104.366692,44.998151],[-104.772765,44.999158],[-105.250545,45.000168],[-105.718212,45.000333],[-105.913382,45.000941],[-105.928183,44.993647],[-106.250717,44.993688],[-106.62561,44.994927],[-107.084938,44.996599],[-107.134179,45.000109],[-107.500025,45.001512],[-107.75065,45.000778],[-107.997369,45.001565],[-108.499092,44.9998],[-109.08137,44.99946],[-109.103373,45.005886],[-109.75073,45.001605],[-110.110103,45.003905],[-110.199503,44.996188],[-110.362698,45.000593],[-110.402927,44.99381],[-110.705272,44.992324],[-110.785008,45.002952],[-111.055199,45.001321],[-111.056889,44.866658],[-111.055208,44.624927],[-111.048974,44.474072],[-111.049077,44.02013],[-111.046516,43.908369],[-111.045383,43.484566],[-111.04412,43.247071],[-111.044078,42.97598],[-111.043547,42.719502],[-111.047023,42.474064],[-111.046689,42.001567],[-111.045794,41.499945],[-111.04658,41.361033],[-111.046723,40.997959],[-110.500718,40.994746],[-110.237905,40.995426],[-110.006495,40.997815],[-109.543221,40.998396],[-109.250735,41.001009],[-109.050076,41.000659],[-108.526029,40.999592],[-108.181227,41.000455],[-108.046539,41.002064],[-107.750627,41.001971],[-107.241194,41.002804],[-107.003119,41.003421],[-106.625592,41.00211],[-106.439563,41.001978],[-106.314218,40.998923],[-106.061181,40.996999],[-105.726441,40.996836],[-105.341415,40.997972],[-104.855273,40.998048],[-104.625519,41.001428],[-104.053249,41.001406],[-104.052245,41.398065],[-104.052859,41.592254],[-104.052967,41.916972],[-104.052547,42.166801],[-104.053107,42.499964],[-104.05262,42.660549],[-104.053028,43.000585],[-104.05479,43.41642],[-104.055122,43.740516],[-104.054688,43.999979],[-104.055389,44.249983],[-104.055926,44.517359],[-104.055913,44.874986],[-104.057697,44.99738]]]},\"properties\":{\"name\":\"Wyoming\",\"ns_code\":\"01779807\",\"geoid\":\"56\",\"usps_abbrev\":\"WY\",\"fips_code\":\"56\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-80.518991,40.638801],[-80.518975,40.349789],[-80.518891,39.890964],[-80.519342,39.721403],[-79.940671,39.720671],[-79.476662,39.721078],[-79.045548,39.722883],[-78.62474,39.722946],[-78.344435,39.722468],[-77.929484,39.722327],[-77.502821,39.719886],[-76.990903,39.719799],[-76.514789,39.721127],[-76.135484,39.721147],[-75.788596,39.722199],[-75.773786,39.7222],[-75.754056,39.756178],[-75.719502,39.790365],[-75.680468,39.813939],[-75.639703,39.828955],[-75.564047,39.839488],[-75.481039,39.829319],[-75.415062,39.801919],[-75.330432,39.849013],[-75.272202,39.849076],[-75.221371,39.861543],[-75.192738,39.879006],[-75.137296,39.889966],[-75.128253,39.913164],[-75.135239,39.950677],[-75.119491,39.965372],[-75.075605,39.978041],[-75.04709,40.009941],[-75.013237,40.020303],[-74.935042,40.067836],[-74.859721,40.085311],[-74.826226,40.124066],[-74.788287,40.120579],[-74.724369,40.146412],[-74.722325,40.160727],[-74.754305,40.185209],[-74.770406,40.214508],[-74.842308,40.250508],[-74.868208,40.295206],[-74.939711,40.338006],[-74.967499,40.398813],[-74.99586,40.410384],[-75.031778,40.404813],[-75.060917,40.421737],[-75.070608,40.456196],[-75.062761,40.480026],[-75.0681,40.541488],[-75.099827,40.567398],[-75.134995,40.575662],[-75.172877,40.564423],[-75.201348,40.614628],[-75.200452,40.649219],[-75.178019,40.671763],[-75.203921,40.691498],[-75.182804,40.73365],[-75.196535,40.751631],[-75.171587,40.777745],[-75.139106,40.773606],[-75.108505,40.791094],[-75.064328,40.848338],[-75.053664,40.87366],[-75.117767,40.953013],[-75.130575,40.991093],[-75.088596,41.015024],[-75.025777,41.039806],[-75.012873,41.066516],[-74.98259,41.079172],[-74.982212,41.108245],[-74.923169,41.138146],[-74.882139,41.180836],[-74.859323,41.220507],[-74.857152,41.248975],[-74.830057,41.2872],[-74.773225,41.324513],[-74.755894,41.34528],[-74.694914,41.357423],[-74.713412,41.389814],[-74.757926,41.423437],[-74.793856,41.422671],[-74.812123,41.442982],[-74.890175,41.438178],[-74.909181,41.472436],[-74.941798,41.483542],[-74.981652,41.479945],[-74.987645,41.508738],[-75.023018,41.533147],[-75.027343,41.563541],[-75.074613,41.605711],[-75.044224,41.617978],[-75.052736,41.688393],[-75.053227,41.751662],[-75.075942,41.771518],[-75.10464,41.774203],[-75.085789,41.811626],[-75.113334,41.822782],[-75.114399,41.843583],[-75.204002,41.869867],[-75.223734,41.857456],[-75.271293,41.887358],[-75.279094,41.938917],[-75.310358,41.949012],[-75.359579,41.999445],[-75.742217,41.997864],[-75.983082,41.999035],[-76.445733,41.998747],[-76.835182,42.002134],[-77.126846,41.999414],[-77.505308,42.00007],[-77.77671,41.998428],[-78.124731,42.000451],[-78.271204,41.998968],[-78.59665,41.999877],[-78.874759,41.997559],[-79.006166,41.999124],[-79.47252,41.998254],[-79.761313,41.998808],[-79.762569,42.375058],[-79.762418,42.516072],[-80.080222,42.394162],[-80.519851,42.327132],[-80.519326,41.972468],[-80.519345,41.473907],[-80.51881,41.041355],[-80.518991,40.638801]]]},\"properties\":{\"name\":\"Pennsylvania\",\"ns_code\":\"01779798\",\"geoid\":\"42\",\"usps_abbrev\":\"PA\",\"fips_code\":\"42\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-84.820157,39.105489],[-84.784484,39.117719],[-84.750345,39.147622],[-84.718096,39.136616],[-84.689354,39.103192],[-84.651692,39.092528],[-84.632506,39.076544],[-84.575499,39.081225],[-84.553509,39.098509],[-84.511041,39.093654],[-84.458818,39.121479],[-84.435388,39.100885],[-84.430561,39.05874],[-84.412487,39.046967],[-84.360608,39.041513],[-84.313835,39.016588],[-84.286536,38.952542],[-84.234027,38.891407],[-84.230147,38.82643],[-84.213115,38.805835],[-84.15662,38.795072],[-84.070779,38.770414],[-84.044576,38.770557],[-83.97734,38.787435],[-83.859973,38.757217],[-83.840657,38.721976],[-83.784386,38.696581],[-83.770178,38.655857],[-83.723497,38.647076],[-83.668718,38.626542],[-83.644498,38.637078],[-83.634901,38.670426],[-83.61141,38.684679],[-83.520755,38.703094],[-83.468133,38.675476],[-83.377573,38.661809],[-83.327626,38.637851],[-83.318399,38.610515],[-83.288041,38.598349],[-83.245222,38.628711],[-83.201445,38.61673],[-83.154483,38.62077],[-83.134754,38.632013],[-83.112058,38.671843],[-83.062579,38.689457],[-83.026976,38.727554],[-82.971567,38.727452],[-82.93935,38.745004],[-82.897189,38.756214],[-82.875174,38.746837],[-82.87736,38.694458],[-82.856895,38.647405],[-82.847588,38.595641],[-82.821205,38.573344],[-82.788313,38.55973],[-82.725554,38.558185],[-82.698285,38.54377],[-82.653457,38.493702],[-82.610808,38.471953],[-82.59348,38.421821],[-82.571613,38.405138],[-82.53769,38.404157],[-82.404882,38.439347],[-82.389746,38.434355],[-82.323999,38.449268],[-82.303663,38.499124],[-82.293471,38.575383],[-82.27147,38.595383],[-82.221468,38.591284],[-82.181167,38.599784],[-82.171966,38.625384],[-82.190867,38.680383],[-82.182467,38.708782],[-82.199999,38.759057],[-82.221566,38.787187],[-82.189293,38.815768],[-82.144167,38.84188],[-82.143167,38.898079],[-82.129166,38.908179],[-82.090165,38.975278],[-82.052063,38.993978],[-82.037749,39.023865],[-82.00599,39.029387],[-81.981159,38.994351],[-81.933186,38.98766],[-81.900595,38.937671],[-81.92667,38.901311],[-81.890899,38.873919],[-81.848653,38.901407],[-81.844486,38.928746],[-81.814235,38.946168],[-81.785647,38.926192],[-81.756131,38.933546],[-81.78182,38.964935],[-81.764253,39.015279],[-81.812354,39.061279],[-81.754154,39.088377],[-81.742353,39.111277],[-81.756282,39.176993],[-81.725622,39.215905],[-81.691339,39.227947],[-81.69662,39.256139],[-81.677595,39.274171],[-81.565247,39.276175],[-81.557547,39.338774],[-81.456143,39.409274],[-81.406689,39.38809],[-81.38456,39.343449],[-81.344927,39.346794],[-81.26842,39.386318],[-81.211654,39.392977],[-81.185145,39.431479],[-81.134434,39.445075],[-81.092434,39.496075],[-81.0239,39.552313],[-80.933292,39.614812],[-80.88036,39.620706],[-80.866646,39.652616],[-80.865805,39.686484],[-80.829723,39.714041],[-80.869933,39.763555],[-80.826079,39.798584],[-80.826964,39.841656],[-80.793131,39.863751],[-80.809565,39.905658],[-80.795598,39.919717],[-80.756113,39.913402],[-80.764242,39.946151],[-80.740139,39.970795],[-80.742114,40.00888],[-80.731024,40.044291],[-80.73919,40.075199],[-80.708937,40.101422],[-80.705246,40.15416],[-80.668083,40.199689],[-80.66154,40.229812],[-80.622508,40.261765],[-80.59989,40.317666],[-80.612795,40.347669],[-80.607587,40.36956],[-80.632196,40.393667],[-80.612325,40.402762],[-80.612876,40.429466],[-80.594664,40.471389],[-80.618003,40.502049],[-80.627507,40.535793],[-80.664372,40.570016],[-80.66354,40.590768],[-80.62645,40.620176],[-80.584002,40.615723],[-80.518991,40.638801],[-80.51881,41.041355],[-80.519345,41.473907],[-80.519326,41.972468],[-80.519851,42.327132],[-80.999825,42.252404],[-81.249833,42.216415],[-81.495181,42.096402],[-81.686447,42.000037],[-81.709202,42.000037],[-81.999865,41.863787],[-82.398348,41.676524],[-82.681119,41.676328],[-83.069312,41.864217],[-83.11246,41.95941],[-83.41585,41.733794],[-83.709474,41.72524],[-84.192317,41.71101],[-84.475933,41.703813],[-84.805972,41.696118],[-84.8036,41.275272],[-84.803148,40.958992],[-84.802253,40.63035],[-84.804159,40.275046],[-84.811209,39.99545],[-84.814187,39.813687],[-84.815754,39.478652],[-84.820102,39.25141],[-84.820157,39.105489]]]},\"properties\":{\"name\":\"Ohio\",\"ns_code\":\"01085497\",\"geoid\":\"39\",\"usps_abbrev\":\"OH\",\"fips_code\":\"39\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-109.045223,36.999084],[-109.045554,36.644972],[-109.045946,36.375002],[-109.045726,36.116908],[-109.046348,35.75724],[-109.046523,35.523433],[-109.046084,35.249986],[-109.045866,34.949127],[-109.046175,34.520102],[-109.046765,34.17382],[-109.047145,33.74001],[-109.046564,33.375059],[-109.047483,33.067912],[-109.047117,32.777569],[-109.047612,32.426377],[-109.048142,32.160736],[-109.049186,31.797381],[-109.050044,31.332502],[-108.816063,31.332253],[-108.500594,31.333382],[-108.208573,31.333395],[-108.208394,31.783599],[-107.875581,31.783584],[-107.422246,31.783599],[-107.00056,31.783513],[-106.528242,31.783148],[-106.54714,31.807299],[-106.577244,31.810406],[-106.635922,31.866237],[-106.645295,31.894858],[-106.622163,31.936007],[-106.639536,31.980343],[-106.618486,32.000495],[-106.125534,32.002445],[-105.731353,32.001565],[-105.429383,32.000577],[-105.000507,32.00042],[-104.466406,32.000116],[-104.096927,31.999997],[-103.748317,32.000197],[-103.375458,32.000334],[-103.064422,32.000517],[-103.06478,32.226978],[-103.064676,32.500115],[-103.064968,32.754674],[-103.064639,33.000106],[-103.053903,33.506965],[-103.049608,33.737766],[-103.044173,33.974557],[-103.043762,34.31275],[-103.042691,34.739882],[-103.04264,35.109974],[-103.042258,35.394355],[-103.041666,35.625043],[-103.040824,36.000037],[-103.041923,36.50035],[-103.002434,36.500397],[-103.002199,37.000104],[-103.499968,36.999185],[-103.817803,36.997339],[-104.338833,36.993535],[-104.644997,36.993379],[-105.029226,36.992727],[-105.222398,36.995166],[-105.66472,36.995874],[-106.201469,36.994122],[-106.591178,36.992923],[-106.869798,36.992424],[-106.877293,37.000141],[-107.10724,37.000008],[-107.464127,37.000004],[-107.729194,37.000002],[-108.249358,36.999015],[-108.875317,36.998839],[-109.045223,36.999084]]]},\"properties\":{\"name\":\"New Mexico\",\"ns_code\":\"00897535\",\"geoid\":\"35\",\"usps_abbrev\":\"NM\",\"fips_code\":\"35\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-74.986282,38.451632],[-75.232115,38.451222],[-75.416925,38.452538],[-75.69367,38.46008],[-75.715684,38.740062],[-75.746992,39.133744],[-75.765719,39.366758],[-75.788521,39.648133],[-75.788596,39.722199],[-76.135484,39.721147],[-76.514789,39.721127],[-76.990903,39.719799],[-77.502821,39.719886],[-77.929484,39.722327],[-78.344435,39.722468],[-78.62474,39.722946],[-79.045548,39.722883],[-79.476662,39.721078],[-79.478866,39.531689],[-79.482366,39.531689],[-79.486875,39.205896],[-79.469793,39.203734],[-79.42423,39.226842],[-79.420836,39.238453],[-79.347416,39.291396],[-79.253878,39.3371],[-79.256882,39.355459],[-79.214443,39.363405],[-79.102816,39.450482],[-79.09113,39.47252],[-79.054694,39.473101],[-78.957301,39.440095],[-78.934298,39.486284],[-78.91672,39.48632],[-78.837596,39.56714],[-78.825968,39.588183],[-78.795268,39.6107],[-78.72371,39.563493],[-78.654697,39.534605],[-78.61991,39.539236],[-78.566115,39.519371],[-78.527333,39.524801],[-78.468923,39.516483],[-78.417295,39.549603],[-78.449874,39.570619],[-78.445456,39.591446],[-78.407865,39.578629],[-78.39704,39.590225],[-78.42478,39.624541],[-78.372013,39.611699],[-78.355323,39.640663],[-78.266648,39.618951],[-78.254252,39.639976],[-78.171549,39.695643],[-78.104433,39.68144],[-78.037405,39.637044],[-78.006719,39.601483],[-77.958716,39.608862],[-77.889135,39.597453],[-77.884519,39.615811],[-77.83777,39.605895],[-77.835676,39.567156],[-77.874231,39.564072],[-77.861176,39.514293],[-77.827301,39.493075],[-77.797252,39.489465],[-77.798396,39.456328],[-77.786332,39.428876],[-77.753728,39.423908],[-77.735869,39.391172],[-77.752987,39.379023],[-77.755728,39.333938],[-77.719519,39.321314],[-77.675467,39.32436],[-77.644084,39.30875],[-77.588777,39.301591],[-77.568753,39.306447],[-77.54084,39.265269],[-77.484731,39.246052],[-77.45771,39.224979],[-77.481202,39.187909],[-77.507558,39.181367],[-77.527322,39.146961],[-77.518447,39.119782],[-77.481015,39.105689],[-77.460602,39.074843],[-77.386818,39.062241],[-77.340144,39.062909],[-77.245093,39.022146],[-77.255759,39.001991],[-77.244911,38.982614],[-77.197774,38.96674],[-77.145081,38.963146],[-77.119759,38.934343],[-77.041018,38.995548],[-76.909393,38.892852],[-77.039006,38.791645],[-77.043338,38.718468],[-77.081614,38.707926],[-77.122632,38.685256],[-77.136293,38.647231],[-77.129682,38.634704],[-77.203696,38.618172],[-77.246362,38.593441],[-77.255703,38.563318],[-77.318542,38.474437],[-77.3204,38.435221],[-77.304938,38.375271],[-77.283796,38.342855],[-77.235208,38.332536],[-77.163219,38.345823],[-77.137734,38.368023],[-77.086678,38.367928],[-77.05342,38.398813],[-77.018498,38.381941],[-77.014796,38.332367],[-77.030683,38.311622],[-77.00115,38.280174],[-76.962086,38.256964],[-76.94902,38.208418],[-76.915867,38.199603],[-76.872716,38.171686],[-76.839368,38.163609],[-76.800199,38.16888],[-76.700899,38.161359],[-76.610384,38.148516],[-76.596128,38.106892],[-76.535402,38.073626],[-76.516609,38.026783],[-76.465008,38.01322],[-76.412934,37.966332],[-76.338617,37.945286],[-76.257527,37.905738],[-76.233415,37.887638],[-76.052021,37.953578],[-75.993338,37.953487],[-75.943693,37.946133],[-75.952672,37.906827],[-75.80124,37.912174],[-75.761642,37.941366],[-75.691321,37.955496],[-75.647606,37.947025],[-75.648228,37.966771],[-75.624449,37.994195],[-75.249609,38.026715],[-75.166435,38.027834],[-75.124625,38.093901],[-75.087813,38.189356],[-75.041457,38.287009],[-75.020805,38.309756],[-74.999625,38.371668],[-74.986282,38.451632]]]},\"properties\":{\"name\":\"Maryland\",\"ns_code\":\"01714934\",\"geoid\":\"24\",\"usps_abbrev\":\"MD\",\"fips_code\":\"24\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-71.653208,41.217805],[-71.670491,41.194503],[-71.678807,41.158912],[-71.668345,41.12825],[-71.611673,41.097413],[-71.528168,41.103114],[-71.491432,41.125104],[-71.476925,41.168157],[-71.489844,41.194698],[-71.492196,41.240876],[-71.51635,41.271549],[-71.557569,41.294018],[-71.624504,41.274129],[-71.653208,41.217805]]],[[[-71.088571,41.431315],[-71.121116,41.492787],[-71.131727,41.593762],[-71.140442,41.605298],[-71.132507,41.660356],[-71.195555,41.675152],[-71.260982,41.752118],[-71.326496,41.779801],[-71.347197,41.823101],[-71.333997,41.8623],[-71.338698,41.898399],[-71.3817,41.893199],[-71.381401,42.018798],[-71.799242,42.008065],[-71.797883,41.931011],[-71.787247,41.656065],[-71.797747,41.416834],[-71.842029,41.410706],[-71.829365,41.342432],[-71.857458,41.320789],[-71.892005,41.330275],[-71.907258,41.304483],[-71.790972,41.184101],[-71.79148,41.272165],[-71.73956,41.275449],[-71.672595,41.289079],[-71.575253,41.320955],[-71.494805,41.307355],[-71.374495,41.339011],[-71.24996,41.377713],[-71.124594,41.411474],[-71.088571,41.431315]]]]},\"properties\":{\"name\":\"Rhode Island\",\"ns_code\":\"01219835\",\"geoid\":\"44\",\"usps_abbrev\":\"RI\",\"fips_code\":\"44\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-124.159546,46.261132],[-124.147029,46.228857],[-124.123418,46.202729],[-124.067313,46.183849],[-124.023064,46.116272],[-124.009796,46.081973],[-124.008125,46.00994],[-124.070819,45.973939],[-124.092434,45.934261],[-124.086508,45.916326],[-124.045819,45.890596],[-124.037704,45.841614],[-124.055646,45.816486],[-124.070395,45.76274],[-124.060016,45.739541],[-124.010805,45.708285],[-124.016648,45.632119],[-124.029324,45.612461],[-124.03623,45.563032],[-124.02666,45.537877],[-124.053115,45.514225],[-124.064549,45.467567],[-124.058949,45.442008],[-124.035819,45.422051],[-124.043308,45.384061],[-124.076977,45.341846],[-124.073829,45.317509],[-124.046552,45.293168],[-124.040257,45.268828],[-124.059487,45.206655],[-124.04508,45.179795],[-124.050131,45.140688],[-124.081425,45.085457],[-124.097475,45.027358],[-124.086205,45.002864],[-124.097932,44.929657],[-124.116574,44.866335],[-124.136209,44.849241],[-124.147638,44.769113],[-124.139118,44.710536],[-124.152005,44.663203],[-124.13744,44.641002],[-124.151221,44.602911],[-124.134313,44.583182],[-124.162619,44.512912],[-124.153428,44.465036],[-124.16352,44.374841],[-124.187649,44.288559],[-124.189008,44.164223],[-124.199841,44.145535],[-124.195377,44.097462],[-124.208808,43.999843],[-124.239694,43.811055],[-124.265705,43.701558],[-124.285296,43.672726],[-124.280333,43.648394],[-124.296769,43.585958],[-124.343836,43.465012],[-124.380274,43.402846],[-124.425643,43.385886],[-124.476948,43.329371],[-124.474849,43.288566],[-124.455498,43.268233],[-124.470326,43.245496],[-124.465235,43.200308],[-124.481613,43.15944],[-124.505969,43.143379],[-124.516361,43.102542],[-124.505331,43.067953],[-124.530606,42.99983],[-124.572353,42.922514],[-124.615956,42.892882],[-124.645154,42.861892],[-124.700283,42.784028],[-124.700342,42.752526],[-124.682414,42.730185],[-124.64213,42.716113],[-124.584689,42.719302],[-124.544129,42.683817],[-124.54059,42.644492],[-124.508492,42.619564],[-124.473554,42.614734],[-124.479422,42.573951],[-124.465351,42.553033],[-124.489838,42.542901],[-124.518687,42.511076],[-124.547516,42.499833],[-124.567221,42.477791],[-124.565352,42.443565],[-124.532572,42.408396],[-124.494154,42.396086],[-124.500838,42.376137],[-124.486544,42.338278],[-124.490334,42.290646],[-124.471356,42.203723],[-124.433514,42.164832],[-124.432455,42.131848],[-124.409314,42.064077],[-124.375047,42.02923],[-124.328835,41.998334],[-124.100376,41.996712],[-123.657,41.995137],[-123.624554,41.999837],[-123.434976,42.001637],[-123.347562,41.999108],[-123.145962,42.009242],[-123.045343,42.003075],[-122.893866,42.002669],[-122.634738,42.004854],[-122.378226,42.009517],[-122.162708,42.007746],[-122.000319,42.003967],[-121.846712,42.00307],[-121.573199,41.99851],[-121.340061,41.996361],[-121.126131,41.996009],[-121.035247,41.993324],[-120.647178,41.993084],[-120.291374,41.993004],[-119.999168,41.99454],[-119.886699,41.997472],[-119.575037,41.995654],[-119.20828,41.993177],[-118.696409,41.991794],[-118.501002,41.995446],[-118.197189,41.996995],[-117.626018,41.998096],[-117.133667,42.000439],[-117.026197,41.99989],[-117.026531,42.374871],[-117.026231,42.831194],[-117.026874,43.127005],[-117.026651,43.374877],[-117.026634,43.80811],[-117.032289,43.828767],[-116.982347,43.86884],[-116.980544,43.91483],[-116.961535,43.918388],[-116.958328,43.983495],[-116.934499,44.021218],[-116.973185,44.049425],[-116.977085,44.08582],[-116.933701,44.100043],[-116.895931,44.154295],[-116.903607,44.179985],[-116.935443,44.193962],[-116.967259,44.194581],[-116.975382,44.242392],[-117.031862,44.248635],[-117.05651,44.230874],[-117.119861,44.278272],[-117.141264,44.258515],[-117.170341,44.258889],[-117.22274,44.298326],[-117.189842,44.335007],[-117.235117,44.373853],[-117.242675,44.396548],[-117.215072,44.427162],[-117.224104,44.483734],[-117.200237,44.492027],[-117.181583,44.52296],[-117.144161,44.545647],[-117.146032,44.568603],[-117.124754,44.583834],[-117.120522,44.614658],[-117.098221,44.640689],[-117.095868,44.664737],[-117.063824,44.703623],[-117.044217,44.74514],[-117.000303,44.756082],[-116.9347,44.783881],[-116.932199,44.802781],[-116.896589,44.846545],[-116.852427,44.887578],[-116.83199,44.933007],[-116.857549,44.982567],[-116.848037,45.021728],[-116.797329,45.060267],[-116.774847,45.105536],[-116.728757,45.144381],[-116.703697,45.240099],[-116.67473,45.275844],[-116.674648,45.315082],[-116.623385,45.393963],[-116.597447,45.41277],[-116.588195,45.44292],[-116.55483,45.46293],[-116.557621,45.503495],[-116.524172,45.548565],[-116.463635,45.602785],[-116.488818,45.650799],[-116.535117,45.691277],[-116.535698,45.734231],[-116.59421,45.77908],[-116.665268,45.782059],[-116.698079,45.820852],[-116.734543,45.826527],[-116.763402,45.81658],[-116.814131,45.877641],[-116.866544,45.916958],[-116.87598,45.954775],[-116.915989,45.995413],[-117.337668,45.998662],[-117.479654,45.997774],[-117.717774,45.999869],[-118.226074,46.000358],[-118.767877,46.001017],[-118.987129,45.999855],[-119.008558,45.97927],[-119.12612,45.932859],[-119.19553,45.92787],[-119.25715,45.939926],[-119.322509,45.933183],[-119.364396,45.921605],[-119.450256,45.917354],[-119.487829,45.906307],[-119.524632,45.908605],[-119.571584,45.925456],[-119.623393,45.905639],[-119.669877,45.856867],[-119.772927,45.845578],[-119.802655,45.84753],[-119.907461,45.828135],[-119.965744,45.824365],[-120.07015,45.785152],[-120.141352,45.773152],[-120.170453,45.761951],[-120.210754,45.725951],[-120.284356,45.72105],[-120.40396,45.699249],[-120.482362,45.694449],[-120.505863,45.700048],[-120.559465,45.738348],[-120.634968,45.745847],[-120.68937,45.715847],[-120.788872,45.686246],[-120.855674,45.671545],[-120.895575,45.642945],[-120.943977,45.656445],[-121.084933,45.647893],[-121.131954,45.609757],[-121.183841,45.606441],[-121.215779,45.671238],[-121.287323,45.687019],[-121.33777,45.704949],[-121.401739,45.692887],[-121.462849,45.701367],[-121.499153,45.720846],[-121.533106,45.726541],[-121.631167,45.704657],[-121.668362,45.705082],[-121.735104,45.694039],[-121.811304,45.706761],[-121.867167,45.693277],[-121.908267,45.654399],[-122.00369,45.61593],[-122.044374,45.609516],[-122.100197,45.584215],[-122.183906,45.577563],[-122.2017,45.564141],[-122.266701,45.543841],[-122.331502,45.548241],[-122.352802,45.569441],[-122.380302,45.575941],[-122.438674,45.563585],[-122.545876,45.596192],[-122.675001,45.618025],[-122.76381,45.657138],[-122.774511,45.680437],[-122.760108,45.734413],[-122.769532,45.780583],[-122.795605,45.81],[-122.785026,45.867699],[-122.8115,45.912724],[-122.813999,45.960983],[-122.837638,45.980821],[-122.856158,46.014469],[-122.878092,46.031281],[-122.904119,46.083734],[-122.962681,46.104817],[-123.004233,46.133823],[-123.115904,46.185268],[-123.166416,46.188979],[-123.280166,46.144843],[-123.371433,46.146372],[-123.430987,46.181571],[-123.427629,46.229348],[-123.479644,46.269131],[-123.586954,46.25589],[-123.648775,46.258803],[-123.687602,46.250214],[-123.741449,46.269155],[-123.778124,46.25425],[-123.871869,46.234949],[-123.930477,46.238805],[-123.976275,46.269907],[-124.035951,46.264183],[-124.159546,46.261132]]]},\"properties\":{\"name\":\"Oregon\",\"ns_code\":\"01155107\",\"geoid\":\"41\",\"usps_abbrev\":\"OR\",\"fips_code\":\"41\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-67.418043,18.373],[-67.434095,18.417164],[-67.462032,18.435741],[-67.506866,18.432477],[-67.537492,18.406337],[-67.535988,18.358278],[-67.494261,18.327896],[-67.453999,18.331264],[-67.425624,18.354511],[-67.418043,18.373]]],[[[-67.81398,18.037399],[-67.791511,18.074672],[-67.791986,18.107458],[-67.81126,18.143885],[-67.852443,18.170111],[-67.894933,18.166674],[-67.91617,18.200104],[-67.936941,18.209366],[-67.973663,18.2036],[-67.998691,18.170776],[-67.98692,18.109298],[-67.998296,18.083761],[-67.976136,18.039593],[-67.945834,18.017536],[-67.900887,17.99948],[-67.871392,18.000313],[-67.838479,18.013851],[-67.81398,18.037399]]],[[[-66.578441,18.541431],[-66.642332,18.543529],[-66.734606,18.526012],[-66.770444,18.540028],[-66.804219,18.543784],[-66.883874,18.541664],[-66.908102,18.533297],[-66.968656,18.542992],[-67.017094,18.562645],[-67.104836,18.566588],[-67.160467,18.552543],[-67.205744,18.517128],[-67.221342,18.476157],[-67.208359,18.442133],[-67.249626,18.418572],[-67.302717,18.401559],[-67.318694,18.381034],[-67.319911,18.343187],[-67.285964,18.282165],[-67.261333,18.25604],[-67.230465,18.24656],[-67.216052,18.213],[-67.237252,18.16765],[-67.235374,18.120145],[-67.253375,18.104651],[-67.255365,18.057672],[-67.266565,17.990341],[-67.26068,17.92854],[-67.233481,17.899512],[-67.194504,17.881745],[-67.129907,17.897151],[-67.006021,17.885795],[-66.976924,17.878733],[-66.917795,17.875479],[-66.881454,17.891299],[-66.82531,17.900449],[-66.788154,17.916064],[-66.713398,17.912852],[-66.672186,17.896901],[-66.641673,17.911973],[-66.58068,17.910465],[-66.586906,17.876437],[-66.571297,17.84548],[-66.548561,17.833252],[-66.513313,17.835415],[-66.479395,17.852022],[-66.461742,17.86967],[-66.424485,17.881992],[-66.394153,17.869538],[-66.303093,17.882112],[-66.243312,17.862565],[-66.213273,17.864509],[-66.179711,17.882565],[-66.148497,17.877927],[-66.11414,17.882628],[-66.076723,17.910671],[-66.0498,17.897745],[-66.002807,17.898089],[-65.972882,17.923245],[-65.94461,17.920571],[-65.852531,17.941486],[-65.797639,17.976982],[-65.782382,18.018998],[-65.748572,18.040939],[-65.739112,18.070778],[-65.716098,18.106266],[-65.68942,18.126404],[-65.63039,18.128965],[-65.627491,18.083413],[-65.588623,18.041081],[-65.549699,18.028449],[-65.495864,18.042021],[-65.447961,18.035921],[-65.312225,18.071142],[-65.2496,18.084305],[-65.223247,18.105224],[-65.215089,18.136258],[-65.232012,18.170845],[-65.278316,18.197918],[-65.328785,18.19993],[-65.394743,18.211742],[-65.431503,18.208672],[-65.464425,18.222214],[-65.456564,18.275163],[-65.46154,18.293299],[-65.423773,18.291377],[-65.360399,18.253436],[-65.28668,18.22599],[-65.252926,18.238022],[-65.227575,18.23577],[-65.191134,18.258884],[-65.168825,18.303516],[-65.181762,18.357727],[-65.208484,18.389371],[-65.235831,18.397733],[-65.302921,18.387502],[-65.322625,18.403295],[-65.362566,18.419472],[-65.392904,18.418946],[-65.424333,18.390867],[-65.463978,18.39348],[-65.58668,18.443333],[-65.624606,18.45126],[-65.678612,18.418533],[-65.723664,18.454449],[-65.757203,18.473204],[-65.857719,18.487249],[-65.876994,18.49955],[-65.932587,18.501043],[-65.993401,18.516598],[-66.107645,18.519209],[-66.19407,18.526512],[-66.21211,18.521844],[-66.260071,18.529443],[-66.31325,18.530695],[-66.342994,18.540861],[-66.415983,18.54362],[-66.452951,18.53896],[-66.478561,18.52442],[-66.515152,18.533752],[-66.578441,18.541431]]]]},\"properties\":{\"name\":\"Puerto Rico\",\"ns_code\":\"01779808\",\"geoid\":\"72\",\"usps_abbrev\":\"PR\",\"fips_code\":\"72\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-90.640927,42.508302],[-90.405927,42.506891],[-90.157406,42.508325],[-89.955237,42.505651],[-89.66982,42.505044],[-89.262669,42.49819],[-88.991611,42.496005],[-88.804148,42.4921],[-88.628997,42.495045],[-88.389994,42.494516],[-88.081066,42.495631],[-87.803218,42.492566],[-87.375058,42.493815],[-87.019935,42.493498],[-87.058716,42.765202],[-87.108397,43.12501],[-87.147166,43.379859],[-87.125079,43.670445],[-87.114102,43.734111],[-87.048847,44],[-87.013234,44.131905],[-87.00008,44.154821],[-86.847694,44.500339],[-86.855184,44.500339],[-86.686288,44.881002],[-86.49993,45.0808],[-86.433128,45.124991],[-86.2501,45.235735],[-86.282304,45.249983],[-86.754236,45.44361],[-87.101133,45.44423],[-87.17117,45.35254],[-87.315422,45.240657],[-87.405694,45.201969],[-87.44251,45.076434],[-87.661211,45.108279],[-87.6839,45.144135],[-87.735282,45.176565],[-87.72796,45.207956],[-87.687729,45.297492],[-87.647768,45.340563],[-87.658175,45.369476],[-87.693956,45.389893],[-87.754104,45.349442],[-87.79039,45.353445],[-87.850418,45.347492],[-87.884855,45.362792],[-87.849668,45.409518],[-87.861697,45.434473],[-87.812884,45.464329],[-87.792769,45.499967],[-87.808366,45.544662],[-87.774682,45.602024],[-87.82271,45.6627],[-87.781007,45.673934],[-87.812338,45.711303],[-87.839402,45.717814],[-87.879812,45.754843],[-87.963452,45.75822],[-87.99454,45.795637],[-88.05064,45.780999],[-88.094754,45.785755],[-88.132456,45.824156],[-88.070483,45.874329],[-88.101757,45.883192],[-88.102461,45.921546],[-88.126654,45.921791],[-88.180498,45.948287],[-88.230193,45.947251],[-88.25498,45.963512],[-88.301378,45.956459],[-88.380891,45.991813],[-88.423493,45.982072],[-88.464731,46.000738],[-88.49798,45.995794],[-88.50748,46.018516],[-88.565127,46.015729],[-88.616471,45.987732],[-88.658654,45.98929],[-88.678868,46.013595],[-88.739671,46.027423],[-88.782073,46.016185],[-88.851133,46.040444],[-89.09164,46.138447],[-89.463498,46.210062],[-89.918893,46.29776],[-90.120572,46.337039],[-90.118822,46.359531],[-90.158345,46.409696],[-90.216172,46.501272],[-90.263139,46.502668],[-90.310839,46.536289],[-90.350684,46.540325],[-90.395069,46.533768],[-90.418392,46.566099],[-90.125164,47.020679],[-90.00014,47.214969],[-89.957102,47.291103],[-90.264218,47.30027],[-90.654661,47.309822],[-91.000198,47.149758],[-91.319638,46.999929],[-91.500181,46.91332],[-91.59214,46.874935],[-91.875186,46.763303],[-91.981341,46.723091],[-92.020294,46.704041],[-92.089493,46.749237],[-92.13789,46.73954],[-92.146292,46.716032],[-92.189091,46.717541],[-92.176259,46.690473],[-92.212392,46.649941],[-92.265993,46.651041],[-92.292192,46.666042],[-92.292683,46.441283],[-92.294033,46.074377],[-92.335335,46.059422],[-92.350407,46.016368],[-92.410014,46.027251],[-92.442259,46.016177],[-92.472761,45.972952],[-92.525186,45.983862],[-92.551186,45.95224],[-92.637841,45.934469],[-92.712503,45.891705],[-92.765681,45.827252],[-92.757947,45.811216],[-92.784617,45.764199],[-92.809837,45.744172],[-92.869193,45.717568],[-92.888114,45.628377],[-92.883749,45.575483],[-92.823309,45.560934],[-92.773412,45.568235],[-92.726082,45.541112],[-92.726677,45.514462],[-92.680234,45.464344],[-92.646602,45.441635],[-92.650284,45.398769],[-92.704054,45.35366],[-92.698967,45.336374],[-92.737121,45.300459],[-92.762004,45.288213],[-92.751708,45.218666],[-92.766808,45.185466],[-92.744938,45.108309],[-92.802911,45.065403],[-92.761953,45.0221],[-92.769445,44.97215],[-92.750645,44.937299],[-92.773946,44.889997],[-92.765668,44.84101],[-92.808415,44.751604],[-92.732043,44.714345],[-92.664715,44.663379],[-92.621847,44.639018],[-92.622537,44.616054],[-92.568946,44.603372],[-92.539909,44.567456],[-92.432527,44.565705],[-92.347567,44.557149],[-92.317357,44.542512],[-92.298484,44.494621],[-92.282364,44.477707],[-92.221083,44.440386],[-92.123679,44.42184],[-92.053549,44.401375],[-92.038147,44.388731],[-91.959996,44.359797],[-91.928224,44.335473],[-91.887189,44.252513],[-91.892963,44.235149],[-91.875158,44.200575],[-91.817302,44.164235],[-91.710597,44.12048],[-91.646446,44.063337],[-91.5826,44.027381],[-91.507121,44.01898],[-91.440541,44.001518],[-91.406011,43.963929],[-91.36472,43.93488],[-91.35674,43.916564],[-91.284138,43.847065],[-91.262433,43.79217],[-91.244135,43.774667],[-91.26845,43.709824],[-91.268748,43.615348],[-91.236666,43.587656],[-91.24382,43.54913],[-91.217706,43.50055],[-91.232241,43.460018],[-91.203144,43.419805],[-91.19767,43.395334],[-91.21477,43.365874],[-91.201847,43.349103],[-91.129121,43.32635],[-91.085652,43.29187],[-91.05791,43.253968],[-91.079278,43.228259],[-91.119115,43.200366],[-91.1562,43.142945],[-91.175253,43.134665],[-91.179382,43.068624],[-91.174692,43.038713],[-91.14655,42.963345],[-91.143491,42.904698],[-91.10056,42.883078],[-91.065606,42.756766],[-91.051275,42.737001],[-90.952415,42.686778],[-90.720209,42.640758],[-90.702671,42.630756],[-90.686975,42.591774],[-90.645627,42.5441],[-90.640927,42.508302]]]},\"properties\":{\"name\":\"Wisconsin\",\"ns_code\":\"01779806\",\"geoid\":\"55\",\"usps_abbrev\":\"WI\",\"fips_code\":\"55\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-97.228722,49.000562],[-97.469707,49.000467],[-98.121446,49.000303],[-98.625396,49.000053],[-99.250409,48.999508],[-99.722716,48.999108],[-100.069304,48.999097],[-100.629258,48.999248],[-101.250435,48.999319],[-101.625438,48.999168],[-102.216993,48.998553],[-102.51754,48.998949],[-102.98106,48.999229],[-103.574797,48.999146],[-104.048736,48.999877],[-104.048275,48.706527],[-104.045937,48.249276],[-104.043865,47.968937],[-104.041443,47.863054],[-104.043273,47.750027],[-104.044406,47.459399],[-104.044777,47.125012],[-104.046067,46.838634],[-104.045045,46.509788],[-104.045235,46.125004],[-104.045441,45.94531],[-103.744941,45.945061],[-103.369029,45.945152],[-102.983434,45.945079],[-102.549895,45.944836],[-102.126414,45.944695],[-101.786099,45.944341],[-101.420443,45.943806],[-101.041137,45.943455],[-100.750596,45.943662],[-100.308163,45.943258],[-99.760635,45.940811],[-99.296714,45.940059],[-98.756598,45.938791],[-98.414398,45.936493],[-98.0285,45.935777],[-97.482542,45.935091],[-97.225797,45.93537],[-96.563672,45.935245],[-96.577875,46.027034],[-96.554436,46.084186],[-96.588387,46.179084],[-96.584378,46.203539],[-96.597792,46.22888],[-96.59966,46.330481],[-96.667422,46.376117],[-96.693927,46.422081],[-96.7162,46.436679],[-96.713672,46.465012],[-96.735054,46.48057],[-96.750448,46.544048],[-96.745697,46.571027],[-96.796041,46.631209],[-96.79951,46.675245],[-96.7811,46.722048],[-96.787136,46.785651],[-96.802544,46.811521],[-96.775059,46.840363],[-96.776713,46.895619],[-96.759599,46.915708],[-96.791758,46.928465],[-96.834839,47.007254],[-96.817737,47.051118],[-96.826147,47.076497],[-96.81619,47.108481],[-96.83472,47.15639],[-96.822062,47.185175],[-96.839359,47.196357],[-96.829126,47.234723],[-96.843857,47.292939],[-96.830618,47.338014],[-96.852871,47.375009],[-96.837974,47.38273],[-96.865852,47.418975],[-96.855814,47.439723],[-96.849853,47.497311],[-96.86175,47.541398],[-96.849908,47.590754],[-96.888092,47.639648],[-96.899237,47.689579],[-97.005447,47.87024],[-97.02544,47.885935],[-97.018107,47.920228],[-97.0556,47.949085],[-97.071288,48.045797],[-97.098829,48.071477],[-97.146531,48.144055],[-97.149755,48.185824],[-97.13165,48.20711],[-97.135835,48.236919],[-97.11657,48.279661],[-97.13125,48.319543],[-97.126745,48.34214],[-97.155481,48.363768],[-97.15721,48.385732],[-97.131479,48.406586],[-97.149236,48.443358],[-97.14208,48.463347],[-97.152765,48.530285],[-97.164405,48.557047],[-97.12424,48.633973],[-97.09991,48.658522],[-97.105442,48.689648],[-97.136403,48.725733],[-97.150559,48.782562],[-97.183988,48.834085],[-97.17302,48.851568],[-97.199891,48.88259],[-97.234128,48.947898],[-97.228722,49.000562]]]},\"properties\":{\"name\":\"North Dakota\",\"ns_code\":\"01779797\",\"geoid\":\"38\",\"usps_abbrev\":\"ND\",\"fips_code\":\"38\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-119.999168,41.99454],[-119.998287,41.618818],[-119.99976,41.236061],[-119.999875,40.999894],[-119.998785,40.77385],[-119.995976,40.57055],[-119.995705,40.37583],[-119.997903,39.981636],[-120.001133,39.752229],[-120.00082,39.606828],[-120.006127,39.374106],[-120.006473,39.272878],[-120.001014,38.999574],[-119.626004,38.740941],[-119.461282,38.627533],[-119.097159,38.372851],[-118.771867,38.141871],[-118.714201,38.102105],[-118.372572,37.856012],[-117.875927,37.497267],[-117.712358,37.374931],[-117.500117,37.22038],[-117.373451,37.124932],[-117.000895,36.847694],[-116.750926,36.658377],[-116.488233,36.459097],[-116.380347,36.374961],[-116.250818,36.276939],[-115.918743,36.019936],[-115.646336,35.807735],[-115.25972,35.503541],[-115.100957,35.375681],[-114.994666,35.292511],[-114.633487,35.001857],[-114.63819,35.022069],[-114.604182,35.073715],[-114.645152,35.104995],[-114.626316,35.120423],[-114.578263,35.12881],[-114.569529,35.162317],[-114.595163,35.321883],[-114.627325,35.410193],[-114.663541,35.447797],[-114.678892,35.501276],[-114.65677,35.534964],[-114.670191,35.583471],[-114.653927,35.611739],[-114.689001,35.65028],[-114.682657,35.688571],[-114.705447,35.711757],[-114.694267,35.756633],[-114.712026,35.805529],[-114.695757,35.833387],[-114.706076,35.845873],[-114.663214,35.873692],[-114.708112,35.909933],[-114.707603,35.92795],[-114.740536,35.975545],[-114.740866,36.012928],[-114.723324,36.026588],[-114.754508,36.086171],[-114.734085,36.104672],[-114.681847,36.109192],[-114.627079,36.140761],[-114.620605,36.131759],[-114.57109,36.151099],[-114.508104,36.149713],[-114.504715,36.127188],[-114.456487,36.138032],[-114.443736,36.125593],[-114.412491,36.146511],[-114.370181,36.142624],[-114.323458,36.101186],[-114.304171,36.07558],[-114.314427,36.060523],[-114.270862,36.045523],[-114.243865,36.015266],[-114.211932,36.014834],[-114.174683,36.02667],[-114.1534,36.02317],[-114.066798,36.179087],[-114.043944,36.19335],[-114.045559,36.288837],[-114.043135,36.380478],[-114.049935,36.709521],[-114.0506,37.000396],[-114.052541,37.112881],[-114.051404,37.23385],[-114.052923,37.592956],[-114.049679,37.8236],[-114.050424,37.999958],[-114.049417,38.264849],[-114.050519,38.443899],[-114.048044,38.874949],[-114.049104,39.005509],[-114.047077,39.499811],[-114.047269,39.759224],[-114.046833,40.035017],[-114.045827,40.424823],[-114.042674,40.845903],[-114.041396,41.219958],[-114.040231,41.491148],[-114.039648,41.884815],[-114.041723,41.99372],[-114.467581,41.995492],[-114.598267,41.994511],[-114.806948,42.001842],[-115.031783,41.996008],[-115.305702,41.996121],[-115.870181,41.996766],[-116.243962,41.997651],[-116.368477,41.99628],[-116.606567,41.99739],[-117.026197,41.99989],[-117.133667,42.000439],[-117.626018,41.998096],[-118.197189,41.996995],[-118.501002,41.995446],[-118.696409,41.991794],[-119.20828,41.993177],[-119.575037,41.995654],[-119.886699,41.997472],[-119.999168,41.99454]]]},\"properties\":{\"name\":\"Nevada\",\"ns_code\":\"01779793\",\"geoid\":\"32\",\"usps_abbrev\":\"NV\",\"fips_code\":\"32\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-83.108614,35.000659],[-83.501536,34.992862],[-83.620185,34.992091],[-83.619985,34.986592],[-83.749894,34.987691],[-84.039375,34.986973],[-84.321869,34.988408],[-84.809184,34.987569],[-84.952772,34.987943],[-85.466509,34.982971],[-85.605165,34.984678],[-85.561416,34.750079],[-85.527261,34.588683],[-85.47045,34.328239],[-85.406748,34.002314],[-85.356921,33.748233],[-85.304439,33.482884],[-85.233108,33.111714],[-85.184737,32.870514],[-85.154855,32.839689],[-85.165635,32.808222],[-85.122326,32.774383],[-85.14298,32.761654],[-85.113167,32.732813],[-85.122738,32.716032],[-85.112337,32.683234],[-85.088483,32.657758],[-85.105437,32.644934],[-85.08224,32.616264],[-85.067199,32.579306],[-85.023645,32.542955],[-85.001532,32.514741],[-84.99353,32.450743],[-84.971831,32.442843],[-84.983866,32.362386],[-85.004582,32.345196],[-85.002791,32.322428],[-84.9338,32.29826],[-84.901191,32.258374],[-84.930727,32.21895],[-84.973328,32.21765],[-84.963728,32.195852],[-85.009224,32.181646],[-85.06206,32.132486],[-85.045063,32.088063],[-85.05883,32.046656],[-85.055075,32.010714],[-85.08683,31.957758],[-85.08213,31.944658],[-85.140731,31.857461],[-85.131231,31.815262],[-85.141831,31.782363],[-85.11913,31.730964],[-85.12593,31.696265],[-85.092429,31.659966],[-85.087029,31.640966],[-85.057473,31.618624],[-85.05796,31.57084],[-85.041428,31.539293],[-85.071621,31.468384],[-85.065639,31.439521],[-85.09217,31.364576],[-85.083828,31.326971],[-85.090334,31.293606],[-85.113261,31.264343],[-85.096843,31.225239],[-85.106963,31.202693],[-85.099647,31.164942],[-85.077801,31.157889],[-85.052867,31.119489],[-85.037062,31.109753],[-85.028573,31.074583],[-85.01139,31.053547],[-85.002499,31.000685],[-85.004026,30.973468],[-84.980427,30.962086],[-84.983527,30.935486],[-84.94252,30.888488],[-84.926824,30.846389],[-84.936824,30.818489],[-84.911122,30.751191],[-84.897622,30.751391],[-84.864693,30.711542],[-84.644815,30.701991],[-84.474409,30.692793],[-84.374894,30.689793],[-83.975655,30.67037],[-83.499925,30.645674],[-83.065223,30.620039],[-82.590548,30.592186],[-82.214677,30.568556],[-82.240403,30.53777],[-82.225072,30.50769],[-82.20124,30.485114],[-82.210733,30.42564],[-82.192596,30.378831],[-82.171623,30.359918],[-82.105018,30.368817],[-82.065598,30.358114],[-82.037218,30.371848],[-82.043762,30.414869],[-82.016906,30.475111],[-82.018554,30.528992],[-82.00581,30.565358],[-82.05022,30.674538],[-82.035927,30.706205],[-82.039795,30.747297],[-82.011603,30.761878],[-82.002385,30.789798],[-81.972458,30.779926],[-81.963928,30.8181],[-81.891052,30.822308],[-81.881858,30.799961],[-81.793968,30.787239],[-81.74096,30.765142],[-81.718109,30.744806],[-81.659805,30.751265],[-81.647483,30.727394],[-81.623771,30.736052],[-81.572395,30.721897],[-81.489898,30.726156],[-81.464013,30.710983],[-81.347015,30.712444],[-81.367416,30.752514],[-81.400482,30.766426],[-81.374809,30.817377],[-81.350204,30.889109],[-81.345207,30.931503],[-81.317535,30.9485],[-81.304779,30.985712],[-81.344498,31.01811],[-81.345306,31.059362],[-81.297874,31.080952],[-81.285881,31.109444],[-81.292145,31.155415],[-81.234337,31.170258],[-81.18277,31.265846],[-81.183571,31.329197],[-81.172546,31.376831],[-81.190758,31.399876],[-81.166939,31.423445],[-81.143845,31.471521],[-81.125435,31.477541],[-81.087067,31.529158],[-81.069633,31.642405],[-81.049789,31.688633],[-81.056623,31.714875],[-81.025824,31.720227],[-80.9972,31.748503],[-80.970726,31.798353],[-80.938316,31.809008],[-80.896988,31.852806],[-80.855339,31.910809],[-80.819275,31.933855],[-80.789841,31.96439],[-80.780693,32.01137],[-80.751429,32.033468],[-80.89296,32.034951],[-80.926769,32.041663],[-80.954483,32.068616],[-81.01197,32.100178],[-81.032633,32.085476],[-81.066965,32.090384],[-81.117225,32.117604],[-81.128142,32.169097],[-81.123149,32.20132],[-81.15577,32.245793],[-81.141414,32.272039],[-81.123276,32.276975],[-81.122631,32.30699],[-81.155114,32.344341],[-81.178283,32.392986],[-81.205565,32.423929],[-81.200408,32.468314],[-81.251069,32.517805],[-81.279516,32.536226],[-81.299796,32.563049],[-81.319121,32.559596],[-81.389205,32.595416],[-81.41866,32.629392],[-81.393033,32.651542],[-81.405711,32.688727],[-81.427505,32.702242],[-81.406074,32.741625],[-81.423331,32.778512],[-81.426475,32.840773],[-81.457061,32.850389],[-81.483198,32.921802],[-81.502716,32.938688],[-81.4919,33.006694],[-81.51059,33.025805],[-81.565501,33.047888],[-81.614994,33.095551],[-81.645433,33.094051],[-81.704634,33.11645],[-81.743835,33.14145],[-81.768235,33.167949],[-81.756935,33.197848],[-81.789236,33.208247],[-81.852822,33.248542],[-81.835738,33.271045],[-81.863336,33.289844],[-81.847736,33.307243],[-81.918337,33.332842],[-81.94474,33.364041],[-81.931549,33.382156],[-81.93139,33.428474],[-81.913126,33.438333],[-81.934136,33.468337],[-81.985938,33.486536],[-82.00983,33.530313],[-82.046335,33.56383],[-82.092016,33.581862],[-82.10624,33.595637],[-82.133523,33.590535],[-82.196584,33.630583],[-82.200164,33.662012],[-82.23432,33.699836],[-82.247472,33.752591],[-82.299393,33.785037],[-82.300213,33.800627],[-82.346933,33.834298],[-82.408353,33.86632],[-82.42281,33.863757],[-82.494811,33.911712],[-82.524515,33.94336],[-82.556765,33.945324],[-82.579553,33.979688],[-82.593887,34.028109],[-82.627907,34.06419],[-82.675219,34.129779],[-82.717459,34.150546],[-82.740585,34.207268],[-82.743172,34.251598],[-82.754242,34.275787],[-82.782128,34.298278],[-82.798658,34.341777],[-82.833571,34.364092],[-82.8644,34.459785],[-82.901551,34.486764],[-82.990684,34.479968],[-83.034565,34.483571],[-83.086807,34.517555],[-83.122904,34.560117],[-83.168278,34.590998],[-83.23258,34.611597],[-83.255281,34.637696],[-83.293183,34.654296],[-83.304641,34.669561],[-83.3389,34.680039],[-83.352692,34.716904],[-83.349075,34.736633],[-83.326309,34.750839],[-83.32415,34.787479],[-83.246361,34.856533],[-83.242599,34.877808],[-83.205032,34.880329],[-83.158966,34.929138],[-83.120872,34.942039],[-83.108614,35.000659]]]},\"properties\":{\"name\":\"Georgia\",\"ns_code\":\"01705317\",\"geoid\":\"13\",\"usps_abbrev\":\"GA\",\"fips_code\":\"13\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-74.694914,41.357423],[-74.457584,41.248225],[-74.301994,41.172594],[-74.18239,41.121595],[-73.902679,40.997307],[-73.896479,40.981701],[-73.929821,40.888682],[-73.963182,40.8269],[-74.013784,40.756601],[-74.026284,40.699902],[-74.055739,40.65176],[-74.143255,40.642149],[-74.18139,40.646475],[-74.202247,40.630903],[-74.19952,40.597539],[-74.218398,40.556996],[-74.250609,40.541851],[-74.25909,40.497207],[-74.228153,40.477399],[-73.949912,40.52554],[-73.886652,40.489794],[-73.826158,40.522874],[-73.768781,40.533747],[-73.624574,40.534511],[-73.486496,40.520505],[-73.326272,40.556916],[-73.274025,40.551314],[-73.249562,40.562565],[-73.124558,40.598366],[-73.014004,40.625099],[-72.874542,40.675019],[-72.749544,40.713405],[-72.624539,40.745567],[-72.374529,40.818595],[-72.249525,40.866765],[-71.999516,40.956019],[-71.932751,40.981952],[-71.884775,40.992269],[-71.837088,41.010892],[-71.794139,41.036977],[-71.777491,41.067292],[-71.790635,41.101837],[-71.790972,41.184101],[-71.907258,41.304483],[-71.929451,41.310387],[-72.019214,41.290594],[-72.081775,41.26224],[-72.527901,41.177774],[-72.999547,41.087108],[-73.249557,41.025866],[-73.612885,40.950943],[-73.656771,40.984509],[-73.655241,41.011852],[-73.727775,41.100696],[-73.482695,41.212772],[-73.550962,41.295421],[-73.521041,41.619773],[-73.510961,41.758749],[-73.489615,42.000092],[-73.487314,42.049638],[-73.508142,42.086257],[-73.356894,42.49836],[-73.264957,42.74594],[-73.276421,42.746019],[-73.290944,42.80192],[-73.278673,42.83341],[-73.27417,42.949028],[-73.256475,43.260093],[-73.246844,43.517252],[-73.241486,43.532621],[-73.258631,43.56495],[-73.284912,43.579272],[-73.302553,43.625709],[-73.365563,43.623441],[-73.398126,43.568065],[-73.428637,43.583994],[-73.41832,43.623325],[-73.42791,43.634428],[-73.405243,43.688368],[-73.370612,43.725329],[-73.351265,43.769823],[-73.391014,43.818411],[-73.372589,43.845354],[-73.3741,43.875304],[-73.408859,43.934505],[-73.410385,44.026503],[-73.437905,44.045125],[-73.411225,44.113767],[-73.390625,44.191068],[-73.363393,44.207737],[-73.343376,44.23819],[-73.319603,44.249909],[-73.310914,44.274298],[-73.324195,44.310033],[-73.333613,44.3723],[-73.31504,44.388526],[-73.296052,44.428334],[-73.299896,44.476651],[-73.342933,44.551907],[-73.381849,44.589316],[-73.38982,44.617211],[-73.370064,44.666071],[-73.363792,44.745254],[-73.333152,44.789588],[-73.335044,44.804109],[-73.379452,44.83801],[-73.356218,44.904492],[-73.338622,44.919366],[-73.338244,44.964751],[-73.354633,44.987352],[-73.343124,45.01084],[-73.639718,45.003464],[-73.874597,45.001223],[-74.150235,44.991357],[-74.335184,44.991905],[-74.45753,44.997033],[-74.683966,44.99969],[-74.731301,44.990419],[-74.826576,45.015865],[-74.888042,44.999658],[-74.907948,44.983369],[-74.972491,44.983405],[-75.005151,44.958406],[-75.064826,44.929449],[-75.134416,44.915102],[-75.165123,44.893324],[-75.273545,44.853084],[-75.301976,44.826637],[-75.344538,44.809139],[-75.477641,44.720224],[-75.505902,44.705081],[-75.618364,44.619637],[-75.662373,44.591927],[-75.766226,44.515847],[-75.807765,44.471641],[-75.820819,44.432239],[-75.912985,44.368084],[-75.970178,44.342828],[-76.000998,44.347534],[-76.045224,44.33172],[-76.097351,44.299541],[-76.130884,44.296635],[-76.161825,44.280776],[-76.164258,44.239603],[-76.191324,44.221239],[-76.245487,44.203664],[-76.312639,44.199045],[-76.352679,44.134743],[-76.438287,44.094172],[-76.49967,43.999538],[-76.753538,43.684275],[-76.776233,43.629529],[-76.999264,43.629213],[-77.12471,43.631118],[-77.696302,43.631262],[-78.124742,43.631175],[-78.374749,43.632092],[-78.688295,43.634742],[-78.749761,43.609258],[-79.12477,43.478464],[-79.200936,43.450422],[-79.070452,43.262455],[-79.05606,43.254156],[-79.048658,43.199655],[-79.052925,43.17346],[-79.042357,43.143654],[-79.069556,43.120255],[-79.057961,43.106957],[-79.074459,43.077863],[-79.00713,43.065757],[-78.999435,43.056057],[-79.023256,43.016356],[-79.011563,42.985256],[-78.961761,42.957756],[-78.93236,42.955857],[-78.905659,42.923357],[-78.905758,42.899957],[-78.930559,42.850105],[-78.99976,42.804368],[-79.532228,42.603855],[-79.762418,42.516072],[-79.762569,42.375058],[-79.761313,41.998808],[-79.47252,41.998254],[-79.006166,41.999124],[-78.874759,41.997559],[-78.59665,41.999877],[-78.271204,41.998968],[-78.124731,42.000451],[-77.77671,41.998428],[-77.505308,42.00007],[-77.126846,41.999414],[-76.835182,42.002134],[-76.445733,41.998747],[-75.983082,41.999035],[-75.742217,41.997864],[-75.359579,41.999445],[-75.310358,41.949012],[-75.279094,41.938917],[-75.271293,41.887358],[-75.223734,41.857456],[-75.204002,41.869867],[-75.114399,41.843583],[-75.113334,41.822782],[-75.085789,41.811626],[-75.10464,41.774203],[-75.075942,41.771518],[-75.053227,41.751662],[-75.052736,41.688393],[-75.044224,41.617978],[-75.074613,41.605711],[-75.027343,41.563541],[-75.023018,41.533147],[-74.987645,41.508738],[-74.981652,41.479945],[-74.941798,41.483542],[-74.909181,41.472436],[-74.890175,41.438178],[-74.812123,41.442982],[-74.793856,41.422671],[-74.757926,41.423437],[-74.713412,41.389814],[-74.694914,41.357423]]]},\"properties\":{\"name\":\"New York\",\"ns_code\":\"01779796\",\"geoid\":\"36\",\"usps_abbrev\":\"NY\",\"fips_code\":\"36\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-94.617919,36.499414],[-94.580391,36.264929],[-94.5105,35.862498],[-94.450445,35.502435],[-94.430662,35.392478],[-94.438963,35.184733],[-94.449013,34.902025],[-94.457209,34.646764],[-94.466046,34.350002],[-94.472906,34.087971],[-94.485878,33.637865],[-94.452701,33.617373],[-94.4532,33.59178],[-94.430041,33.591124],[-94.38953,33.546739],[-94.35359,33.544005],[-94.352643,33.56063],[-94.309582,33.551673],[-94.291212,33.581478],[-94.238866,33.576748],[-94.226188,33.552976],[-94.203594,33.566534],[-94.184308,33.594578],[-94.162266,33.588906],[-94.147428,33.565183],[-94.082352,33.57572],[-94.073833,33.556006],[-94.04345,33.552253],[-94.04273,33.241822],[-94.042964,33.019219],[-93.49052,33.018442],[-93.000211,33.017625],[-92.55941,33.012734],[-92.219778,33.009166],[-92.041143,33.007784],[-91.587251,33.00631],[-91.166073,33.004106],[-91.162363,33.019683],[-91.129087,33.033554],[-91.124639,33.064127],[-91.201125,33.108185],[-91.183662,33.141691],[-91.150362,33.130695],[-91.104317,33.131597],[-91.087589,33.145176],[-91.084366,33.180856],[-91.09171,33.220813],[-91.065629,33.232982],[-91.043985,33.269835],[-91.07853,33.283306],[-91.1001,33.238124],[-91.141216,33.298397],[-91.142219,33.348989],[-91.120409,33.363809],[-91.058152,33.428705],[-91.086498,33.451576],[-91.096942,33.41099],[-91.120678,33.388545],[-91.166304,33.379709],[-91.209295,33.404895],[-91.131885,33.430062],[-91.117975,33.453806],[-91.125109,33.472842],[-91.1646,33.497841],[-91.177381,33.484853],[-91.16946,33.452137],[-91.202829,33.434469],[-91.235181,33.438972],[-91.183372,33.498157],[-91.219297,33.532364],[-91.228489,33.564667],[-91.175979,33.582968],[-91.152148,33.582721],[-91.130445,33.606034],[-91.139209,33.625658],[-91.171168,33.647766],[-91.221117,33.662988],[-91.227857,33.683073],[-91.207807,33.700095],[-91.159746,33.706096],[-91.133416,33.67679],[-91.078507,33.658283],[-91.034565,33.673018],[-91.04678,33.706313],[-91.06829,33.716477],[-91.125527,33.70878],[-91.14662,33.732456],[-91.134744,33.782285],[-91.107318,33.770619],[-91.053886,33.778701],[-91.026782,33.763642],[-91.000106,33.769165],[-90.99175,33.792597],[-91.046849,33.815365],[-91.073011,33.857449],[-91.061247,33.877505],[-91.01151,33.924733],[-91.020097,33.937127],[-91.088164,33.960078],[-91.087921,33.975335],[-91.048367,33.985078],[-91.01889,34.003151],[-91.000108,33.966428],[-90.967632,33.963324],[-90.961551,33.979946],[-90.987948,34.019038],[-90.896897,34.024627],[-90.870738,34.084281],[-90.882623,34.096613],[-90.92123,34.093948],[-90.95517,34.118833],[-90.954299,34.138496],[-90.910007,34.165502],[-90.86788,34.142146],[-90.822593,34.144054],[-90.807223,34.163745],[-90.816572,34.183023],[-90.887887,34.181982],[-90.916056,34.196923],[-90.937042,34.235341],[-90.905876,34.243541],[-90.882162,34.216975],[-90.847808,34.20653],[-90.833335,34.264264],[-90.802924,34.282465],[-90.765165,34.280524],[-90.740889,34.306538],[-90.765174,34.342818],[-90.756197,34.367256],[-90.712088,34.363805],[-90.67666,34.371411],[-90.693129,34.32257],[-90.657371,34.327287],[-90.666787,34.355817],[-90.638672,34.385043],[-90.580681,34.410551],[-90.565809,34.4354],[-90.589919,34.484794],[-90.580115,34.514537],[-90.545728,34.53775],[-90.545891,34.563257],[-90.581693,34.604227],[-90.588419,34.670963],[-90.561198,34.69676],[-90.539401,34.670911],[-90.555104,34.646236],[-90.517168,34.630928],[-90.479718,34.659934],[-90.46282,34.684074],[-90.477031,34.701174],[-90.546053,34.702076],[-90.567486,34.72329],[-90.535408,34.750062],[-90.547859,34.779194],[-90.514708,34.801766],[-90.50152,34.77479],[-90.522077,34.754649],[-90.520181,34.731902],[-90.473384,34.725962],[-90.451758,34.741904],[-90.47228,34.802465],[-90.456935,34.823379],[-90.483876,34.861333],[-90.46615,34.890989],[-90.438311,34.884583],[-90.428756,34.841406],[-90.414859,34.831846],[-90.313476,34.871698],[-90.250095,34.90732],[-90.246116,34.944316],[-90.296422,34.976346],[-90.309289,34.995694],[-90.291996,35.041793],[-90.209397,35.026546],[-90.173603,35.118073],[-90.139024,35.133995],[-90.10059,35.116691],[-90.066591,35.13599],[-90.117393,35.18789],[-90.097408,35.194794],[-90.07934,35.228838],[-90.09949,35.251393],[-90.152094,35.255989],[-90.168794,35.279088],[-90.163812,35.296115],[-90.109093,35.304987],[-90.107312,35.343143],[-90.074992,35.384152],[-90.114373,35.411108],[-90.142624,35.407669],[-90.143475,35.387602],[-90.179265,35.385194],[-90.168882,35.422206],[-90.129447,35.44193],[-90.118928,35.468328],[-90.08365,35.478297],[-90.068156,35.46678],[-90.069434,35.408163],[-90.031584,35.427662],[-90.019108,35.465067],[-90.043462,35.492339],[-90.050437,35.515894],[-90.03955,35.548557],[-89.989499,35.560136],[-89.957113,35.539818],[-89.945026,35.519388],[-89.918001,35.514136],[-89.910408,35.548761],[-89.945651,35.561558],[-89.957898,35.585474],[-89.947815,35.600093],[-89.896998,35.614882],[-89.856619,35.634444],[-89.857942,35.668312],[-89.886979,35.653637],[-89.929435,35.658974],[-89.955753,35.690621],[-89.950277,35.738494],[-89.906847,35.759699],[-89.877256,35.741369],[-89.820876,35.756868],[-89.796324,35.792807],[-89.758396,35.810469],[-89.74164,35.805633],[-89.704387,35.819607],[-89.709261,35.838911],[-89.761524,35.857239],[-89.772855,35.876244],[-89.742606,35.906653],[-89.714934,35.906247],[-89.66599,35.882868],[-89.644395,35.894782],[-89.656147,35.92581],[-89.71304,35.961644],[-89.733095,36.000608],[-90.120302,35.997876],[-90.37789,35.995683],[-90.348208,36.041573],[-90.300557,36.097525],[-90.29293,36.114499],[-90.235372,36.13948],[-90.220846,36.184146],[-90.187556,36.20566],[-90.125487,36.231232],[-90.115101,36.265282],[-90.083795,36.271938],[-90.064413,36.302108],[-90.08241,36.321949],[-90.063594,36.384235],[-90.078873,36.399358],[-90.127286,36.411063],[-90.15228,36.49795],[-90.513782,36.49844],[-90.948379,36.498463],[-91.422068,36.497138],[-91.539505,36.499236],[-92.019432,36.498338],[-92.375159,36.497199],[-92.46094,36.498671],[-92.753678,36.497975],[-93.136615,36.49796],[-93.426451,36.498935],[-93.875197,36.498722],[-94.114073,36.498632],[-94.617919,36.499414]]]},\"properties\":{\"name\":\"Arkansas\",\"ns_code\":\"00068085\",\"geoid\":\"05\",\"usps_abbrev\":\"AR\",\"fips_code\":\"05\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-102.042089,36.993016],[-101.602243,36.995047],[-101.090254,36.997876],[-100.734513,36.999059],[-100.107891,37.002312],[-99.630997,36.99997],[-99.105701,36.999451],[-98.797019,36.999459],[-98.300465,36.997684],[-97.994149,36.998508],[-97.595028,36.998546],[-97.039692,36.99896],[-96.500289,36.998643],[-96.00081,36.99886],[-95.45793,36.999234],[-95.165191,36.999631],[-94.75258,36.99859],[-94.617964,36.998905],[-94.618313,37.194687],[-94.616724,37.500123],[-94.617669,37.776193],[-94.614164,37.974007],[-94.612503,38.220379],[-94.613356,38.457967],[-94.608172,38.811095],[-94.607151,39.105582],[-94.588658,39.151163],[-94.606455,39.160827],[-94.650647,39.154092],[-94.659162,39.174126],[-94.688788,39.183228],[-94.735743,39.169191],[-94.762858,39.179991],[-94.782951,39.207162],[-94.831679,39.215938],[-94.827479,39.249896],[-94.84632,39.268481],[-94.898516,39.29845],[-94.910321,39.351295],[-94.879088,39.375703],[-94.888972,39.392432],[-94.919225,39.385174],[-94.972952,39.421705],[-94.990172,39.446192],[-95.033408,39.460876],[-95.050552,39.497514],[-95.108065,39.539919],[-95.107474,39.573743],[-95.056897,39.580567],[-95.055152,39.621657],[-95.024595,39.668485],[-94.969333,39.69073],[-94.970422,39.732121],[-94.947541,39.745337],[-94.902612,39.724202],[-94.875634,39.730492],[-94.859035,39.754116],[-94.869655,39.772899],[-94.912293,39.759338],[-94.91955,39.790291],[-94.89053,39.791425],[-94.876244,39.807394],[-94.891481,39.834644],[-94.939854,39.852044],[-94.934907,39.893784],[-94.95518,39.901288],[-95.015135,39.899643],[-95.035246,39.865921],[-95.085003,39.861883],[-95.139679,39.880592],[-95.146055,39.904183],[-95.189561,39.900145],[-95.206063,39.912061],[-95.205266,39.93975],[-95.250254,39.948644],[-95.30829,39.999998],[-95.669801,40.000333],[-95.982996,40.000586],[-96.30103,40.000837],[-96.756119,40.001401],[-97.049663,40.001323],[-97.415964,40.00196],[-97.830746,40.002023],[-98.153874,40.002204],[-98.719927,40.002205],[-99.309801,40.001812],[-99.772102,40.001751],[-100.231898,40.001604],[-100.49558,40.001714],[-101.048305,40.002283],[-101.624635,40.00269],[-102.051744,40.003078],[-102.050952,39.749993],[-102.049052,39.373991],[-102.046046,38.981776],[-102.045075,38.669561],[-102.044946,38.384358],[-102.044515,37.984575],[-102.041626,37.696699],[-102.04182,37.410149],[-102.042089,36.993016]]]},\"properties\":{\"name\":\"Kansas\",\"ns_code\":\"00481813\",\"geoid\":\"20\",\"usps_abbrev\":\"KS\",\"fips_code\":\"20\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-101.22811,42.998127],[-101.633007,42.996255],[-101.862762,42.999374],[-102.125427,42.999196],[-102.625701,42.999773],[-102.889187,43.000438],[-103.475638,43.00048],[-104.053028,43.000585],[-104.05262,42.660549],[-104.053107,42.499964],[-104.052547,42.166801],[-104.052967,41.916972],[-104.052859,41.592254],[-104.052245,41.398065],[-104.053249,41.001406],[-103.669054,41.001442],[-103.365314,41.001846],[-102.827237,41.002143],[-102.556721,41.0022],[-102.051717,41.002359],[-102.051417,40.742793],[-102.051554,40.500023],[-102.051744,40.003078],[-101.624635,40.00269],[-101.048305,40.002283],[-100.49558,40.001714],[-100.231898,40.001604],[-99.772102,40.001751],[-99.309801,40.001812],[-98.719927,40.002205],[-98.153874,40.002204],[-97.830746,40.002023],[-97.415964,40.00196],[-97.049663,40.001323],[-96.756119,40.001401],[-96.30103,40.000837],[-95.982996,40.000586],[-95.669801,40.000333],[-95.30829,39.999998],[-95.363983,40.031498],[-95.391527,40.027058],[-95.419999,40.05044],[-95.396549,40.124701],[-95.431351,40.139163],[-95.440694,40.162282],[-95.47792,40.183844],[-95.4699,40.2227],[-95.478694,40.243661],[-95.516107,40.249187],[-95.556275,40.270761],[-95.553292,40.291158],[-95.653729,40.322582],[-95.622684,40.342323],[-95.642414,40.369829],[-95.66144,40.415917],[-95.659734,40.444484],[-95.69313,40.469396],[-95.699969,40.505275],[-95.661681,40.517312],[-95.652269,40.538108],[-95.671776,40.562628],[-95.694147,40.556942],[-95.697202,40.528475],[-95.75711,40.52599],[-95.773549,40.578205],[-95.765645,40.585208],[-95.751271,40.609057],[-95.769759,40.622884],[-95.789485,40.659388],[-95.842801,40.677496],[-95.852615,40.702262],[-95.883178,40.717579],[-95.881529,40.750611],[-95.836903,40.776477],[-95.843745,40.803783],[-95.837186,40.835347],[-95.847785,40.864328],[-95.812083,40.884239],[-95.836438,40.921642],[-95.832055,40.98114],[-95.863492,40.99734],[-95.860862,41.038547],[-95.882225,41.063539],[-95.862427,41.089687],[-95.882944,41.154572],[-95.867624,41.188416],[-95.915428,41.185145],[-95.926117,41.211619],[-95.911296,41.239442],[-95.928646,41.281332],[-95.87748,41.284583],[-95.885554,41.318097],[-95.925185,41.321355],[-95.95713,41.349593],[-95.928774,41.37008],[-95.937694,41.39477],[-95.919989,41.452604],[-95.93634,41.465304],[-96.004047,41.472146],[-96.01738,41.492139],[-95.992777,41.514596],[-95.999966,41.53948],[-96.026657,41.540366],[-96.03429,41.513036],[-96.057331,41.511051],[-96.096617,41.545626],[-96.081188,41.574296],[-96.11364,41.601955],[-96.115995,41.622024],[-96.095131,41.647767],[-96.121048,41.677002],[-96.075865,41.714785],[-96.105194,41.731195],[-96.064897,41.791673],[-96.109746,41.823992],[-96.111252,41.850229],[-96.136525,41.864151],[-96.162067,41.903047],[-96.142265,41.915379],[-96.129186,41.965136],[-96.191971,41.985347],[-96.184711,42.004972],[-96.222823,42.023817],[-96.234356,42.040712],[-96.268063,42.041705],[-96.279201,42.073544],[-96.267486,42.10978],[-96.309758,42.132101],[-96.346688,42.164686],[-96.35987,42.210553],[-96.323723,42.229887],[-96.331312,42.259408],[-96.367977,42.290681],[-96.369969,42.310878],[-96.407998,42.337408],[-96.41031,42.412965],[-96.380705,42.446393],[-96.401962,42.48644],[-96.445483,42.49063],[-96.501319,42.482749],[-96.525088,42.510183],[-96.557776,42.52038],[-96.60347,42.50446],[-96.625961,42.513578],[-96.63803,42.551959],[-96.687005,42.579225],[-96.711,42.608129],[-96.687667,42.653127],[-96.728027,42.66688],[-96.764061,42.661983],[-96.802177,42.672235],[-96.806213,42.704154],[-96.907922,42.733857],[-96.948902,42.719465],[-96.97912,42.76009],[-97.071849,42.772305],[-97.130257,42.771541],[-97.150763,42.795566],[-97.212659,42.812069],[-97.218046,42.845113],[-97.248556,42.855386],[-97.289859,42.855499],[-97.306677,42.867604],[-97.341181,42.855882],[-97.417066,42.865918],[-97.43911,42.84711],[-97.504847,42.858477],[-97.561928,42.847552],[-97.60376,42.858329],[-97.686477,42.842212],[-97.774381,42.84974],[-97.831393,42.86896],[-97.875531,42.858656],[-97.888647,42.817177],[-97.947189,42.770709],[-98.013072,42.762219],[-98.062999,42.781001],[-98.129223,42.821056],[-98.219826,42.853157],[-98.258276,42.87439],[-98.325864,42.8865],[-98.342408,42.900847],[-98.467356,42.947556],[-98.49855,42.998559],[-98.88551,42.998181],[-99.195658,42.998084],[-99.635934,42.997834],[-100.034201,42.998145],[-100.525978,42.999042],[-100.88752,42.998015],[-101.22811,42.998127]]]},\"properties\":{\"name\":\"Nebraska\",\"ns_code\":\"01779792\",\"geoid\":\"31\",\"usps_abbrev\":\"NE\",\"fips_code\":\"31\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-114.041723,41.99372],[-114.039648,41.884815],[-114.040231,41.491148],[-114.041396,41.219958],[-114.042674,40.845903],[-114.045827,40.424823],[-114.046833,40.035017],[-114.047269,39.759224],[-114.047077,39.499811],[-114.049104,39.005509],[-114.048044,38.874949],[-114.050519,38.443899],[-114.049417,38.264849],[-114.050424,37.999958],[-114.049679,37.8236],[-114.052923,37.592956],[-114.051404,37.23385],[-114.052541,37.112881],[-114.0506,37.000396],[-113.572079,36.999981],[-113.052912,36.999983],[-112.750761,37.000483],[-112.302044,37.001053],[-112.000735,37.000959],[-111.751401,37.001234],[-111.278221,37.000467],[-110.75723,37.003273],[-110.509004,37.003985],[-110.47019,36.997997],[-110.023043,36.998601],[-109.750669,36.99816],[-109.495338,36.999105],[-109.045223,36.999084],[-109.04581,37.374993],[-109.04192,37.530525],[-109.041058,37.907236],[-109.042816,37.962359],[-109.041762,38.16469],[-109.060062,38.275489],[-109.059538,38.719986],[-109.054189,38.874984],[-109.051516,39.124982],[-109.050765,39.366677],[-109.051263,39.624974],[-109.050613,39.875055],[-109.05082,40.231093],[-109.050946,40.444368],[-109.048044,40.619241],[-109.050076,41.000659],[-109.250735,41.001009],[-109.543221,40.998396],[-110.006495,40.997815],[-110.237905,40.995426],[-110.500718,40.994746],[-111.046723,40.997959],[-111.04658,41.361033],[-111.045794,41.499945],[-111.046689,42.001567],[-111.491095,41.999522],[-112.01218,41.99835],[-112.173351,41.996568],[-112.192976,42.001167],[-112.624578,42.000353],[-112.979918,41.998441],[-113.496548,41.993305],[-113.893261,41.988057],[-114.041723,41.99372]]]},\"properties\":{\"name\":\"Utah\",\"ns_code\":\"01455989\",\"geoid\":\"49\",\"usps_abbrev\":\"UT\",\"fips_code\":\"49\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-162.637688,54.801121],[-162.67295,54.843441],[-162.7432,54.857851],[-162.780742,54.847741],[-162.81901,54.811158],[-162.807411,54.779871],[-162.740178,54.753102],[-162.677799,54.762716],[-162.637688,54.801121]]],[[[-161.006232,55.165759],[-161.062748,55.206796],[-161.128954,55.206546],[-161.175006,55.184381],[-161.194399,55.154961],[-161.151562,55.11513],[-161.084405,55.104767],[-161.019971,55.129239],[-161.006232,55.165759]]],[[[-175.102323,52.168005],[-175.06419,52.186101],[-175.036327,52.217628],[-175.053573,52.251305],[-175.119803,52.28054],[-175.204645,52.259112],[-175.224817,52.232965],[-175.205312,52.19123],[-175.15254,52.162979],[-175.102323,52.168005]]],[[[-169.847694,57.184897],[-169.87123,57.221536],[-169.90975,57.236295],[-169.960408,57.237419],[-170.007985,57.225299],[-170.04222,57.205047],[-170.044012,57.165052],[-170.022459,57.144325],[-169.95559,57.128227],[-169.886869,57.145438],[-169.847694,57.184897]]],[[[-168.002249,53.988041],[-168.082956,53.992437],[-168.110973,53.981564],[-168.135846,53.946733],[-168.123085,53.911965],[-168.08304,53.882959],[-168.022944,53.87492],[-167.984062,53.886496],[-167.953453,53.921158],[-167.959039,53.9637],[-168.002249,53.988041]]],[[[-161.645699,63.624153],[-161.69639,63.648986],[-161.75849,63.654517],[-161.814522,63.64667],[-161.85585,63.621632],[-161.83922,63.570355],[-161.78447,63.553788],[-161.72105,63.553739],[-161.641536,63.588497],[-161.645699,63.624153]]],[[[-168.820067,65.66519],[-168.869229,65.620853],[-168.84605,65.592512],[-168.80835,65.578526],[-168.73316,65.572244],[-168.633681,65.601358],[-168.63791,65.639863],[-168.71059,65.673136],[-168.820067,65.66519]]],[[[-175.403742,52.181994],[-175.419071,52.205473],[-175.492047,52.237577],[-175.532789,52.236606],[-175.5793,52.220238],[-175.60402,52.198683],[-175.598652,52.146684],[-175.561255,52.122571],[-175.512361,52.107374],[-175.485742,52.109387],[-175.43701,52.132807],[-175.403742,52.181994]]],[[[-172.729822,60.261733],[-172.773586,60.25549],[-172.867009,60.222142],[-172.89036,60.182124],[-172.84832,60.148778],[-172.79954,60.138804],[-172.69419,60.15461],[-172.634324,60.200743],[-172.649219,60.236895],[-172.729822,60.261733]]],[[[-178.187623,51.482227],[-178.23862,51.529892],[-178.272583,51.541014],[-178.359982,51.537475],[-178.42294,51.508996],[-178.430875,51.476584],[-178.387409,51.43681],[-178.304339,51.421343],[-178.248645,51.423171],[-178.198412,51.443653],[-178.187623,51.482227]]],[[[-159.863447,58.578686],[-159.867681,58.610517],[-159.914198,58.65023],[-160.00464,58.665602],[-160.07488,58.639886],[-160.09759,58.599914],[-160.059052,58.547826],[-160.008535,58.534684],[-159.921321,58.543964],[-159.863447,58.578686]]],[[[-168.97173,65.713941],[-168.971903,65.688765],[-168.856091,65.691236],[-168.785494,65.716319],[-168.762466,65.751166],[-168.80098,65.795173],[-168.861,65.817807],[-168.97103,65.814645],[-168.97173,65.713941]]],[[[-177.344,51.932196],[-177.374366,51.962616],[-177.45955,51.982884],[-177.509138,51.965746],[-177.54292,51.935127],[-177.551982,51.908147],[-177.523687,51.866185],[-177.48673,51.843649],[-177.439542,51.840354],[-177.361935,51.864284],[-177.33258,51.900771],[-177.344,51.932196]]],[[[-161.196381,64.091974],[-161.176662,64.146856],[-161.223548,64.176433],[-161.310363,64.189368],[-161.355713,64.183777],[-161.420197,64.158416],[-161.43692,64.133553],[-161.414441,64.08677],[-161.37318,64.069742],[-161.30603,64.061969],[-161.252273,64.065963],[-161.196381,64.091974]]],[[[-168.00487,65.038897],[-168.07611,65.043289],[-168.156162,65.029174],[-168.207906,65.003291],[-168.21011,64.951372],[-168.137033,64.918432],[-168.037032,64.909284],[-167.953094,64.932994],[-167.913829,64.969943],[-167.924296,65.002728],[-168.00487,65.038897]]],[[[-163.03943,55.450724],[-163.076008,55.488687],[-163.13653,55.525024],[-163.190419,55.530558],[-163.242512,55.517718],[-163.2715,55.477015],[-163.252129,55.453824],[-163.27089,55.431718],[-163.262371,55.394197],[-163.235362,55.366112],[-163.153941,55.343123],[-163.070964,55.361327],[-163.027938,55.42083],[-163.03943,55.450724]]],[[[-178.658607,51.80713],[-178.690328,51.849529],[-178.786532,51.88788],[-178.8451,51.88794],[-178.89036,51.873106],[-178.93955,51.833605],[-178.957281,51.780799],[-178.949954,51.760244],[-178.86849,51.711691],[-178.82802,51.694776],[-178.771313,51.696042],[-178.700904,51.716539],[-178.66349,51.747358],[-178.6468,51.7903],[-178.658607,51.80713]]],[[[-171.198241,52.402451],[-171.136496,52.445671],[-171.111845,52.507559],[-171.0646,52.521246],[-171.033563,52.548524],[-171.02243,52.603717],[-171.069735,52.627062],[-171.140234,52.639633],[-171.205941,52.624928],[-171.24839,52.583474],[-171.31242,52.570745],[-171.374433,52.537118],[-171.39372,52.46813],[-171.358353,52.412545],[-171.294308,52.393134],[-171.22662,52.393702],[-171.198241,52.402451]]],[[[-146.43075,59.46574],[-146.489737,59.424577],[-146.50791,59.38551],[-146.48078,59.355229],[-146.415695,59.336443],[-146.35536,59.336443],[-146.280741,59.348838],[-146.21853,59.383517],[-146.16202,59.403876],[-146.145602,59.427407],[-146.16967,59.479664],[-146.201044,59.507329],[-146.24292,59.519954],[-146.24425,59.557122],[-146.2702,59.57529],[-146.358492,59.586056],[-146.41216,59.568349],[-146.43144,59.546291],[-146.42064,59.513565],[-146.43075,59.46574]]],[[[-155.53824,55.925467],[-155.634113,55.925469],[-155.67676,55.907663],[-155.75146,55.854242],[-155.80476,55.836437],[-155.826107,55.81863],[-155.836841,55.783015],[-155.794349,55.729594],[-155.719822,55.711786],[-155.581366,55.699912],[-155.528092,55.705848],[-155.474751,55.741459],[-155.48522,55.818623],[-155.453119,55.883916],[-155.474353,55.913594],[-155.53824,55.925467]]],[[[-159.426336,54.758642],[-159.416772,54.792197],[-159.44065,54.817541],[-159.497605,54.855459],[-159.556116,54.880262],[-159.636302,54.871797],[-159.681326,54.887434],[-159.761051,54.891324],[-159.83703,54.870286],[-159.88583,54.847189],[-159.91167,54.809288],[-159.85522,54.757704],[-159.8135,54.740418],[-159.74679,54.742672],[-159.688578,54.765596],[-159.652435,54.717778],[-159.624756,54.707921],[-159.54787,54.699296],[-159.469294,54.720968],[-159.426336,54.758642]]],[[[-169.40164,56.636553],[-169.438667,56.653698],[-169.509265,56.667471],[-169.621913,56.661341],[-169.74735,56.675043],[-169.802528,56.673969],[-169.852881,56.658881],[-169.877592,56.630537],[-169.86824,56.587031],[-169.804777,56.547369],[-169.763735,56.540724],[-169.727524,56.514639],[-169.67609,56.496226],[-169.58126,56.483228],[-169.526637,56.492976],[-169.479413,56.526851],[-169.420474,56.549259],[-169.377545,56.587729],[-169.40164,56.636553]]],[[[-178.977381,51.252972],[-178.928604,51.270441],[-178.900772,51.290692],[-178.856488,51.304901],[-178.832806,51.323588],[-178.82402,51.358068],[-178.83522,51.390808],[-178.87316,51.424791],[-178.913908,51.444215],[-178.969425,51.457109],[-179.005041,51.452652],[-179.049873,51.42844],[-179.09324,51.390936],[-179.107599,51.353676],[-179.183665,51.338491],[-179.225686,51.299272],[-179.231086,51.267531],[-179.20788,51.202723],[-179.18381,51.183776],[-179.13694,51.175092],[-179.073599,51.179041],[-179.024194,51.209831],[-178.977381,51.252972]]],[[[-160.509115,58.642378],[-160.475339,58.627192],[-160.39486,58.621271],[-160.41429,58.586934],[-160.396861,58.553919],[-160.367619,58.541989],[-160.301029,58.535758],[-160.258761,58.544569],[-160.225597,58.565539],[-160.21216,58.595046],[-160.154086,58.637109],[-160.15273,58.658199],[-160.096973,58.697283],[-160.101914,58.724812],[-160.137618,58.748898],[-160.214084,58.755477],[-160.282393,58.774968],[-160.323241,58.775058],[-160.36764,58.794097],[-160.416039,58.798445],[-160.490372,58.773631],[-160.52579,58.730237],[-160.54432,58.674326],[-160.509115,58.642378]]],[[[-156.58446,56.005636],[-156.553324,56.027075],[-156.556048,56.090162],[-156.626815,56.140192],[-156.671961,56.148351],[-156.65377,56.167594],[-156.651112,56.20096],[-156.690626,56.257249],[-156.77023,56.279793],[-156.841994,56.27008],[-156.872228,56.247023],[-156.900164,56.191611],[-156.903153,56.155053],[-156.883602,56.12284],[-156.81717,56.104096],[-156.84491,56.022111],[-156.826412,56.007578],[-156.82221,55.967556],[-156.764,55.953001],[-156.647785,55.962302],[-156.58446,56.005636]]],[[[-170.47402,52.638387],[-170.480994,52.69041],[-170.50831,52.721165],[-170.56411,52.742205],[-170.630402,52.751594],[-170.680452,52.749586],[-170.770387,52.721094],[-170.888913,52.649271],[-170.923514,52.574987],[-170.927388,52.549886],[-170.887588,52.513829],[-170.793769,52.487099],[-170.738226,52.500109],[-170.67443,52.539876],[-170.575685,52.554871],[-170.536133,52.573847],[-170.47402,52.638387]]],[[[-178.501252,51.532716],[-178.456158,51.543393],[-178.42668,51.561798],[-178.421676,51.587283],[-178.452206,51.621002],[-178.50447,51.633527],[-178.539807,51.652336],[-178.660122,51.665879],[-178.72589,51.652661],[-178.76874,51.618005],[-178.844584,51.637626],[-178.89076,51.62853],[-178.949936,51.591544],[-178.988333,51.624581],[-179.056029,51.645972],[-179.11334,51.632387],[-179.149873,51.595358],[-179.14889,51.554231],[-179.119926,51.519066],[-179.054123,51.511401],[-178.979778,51.531105],[-178.94862,51.560879],[-178.900273,51.522281],[-178.836422,51.492037],[-178.80019,51.485859],[-178.7148,51.493813],[-178.63642,51.524794],[-178.574458,51.507529],[-178.55019,51.508484],[-178.501252,51.532716]]],[[[-172.223819,52.362614],[-172.24966,52.393454],[-172.399296,52.444505],[-172.483982,52.436651],[-172.633239,52.387863],[-172.71434,52.294751],[-172.720689,52.264364],[-172.707503,52.234227],[-172.676008,52.210576],[-172.616163,52.199088],[-172.510603,52.199946],[-172.427201,52.225424],[-172.377915,52.228363],[-172.335,52.252303],[-172.287706,52.265651],[-172.228442,52.296193],[-172.210712,52.316267],[-172.223819,52.362614]]],[[[-151.875171,58.950788],[-151.937184,58.9883],[-152.019555,59.010921],[-152.083367,59.005021],[-152.12905,59.016751],[-152.188222,59.016144],[-152.245651,58.993457],[-152.32345,59.007459],[-152.414007,58.992237],[-152.464855,58.92422],[-152.452584,58.884872],[-152.383294,58.82829],[-152.31462,58.819997],[-152.24964,58.840674],[-152.195357,58.840633],[-152.134072,58.858118],[-152.066781,58.833013],[-152.017165,58.828518],[-151.981582,58.837277],[-151.936387,58.86738],[-151.891805,58.876391],[-151.85389,58.908764],[-151.875171,58.950788]]],[[[-170.14768,57.121464],[-170.111782,57.136709],[-170.07831,57.169458],[-170.080803,57.192273],[-170.029775,57.220856],[-170.016511,57.266581],[-170.065379,57.297926],[-170.109481,57.305269],[-170.191024,57.297351],[-170.261697,57.264134],[-170.33446,57.272368],[-170.449598,57.249332],[-170.522709,57.193374],[-170.519468,57.162183],[-170.49526,57.128308],[-170.442669,57.102263],[-170.492045,57.080177],[-170.508488,57.046404],[-170.48728,57.012518],[-170.444143,56.996134],[-170.354132,57.002323],[-170.306096,57.029515],[-170.295725,57.056169],[-170.223246,57.076596],[-170.14768,57.121464]]],[[[-162.628942,54.25],[-162.56315,54.243579],[-162.526588,54.257984],[-162.494972,54.291723],[-162.429359,54.298514],[-162.358961,54.328523],[-162.2822,54.330965],[-162.250159,54.346743],[-162.238381,54.369921],[-162.248209,54.399265],[-162.354352,54.456691],[-162.411667,54.459125],[-162.44194,54.475336],[-162.50792,54.48198],[-162.582692,54.505767],[-162.61057,54.500422],[-162.691583,54.526388],[-162.880706,54.553213],[-162.93884,54.521145],[-162.940975,54.475362],[-162.959878,54.428017],[-163.008446,54.400172],[-163.004751,54.35158],[-162.952627,54.32573],[-162.89007,54.330766],[-162.81792,54.31482],[-162.77997,54.293215],[-162.768203,54.27044],[-162.726483,54.242065],[-162.700084,54.237969],[-162.628942,54.25]]],[[[-169.617649,52.77755],[-169.588613,52.800563],[-169.593389,52.88952],[-169.627824,52.92674],[-169.575239,52.993707],[-169.580644,53.032165],[-169.60445,53.062031],[-169.664233,53.085685],[-169.68986,53.112064],[-169.75915,53.134054],[-169.814087,53.126641],[-169.86075,53.105012],[-169.883215,53.045187],[-169.832955,53.009965],[-169.84289,52.969516],[-169.831898,52.935538],[-169.910493,52.935215],[-169.94175,52.958784],[-170.046106,52.973116],[-170.11652,52.963131],[-170.17689,52.935137],[-170.20338,52.897647],[-170.190133,52.862832],[-170.156587,52.836113],[-170.206764,52.822763],[-170.267285,52.764249],[-170.274914,52.737634],[-170.250063,52.681089],[-170.173754,52.662888],[-170.086581,52.662618],[-170.03259,52.675744],[-169.981799,52.71202],[-169.975547,52.738118],[-169.90385,52.740574],[-169.84875,52.76311],[-169.817313,52.757983],[-169.784986,52.736734],[-169.703061,52.716099],[-169.65972,52.726226],[-169.62746,52.750675],[-169.617649,52.77755]]],[[[-172.815327,60.557852],[-172.819641,60.61349],[-172.888646,60.644135],[-172.943849,60.6507],[-172.953052,60.694481],[-172.98976,60.731811],[-173.059068,60.747748],[-173.158632,60.72877],[-173.218431,60.676231],[-173.23223,60.636827],[-173.140223,60.575533],[-173.163222,60.538316],[-173.167818,60.48108],[-173.144869,60.457101],[-173.06739,60.439905],[-172.93145,60.389684],[-172.910228,60.339623],[-172.84794,60.318383],[-172.802382,60.31865],[-172.715375,60.296219],[-172.67936,60.278026],[-172.596125,60.258622],[-172.49604,60.274221],[-172.407526,60.278764],[-172.379188,60.260714],[-172.29551,60.23883],[-172.18949,60.250816],[-172.120378,60.287337],[-172.10909,60.314012],[-172.168051,60.348912],[-172.278507,60.388328],[-172.319929,60.416798],[-172.39357,60.436506],[-172.53002,60.435741],[-172.68784,60.48012],[-172.760255,60.514181],[-172.815327,60.557852]]],[[[-165.649483,60.348823],[-165.653928,60.37176],[-165.738465,60.405609],[-165.782465,60.404873],[-165.82845,60.39005],[-165.899385,60.403202],[-166.00953,60.387704],[-166.005798,60.419573],[-166.054192,60.469491],[-166.15243,60.510747],[-166.208589,60.505186],[-166.270933,60.478621],[-166.276332,60.451613],[-166.349823,60.417745],[-166.443624,60.444484],[-166.528839,60.44427],[-166.570194,60.422919],[-166.62012,60.412947],[-166.666308,60.385785],[-166.73934,60.387861],[-166.844681,60.345031],[-166.929756,60.295497],[-166.943778,60.278103],[-167.01806,60.266235],[-167.069946,60.284747],[-167.120546,60.291225],[-167.153458,60.282431],[-167.30983,60.290718],[-167.4377,60.262848],[-167.508074,60.263109],[-167.5508,60.240554],[-167.560296,60.205956],[-167.536351,60.178245],[-167.43645,60.108761],[-167.44077,60.056462],[-167.38558,60.021207],[-167.32415,60.018457],[-167.293405,59.999186],[-167.22273,59.993853],[-167.13784,59.944033],[-167.04204,59.935757],[-166.940622,59.911908],[-166.850852,59.871056],[-166.828905,59.85233],[-166.736783,59.838301],[-166.68902,59.815338],[-166.626126,59.797061],[-166.42967,59.801636],[-166.321854,59.773141],[-166.273867,59.733847],[-166.196613,59.703384],[-166.05789,59.695535],[-165.96482,59.722971],[-165.824859,59.806746],[-165.735491,59.839855],[-165.657822,59.836263],[-165.560383,59.852868],[-165.49547,59.870184],[-165.460906,59.895005],[-165.43913,59.936036],[-165.402458,59.970448],[-165.45188,59.990418],[-165.497496,60.027887],[-165.539365,60.034979],[-165.573173,60.075063],[-165.55746,60.099194],[-165.559676,60.133332],[-165.588498,60.178876],[-165.5666,60.21565],[-165.588301,60.241414],[-165.570708,60.258956],[-165.561519,60.301726],[-165.600197,60.338927],[-165.649483,60.348823]]],[[[-168.87631,63.373553],[-169.035,63.387453],[-169.234552,63.390727],[-169.45554,63.41502],[-169.4838,63.435631],[-169.602285,63.480635],[-169.792993,63.49074],[-169.926599,63.526739],[-169.985628,63.622959],[-170.16807,63.719891],[-170.296846,63.755572],[-170.50403,63.7534],[-170.619933,63.723455],[-170.705152,63.713083],[-170.959155,63.625888],[-171.16504,63.658988],[-171.251321,63.667442],[-171.5356,63.721875],[-171.520679,63.750236],[-171.54883,63.800461],[-171.617503,63.833641],[-171.674734,63.84368],[-171.77637,63.837724],[-171.816149,63.821162],[-171.855926,63.78567],[-171.835333,63.743361],[-171.86517,63.705497],[-171.860196,63.674736],[-171.90495,63.639243],[-171.944727,63.570618],[-171.965319,63.472874],[-171.884887,63.37759],[-171.839244,63.342682],[-171.772284,63.315327],[-171.651548,63.29104],[-171.62201,63.277154],[-171.481935,63.254531],[-171.398887,63.257642],[-171.326229,63.282697],[-171.251398,63.292568],[-171.166047,63.348158],[-171.100519,63.356513],[-171.066836,63.371108],[-170.99359,63.37484],[-170.908249,63.369945],[-170.8166,63.354977],[-170.596895,63.311616],[-170.515276,63.28791],[-170.439075,63.251596],[-170.38345,63.210752],[-170.394358,63.171056],[-170.32802,63.138724],[-170.190582,63.126842],[-170.11542,63.127247],[-169.95276,63.064072],[-169.901408,63.037365],[-169.863264,62.980144],[-169.864597,62.944697],[-169.838952,62.925889],[-169.727891,62.896333],[-169.638796,62.884908],[-169.591036,62.887867],[-169.525341,62.924659],[-169.46155,62.932839],[-169.419861,62.973856],[-169.45108,63.03856],[-169.367966,63.061076],[-169.282326,63.114405],[-169.23064,63.123502],[-169.066713,63.128214],[-168.92278,63.099567],[-168.946607,63.074451],[-168.940357,63.046329],[-168.86855,63.019792],[-168.80323,63.017652],[-168.700727,63.056925],[-168.682789,63.084668],[-168.73346,63.131553],[-168.609261,63.229362],[-168.586695,63.27247],[-168.587718,63.325229],[-168.642768,63.34488],[-168.87631,63.373553]]],[[[-175.91303,51.800354],[-175.849754,51.795493],[-175.814628,51.81527],[-175.802425,51.860415],[-175.711936,51.873207],[-175.635931,51.91117],[-175.585375,51.904899],[-175.446239,51.930588],[-175.409982,51.930184],[-175.348719,51.962585],[-175.306859,51.952337],[-175.237093,51.961519],[-175.05636,51.948159],[-175.000328,51.954493],[-174.876732,51.985908],[-174.745065,51.955646],[-174.68793,51.957525],[-174.594282,51.978412],[-174.533659,51.958019],[-174.48089,51.952497],[-174.4425,51.963875],[-174.37856,51.968524],[-174.350394,51.980336],[-174.313974,52.036226],[-174.181141,52.047377],[-174.15099,52.043212],[-174.056503,52.051633],[-173.935161,52.004442],[-173.815227,51.989807],[-173.710034,52.00684],[-173.617415,52.000948],[-173.584913,51.982463],[-173.487318,51.971184],[-173.427865,51.984867],[-173.344224,51.98339],[-173.304522,52.005426],[-173.238867,52.008675],[-173.237169,51.988338],[-173.203568,51.960324],[-173.1667,51.946706],[-173.103229,51.959472],[-173.085163,51.974931],[-173.079672,52.017915],[-173.00327,52.027983],[-172.976035,52.020275],[-172.923058,52.02416],[-172.83923,52.071564],[-172.82025,52.100274],[-172.828798,52.123437],[-172.866641,52.148933],[-172.925173,52.155211],[-172.943681,52.146585],[-173.177817,52.161809],[-173.2256,52.15568],[-173.342973,52.166525],[-173.440764,52.168764],[-173.455704,52.19168],[-173.506303,52.213138],[-173.56514,52.203661],[-173.587328,52.21222],[-173.638568,52.206069],[-173.732104,52.183254],[-173.785069,52.18555],[-173.840597,52.201325],[-173.89858,52.19901],[-173.936392,52.184559],[-173.990472,52.183143],[-173.980493,52.211618],[-173.921781,52.267835],[-173.90283,52.319228],[-173.921785,52.347448],[-173.92292,52.378067],[-173.940226,52.396256],[-173.985943,52.412116],[-174.030867,52.439383],[-174.06575,52.445654],[-174.110582,52.468321],[-174.186419,52.471661],[-174.244998,52.454843],[-174.312733,52.456712],[-174.375813,52.421774],[-174.410698,52.376437],[-174.494712,52.366951],[-174.52486,52.348166],[-174.53765,52.317036],[-174.50444,52.267369],[-174.553484,52.259493],[-174.588998,52.228725],[-174.668509,52.225201],[-174.69613,52.20936],[-174.731558,52.171447],[-174.796625,52.15347],[-174.866087,52.165637],[-174.925233,52.16391],[-174.993214,52.136408],[-175.05942,52.136614],[-175.102363,52.108619],[-175.193097,52.104358],[-175.231624,52.090196],[-175.296617,52.08882],[-175.38433,52.063295],[-175.41243,52.035986],[-175.47036,52.042835],[-175.54913,52.009879],[-175.713203,52.026298],[-175.739328,52.0213],[-175.811998,52.047948],[-175.830283,52.085394],[-175.85366,52.096526],[-175.927769,52.091068],[-175.97708,52.141286],[-176.02774,52.160175],[-176.080595,52.159043],[-176.14837,52.171465],[-176.224434,52.153126],[-176.278241,52.103543],[-176.29313,52.065427],[-176.242864,51.926742],[-176.26931,51.918966],[-176.325984,51.922374],[-176.429137,51.91535],[-176.472908,51.963565],[-176.469554,51.981221],[-176.48848,52.026042],[-176.54119,52.05088],[-176.60128,52.05656],[-176.661916,52.040092],[-176.697341,52.019103],[-176.802469,52.016668],[-176.847819,51.995517],[-176.87553,51.959674],[-176.89741,51.907013],[-176.918866,51.882132],[-176.97098,51.861107],[-176.970048,51.924505],[-177.016896,51.965032],[-177.096462,51.990146],[-177.16806,51.999512],[-177.206209,51.99311],[-177.26746,51.959628],[-177.284361,51.913148],[-177.282892,51.863073],[-177.308894,51.836793],[-177.406215,51.836235],[-177.445697,51.809486],[-177.51532,51.79197],[-177.55783,51.795257],[-177.538271,51.820932],[-177.538368,51.873127],[-177.577068,51.901187],[-177.614912,51.905],[-177.759358,51.894791],[-177.826807,51.896414],[-177.834499,51.914069],[-177.889927,51.952575],[-177.947003,51.971312],[-178.032431,51.972654],[-178.064958,51.964771],[-178.10724,51.976507],[-178.24738,51.950166],[-178.298847,51.903705],[-178.307787,51.881608],[-178.29765,51.845229],[-178.256047,51.812017],[-178.15166,51.773756],[-178.192339,51.720847],[-178.2032,51.661324],[-178.189368,51.633673],[-178.131774,51.609907],[-178.090227,51.603743],[-178.05071,51.579909],[-177.928257,51.541895],[-177.878264,51.543324],[-177.843809,51.558555],[-177.820492,51.584422],[-177.818695,51.618741],[-177.783402,51.647859],[-177.751949,51.637355],[-177.727713,51.610964],[-177.646772,51.599043],[-177.562724,51.622922],[-177.452033,51.640201],[-177.40298,51.670381],[-177.372288,51.641571],[-177.2811,51.627612],[-177.228637,51.632033],[-177.183654,51.648583],[-177.126859,51.65337],[-177.079985,51.674231],[-177.03409,51.732737],[-177.027623,51.767049],[-177.004157,51.78778],[-176.970216,51.743689],[-177.03393,51.703981],[-177.06375,51.664656],[-177.074333,51.620907],[-177.06865,51.592792],[-177.04256,51.566089],[-176.96961,51.538985],[-176.917886,51.538468],[-176.86701,51.56358],[-176.801395,51.559409],[-176.756777,51.572649],[-176.698731,51.578428],[-176.651242,51.603913],[-176.578277,51.622351],[-176.53977,51.648438],[-176.40314,51.683909],[-176.317809,51.681388],[-176.25519,51.695376],[-176.211341,51.73314],[-176.163021,51.719978],[-176.115566,51.726725],[-176.04184,51.755847],[-176.002634,51.748448],[-175.964713,51.755425],[-175.926598,51.776034],[-175.91303,51.800354]]],[[[-166.22565,54.041993],[-166.243221,54.030232],[-166.305071,54.051056],[-166.375582,54.061796],[-166.511146,54.05779],[-166.69242,54.067286],[-166.785194,54.065979],[-166.820198,54.048041],[-166.910406,54.040017],[-166.936528,54.027156],[-166.98868,54.023946],[-167.053993,54.008613],[-167.106024,53.975259],[-167.160743,53.953897],[-167.1763,53.911854],[-167.230077,53.888252],[-167.24289,53.854673],[-167.234071,53.814243],[-167.164881,53.773842],[-167.138205,53.709423],[-167.155683,53.67834],[-167.215232,53.658204],[-167.247031,53.614692],[-167.23038,53.566734],[-167.296282,53.539731],[-167.341149,53.543595],[-167.392666,53.528259],[-167.42239,53.498127],[-167.50266,53.495318],[-167.555203,53.465421],[-167.60802,53.458057],[-167.648125,53.442094],[-167.746769,53.426335],[-167.701143,53.487629],[-167.698213,53.524223],[-167.718226,53.548623],[-167.762306,53.568024],[-167.875044,53.56905],[-167.92564,53.601948],[-167.959882,53.612812],[-168.04033,53.610276],[-168.090352,53.619667],[-168.185396,53.598652],[-168.284012,53.571137],[-168.33443,53.538671],[-168.415259,53.509447],[-168.42524,53.486313],[-168.476672,53.450596],[-168.49636,53.421076],[-168.477602,53.378691],[-168.504296,53.362889],[-168.689337,53.303273],[-168.699054,53.280371],[-168.749414,53.266498],[-168.806462,53.230606],[-168.85945,53.188152],[-168.883072,53.150172],[-168.8907,53.108547],[-168.872783,53.081724],[-168.90559,53.065532],[-168.965352,53.050747],[-169.003346,52.996552],[-169.065733,52.981287],[-169.109409,52.95839],[-169.197133,52.964882],[-169.254085,52.924292],[-169.255424,52.884499],[-169.194679,52.849326],[-169.29427,52.811626],[-169.320551,52.78372],[-169.326592,52.753542],[-169.29146,52.722648],[-169.22806,52.71461],[-169.162008,52.732682],[-169.066077,52.77602],[-169.006222,52.785561],[-168.849464,52.840921],[-168.82705,52.85839],[-168.743002,52.854731],[-168.629249,52.905151],[-168.58518,52.946496],[-168.554686,52.955036],[-168.522027,52.933733],[-168.476273,52.919483],[-168.402684,52.929253],[-168.337155,52.956925],[-168.312586,52.988951],[-168.338074,53.029307],[-168.320494,53.050071],[-168.33399,53.083187],[-168.310851,53.090002],[-168.257713,53.141895],[-168.23262,53.138775],[-168.1699,53.158595],[-168.132158,53.219282],[-168.08203,53.226371],[-168.044049,53.244187],[-168.02627,53.226137],[-167.947562,53.211911],[-167.87079,53.205933],[-167.82921,53.191981],[-167.669278,53.17205],[-167.592511,53.174046],[-167.52214,53.186009],[-167.448576,53.213919],[-167.366475,53.273641],[-167.287372,53.275954],[-167.255145,53.28733],[-167.219879,53.325512],[-167.106758,53.366991],[-167.03217,53.379119],[-166.989565,53.373246],[-166.927364,53.385134],[-166.882904,53.377568],[-166.788669,53.383888],[-166.70179,53.402033],[-166.604933,53.448766],[-166.581176,53.472544],[-166.54213,53.482384],[-166.37865,53.602141],[-166.344447,53.614623],[-166.174938,53.659778],[-166.10738,53.694699],[-166.081516,53.725458],[-165.97636,53.771061],[-165.930337,53.818869],[-165.92267,53.845957],[-165.96103,53.91608],[-165.886883,53.97026],[-165.828073,53.978227],[-165.788966,54.016236],[-165.750107,54.016607],[-165.657173,54.036336],[-165.606713,53.976844],[-165.563239,53.967282],[-165.519767,53.97206],[-165.380965,54.016436],[-165.302878,53.980735],[-165.269632,53.980731],[-165.184461,54.01701],[-165.027259,54.012943],[-164.922796,54.028475],[-164.881048,54.043429],[-164.846047,54.083254],[-164.82194,54.130809],[-164.77619,54.135855],[-164.714485,54.166117],[-164.682382,54.201862],[-164.68775,54.250118],[-164.758529,54.278177],[-164.80537,54.282392],[-164.911241,54.254688],[-164.97497,54.214884],[-165.031512,54.208878],[-165.066871,54.180086],[-165.150257,54.180523],[-165.243823,54.15929],[-165.232074,54.21652],[-165.263023,54.236544],[-165.34518,54.251033],[-165.388479,54.288134],[-165.406766,54.326678],[-165.462493,54.346822],[-165.542023,54.348715],[-165.578685,54.341436],[-165.65555,54.347051],[-165.69617,54.332234],[-165.743352,54.288891],[-165.770886,54.231134],[-165.83664,54.25822],[-165.93722,54.276985],[-166.033944,54.260954],[-166.068456,54.239919],[-166.12066,54.223141],[-166.165117,54.195088],[-166.199075,54.135541],[-166.19901,54.103513],[-166.140534,54.057132],[-166.22565,54.041993]]],[[[-154.623967,56.990886],[-154.577483,56.952773],[-154.50244,56.940788],[-154.440058,56.9236],[-154.387719,56.895618],[-154.385524,56.830269],[-154.217826,56.720467],[-154.239296,56.692855],[-154.236672,56.667573],[-154.360108,56.633524],[-154.41504,56.651672],[-154.50752,56.658844],[-154.597483,56.636033],[-154.668697,56.597825],[-154.758106,56.559815],[-154.812367,56.523827],[-154.893847,56.444724],[-154.893089,56.422861],[-154.86294,56.383851],[-154.794933,56.359288],[-154.725732,56.355219],[-154.62888,56.386089],[-154.58199,56.426811],[-154.498209,56.455784],[-154.452603,56.443239],[-154.403176,56.441668],[-154.340563,56.464402],[-154.27504,56.446627],[-154.233608,56.443474],[-154.153344,56.46005],[-154.05788,56.448434],[-153.936276,56.453461],[-153.848849,56.486046],[-153.806605,56.50936],[-153.762432,56.556745],[-153.76494,56.588083],[-153.790896,56.610105],[-153.842106,56.620565],[-153.98212,56.609011],[-153.986319,56.625872],[-153.919919,56.642539],[-153.890075,56.671708],[-153.800432,56.689928],[-153.769789,56.711807],[-153.74755,56.745044],[-153.760419,56.776474],[-153.719091,56.778713],[-153.666038,56.794844],[-153.61518,56.834385],[-153.544543,56.842366],[-153.501321,56.856673],[-153.45095,56.90034],[-153.470372,56.939332],[-153.439189,56.963958],[-153.404529,56.950773],[-153.32,56.940938],[-153.274315,56.94273],[-153.180959,56.96068],[-153.11045,57.011579],[-153.098289,57.040313],[-152.983991,57.066455],[-152.938004,57.066172],[-152.834162,57.094425],[-152.77738,57.14145],[-152.78521,57.192457],[-152.83563,57.217365],[-152.676554,57.22727],[-152.642719,57.235372],[-152.537667,57.300752],[-152.52876,57.327327],[-152.415584,57.38447],[-152.383147,57.335407],[-152.314917,57.318585],[-152.25848,57.323429],[-152.186344,57.353181],[-152.159698,57.380412],[-152.156516,57.405855],[-152.216486,57.438229],[-152.222437,57.475299],[-152.0635,57.571205],[-152.031006,57.628631],[-152.034856,57.653319],[-152.07979,57.686297],[-152.12409,57.811725],[-152.16112,57.842956],[-152.25638,57.868605],[-152.220211,57.930223],[-152.265633,57.971139],[-152.31662,57.979604],[-152.329642,57.999268],[-152.414453,58.036577],[-152.404233,58.059237],[-152.325253,58.047918],[-152.260737,58.073357],[-152.127475,58.092019],[-152.033519,58.111781],[-151.96567,58.152192],[-151.908223,58.117206],[-151.861054,58.110604],[-151.76402,58.136626],[-151.706604,58.187821],[-151.690505,58.235134],[-151.705003,58.270515],[-151.74009,58.297366],[-151.707285,58.331345],[-151.712804,58.37398],[-151.750493,58.394655],[-151.794995,58.400596],[-151.854532,58.391451],[-151.895216,58.371705],[-151.932291,58.390667],[-152.024262,58.412618],[-152.071909,58.440027],[-152.126348,58.449589],[-152.060881,58.50083],[-152.065493,58.52676],[-152.113481,58.561664],[-152.182827,58.573973],[-152.18932,58.620255],[-152.247712,58.680728],[-152.312588,58.700448],[-152.38077,58.711269],[-152.392986,58.730459],[-152.473569,58.757258],[-152.545567,58.742446],[-152.61001,58.692279],[-152.69841,58.648644],[-152.767549,58.5866],[-152.775121,58.549412],[-152.81969,58.542675],[-152.861747,58.520049],[-152.88086,58.489791],[-152.922284,58.472185],[-153.12307,58.330216],[-153.200035,58.266252],[-153.288207,58.225534],[-153.314674,58.193012],[-153.367469,58.179292],[-153.431014,58.128194],[-153.4702,58.108699],[-153.500135,58.079555],[-153.504763,58.040328],[-153.48019,58.018844],[-153.538946,58.007366],[-153.634354,57.971368],[-153.744876,57.940008],[-153.786622,57.917712],[-153.870567,57.928779],[-153.917133,57.916559],[-153.94345,57.885667],[-154.105895,57.788574],[-154.128419,57.770665],[-154.24006,57.715467],[-154.266241,57.715952],[-154.32974,57.693178],[-154.407011,57.690525],[-154.48094,57.631856],[-154.57273,57.62059],[-154.606884,57.603253],[-154.625182,57.572904],[-154.703145,57.542016],[-154.745672,57.507105],[-154.75278,57.487178],[-154.790158,57.461417],[-154.815397,57.412787],[-154.88863,57.381918],[-154.915559,57.349873],[-154.931222,57.28237],[-154.914026,57.266734],[-154.831099,57.230644],[-154.767085,57.214507],[-154.685766,57.214222],[-154.679763,57.198084],[-154.61978,57.16236],[-154.60113,57.036382],[-154.623967,56.990886]]],[[[-132.119034,56.891264],[-132.051044,57.051155],[-132.37131,57.095229],[-132.252187,57.215655],[-132.367984,57.348685],[-132.559178,57.503927],[-132.65812,57.619486],[-132.756813,57.705093],[-132.869318,57.842941],[-133.07642,57.999762],[-133.176444,58.150151],[-133.343694,58.270898],[-133.461474,58.385522],[-133.379907,58.427909],[-133.55995,58.522331],[-133.699835,58.60729],[-133.840392,58.727991],[-133.99208,58.774581],[-134.250526,58.858046],[-134.32896,58.919593],[-134.30639,58.959238],[-134.401042,58.976221],[-134.379771,59.034961],[-134.442196,59.08301],[-134.481241,59.128071],[-134.566689,59.128278],[-134.68192,59.190843],[-134.702383,59.247836],[-134.961972,59.280376],[-135.029245,59.345364],[-134.993255,59.387796],[-135.097985,59.427783],[-135.071239,59.453309],[-135.026328,59.474658],[-135.027456,59.563692],[-135.114588,59.623415],[-135.153113,59.625159],[-135.21434,59.664343],[-135.25412,59.701339],[-135.477436,59.799626],[-135.854169,59.691846],[-135.9459,59.663802],[-136.19035,59.639854],[-136.35062,59.599326],[-136.23734,59.558734],[-136.234229,59.524731],[-136.301846,59.464129],[-136.365822,59.448007],[-136.474324,59.464193],[-136.466815,59.284252],[-136.581521,59.164909],[-136.826633,59.158389],[-136.863896,59.138472],[-137.26475,59.002352],[-137.447383,58.909513],[-137.526424,58.906834],[-137.498558,58.986694],[-137.604277,59.243057],[-138.248099,59.57985],[-138.62093,59.770559],[-138.662769,59.813719],[-138.702053,59.910245],[-138.796083,59.928701],[-139.031643,59.9937],[-139.200346,60.090701],[-139.082246,60.323825],[-139.086669,60.357654],[-139.698361,60.340421],[-139.989142,60.18524],[-140.47229,60.31059],[-140.53509,60.224224],[-141.00184,60.306105],[-141.001871,60.501323],[-141.001926,60.913756],[-141.001958,61.211789],[-141.002019,61.562799],[-141.002115,62.037104],[-141.002157,62.53603],[-141.002186,62.977563],[-141.002226,63.344065],[-141.002238,63.575872],[-141.00226,64.010731],[-141.002276,64.314949],[-141.0023,64.737229],[-141.002338,65.049797],[-141.002383,65.297103],[-141.00242,65.609797],[-141.0025,65.999747],[-141.002543,66.288872],[-141.00262,66.771524],[-141.002646,67.028782],[-141.002682,67.397255],[-141.002693,67.671929],[-141.002712,67.92919],[-141.00271,68.185643],[-141.002692,68.568002],[-141.002681,68.825256],[-141.002679,69.107202],[-141.002665,69.344169],[-141.002713,69.703632],[-141.133589,69.725351],[-141.377896,69.744147],[-141.591453,69.814582],[-141.74611,69.848242],[-141.914016,69.87204],[-142.079087,69.908502],[-142.425343,70.011497],[-142.480273,70.033905],[-142.706295,70.088196],[-143.027932,70.151191],[-143.167885,70.193493],[-143.22643,70.201998],[-143.36079,70.196324],[-143.411447,70.183571],[-143.560275,70.20487],[-143.641492,70.203707],[-143.679802,70.191153],[-143.846023,70.176849],[-143.91015,70.180257],[-144.00505,70.168415],[-144.063171,70.140365],[-144.11443,70.139334],[-144.227915,70.114968],[-144.378073,70.092397],[-144.481218,70.090669],[-144.610951,70.050427],[-144.678778,70.014434],[-144.86703,70.042809],[-145.032934,70.029489],[-145.08388,70.074326],[-145.150363,70.091552],[-145.296354,70.092257],[-145.507129,70.14187],[-145.574554,70.146143],[-145.82603,70.214751],[-145.979267,70.244111],[-146.20365,70.257153],[-146.405494,70.28886],[-146.447378,70.283893],[-146.634489,70.290525],[-146.716892,70.286803],[-146.869702,70.347089],[-146.999177,70.363409],[-147.168428,70.346097],[-147.188418,70.375048],[-147.451039,70.446685],[-147.610653,70.435673],[-147.652482,70.459704],[-147.779436,70.471869],[-147.811574,70.51279],[-147.906875,70.538927],[-148.037321,70.535943],[-148.129946,70.500016],[-148.283356,70.529175],[-148.39671,70.530539],[-148.472704,70.516586],[-148.514747,70.48817],[-148.515214,70.458807],[-148.659483,70.478817],[-148.679452,70.495865],[-148.786709,70.526549],[-148.973162,70.532544],[-149.042077,70.555758],[-149.422243,70.607998],[-149.694987,70.625558],[-149.796647,70.612307],[-149.84144,70.617932],[-149.954163,70.6111],[-150.009005,70.597663],[-150.111646,70.615438],[-150.20087,70.615382],[-150.283874,70.604643],[-150.346103,70.560511],[-150.424456,70.563956],[-150.489909,70.553754],[-150.749234,70.554579],[-150.876123,70.538942],[-150.924933,70.517034],[-151.053392,70.49968],[-151.169822,70.496959],[-151.246641,70.480015],[-151.426513,70.47848],[-151.54501,70.490319],[-151.499482,70.558593],[-151.559228,70.589308],[-151.61557,70.597089],[-152.04436,70.818152],[-152.056976,70.84659],[-152.124528,70.880388],[-152.199518,70.898779],[-152.376612,70.922249],[-152.47715,70.94244],[-152.655498,70.94591],[-152.8227,70.974842],[-152.914057,70.971677],[-152.98413,70.982633],[-153.045401,70.971595],[-153.12296,70.978901],[-153.242774,70.976459],[-153.494346,70.932583],[-153.749519,70.941522],[-153.90042,70.930736],[-154.32616,71.0057],[-154.372454,71.010475],[-154.570158,71.077386],[-154.769903,71.149381],[-154.910955,71.185399],[-155.096302,71.223556],[-155.300076,71.240326],[-155.436059,71.270632],[-155.658561,71.289227],[-155.759197,71.283526],[-155.843832,71.336606],[-155.892559,71.354003],[-155.97864,71.362791],[-156.19882,71.395384],[-156.388046,71.435118],[-156.4604,71.441059],[-156.59331,71.423152],[-156.634751,71.397383],[-156.789622,71.373586],[-156.945338,71.311989],[-157.129029,71.21751],[-157.35999,71.091872],[-157.38421,71.07243],[-157.5533,70.999305],[-157.83968,70.919023],[-157.999427,70.890278],[-158.151772,70.870894],[-158.248,70.867639],[-158.438821,70.882438],[-158.533379,70.900935],[-158.59909,70.94636],[-158.685867,70.961138],[-158.896032,70.953264],[-158.97077,70.942164],[-159.175841,70.929085],[-159.552085,70.875431],[-159.712427,70.84683],[-159.900387,70.76869],[-160.087483,70.700487],[-160.22957,70.637541],[-160.49101,70.535492],[-160.729805,70.457197],[-160.975017,70.40129],[-161.182082,70.359842],[-161.283231,70.349866],[-161.444295,70.347876],[-161.565054,70.354289],[-161.87486,70.380652],[-162.000221,70.358974],[-162.21567,70.299506],[-162.400766,70.238475],[-162.57257,70.169426],[-162.896117,69.966964],[-163.049353,69.893206],[-163.183493,69.806446],[-163.28648,69.640408],[-163.282255,69.456319],[-163.30135,69.397212],[-163.36288,69.330192],[-163.611741,69.210737],[-163.654536,69.172984],[-163.759838,69.121831],[-163.935268,69.070312],[-164.001904,69.044417],[-164.23015,68.986884],[-164.288557,68.976432],[-164.61956,68.965217],[-164.779354,68.947764],[-164.966851,68.941386],[-165.007541,68.946355],[-165.291464,68.921124],[-165.498777,68.913129],[-165.666756,68.91463],[-165.817383,68.921993],[-166.109959,68.929078],[-166.185328,68.935706],[-166.320586,68.915738],[-166.36694,68.858327],[-166.320642,68.774862],[-166.338392,68.706885],[-166.328027,68.693065],[-166.37025,68.648945],[-166.369922,68.571189],[-166.425061,68.538237],[-166.44433,68.476069],[-166.54161,68.448919],[-166.770595,68.415188],[-166.949969,68.359182],[-166.985424,68.333121],[-166.973264,68.309697],[-166.90058,68.286272],[-166.843652,68.280811],[-166.759607,68.293008],[-166.67276,68.290192],[-166.53935,68.276221],[-166.410026,68.250761],[-166.274529,68.212918],[-166.14844,68.168879],[-166.043858,68.084891],[-165.951112,68.056624],[-165.820035,68.03058],[-165.684734,68.025163],[-165.656222,68.038727],[-165.499453,68.018782],[-165.206176,67.90502],[-164.767555,67.741696],[-164.65522,67.692448],[-164.549011,67.661994],[-164.28127,67.591666],[-164.175429,67.547599],[-164.00451,67.397808],[-163.882534,67.214976],[-163.962463,67.196696],[-164.002718,67.172554],[-163.99123,67.147883],[-163.911385,67.104695],[-163.781524,67.06976],[-163.617682,67.048732],[-163.340526,67.025915],[-163.04947,66.986611],[-162.876849,66.947828],[-162.832032,66.926813],[-163.20324,66.782197],[-163.40354,66.710324],[-163.626215,66.624174],[-163.805093,66.641286],[-164.061795,66.641872],[-164.424297,66.630217],[-164.751902,66.592546],[-164.880444,66.572628],[-165.43444,66.47523],[-165.777739,66.397362],[-166.093966,66.315998],[-166.317088,66.251935],[-166.480248,66.211933],[-166.719474,66.148046],[-167.007857,66.05141],[-167.178403,65.999567],[-167.30834,65.944325],[-167.520823,65.878152],[-167.718856,65.834922],[-167.883398,65.803275],[-168.077126,65.756089],[-168.15513,65.728351],[-168.221307,65.675449],[-168.231887,65.622567],[-168.218653,65.596123],[-168.16758,65.546902],[-168.126639,65.530526],[-168.03105,65.50943],[-167.938029,65.499896],[-167.89687,65.476685],[-167.756709,65.447226],[-167.60639,65.385626],[-167.400891,65.34727],[-167.191299,65.337629],[-167.070331,65.327491],[-166.94893,65.307909],[-167.008529,65.276652],[-167.072439,65.199782],[-167.084339,65.155695],[-167.04093,65.109248],[-166.978964,65.06941],[-166.87816,65.02995],[-166.824467,65.017457],[-166.828999,64.999315],[-166.761901,64.963493],[-166.58918,64.908291],[-166.522688,64.871103],[-166.51721,64.857383],[-166.570203,64.833417],[-166.586748,64.801469],[-166.590163,64.738948],[-166.50784,64.631373],[-166.352029,64.568137],[-166.281388,64.544956],[-166.353875,64.506444],[-166.351132,64.477883],[-166.27196,64.43778],[-166.153861,64.432974],[-166.095696,64.459804],[-166.070873,64.490159],[-166.07779,64.520459],[-165.850211,64.493879],[-165.680679,64.483641],[-165.476868,64.45769],[-165.335918,64.444417],[-165.0322,64.387987],[-164.804228,64.401061],[-164.7548,64.407824],[-164.465135,64.483867],[-164.355355,64.508447],[-164.266312,64.518333],[-164.180902,64.519569],[-163.97622,64.508316],[-163.850767,64.519398],[-163.814064,64.531186],[-163.75039,64.51964],[-163.656951,64.52194],[-163.536172,64.503581],[-163.457729,64.48211],[-163.363754,64.445642],[-163.323338,64.407866],[-163.241932,64.360743],[-163.165797,64.349864],[-162.842152,64.283171],[-162.7958,64.275674],[-162.728062,64.287467],[-162.632747,64.331926],[-162.59477,64.338437],[-162.517872,64.384906],[-162.486691,64.470897],[-162.44188,64.504119],[-162.313491,64.542789],[-162.237403,64.524356],[-162.027939,64.461317],[-162.002127,64.458789],[-161.59084,64.335541],[-161.49343,64.328378],[-161.413899,64.358587],[-161.326796,64.334012],[-161.075402,64.206652],[-161.0706,64.084975],[-161.04566,64.022467],[-161.009272,63.979281],[-160.94464,63.921532],[-160.92012,63.859745],[-160.880051,63.812181],[-160.8862,63.771397],[-161.024205,63.671793],[-161.098069,63.656136],[-161.139971,63.625226],[-161.14254,63.604178],[-161.20207,63.587368],[-161.270082,63.530191],[-161.314358,63.519229],[-161.424338,63.5161],[-161.511954,63.519746],[-161.603114,63.504155],[-161.660178,63.514387],[-161.840405,63.493288],[-161.89621,63.50822],[-161.95696,63.557209],[-162.222744,63.596914],[-162.25462,63.646467],[-162.30435,63.672025],[-162.394359,63.688304],[-162.481046,63.685007],[-162.584319,63.691392],[-162.691327,63.667729],[-162.786166,63.624067],[-162.82644,63.57636],[-162.772898,63.529747],[-162.679831,63.491626],[-162.552552,63.482598],[-162.437773,63.491994],[-162.700287,63.291175],[-162.739517,63.268925],[-162.870861,63.253559],[-162.937697,63.215102],[-162.958127,63.176791],[-163.02618,63.153468],[-163.1187,63.101558],[-163.252479,63.092454],[-163.349816,63.123687],[-163.455166,63.174955],[-163.58471,63.205803],[-163.6581,63.249717],[-163.771848,63.276228],[-163.870625,63.284227],[-163.932943,63.300173],[-164.051256,63.317355],[-164.128906,63.315302],[-164.250047,63.303452],[-164.395461,63.279893],[-164.495673,63.251159],[-164.609283,63.199523],[-164.746821,63.110398],[-164.743802,63.093231],[-164.830317,63.08225],[-164.906736,63.044895],[-164.910614,63.005155],[-164.895533,62.948636],[-164.939272,62.877516],[-164.996533,62.866583],[-165.03814,62.820647],[-165.01689,62.784435],[-165.021888,62.756215],[-165.063015,62.733981],[-165.126,62.740517],[-165.22189,62.722466],[-165.25901,62.6922],[-165.239301,62.634082],[-165.338687,62.576038],[-165.34621,62.526699],[-165.383615,62.496962],[-165.37958,62.469606],[-165.41873,62.440431],[-165.44887,62.391022],[-165.525885,62.35344],[-165.62127,62.291784],[-165.668903,62.250811],[-165.739566,62.204852],[-165.79247,62.190323],[-165.820598,62.166228],[-165.89259,62.138152],[-165.90181,62.119276],[-166.005766,62.123444],[-166.049213,62.108022],[-166.126052,62.038723],[-166.118314,62.009699],[-166.07595,61.98305],[-166.09485,61.946659],[-166.11251,61.873144],[-166.197763,61.834273],[-166.237551,61.780565],[-166.237946,61.740862],[-166.262127,61.693264],[-166.260245,61.658725],[-166.288613,61.580809],[-166.254094,61.522252],[-166.19449,61.459867],[-166.0313,61.395299],[-166.032598,61.378437],[-165.990387,61.316435],[-165.90327,61.256945],[-165.837561,61.238721],[-165.786881,61.198115],[-165.743395,61.125193],[-165.66365,61.057809],[-165.538782,61.02678],[-165.41667,61.01248],[-165.346602,61.015442],[-165.290151,61.03406],[-165.2618,61.021513],[-165.30114,60.990243],[-165.30215,60.96644],[-165.263026,60.923133],[-165.22348,60.89645],[-165.170523,60.876928],[-165.17749,60.847966],[-165.157507,60.829298],[-165.176175,60.796326],[-165.123369,60.736627],[-165.164241,60.707675],[-165.222048,60.695373],[-165.245902,60.675005],[-165.29137,60.665542],[-165.340191,60.638268],[-165.445579,60.623523],[-165.518263,60.566016],[-165.501449,60.52618],[-165.42653,60.474383],[-165.387519,60.459507],[-165.333575,60.459266],[-165.235411,60.42515],[-165.176786,60.381245],[-165.09866,60.334709],[-164.971916,60.277019],[-164.851129,60.24899],[-164.74881,60.240313],[-164.6586,60.175533],[-164.597386,60.158238],[-164.52145,60.091965],[-164.488226,60.054962],[-164.445735,60.030511],[-164.364332,59.999223],[-164.420269,59.9781],[-164.449658,59.953338],[-164.415696,59.884867],[-164.346545,59.848355],[-164.313152,59.806286],[-164.230963,59.753062],[-164.124795,59.716276],[-164.080776,59.670513],[-164.0133,59.637759],[-163.919717,59.640464],[-163.847071,59.617927],[-163.760961,59.639716],[-163.739949,59.668649],[-163.7489,59.686422],[-163.81367,59.716942],[-163.896892,59.714642],[-163.95286,59.735916],[-163.98239,59.761465],[-163.78437,59.740569],[-163.64742,59.743609],[-163.582016,59.751635],[-163.330029,59.769662],[-163.145863,59.795642],[-163.10567,59.778241],[-163.025586,59.767542],[-162.947224,59.768288],[-162.868661,59.780381],[-162.82609,59.80066],[-162.818048,59.840276],[-162.707559,59.880175],[-162.698375,59.891816],[-162.606411,59.888301],[-162.562638,59.896826],[-162.449848,59.884616],[-162.441661,59.844078],[-162.411383,59.823256],[-162.364341,59.810869],[-162.227838,59.8065],[-162.178329,59.814842],[-162.138506,59.772392],[-162.06832,59.716426],[-162.042606,59.670729],[-162.00306,59.644654],[-161.871266,59.524791],[-161.914449,59.469289],[-162.014024,59.432134],[-162.05361,59.40899],[-162.064755,59.360032],[-162.111558,59.332929],[-162.166604,59.275075],[-162.163811,59.224461],[-162.1022,59.128752],[-162.04682,59.079083],[-162.00113,59.070226],[-161.953346,59.032703],[-161.90247,58.977602],[-161.881765,58.944146],[-161.88179,58.857504],[-161.851201,58.807983],[-161.889159,58.7843],[-161.897844,58.762043],[-161.963028,58.726233],[-162.091043,58.722735],[-162.168522,58.710276],[-162.26898,58.667557],[-162.270411,58.622126],[-162.230286,58.598551],[-162.171742,58.589944],[-162.132217,58.571376],[-161.998613,58.561078],[-161.90905,58.572739],[-161.865052,58.566087],[-161.855038,58.532169],[-161.817568,58.505726],[-161.74924,58.496019],[-161.64025,58.502326],[-161.586982,58.518419],[-161.5715,58.543755],[-161.489866,58.561895],[-161.457832,58.585966],[-161.353457,58.603585],[-161.233978,58.643453],[-161.17704,58.607068],[-161.18677,58.573297],[-161.158918,58.512383],[-161.095794,58.490027],[-161.015597,58.503062],[-160.933436,58.496109],[-160.87415,58.510066],[-160.801773,58.551428],[-160.78727,58.58157],[-160.700028,58.672074],[-160.601628,58.793323],[-160.5935,58.821728],[-160.60871,58.843621],[-160.343087,58.880542],[-160.333681,58.848314],[-160.2976,58.79915],[-160.234,58.773956],[-160.150765,58.771996],[-160.089153,58.806303],[-160.014428,58.759786],[-160.004365,58.744612],[-159.93908,58.717906],[-159.89386,58.715036],[-159.81172,58.73545],[-159.748103,58.760305],[-159.701694,58.796397],[-159.675652,58.789483],[-159.54468,58.778419],[-159.404728,58.665957],[-159.151388,58.415773],[-159.148388,58.370302],[-159.107876,58.347541],[-159.018722,58.33414],[-158.96551,58.352805],[-158.91168,58.342909],[-158.810329,58.347151],[-158.744724,58.363725],[-158.689543,58.401682],[-158.624733,58.422033],[-158.605235,58.455592],[-158.213032,58.542604],[-158.147875,58.54378],[-158.062812,58.485518],[-158.089925,58.424193],[-158.168903,58.273056],[-158.281147,58.044249],[-158.435807,57.748042],[-158.487396,57.615627],[-158.51084,57.58447],[-158.529604,57.535139],[-158.55774,57.493597],[-158.667664,57.295653],[-158.866283,56.997563],[-158.8934,56.945108],[-158.98157,56.911126],[-159.08157,56.852889],[-159.173914,56.81445],[-159.35122,56.757721],[-159.425744,56.740187],[-159.50228,56.702802],[-159.580984,56.672923],[-159.807911,56.610327],[-159.905139,56.578175],[-159.994064,56.516406],[-160.069137,56.480279],[-160.225416,56.4336],[-160.305584,56.369214],[-160.413157,56.321734],[-160.439433,56.298814],[-160.483833,56.229537],[-160.513669,56.203017],[-160.545655,56.115279],[-160.584043,56.079386],[-160.616035,56.067421],[-160.929572,56.07938],[-161.04474,56.071402],[-161.211108,56.06741],[-161.42866,56.011574],[-161.600409,55.986716],[-161.729498,55.954868],[-161.82575,55.942655],[-161.906193,55.912201],[-161.981019,55.859419],[-162.043579,55.832035],[-162.134185,55.809061],[-162.17227,55.77353],[-162.289791,55.724866],[-162.415091,55.626611],[-162.50023,55.572852],[-162.547228,55.528745],[-162.608353,55.497775],[-162.645035,55.488497],[-162.68156,55.463181],[-162.72697,55.45887],[-162.84709,55.423916],[-162.935161,55.382051],[-162.982797,55.335617],[-163.150455,55.229505],[-163.20752,55.219671],[-163.29068,55.193397],[-163.458942,55.128136],[-163.567947,55.100158],[-163.630692,55.093229],[-163.770299,55.108826],[-163.858656,55.088983],[-163.912702,55.090584],[-163.950176,55.078812],[-164.04745,55.020657],[-164.157871,55.015654],[-164.226566,54.990329],[-164.307526,54.951458],[-164.418151,54.979558],[-164.521182,54.969124],[-164.614255,54.923877],[-164.637539,54.900759],[-164.651179,54.844199],[-164.698512,54.801539],[-164.70608,54.773484],[-164.78278,54.691702],[-164.835173,54.687341],[-164.903045,54.666465],[-164.999482,54.626249],[-165.03616,54.576598],[-165.026975,54.520773],[-164.99563,54.457309],[-164.901297,54.374651],[-164.805378,54.345537],[-164.74062,54.337261],[-164.70597,54.344197],[-164.617109,54.338874],[-164.48706,54.369602],[-164.414987,54.368823],[-164.346873,54.396565],[-164.27668,54.437207],[-164.250013,54.46793],[-164.22786,54.523664],[-164.122671,54.556119],[-164.056175,54.563649],[-163.876218,54.57047],[-163.712797,54.570191],[-163.59356,54.54114],[-163.52618,54.551662],[-163.501081,54.582011],[-163.413938,54.600378],[-163.225096,54.617287],[-163.059141,54.606203],[-162.999786,54.623531],[-162.95478,54.659815],[-162.968606,54.708702],[-163.028243,54.757222],[-163.104935,54.809385],[-163.141738,54.820251],[-163.110092,54.846192],[-163.07939,54.853838],[-163.0632,54.879518],[-162.967801,54.882516],[-162.893903,54.844238],[-162.822063,54.85659],[-162.790797,54.888816],[-162.732588,54.895763],[-162.651105,54.922157],[-162.602183,54.909218],[-162.618933,54.845228],[-162.56937,54.815132],[-162.512606,54.811487],[-162.48308,54.792724],[-162.533142,54.776612],[-162.554931,54.749344],[-162.539364,54.707207],[-162.507013,54.682701],[-162.459877,54.666524],[-162.49035,54.622377],[-162.477528,54.595216],[-162.479815,54.540932],[-162.428512,54.507669],[-162.365035,54.507693],[-162.337814,54.517803],[-162.31511,54.545719],[-162.317308,54.572429],[-162.279405,54.582348],[-162.242297,54.616666],[-162.23943,54.638708],[-162.187276,54.639579],[-162.161091,54.626509],[-162.104846,54.618286],[-162.062883,54.625184],[-162.03465,54.644777],[-162.033252,54.681056],[-162.05114,54.701677],[-162.013598,54.715976],[-161.966844,54.714802],[-161.935837,54.695241],[-161.841016,54.684516],[-161.782728,54.706758],[-161.765058,54.724459],[-161.710187,54.739154],[-161.63984,54.736159],[-161.576552,54.760705],[-161.564744,54.797197],[-161.667594,54.882865],[-161.710014,54.891592],[-161.66668,54.928116],[-161.659481,54.956383],[-161.591366,54.963672],[-161.525872,54.951589],[-161.47009,54.949729],[-161.3941,54.968485],[-161.359522,54.991451],[-161.361235,55.033825],[-161.417585,55.062056],[-161.37539,55.089157],[-161.313573,55.086621],[-161.272255,55.100089],[-161.246501,55.126975],[-161.24851,55.193258],[-161.13167,55.213938],[-161.08038,55.214364],[-161.039625,55.229462],[-161.01604,55.25142],[-161.019724,55.284664],[-161.068997,55.312391],[-161.12859,55.313389],[-161.18561,55.285761],[-161.219665,55.294142],[-161.112538,55.343435],[-160.995901,55.381243],[-160.907905,55.420374],[-160.859006,55.419874],[-160.892405,55.380674],[-160.93109,55.356524],[-160.95342,55.325367],[-160.93864,55.291764],[-160.94394,55.263825],[-160.922363,55.238058],[-160.942697,55.202328],[-160.935588,55.178226],[-160.90506,55.159454],[-160.899676,55.09535],[-160.83677,55.068151],[-160.786356,55.070815],[-160.709692,55.112055],[-160.648969,55.096288],[-160.598691,55.094056],[-160.59173,55.052774],[-160.57326,55.036982],[-160.51947,55.025123],[-160.460837,55.035845],[-160.430926,55.064862],[-160.446923,55.108174],[-160.41152,55.146275],[-160.376906,55.16542],[-160.3536,55.193655],[-160.278369,55.205074],[-160.242536,55.241055],[-160.221351,55.289228],[-160.18199,55.288604],[-160.130092,55.238675],[-160.149679,55.196376],[-160.190247,55.185347],[-160.214594,55.160117],[-160.270951,55.138869],[-160.288523,55.111625],[-160.262712,55.072852],[-160.261919,55.035201],[-160.244364,55.022575],[-160.292857,55.000674],[-160.29766,54.976474],[-160.326853,54.959691],[-160.343393,54.930881],[-160.337202,54.877276],[-160.30281,54.829746],[-160.241964,54.803465],[-160.20772,54.807342],[-160.113037,54.854921],[-160.098023,54.885416],[-160.034488,54.879604],[-159.986819,54.891218],[-159.949128,54.926216],[-159.90692,54.909474],[-159.8461,54.902435],[-159.81604,54.909532],[-159.775735,54.937838],[-159.768599,54.968668],[-159.734735,54.990245],[-159.580341,54.987598],[-159.543252,54.976369],[-159.546603,54.933583],[-159.50326,54.900755],[-159.43928,54.896002],[-159.362297,54.828315],[-159.300766,54.812881],[-159.26057,54.812737],[-159.201515,54.833672],[-159.132695,54.885531],[-159.11179,54.91569],[-159.13358,54.956011],[-159.213608,54.984254],[-159.25151,55.006699],[-159.212298,55.025842],[-159.204305,55.066011],[-159.216575,55.087442],[-159.252834,55.106171],[-159.321993,55.118247],[-159.348479,55.112897],[-159.410886,55.123496],[-159.408095,55.174397],[-159.427079,55.201146],[-159.419793,55.249896],[-159.399493,55.259793],[-159.398063,55.29337],[-159.41531,55.315381],[-159.4826,55.336294],[-159.544335,55.328407],[-159.57302,55.312141],[-159.609059,55.26779],[-159.66021,55.24291],[-159.744341,55.233364],[-159.7668,55.293085],[-159.792496,55.317154],[-159.8433,55.340049],[-159.883555,55.345198],[-159.932584,55.336981],[-159.996367,55.390194],[-160.050544,55.395777],[-160.059297,55.416208],[-160.04116,55.448879],[-160.002045,55.459081],[-159.951138,55.507583],[-159.655302,55.528886],[-159.584658,55.524259],[-159.538153,55.532006],[-159.50398,55.567355],[-159.52152,55.600894],[-159.457991,55.628875],[-158.979703,55.630341],[-158.23744,55.63499],[-157.964441,55.812036],[-157.71187,55.969416],[-157.32134,56.21831],[-157.000102,56.420097],[-156.745451,56.576315],[-156.357314,56.822259],[-156.358967,57.037181],[-156.31167,57.067916],[-156.316483,57.105502],[-156.262757,57.133598],[-156.227811,57.162455],[-156.214506,57.19518],[-156.247041,57.243776],[-156.167196,57.345852],[-156.098355,57.379848],[-155.978919,57.395895],[-155.93976,57.41287],[-155.844485,57.497513],[-155.719259,57.497419],[-155.645503,57.522358],[-155.632567,57.586813],[-155.589109,57.608276],[-155.45986,57.637648],[-155.360914,57.625029],[-155.300397,57.642894],[-155.277003,57.676112],[-155.245788,57.681595],[-155.209955,57.716257],[-155.15006,57.750022],[-155.160861,57.790588],[-155.078876,57.79534],[-155.04352,57.81239],[-154.99402,57.863188],[-154.969687,57.927854],[-154.940742,57.975071],[-154.893803,57.983541],[-154.8653,57.967639],[-154.800929,57.959148],[-154.708989,57.982295],[-154.589135,57.989779],[-154.539595,57.988197],[-154.39074,57.999301],[-154.335126,58.026711],[-154.265523,58.038228],[-154.211801,58.080288],[-154.155186,58.090983],[-154.119565,58.113228],[-154.10892,58.14601],[-154.05897,58.175206],[-154.03904,58.230362],[-153.97004,58.312724],[-153.909327,58.329104],[-153.858557,58.374274],[-153.881794,58.456499],[-153.778412,58.504044],[-153.692361,58.493506],[-153.609804,58.519746],[-153.592668,58.542253],[-153.51515,58.548397],[-153.468709,58.566502],[-153.44702,58.598409],[-153.47878,58.641694],[-153.371581,58.673275],[-153.31802,58.712962],[-153.244798,58.70747],[-153.18696,58.729345],[-153.166846,58.755175],[-153.176636,58.784665],[-153.2025,58.801333],[-153.152349,58.847582],[-153.165141,58.892714],[-153.184086,58.905729],[-153.02118,59.002452],[-152.271933,59.05076],[-151.85538,59.077617],[-151.444173,59.099687],[-151.374719,59.110669],[-151.255287,59.137764],[-150.99219,59.180837],[-150.801062,59.231825],[-150.710969,59.250224],[-150.456456,59.308688],[-150.00212,59.427483],[-149.6213,59.527516],[-149.467504,59.733767],[-149.269625,59.84542],[-148.980202,59.893721],[-148.605476,59.884296],[-148.437224,59.89281],[-148.218506,59.87564],[-148.100746,59.815697],[-148.039657,59.769047],[-147.95644,59.744131],[-147.819382,59.73667],[-147.68724,59.74168],[-147.520846,59.774079],[-147.334827,59.833874],[-147.28096,59.878726],[-147.271129,59.963437],[-147.24174,59.998317],[-147.16157,60.052993],[-147.10194,60.081337],[-146.939279,60.103848],[-146.734306,60.154142],[-146.465707,60.212501],[-146.17931,60.280056],[-145.97657,60.314652],[-145.824323,60.315117],[-145.62169,60.255803],[-145.317687,60.179691],[-145.097975,60.146112],[-144.776015,60.129592],[-144.607277,60.078347],[-144.539684,60.010149],[-144.641727,59.924389],[-144.777798,59.829935],[-144.7781,59.778563],[-144.67677,59.727395],[-144.542182,59.719321],[-144.410509,59.746274],[-144.144078,59.919452],[-143.943218,59.919851],[-143.08325,59.976914],[-142.859039,60.011367],[-142.66896,60.011485],[-142.397798,59.98485],[-142.08237,59.941066],[-141.646571,59.861038],[-141.38329,59.800361],[-141.001683,59.723722],[-141.001678,59.700999],[-140.896295,59.68191],[-140.819288,59.681738],[-140.656438,59.657369],[-140.52559,59.658157],[-140.363094,59.653566],[-140.035167,59.546847],[-139.922459,59.501883],[-139.731919,59.438497],[-139.681604,59.415006],[-139.53141,59.357192],[-139.318925,59.293719],[-139.116489,59.249399],[-138.89056,59.180721],[-138.772796,59.125694],[-138.667201,59.083119],[-138.489984,59.042349],[-138.328414,59.011938],[-138.291085,59.000384],[-138.2044,58.954633],[-138.133478,58.92352],[-138.025971,58.85244],[-138.037571,58.790109],[-137.99604,58.749505],[-137.869552,58.691561],[-137.775938,58.640754],[-137.762218,58.596502],[-137.675051,58.554838],[-137.602565,58.541748],[-137.450459,58.464324],[-137.37417,58.440911],[-137.315833,58.410112],[-137.247535,58.392847],[-137.149586,58.341307],[-137.072353,58.32362],[-137.0145,58.344311],[-136.974838,58.296044],[-136.937829,58.2686],[-136.89777,58.259519],[-136.855208,58.23085],[-136.814412,58.18879],[-136.761991,58.179405],[-136.711549,58.155669],[-136.61634,58.14723],[-136.544755,58.164448],[-136.510268,58.153464],[-136.615165,58.120712],[-136.650241,58.102629],[-136.678891,58.036293],[-136.679148,58.014266],[-136.65113,57.993777],[-136.658568,57.901455],[-136.616659,57.869218],[-136.454479,57.83078],[-136.41081,57.754657],[-136.244471,57.538702],[-136.05388,57.277261],[-135.870005,57.000592],[-135.569244,56.778592],[-135.206949,56.50889],[-134.867499,56.247413],[-134.823462,56.218321],[-134.661229,56.142169],[-134.53279,56.028267],[-134.49417,55.999638],[-134.496541,55.969896],[-134.524971,55.948566],[-134.60156,55.922925],[-134.645685,55.871472],[-134.629053,55.843592],[-134.56937,55.80401],[-134.42103,55.748631],[-134.192027,55.693053],[-134.04661,55.850477],[-134.002184,55.847262],[-134.007385,55.805938],[-133.965255,55.780699],[-133.8859,55.780235],[-133.856716,55.792872],[-133.831784,55.74151],[-133.879562,55.715451],[-133.88768,55.683639],[-133.867501,55.657325],[-133.798246,55.633231],[-133.829339,55.570917],[-133.83484,55.53326],[-133.861198,55.501623],[-133.861381,55.480639],[-133.897856,55.459075],[-133.907153,55.434003],[-133.873917,55.39884],[-133.83304,55.388568],[-133.7716,55.394323],[-133.76138,55.346261],[-133.778449,55.319616],[-133.775623,55.282245],[-133.707609,55.229371],[-133.69122,55.198473],[-133.61366,55.170265],[-133.70094,55.078617],[-133.790528,55.000581],[-133.78452,54.95349],[-133.741566,54.822922],[-133.69665,54.736041],[-133.648903,54.632045],[-132.925702,54.65897],[-132.487936,54.670139],[-132.004808,54.682448],[-131.312951,54.694032],[-130.878279,54.700836],[-130.615397,54.705491],[-130.626368,54.738068],[-130.657754,54.761828],[-130.636745,54.778456],[-130.569366,54.790869],[-130.474605,54.838102],[-130.339504,54.921376],[-130.27556,54.97293],[-130.187541,55.064665],[-130.182707,55.093212],[-130.144723,55.146038],[-130.09655,55.197953],[-129.98006,55.28423],[-129.982348,55.302079],[-130.023558,55.338259],[-130.044303,55.45197],[-130.08541,55.491517],[-130.126743,55.581282],[-130.11168,55.682051],[-130.14804,55.715041],[-130.150595,55.767031],[-130.12372,55.80704],[-130.08451,55.823997],[-130.024731,55.915901],[-130.016871,56.017333],[-130.10277,56.116692],[-130.24554,56.096876],[-130.343714,56.127162],[-130.425579,56.140678],[-130.466874,56.23979],[-130.54117,56.248017],[-130.622488,56.267937],[-130.78223,56.367509],[-131.085704,56.40654],[-131.167925,56.448361],[-131.461806,56.547904],[-131.581221,56.613275],[-131.83513,56.601849],[-131.862035,56.704136],[-131.90176,56.753158],[-131.871725,56.804965],[-132.125934,56.874698],[-132.119034,56.891264]]]]},\"properties\":{\"name\":\"Alaska\",\"ns_code\":\"01785533\",\"geoid\":\"02\",\"usps_abbrev\":\"AK\",\"fips_code\":\"02\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-91.166073,33.004106],[-91.201842,32.961212],[-91.212837,32.922103],[-91.196785,32.906784],[-91.163877,32.899476],[-91.131304,32.926918],[-91.135095,32.980108],[-91.106581,32.988937],[-91.086802,32.976266],[-91.080431,32.943206],[-91.064804,32.926464],[-91.070602,32.888658],[-91.105631,32.858396],[-91.143559,32.844738],[-91.16167,32.812465],[-91.165814,32.757841],[-91.1495,32.741931],[-91.125107,32.743195],[-91.060766,32.727494],[-91.063946,32.702925],[-91.126201,32.667135],[-91.152699,32.640757],[-91.142782,32.598899],[-91.121157,32.584806],[-91.049796,32.607188],[-91.025769,32.646572],[-91.003545,32.612271],[-91.030991,32.583347],[-91.080411,32.556468],[-91.057706,32.533777],[-91.018469,32.521019],[-90.987669,32.498481],[-91.000106,32.48229],[-91.049307,32.498539],[-91.093742,32.549127],[-91.116708,32.500138],[-91.115708,32.48234],[-91.093508,32.457441],[-91.027406,32.433942],[-90.993863,32.450851],[-90.965987,32.420654],[-90.99408,32.403861],[-91.004706,32.367244],[-90.986672,32.35176],[-90.912363,32.339454],[-90.916157,32.303582],[-90.947834,32.283486],[-90.984077,32.279953],[-90.969515,32.252053],[-90.983381,32.211767],[-91.034302,32.241548],[-91.061408,32.21865],[-91.083708,32.22645],[-91.11727,32.206668],[-91.133587,32.213431],[-91.16488,32.195743],[-91.174577,32.157311],[-91.162822,32.132694],[-91.11099,32.124644],[-91.08163,32.133992],[-91.052754,32.125777],[-91.049707,32.157352],[-91.002906,32.162052],[-91.005006,32.142851],[-91.030891,32.120558],[-91.038607,32.098253],[-91.080008,32.079154],[-91.098708,32.048355],[-91.139309,32.081753],[-91.16201,32.061855],[-91.141109,32.045654],[-91.087408,32.034056],[-91.075908,32.016827],[-91.106908,31.989357],[-91.16441,31.982557],[-91.18811,31.961358],[-91.20101,31.909159],[-91.233903,31.877607],[-91.265812,31.86486],[-91.245624,31.833165],[-91.262011,31.809362],[-91.283212,31.815762],[-91.294713,31.86046],[-91.345214,31.843861],[-91.363514,31.783363],[-91.348114,31.758163],[-91.28742,31.771964],[-91.279789,31.744751],[-91.320913,31.750064],[-91.369375,31.746903],[-91.397115,31.711364],[-91.395715,31.644165],[-91.400815,31.620465],[-91.423016,31.611265],[-91.463817,31.620365],[-91.497665,31.645371],[-91.517233,31.61346],[-91.486518,31.586566],[-91.422716,31.597065],[-91.407915,31.569366],[-91.443916,31.542466],[-91.475917,31.531266],[-91.515919,31.530729],[-91.513366,31.444396],[-91.499812,31.4201],[-91.472067,31.397076],[-91.472542,31.371069],[-91.504756,31.36498],[-91.532336,31.390275],[-91.548465,31.432668],[-91.578334,31.399067],[-91.568953,31.377629],[-91.54638,31.382763],[-91.548213,31.346084],[-91.510049,31.316822],[-91.518578,31.275283],[-91.558684,31.262176],[-91.641253,31.266917],[-91.644356,31.234414],[-91.595029,31.201969],[-91.589046,31.178586],[-91.624217,31.133729],[-91.625199,31.115963],[-91.56415,31.06683],[-91.564397,31.038965],[-91.590463,31.01727],[-91.636942,30.999416],[-91.122154,30.998908],[-90.7335,30.999218],[-90.210877,31.000654],[-89.728147,31.002431],[-89.735686,30.966573],[-89.75595,30.944119],[-89.750073,30.91293],[-89.770269,30.89939],[-89.804632,30.802511],[-89.831537,30.76761],[-89.822969,30.740075],[-89.842525,30.719942],[-89.841521,30.686568],[-89.820068,30.632808],[-89.818771,30.59288],[-89.78968,30.544656],[-89.779159,30.544029],[-89.752807,30.502962],[-89.721405,30.488883],[-89.683631,30.451787],[-89.683708,30.405586],[-89.657287,30.357183],[-89.630622,30.337491],[-89.631614,30.257013],[-89.616828,30.225772],[-89.571907,30.180721],[-89.538635,30.19589],[-89.504735,30.157923],[-89.416711,30.177639],[-89.288394,30.17847],[-89.25004,30.185519],[-89.183669,30.212155],[-89.074653,30.154294],[-89.029181,30.177874],[-89.000032,30.1611],[-88.94161,30.159419],[-88.90033,30.16998],[-88.85039,30.192282],[-88.821387,30.21737],[-88.800636,30.202385],[-88.753899,30.188205],[-88.717386,30.190875],[-88.667249,30.178326],[-88.603881,30.170609],[-88.545855,30.176653],[-88.509786,30.157943],[-88.461283,30.146686],[-88.384431,30.158543],[-88.400618,30.476084],[-88.411726,30.722702],[-88.425432,30.998323],[-88.451573,31.481531],[-88.464908,31.705476],[-88.473227,31.893856],[-88.428209,32.25103],[-88.392176,32.548377],[-88.354292,32.875131],[-88.322505,33.142358],[-88.294867,33.367103],[-88.252254,33.719586],[-88.210741,34.0292],[-88.165583,34.383944],[-88.135204,34.615878],[-88.118407,34.724292],[-88.097888,34.892202],[-88.152413,34.919741],[-88.200064,34.995636],[-88.620196,34.995465],[-88.925242,34.994842],[-89.486693,34.993978],[-89.617973,34.995217],[-89.878364,34.994327],[-90.309289,34.995694],[-90.296422,34.976346],[-90.246116,34.944316],[-90.250095,34.90732],[-90.313476,34.871698],[-90.414859,34.831846],[-90.428756,34.841406],[-90.438311,34.884583],[-90.46615,34.890989],[-90.483876,34.861333],[-90.456935,34.823379],[-90.47228,34.802465],[-90.451758,34.741904],[-90.473384,34.725962],[-90.520181,34.731902],[-90.522077,34.754649],[-90.50152,34.77479],[-90.514708,34.801766],[-90.547859,34.779194],[-90.535408,34.750062],[-90.567486,34.72329],[-90.546053,34.702076],[-90.477031,34.701174],[-90.46282,34.684074],[-90.479718,34.659934],[-90.517168,34.630928],[-90.555104,34.646236],[-90.539401,34.670911],[-90.561198,34.69676],[-90.588419,34.670963],[-90.581693,34.604227],[-90.545891,34.563257],[-90.545728,34.53775],[-90.580115,34.514537],[-90.589919,34.484794],[-90.565809,34.4354],[-90.580681,34.410551],[-90.638672,34.385043],[-90.666787,34.355817],[-90.657371,34.327287],[-90.693129,34.32257],[-90.67666,34.371411],[-90.712088,34.363805],[-90.756197,34.367256],[-90.765174,34.342818],[-90.740889,34.306538],[-90.765165,34.280524],[-90.802924,34.282465],[-90.833335,34.264264],[-90.847808,34.20653],[-90.882162,34.216975],[-90.905876,34.243541],[-90.937042,34.235341],[-90.916056,34.196923],[-90.887887,34.181982],[-90.816572,34.183023],[-90.807223,34.163745],[-90.822593,34.144054],[-90.86788,34.142146],[-90.910007,34.165502],[-90.954299,34.138496],[-90.95517,34.118833],[-90.92123,34.093948],[-90.882623,34.096613],[-90.870738,34.084281],[-90.896897,34.024627],[-90.987948,34.019038],[-90.961551,33.979946],[-90.967632,33.963324],[-91.000108,33.966428],[-91.01889,34.003151],[-91.048367,33.985078],[-91.087921,33.975335],[-91.088164,33.960078],[-91.020097,33.937127],[-91.01151,33.924733],[-91.061247,33.877505],[-91.073011,33.857449],[-91.046849,33.815365],[-90.99175,33.792597],[-91.000106,33.769165],[-91.026782,33.763642],[-91.053886,33.778701],[-91.107318,33.770619],[-91.134744,33.782285],[-91.14662,33.732456],[-91.125527,33.70878],[-91.06829,33.716477],[-91.04678,33.706313],[-91.034565,33.673018],[-91.078507,33.658283],[-91.133416,33.67679],[-91.159746,33.706096],[-91.207807,33.700095],[-91.227857,33.683073],[-91.221117,33.662988],[-91.171168,33.647766],[-91.139209,33.625658],[-91.130445,33.606034],[-91.152148,33.582721],[-91.175979,33.582968],[-91.228489,33.564667],[-91.219297,33.532364],[-91.183372,33.498157],[-91.235181,33.438972],[-91.202829,33.434469],[-91.16946,33.452137],[-91.177381,33.484853],[-91.1646,33.497841],[-91.125109,33.472842],[-91.117975,33.453806],[-91.131885,33.430062],[-91.209295,33.404895],[-91.166304,33.379709],[-91.120678,33.388545],[-91.096942,33.41099],[-91.086498,33.451576],[-91.058152,33.428705],[-91.120409,33.363809],[-91.142219,33.348989],[-91.141216,33.298397],[-91.1001,33.238124],[-91.07853,33.283306],[-91.043985,33.269835],[-91.065629,33.232982],[-91.09171,33.220813],[-91.084366,33.180856],[-91.087589,33.145176],[-91.104317,33.131597],[-91.150362,33.130695],[-91.183662,33.141691],[-91.201125,33.108185],[-91.124639,33.064127],[-91.129087,33.033554],[-91.162363,33.019683],[-91.166073,33.004106]]]},\"properties\":{\"name\":\"Mississippi\",\"ns_code\":\"01779790\",\"geoid\":\"28\",\"usps_abbrev\":\"MS\",\"fips_code\":\"28\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-103.002434,36.500397],[-102.625467,36.500364],[-102.22058,36.500333],[-101.834063,36.499556],[-101.496598,36.499455],[-101.219265,36.499463],[-100.824105,36.499619],[-100.522227,36.499291],[-100.000406,36.499702],[-100.000398,35.967746],[-100.000393,35.692515],[-100.000395,35.306413],[-100.000384,35.095109],[-100.00038,34.769239],[-100.000277,34.56045],[-99.971013,34.562501],[-99.953817,34.578527],[-99.921801,34.570253],[-99.888741,34.550452],[-99.85305,34.511585],[-99.821839,34.497848],[-99.814313,34.476204],[-99.753447,34.420853],[-99.727031,34.410554],[-99.694528,34.378218],[-99.648142,34.382312],[-99.600026,34.374688],[-99.569696,34.418418],[-99.517624,34.414494],[-99.398728,34.375832],[-99.387181,34.410842],[-99.395187,34.44203],[-99.37555,34.458789],[-99.320073,34.409267],[-99.28791,34.41403],[-99.261275,34.403508],[-99.269796,34.380843],[-99.237562,34.366701],[-99.234298,34.340451],[-99.208832,34.339172],[-99.213161,34.308476],[-99.189776,34.214357],[-99.121004,34.20185],[-99.080577,34.211483],[-99.05635,34.200014],[-98.951284,34.212189],[-98.920704,34.183435],[-98.887112,34.16826],[-98.831115,34.162154],[-98.766671,34.136834],[-98.690072,34.133155],[-98.643223,34.164531],[-98.603096,34.160501],[-98.577356,34.1491],[-98.504149,34.072287],[-98.486328,34.062598],[-98.442808,34.083144],[-98.418805,34.082655],[-98.398389,34.104566],[-98.398441,34.128456],[-98.36402,34.157109],[-98.32258,34.14972],[-98.300209,34.134579],[-98.232474,34.134641],[-98.200075,34.116783],[-98.16879,34.114262],[-98.130816,34.150532],[-98.109462,34.154111],[-98.09055,34.122484],[-98.120208,34.072127],[-98.10063,34.049984],[-98.103617,34.029207],[-98.084435,34.002893],[-98.041117,33.993456],[-97.97167,34.005434],[-97.946802,33.990893],[-97.956917,33.958502],[-97.957155,33.914454],[-97.984373,33.898024],[-97.877387,33.850236],[-97.833505,33.858096],[-97.805972,33.876571],[-97.763043,33.934145],[-97.732224,33.936681],[-97.687694,33.98718],[-97.66149,33.990818],[-97.609091,33.968093],[-97.59798,33.955247],[-97.589254,33.903922],[-97.55827,33.897099],[-97.500929,33.919538],[-97.458343,33.901887],[-97.452182,33.86896],[-97.463202,33.84309],[-97.426799,33.818641],[-97.37469,33.818552],[-97.340052,33.859841],[-97.313983,33.867535],[-97.255639,33.863702],[-97.245049,33.903216],[-97.212601,33.9159],[-97.185458,33.9007],[-97.166824,33.840395],[-97.194678,33.831192],[-97.205705,33.802908],[-97.192969,33.760784],[-97.154367,33.724094],[-97.123578,33.717044],[-97.095644,33.729222],[-97.085191,33.764738],[-97.092646,33.804198],[-97.048324,33.817269],[-97.057599,33.836506],[-97.02144,33.846379],[-96.98374,33.89209],[-96.996251,33.942664],[-96.973807,33.935697],[-96.924268,33.959159],[-96.905253,33.947219],[-96.895728,33.896414],[-96.866438,33.853149],[-96.841592,33.852894],[-96.832157,33.874835],[-96.794276,33.868886],[-96.761587,33.824407],[-96.713734,33.831328],[-96.690708,33.849959],[-96.673449,33.912278],[-96.630117,33.895422],[-96.59217,33.895513],[-96.597348,33.875101],[-96.62315,33.841496],[-96.573151,33.819182],[-96.532651,33.823143],[-96.500984,33.772801],[-96.430216,33.778654],[-96.370847,33.717996],[-96.359374,33.689432],[-96.318759,33.696753],[-96.306078,33.744307],[-96.290359,33.770831],[-96.263797,33.767522],[-96.220522,33.74739],[-96.179846,33.759618],[-96.165199,33.78013],[-96.149227,33.837091],[-96.084626,33.846656],[-96.048834,33.836468],[-95.998351,33.85105],[-95.94603,33.859338],[-95.92478,33.884843],[-95.830406,33.834785],[-95.815723,33.859815],[-95.77024,33.845202],[-95.75142,33.860683],[-95.756612,33.892028],[-95.733183,33.896447],[-95.688876,33.888486],[-95.665478,33.909004],[-95.627318,33.907806],[-95.597522,33.942342],[-95.552799,33.924311],[-95.551216,33.889713],[-95.533798,33.881153],[-95.442746,33.866506],[-95.363209,33.866812],[-95.325535,33.885734],[-95.306397,33.874906],[-95.253814,33.893977],[-95.250885,33.936063],[-95.21668,33.962652],[-95.157145,33.936727],[-95.049025,33.86409],[-94.998019,33.860504],[-94.964401,33.837021],[-94.942217,33.80578],[-94.923289,33.808742],[-94.91143,33.778383],[-94.886934,33.769067],[-94.860687,33.741945],[-94.827948,33.740882],[-94.773126,33.727187],[-94.736814,33.692332],[-94.684796,33.684353],[-94.639198,33.663737],[-94.626844,33.682035],[-94.593294,33.665258],[-94.564076,33.626265],[-94.505655,33.620668],[-94.485878,33.637865],[-94.472906,34.087971],[-94.466046,34.350002],[-94.457209,34.646764],[-94.449013,34.902025],[-94.438963,35.184733],[-94.430662,35.392478],[-94.450445,35.502435],[-94.5105,35.862498],[-94.580391,36.264929],[-94.617919,36.499414],[-94.618471,36.816277],[-94.617964,36.998905],[-94.75258,36.99859],[-95.165191,36.999631],[-95.45793,36.999234],[-96.00081,36.99886],[-96.500289,36.998643],[-97.039692,36.99896],[-97.595028,36.998546],[-97.994149,36.998508],[-98.300465,36.997684],[-98.797019,36.999459],[-99.105701,36.999451],[-99.630997,36.99997],[-100.107891,37.002312],[-100.734513,36.999059],[-101.090254,36.997876],[-101.602243,36.995047],[-102.042089,36.993016],[-102.375461,36.99458],[-102.698142,36.995149],[-102.815074,36.999774],[-103.002199,37.000104],[-103.002434,36.500397]]]},\"properties\":{\"name\":\"Oklahoma\",\"ns_code\":\"01102857\",\"geoid\":\"40\",\"usps_abbrev\":\"OK\",\"fips_code\":\"40\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-80.518991,40.638801],[-80.584002,40.615723],[-80.62645,40.620176],[-80.66354,40.590768],[-80.664372,40.570016],[-80.627507,40.535793],[-80.618003,40.502049],[-80.594664,40.471389],[-80.612876,40.429466],[-80.612325,40.402762],[-80.632196,40.393667],[-80.607587,40.36956],[-80.612795,40.347669],[-80.59989,40.317666],[-80.622508,40.261765],[-80.66154,40.229812],[-80.668083,40.199689],[-80.705246,40.15416],[-80.708937,40.101422],[-80.73919,40.075199],[-80.731024,40.044291],[-80.742114,40.00888],[-80.740139,39.970795],[-80.764242,39.946151],[-80.756113,39.913402],[-80.795598,39.919717],[-80.809565,39.905658],[-80.793131,39.863751],[-80.826964,39.841656],[-80.826079,39.798584],[-80.869933,39.763555],[-80.829723,39.714041],[-80.865805,39.686484],[-80.866646,39.652616],[-80.88036,39.620706],[-80.933292,39.614812],[-81.0239,39.552313],[-81.092434,39.496075],[-81.134434,39.445075],[-81.185145,39.431479],[-81.211654,39.392977],[-81.26842,39.386318],[-81.344927,39.346794],[-81.38456,39.343449],[-81.406689,39.38809],[-81.456143,39.409274],[-81.557547,39.338774],[-81.565247,39.276175],[-81.677595,39.274171],[-81.69662,39.256139],[-81.691339,39.227947],[-81.725622,39.215905],[-81.756282,39.176993],[-81.742353,39.111277],[-81.754154,39.088377],[-81.812354,39.061279],[-81.764253,39.015279],[-81.78182,38.964935],[-81.756131,38.933546],[-81.785647,38.926192],[-81.814235,38.946168],[-81.844486,38.928746],[-81.848653,38.901407],[-81.890899,38.873919],[-81.92667,38.901311],[-81.900595,38.937671],[-81.933186,38.98766],[-81.981159,38.994351],[-82.00599,39.029387],[-82.037749,39.023865],[-82.052063,38.993978],[-82.090165,38.975278],[-82.129166,38.908179],[-82.143167,38.898079],[-82.144167,38.84188],[-82.189293,38.815768],[-82.221566,38.787187],[-82.199999,38.759057],[-82.182467,38.708782],[-82.190867,38.680383],[-82.171966,38.625384],[-82.181167,38.599784],[-82.221468,38.591284],[-82.27147,38.595383],[-82.293471,38.575383],[-82.303663,38.499124],[-82.323999,38.449268],[-82.389746,38.434355],[-82.404882,38.439347],[-82.53769,38.404157],[-82.571613,38.405138],[-82.59348,38.421821],[-82.598121,38.345933],[-82.57672,38.328515],[-82.582655,38.298365],[-82.574518,38.264505],[-82.598295,38.217361],[-82.611601,38.171339],[-82.644443,38.166561],[-82.634702,38.136941],[-82.585342,38.106634],[-82.583357,38.090032],[-82.54924,38.067952],[-82.518956,38.002153],[-82.488655,37.998912],[-82.468851,37.97323],[-82.502198,37.933052],[-82.475056,37.910859],[-82.420484,37.885446],[-82.412119,37.843891],[-82.376169,37.80203],[-82.340596,37.786028],[-82.321174,37.756087],[-82.319566,37.734238],[-82.295881,37.686736],[-82.258068,37.656883],[-82.226156,37.652913],[-82.2145,37.6254],[-82.181691,37.627147],[-82.156297,37.592365],[-82.132284,37.593327],[-82.117304,37.559226],[-82.075651,37.555915],[-82.056353,37.536804],[-81.968012,37.538035],[-81.939989,37.512591],[-81.996578,37.476705],[-81.99227,37.460916],[-81.945765,37.440214],[-81.928154,37.4145],[-81.93587,37.382856],[-81.928463,37.360871],[-81.897936,37.33367],[-81.874059,37.327674],[-81.853553,37.287706],[-81.825851,37.279311],[-81.789294,37.284416],[-81.746039,37.263398],[-81.740124,37.237752],[-81.678221,37.20154],[-81.560631,37.206663],[-81.437158,37.274065],[-81.416663,37.273214],[-81.394287,37.316411],[-81.362156,37.337687],[-81.320105,37.299323],[-81.225104,37.234874],[-81.167029,37.262881],[-81.104147,37.280605],[-80.987319,37.301359],[-80.951018,37.29497],[-80.89805,37.316779],[-80.849451,37.346909],[-80.883247,37.383933],[-80.863003,37.411514],[-80.837678,37.425658],[-80.784188,37.394587],[-80.770082,37.372363],[-80.705203,37.394618],[-80.645893,37.422147],[-80.552036,37.473563],[-80.511391,37.481672],[-80.492981,37.457749],[-80.49486,37.435072],[-80.475601,37.422949],[-80.425629,37.449885],[-80.299789,37.508271],[-80.31237,37.546225],[-80.328504,37.564315],[-80.258919,37.595499],[-80.220984,37.627767],[-80.267504,37.646156],[-80.296078,37.691731],[-80.258143,37.720612],[-80.262765,37.738336],[-80.248389,37.76825],[-80.206482,37.81597],[-80.162202,37.875122],[-80.140609,37.883932],[-80.069431,37.945463],[-80.008888,37.99083],[-79.98029,38.027596],[-79.953511,38.08148],[-79.92633,38.107147],[-79.929031,38.139771],[-79.897335,38.193381],[-79.850326,38.233324],[-79.788945,38.268703],[-79.809934,38.304681],[-79.742771,38.353367],[-79.706634,38.41573],[-79.689667,38.431462],[-79.699473,38.474527],[-79.680074,38.510617],[-79.662875,38.570416],[-79.649075,38.591515],[-79.53687,38.550917],[-79.521469,38.533918],[-79.476638,38.457228],[-79.312276,38.411876],[-79.279692,38.424211],[-79.254482,38.45593],[-79.210591,38.492913],[-79.205859,38.524521],[-79.158257,38.593119],[-79.131056,38.653218],[-79.092655,38.660417],[-79.092955,38.702002],[-79.047349,38.79181],[-78.993761,38.850021],[-78.869276,38.762991],[-78.78803,38.885123],[-78.759085,38.900529],[-78.739147,38.927134],[-78.719914,38.906076],[-78.680456,38.925313],[-78.629553,38.980866],[-78.601655,38.964603],[-78.550467,39.018065],[-78.571901,39.031995],[-78.479597,39.109505],[-78.459311,39.114273],[-78.427294,39.152726],[-78.403907,39.167738],[-78.438695,39.198093],[-78.399669,39.243874],[-78.419422,39.257476],[-78.360571,39.317597],[-78.340415,39.353628],[-78.366867,39.35929],[-78.343214,39.388807],[-78.359918,39.409028],[-78.346718,39.427618],[-78.347087,39.466012],[-78.262815,39.41434],[-78.158193,39.343391],[-77.828806,39.132773],[-77.798157,39.200703],[-77.771365,39.236493],[-77.747587,39.294834],[-77.719519,39.321314],[-77.755728,39.333938],[-77.752987,39.379023],[-77.735869,39.391172],[-77.753728,39.423908],[-77.786332,39.428876],[-77.798396,39.456328],[-77.797252,39.489465],[-77.827301,39.493075],[-77.861176,39.514293],[-77.874231,39.564072],[-77.835676,39.567156],[-77.83777,39.605895],[-77.884519,39.615811],[-77.889135,39.597453],[-77.958716,39.608862],[-78.006719,39.601483],[-78.037405,39.637044],[-78.104433,39.68144],[-78.171549,39.695643],[-78.254252,39.639976],[-78.266648,39.618951],[-78.355323,39.640663],[-78.372013,39.611699],[-78.42478,39.624541],[-78.39704,39.590225],[-78.407865,39.578629],[-78.445456,39.591446],[-78.449874,39.570619],[-78.417295,39.549603],[-78.468923,39.516483],[-78.527333,39.524801],[-78.566115,39.519371],[-78.61991,39.539236],[-78.654697,39.534605],[-78.72371,39.563493],[-78.795268,39.6107],[-78.825968,39.588183],[-78.837596,39.56714],[-78.91672,39.48632],[-78.934298,39.486284],[-78.957301,39.440095],[-79.054694,39.473101],[-79.09113,39.47252],[-79.102816,39.450482],[-79.214443,39.363405],[-79.256882,39.355459],[-79.253878,39.3371],[-79.347416,39.291396],[-79.420836,39.238453],[-79.42423,39.226842],[-79.469793,39.203734],[-79.486875,39.205896],[-79.482366,39.531689],[-79.478866,39.531689],[-79.476662,39.721078],[-79.940671,39.720671],[-80.519342,39.721403],[-80.518891,39.890964],[-80.518975,40.349789],[-80.518991,40.638801]]]},\"properties\":{\"name\":\"West Virginia\",\"ns_code\":\"01779805\",\"geoid\":\"54\",\"usps_abbrev\":\"WV\",\"fips_code\":\"54\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-87.019935,42.493498],[-87.125042,42.097098],[-87.207774,41.760956],[-86.87503,41.760335],[-86.443566,41.759553],[-86.108465,41.760697],[-85.750291,41.758969],[-85.308274,41.759947],[-84.980969,41.759491],[-84.80588,41.760171],[-84.805972,41.696118],[-84.475933,41.703813],[-84.192317,41.71101],[-83.709474,41.72524],[-83.41585,41.733794],[-83.11246,41.95941],[-83.14945,42.040264],[-83.119228,42.125263],[-83.134384,42.174621],[-83.126361,42.238412],[-83.099977,42.286698],[-83.063637,42.316857],[-82.989197,42.332481],[-82.945485,42.347396],[-82.83025,42.373492],[-82.668064,42.533112],[-82.64307,42.554321],[-82.606967,42.548282],[-82.524833,42.605976],[-82.510039,42.636793],[-82.510851,42.664992],[-82.467667,42.761906],[-82.482574,42.808644],[-82.468144,42.859423],[-82.469901,42.886265],[-82.456249,42.925177],[-82.413474,42.976887],[-82.424916,42.993055],[-82.356242,43.125029],[-82.295101,43.250028],[-82.122971,43.590827],[-82.158001,43.750023],[-82.249914,44.165143],[-82.356455,44.625015],[-82.374927,44.716437],[-82.44222,45.000018],[-82.52064,45.335902],[-82.74995,45.442221],[-82.874956,45.496723],[-82.999963,45.556378],[-83.48397,45.771937],[-83.636867,45.773944],[-83.547626,45.87502],[-83.433558,45.998895],[-83.571467,46.105998],[-83.654647,46.122055],[-83.759529,46.103173],[-83.826243,46.119288],[-83.903737,46.060819],[-83.955424,46.057188],[-83.976134,46.102554],[-84.005873,46.118469],[-84.005685,46.149551],[-84.077027,46.187558],[-84.108089,46.241238],[-84.11963,46.315013],[-84.106247,46.321963],[-84.138906,46.372221],[-84.146172,46.41852],[-84.111225,46.504119],[-84.128925,46.530119],[-84.177428,46.52692],[-84.226131,46.53392],[-84.25443,46.500821],[-84.293016,46.492803],[-84.341427,46.507689],[-84.420274,46.501077],[-84.445149,46.489016],[-84.475594,46.452864],[-84.556976,46.46065],[-84.762957,46.634324],[-84.859335,46.888843],[-85.125524,47.000018],[-85.250111,47.05654],[-85.500131,47.158284],[-85.750143,47.261387],[-85.875144,47.316261],[-86.125142,47.416744],[-86.125141,47.423468],[-86.312774,47.49994],[-86.50013,47.569523],[-86.631321,47.62492],[-86.949226,47.749905],[-87.0631,47.799991],[-87.250103,47.870477],[-87.500095,47.968114],[-87.625091,48.019019],[-87.875082,48.114726],[-88.000078,48.155883],[-88.000077,48.16405],[-88.369862,48.306063],[-88.677442,48.245397],[-88.9712,48.12488],[-89.33755,47.974261],[-89.483385,48.013716],[-89.625167,47.799549],[-89.820034,47.49991],[-89.957102,47.291103],[-90.00014,47.214969],[-90.125164,47.020679],[-90.418392,46.566099],[-90.395069,46.533768],[-90.350684,46.540325],[-90.310839,46.536289],[-90.263139,46.502668],[-90.216172,46.501272],[-90.158345,46.409696],[-90.118822,46.359531],[-90.120572,46.337039],[-89.918893,46.29776],[-89.463498,46.210062],[-89.09164,46.138447],[-88.851133,46.040444],[-88.782073,46.016185],[-88.739671,46.027423],[-88.678868,46.013595],[-88.658654,45.98929],[-88.616471,45.987732],[-88.565127,46.015729],[-88.50748,46.018516],[-88.49798,45.995794],[-88.464731,46.000738],[-88.423493,45.982072],[-88.380891,45.991813],[-88.301378,45.956459],[-88.25498,45.963512],[-88.230193,45.947251],[-88.180498,45.948287],[-88.126654,45.921791],[-88.102461,45.921546],[-88.101757,45.883192],[-88.070483,45.874329],[-88.132456,45.824156],[-88.094754,45.785755],[-88.05064,45.780999],[-87.99454,45.795637],[-87.963452,45.75822],[-87.879812,45.754843],[-87.839402,45.717814],[-87.812338,45.711303],[-87.781007,45.673934],[-87.82271,45.6627],[-87.774682,45.602024],[-87.808366,45.544662],[-87.792769,45.499967],[-87.812884,45.464329],[-87.861697,45.434473],[-87.849668,45.409518],[-87.884855,45.362792],[-87.850418,45.347492],[-87.79039,45.353445],[-87.754104,45.349442],[-87.693956,45.389893],[-87.658175,45.369476],[-87.647768,45.340563],[-87.687729,45.297492],[-87.72796,45.207956],[-87.735282,45.176565],[-87.6839,45.144135],[-87.661211,45.108279],[-87.44251,45.076434],[-87.405694,45.201969],[-87.315422,45.240657],[-87.17117,45.35254],[-87.101133,45.44423],[-86.754236,45.44361],[-86.282304,45.249983],[-86.2501,45.235735],[-86.433128,45.124991],[-86.49993,45.0808],[-86.686288,44.881002],[-86.855184,44.500339],[-86.847694,44.500339],[-87.00008,44.154821],[-87.013234,44.131905],[-87.048847,44],[-87.114102,43.734111],[-87.125079,43.670445],[-87.147166,43.379859],[-87.108397,43.12501],[-87.058716,42.765202],[-87.019935,42.493498]]]},\"properties\":{\"name\":\"Michigan\",\"ns_code\":\"01779789\",\"geoid\":\"26\",\"usps_abbrev\":\"MI\",\"fips_code\":\"26\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-109.050076,41.000659],[-109.048044,40.619241],[-109.050946,40.444368],[-109.05082,40.231093],[-109.050613,39.875055],[-109.051263,39.624974],[-109.050765,39.366677],[-109.051516,39.124982],[-109.054189,38.874984],[-109.059538,38.719986],[-109.060062,38.275489],[-109.041762,38.16469],[-109.042816,37.962359],[-109.041058,37.907236],[-109.04192,37.530525],[-109.04581,37.374993],[-109.045223,36.999084],[-108.875317,36.998839],[-108.249358,36.999015],[-107.729194,37.000002],[-107.464127,37.000004],[-107.10724,37.000008],[-106.877293,37.000141],[-106.869798,36.992424],[-106.591178,36.992923],[-106.201469,36.994122],[-105.66472,36.995874],[-105.222398,36.995166],[-105.029226,36.992727],[-104.644997,36.993379],[-104.338833,36.993535],[-103.817803,36.997339],[-103.499968,36.999185],[-103.002199,37.000104],[-102.815074,36.999774],[-102.698142,36.995149],[-102.375461,36.99458],[-102.042089,36.993016],[-102.04182,37.410149],[-102.041626,37.696699],[-102.044515,37.984575],[-102.044946,38.384358],[-102.045075,38.669561],[-102.046046,38.981776],[-102.049052,39.373991],[-102.050952,39.749993],[-102.051744,40.003078],[-102.051554,40.500023],[-102.051417,40.742793],[-102.051717,41.002359],[-102.556721,41.0022],[-102.827237,41.002143],[-103.365314,41.001846],[-103.669054,41.001442],[-104.053249,41.001406],[-104.625519,41.001428],[-104.855273,40.998048],[-105.341415,40.997972],[-105.726441,40.996836],[-106.061181,40.996999],[-106.314218,40.998923],[-106.439563,41.001978],[-106.625592,41.00211],[-107.003119,41.003421],[-107.241194,41.002804],[-107.750627,41.001971],[-108.046539,41.002064],[-108.181227,41.000455],[-108.526029,40.999592],[-109.050076,41.000659]]]},\"properties\":{\"name\":\"Colorado\",\"ns_code\":\"01779779\",\"geoid\":\"08\",\"usps_abbrev\":\"CO\",\"fips_code\":\"08\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-74.694914,41.357423],[-74.755894,41.34528],[-74.773225,41.324513],[-74.830057,41.2872],[-74.857152,41.248975],[-74.859323,41.220507],[-74.882139,41.180836],[-74.923169,41.138146],[-74.982212,41.108245],[-74.98259,41.079172],[-75.012873,41.066516],[-75.025777,41.039806],[-75.088596,41.015024],[-75.130575,40.991093],[-75.117767,40.953013],[-75.053664,40.87366],[-75.064328,40.848338],[-75.108505,40.791094],[-75.139106,40.773606],[-75.171587,40.777745],[-75.196535,40.751631],[-75.182804,40.73365],[-75.203921,40.691498],[-75.178019,40.671763],[-75.200452,40.649219],[-75.201348,40.614628],[-75.172877,40.564423],[-75.134995,40.575662],[-75.099827,40.567398],[-75.0681,40.541488],[-75.062761,40.480026],[-75.070608,40.456196],[-75.060917,40.421737],[-75.031778,40.404813],[-74.99586,40.410384],[-74.967499,40.398813],[-74.939711,40.338006],[-74.868208,40.295206],[-74.842308,40.250508],[-74.770406,40.214508],[-74.754305,40.185209],[-74.722325,40.160727],[-74.724369,40.146412],[-74.788287,40.120579],[-74.826226,40.124066],[-74.859721,40.085311],[-74.935042,40.067836],[-75.013237,40.020303],[-75.04709,40.009941],[-75.075605,39.978041],[-75.119491,39.965372],[-75.135239,39.950677],[-75.128253,39.913164],[-75.137296,39.889966],[-75.192738,39.879006],[-75.221371,39.861543],[-75.272202,39.849076],[-75.330432,39.849013],[-75.415062,39.801919],[-75.45944,39.765811],[-75.475524,39.733033],[-75.50638,39.697385],[-75.515075,39.666911],[-75.559737,39.630452],[-75.553491,39.601856],[-75.51277,39.57829],[-75.533412,39.542044],[-75.527861,39.498139],[-75.55909,39.495806],[-75.560538,39.455645],[-75.444751,39.355089],[-75.321021,39.251379],[-75.249017,39.158945],[-75.191671,39.079],[-75.168462,39.056179],[-75.015123,38.788657],[-74.910968,38.875112],[-74.815052,38.901819],[-74.773938,38.942794],[-74.728402,38.974249],[-74.715571,39.009216],[-74.682193,39.049824],[-74.642169,39.084574],[-74.644279,39.114824],[-74.555609,39.219755],[-74.461615,39.301023],[-74.392196,39.312286],[-74.362513,39.325781],[-74.265918,39.422415],[-74.249585,39.4526],[-74.183189,39.532994],[-74.096531,39.649949],[-74.038158,39.75012],[-74.019899,39.875119],[-73.999577,39.976798],[-73.966265,40.108597],[-73.898208,40.274353],[-73.887082,40.345009],[-73.886652,40.489794],[-73.949912,40.52554],[-74.228153,40.477399],[-74.25909,40.497207],[-74.250609,40.541851],[-74.218398,40.556996],[-74.19952,40.597539],[-74.202247,40.630903],[-74.18139,40.646475],[-74.143255,40.642149],[-74.055739,40.65176],[-74.026284,40.699902],[-74.013784,40.756601],[-73.963182,40.8269],[-73.929821,40.888682],[-73.896479,40.981701],[-73.902679,40.997307],[-74.18239,41.121595],[-74.301994,41.172594],[-74.457584,41.248225],[-74.694914,41.357423]]]},\"properties\":{\"name\":\"New Jersey\",\"ns_code\":\"01779795\",\"geoid\":\"34\",\"usps_abbrev\":\"NJ\",\"fips_code\":\"34\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-75.788596,39.722199],[-75.788521,39.648133],[-75.765719,39.366758],[-75.746992,39.133744],[-75.715684,38.740062],[-75.69367,38.46008],[-75.416925,38.452538],[-75.232115,38.451222],[-74.986282,38.451632],[-74.985171,38.528171],[-74.993519,38.612501],[-75.001968,38.631683],[-75.003025,38.685964],[-75.014212,38.745036],[-75.015123,38.788657],[-75.168462,39.056179],[-75.191671,39.079],[-75.249017,39.158945],[-75.321021,39.251379],[-75.444751,39.355089],[-75.560538,39.455645],[-75.55909,39.495806],[-75.527861,39.498139],[-75.533412,39.542044],[-75.51277,39.57829],[-75.553491,39.601856],[-75.559737,39.630452],[-75.515075,39.666911],[-75.50638,39.697385],[-75.475524,39.733033],[-75.45944,39.765811],[-75.415062,39.801919],[-75.481039,39.829319],[-75.564047,39.839488],[-75.639703,39.828955],[-75.680468,39.813939],[-75.719502,39.790365],[-75.754056,39.756178],[-75.773786,39.7222],[-75.788596,39.722199]]]},\"properties\":{\"name\":\"Delaware\",\"ns_code\":\"01779781\",\"geoid\":\"10\",\"usps_abbrev\":\"DE\",\"fips_code\":\"10\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-116.049193,49.000912],[-116.049273,48.714641],[-116.049976,48.237463],[-116.048464,47.976802],[-116.030587,47.972247],[-115.993678,47.926183],[-115.965173,47.910654],[-115.901401,47.843097],[-115.881523,47.849672],[-115.852291,47.827991],[-115.846949,47.784286],[-115.824575,47.752137],[-115.797299,47.75752],[-115.783504,47.729305],[-115.722629,47.695162],[-115.73627,47.654762],[-115.694284,47.62346],[-115.692138,47.596425],[-115.755295,47.551438],[-115.750993,47.541594],[-115.649524,47.477752],[-115.646384,47.456695],[-115.69293,47.457237],[-115.749714,47.440362],[-115.657681,47.40065],[-115.643818,47.377537],[-115.617247,47.38252],[-115.551374,47.350042],[-115.52605,47.299252],[-115.426636,47.280158],[-115.409306,47.263988],[-115.323575,47.256529],[-115.294785,47.220914],[-115.300504,47.188139],[-115.261885,47.181742],[-115.245859,47.151964],[-115.191803,47.132048],[-115.107133,47.04904],[-115.087807,47.045518],[-115.047857,46.969532],[-115.00091,46.967703],[-114.959625,46.929493],[-114.926985,46.919586],[-114.936822,46.89716],[-114.920459,46.827697],[-114.856874,46.801633],[-114.830671,46.783605],[-114.787913,46.780818],[-114.765059,46.758081],[-114.788656,46.714033],[-114.75112,46.697385],[-114.717645,46.713994],[-114.696656,46.740572],[-114.646665,46.730802],[-114.620859,46.707415],[-114.641471,46.679515],[-114.635868,46.65951],[-114.593914,46.633046],[-114.546333,46.64498],[-114.466902,46.631695],[-114.453601,46.648967],[-114.359108,46.670061],[-114.320665,46.646963],[-114.329765,46.575665],[-114.347946,46.551642],[-114.348521,46.510241],[-114.403019,46.498675],[-114.379338,46.460166],[-114.384756,46.411784],[-114.422458,46.387097],[-114.410083,46.365625],[-114.413758,46.335945],[-114.433478,46.305502],[-114.427309,46.283624],[-114.47077,46.264517],[-114.451795,46.239666],[-114.44521,46.167885],[-114.477345,46.160629],[-114.515703,46.165687],[-114.5213,46.125287],[-114.474777,46.113191],[-114.459526,46.093907],[-114.466937,46.065689],[-114.493475,46.052402],[-114.457101,45.997296],[-114.414118,45.979278],[-114.402291,45.957799],[-114.431159,45.935737],[-114.395059,45.901458],[-114.406307,45.869145],[-114.509303,45.845531],[-114.514586,45.82685],[-114.566172,45.773864],[-114.547896,45.743606],[-114.504307,45.72173],[-114.495421,45.703321],[-114.507754,45.657626],[-114.563305,45.631612],[-114.538467,45.605892],[-114.558253,45.585104],[-114.549508,45.56059],[-114.517761,45.568129],[-114.43012,45.529309],[-114.429048,45.516624],[-114.36562,45.490416],[-114.345019,45.459916],[-114.279217,45.480616],[-114.248988,45.520636],[-114.248532,45.545419],[-114.192802,45.536596],[-114.180043,45.551432],[-114.135249,45.557465],[-114.122322,45.58426],[-114.086584,45.59118],[-114.067619,45.627706],[-114.013786,45.658238],[-114.019315,45.692937],[-113.973624,45.702809],[-113.933957,45.693061],[-113.931494,45.672965],[-113.897663,45.646706],[-113.904571,45.624767],[-113.862169,45.623784],[-113.806729,45.602146],[-113.797009,45.582017],[-113.821437,45.565973],[-113.83388,45.520632],[-113.772791,45.514411],[-113.760014,45.480138],[-113.775924,45.470998],[-113.77407,45.406659],[-113.734402,45.392353],[-113.738729,45.329741],[-113.67886,45.276092],[-113.69276,45.264299],[-113.650064,45.23471],[-113.636889,45.212983],[-113.59252,45.184689],[-113.58968,45.15536],[-113.556314,45.115243],[-113.513342,45.115225],[-113.520134,45.093033],[-113.45197,45.059247],[-113.443793,44.959894],[-113.494444,44.948597],[-113.49167,44.924408],[-113.461084,44.886742],[-113.455071,44.865424],[-113.422376,44.842595],[-113.356062,44.819798],[-113.340471,44.784682],[-113.247166,44.82295],[-113.193117,44.801653],[-113.179366,44.787149],[-113.131151,44.772392],[-113.137704,44.760109],[-113.102129,44.729032],[-113.098119,44.697975],[-113.067762,44.679474],[-113.056772,44.618659],[-113.087851,44.599243],[-113.039836,44.566842],[-113.045502,44.543934],[-113.00703,44.525872],[-113.027148,44.495716],[-113.003544,44.450814],[-112.951146,44.416699],[-112.919625,44.409581],[-112.844859,44.358221],[-112.812735,44.377181],[-112.813272,44.398173],[-112.840278,44.424964],[-112.781294,44.484888],[-112.71793,44.504273],[-112.664485,44.48645],[-112.60293,44.490939],[-112.550557,44.484928],[-112.50031,44.463051],[-112.473207,44.480027],[-112.390543,44.450666],[-112.366437,44.467358],[-112.356688,44.492621],[-112.358917,44.528847],[-112.319198,44.53911],[-112.286187,44.568472],[-112.228894,44.564002],[-112.221698,44.543519],[-112.183945,44.533053],[-112.130952,44.537791],[-112.096299,44.523212],[-112.032707,44.546642],[-111.978786,44.53589],[-111.947941,44.556776],[-111.866126,44.562811],[-111.821488,44.509286],[-111.704218,44.560205],[-111.617695,44.551012],[-111.591768,44.561502],[-111.499991,44.54192],[-111.525764,44.604883],[-111.473633,44.664968],[-111.490008,44.699537],[-111.465497,44.713824],[-111.397766,44.72476],[-111.37476,44.750295],[-111.337113,44.733204],[-111.294843,44.70006],[-111.255082,44.651011],[-111.219236,44.622616],[-111.241502,44.614225],[-111.230351,44.579727],[-111.180773,44.569038],[-111.131379,44.499925],[-111.048974,44.474072],[-111.055208,44.624927],[-111.056889,44.866658],[-111.055199,45.001321],[-110.785008,45.002952],[-110.705272,44.992324],[-110.402927,44.99381],[-110.362698,45.000593],[-110.199503,44.996188],[-110.110103,45.003905],[-109.75073,45.001605],[-109.103373,45.005886],[-109.08137,44.99946],[-108.499092,44.9998],[-107.997369,45.001565],[-107.75065,45.000778],[-107.500025,45.001512],[-107.134179,45.000109],[-107.084938,44.996599],[-106.62561,44.994927],[-106.250717,44.993688],[-105.928183,44.993647],[-105.913382,45.000941],[-105.718212,45.000333],[-105.250545,45.000168],[-104.772765,44.999158],[-104.366692,44.998151],[-104.057697,44.99738],[-104.040131,45.011116],[-104.040188,45.374158],[-104.042596,45.749942],[-104.045441,45.94531],[-104.045235,46.125004],[-104.045045,46.509788],[-104.046067,46.838634],[-104.044777,47.125012],[-104.044406,47.459399],[-104.043273,47.750027],[-104.041443,47.863054],[-104.043865,47.968937],[-104.045937,48.249276],[-104.048275,48.706527],[-104.048736,48.999877],[-104.250498,48.999469],[-104.750522,48.998986],[-105.343466,48.999276],[-105.703623,48.999666],[-106.112108,48.999052],[-106.617539,48.999583],[-107.088925,49.000021],[-107.441017,48.999363],[-108.046984,48.999393],[-108.375681,48.99926],[-108.99236,48.999215],[-109.285975,49.000479],[-109.731522,48.999862],[-110.217815,48.99915],[-110.8749,48.998105],[-111.500812,48.996963],[-111.750648,48.997358],[-112.125884,48.998904],[-112.699455,48.998685],[-113.144004,48.998365],[-113.692982,48.997632],[-114.059188,48.998856],[-114.250971,49.000905],[-114.537821,49.000782],[-115.207912,48.999228],[-115.619839,49.000596],[-116.049193,49.000912]]]},\"properties\":{\"name\":\"Montana\",\"ns_code\":\"00767982\",\"geoid\":\"30\",\"usps_abbrev\":\"MT\",\"fips_code\":\"30\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-116.915989,45.995413],[-116.942656,46.061],[-116.981747,46.092881],[-116.959548,46.099058],[-116.922648,46.160744],[-116.962966,46.19968],[-116.956031,46.225976],[-116.970298,46.261233],[-116.991134,46.276342],[-116.987939,46.298031],[-117.022293,46.31747],[-117.023844,46.335976],[-117.053953,46.344566],[-117.039813,46.425425],[-117.039498,46.931999],[-117.040019,47.259272],[-117.039921,47.492266],[-117.04247,47.839009],[-117.041107,48.124904],[-117.035086,48.38521],[-117.035425,48.499914],[-117.032107,48.874926],[-117.032351,48.999188],[-117.463929,49.000653],[-117.8761,49.000546],[-118.174832,49.000261],[-118.535931,49.000163],[-119.126155,49.000236],[-119.376171,49.000224],[-120.001199,48.999419],[-120.716604,49.000188],[-121.12624,49.001412],[-121.751252,48.997399],[-122.098357,49.002146],[-122.737962,49.002075],[-123.32242,49.001962],[-123.008472,48.831338],[-123.008865,48.767302],[-123.274009,48.692969],[-123.251303,48.64152],[-123.219078,48.548822],[-123.160797,48.453899],[-123.115529,48.422974],[-123.249034,48.28431],[-123.542428,48.224803],[-123.684805,48.240845],[-124.01381,48.297521],[-124.376335,48.398442],[-124.74974,48.499783],[-124.772085,48.486117],[-124.746194,48.445448],[-124.807732,48.417554],[-124.816993,48.385475],[-124.796636,48.344126],[-124.770442,48.323537],[-124.761387,48.295688],[-124.786406,48.272291],[-124.780504,48.22858],[-124.827681,48.21383],[-124.848537,48.174005],[-124.824046,48.129308],[-124.785187,48.087115],[-124.801155,48.037809],[-124.802764,47.999789],[-124.791162,47.961705],[-124.758185,47.943978],[-124.753795,47.910523],[-124.720862,47.888311],[-124.687601,47.844037],[-124.63969,47.825176],[-124.628538,47.804861],[-124.596179,47.786023],[-124.573581,47.752365],[-124.534783,47.718756],[-124.558225,47.692423],[-124.564516,47.66114],[-124.547767,47.638349],[-124.493366,47.6211],[-124.458973,47.62672],[-124.439265,47.575511],[-124.417057,47.481538],[-124.42056,47.455972],[-124.406542,47.428021],[-124.421728,47.393293],[-124.391354,47.329766],[-124.359644,47.309336],[-124.351299,47.27639],[-124.306367,47.248083],[-124.251291,47.096654],[-124.241539,46.999808],[-124.249705,46.956288],[-124.234393,46.891188],[-124.209469,46.874813],[-124.194581,46.844092],[-124.173221,46.755617],[-124.141277,46.660313],[-124.133291,46.603129],[-124.130868,46.371597],[-124.162552,46.271727],[-124.159546,46.261132],[-124.035951,46.264183],[-123.976275,46.269907],[-123.930477,46.238805],[-123.871869,46.234949],[-123.778124,46.25425],[-123.741449,46.269155],[-123.687602,46.250214],[-123.648775,46.258803],[-123.586954,46.25589],[-123.479644,46.269131],[-123.427629,46.229348],[-123.430987,46.181571],[-123.371433,46.146372],[-123.280166,46.144843],[-123.166416,46.188979],[-123.115904,46.185268],[-123.004233,46.133823],[-122.962681,46.104817],[-122.904119,46.083734],[-122.878092,46.031281],[-122.856158,46.014469],[-122.837638,45.980821],[-122.813999,45.960983],[-122.8115,45.912724],[-122.785026,45.867699],[-122.795605,45.81],[-122.769532,45.780583],[-122.760108,45.734413],[-122.774511,45.680437],[-122.76381,45.657138],[-122.675001,45.618025],[-122.545876,45.596192],[-122.438674,45.563585],[-122.380302,45.575941],[-122.352802,45.569441],[-122.331502,45.548241],[-122.266701,45.543841],[-122.2017,45.564141],[-122.183906,45.577563],[-122.100197,45.584215],[-122.044374,45.609516],[-122.00369,45.61593],[-121.908267,45.654399],[-121.867167,45.693277],[-121.811304,45.706761],[-121.735104,45.694039],[-121.668362,45.705082],[-121.631167,45.704657],[-121.533106,45.726541],[-121.499153,45.720846],[-121.462849,45.701367],[-121.401739,45.692887],[-121.33777,45.704949],[-121.287323,45.687019],[-121.215779,45.671238],[-121.183841,45.606441],[-121.131954,45.609757],[-121.084933,45.647893],[-120.943977,45.656445],[-120.895575,45.642945],[-120.855674,45.671545],[-120.788872,45.686246],[-120.68937,45.715847],[-120.634968,45.745847],[-120.559465,45.738348],[-120.505863,45.700048],[-120.482362,45.694449],[-120.40396,45.699249],[-120.284356,45.72105],[-120.210754,45.725951],[-120.170453,45.761951],[-120.141352,45.773152],[-120.07015,45.785152],[-119.965744,45.824365],[-119.907461,45.828135],[-119.802655,45.84753],[-119.772927,45.845578],[-119.669877,45.856867],[-119.623393,45.905639],[-119.571584,45.925456],[-119.524632,45.908605],[-119.487829,45.906307],[-119.450256,45.917354],[-119.364396,45.921605],[-119.322509,45.933183],[-119.25715,45.939926],[-119.19553,45.92787],[-119.12612,45.932859],[-119.008558,45.97927],[-118.987129,45.999855],[-118.767877,46.001017],[-118.226074,46.000358],[-117.717774,45.999869],[-117.479654,45.997774],[-117.337668,45.998662],[-116.915989,45.995413]]]},\"properties\":{\"name\":\"Washington\",\"ns_code\":\"01779804\",\"geoid\":\"53\",\"usps_abbrev\":\"WA\",\"fips_code\":\"53\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-71.799242,42.008065],[-71.800649,42.023569],[-72.073426,42.027562],[-72.143104,42.030586],[-72.52813,42.034295],[-72.573235,42.03014],[-72.700036,42.036895],[-72.751765,42.029792],[-72.766738,42.002995],[-72.816741,41.997595],[-72.813541,42.036494],[-73.030063,42.039197],[-73.432812,42.050587],[-73.487314,42.049638],[-73.489615,42.000092],[-73.510961,41.758749],[-73.521041,41.619773],[-73.550962,41.295421],[-73.482695,41.212772],[-73.727775,41.100696],[-73.655241,41.011852],[-73.656771,40.984509],[-73.612885,40.950943],[-73.249557,41.025866],[-72.999547,41.087108],[-72.527901,41.177774],[-72.081775,41.26224],[-72.019214,41.290594],[-71.929451,41.310387],[-71.907258,41.304483],[-71.892005,41.330275],[-71.857458,41.320789],[-71.829365,41.342432],[-71.842029,41.410706],[-71.797747,41.416834],[-71.787247,41.656065],[-71.797883,41.931011],[-71.799242,42.008065]]]},\"properties\":{\"name\":\"Connecticut\",\"ns_code\":\"01779780\",\"geoid\":\"09\",\"usps_abbrev\":\"CT\",\"fips_code\":\"09\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-119.000932,33.535895],[-119.076497,33.535148],[-119.108718,33.519264],[-119.108879,33.451151],[-119.071893,33.413799],[-119.027044,33.412874],[-118.980512,33.428593],[-118.962728,33.473301],[-118.966018,33.500023],[-119.000932,33.535895]]],[[[-123.00111,37.772053],[-123.033841,37.778375],[-123.070494,37.81383],[-123.118289,37.823058],[-123.169107,37.792365],[-123.172926,37.7636],[-123.132597,37.719393],[-123.09285,37.713688],[-123.068589,37.673041],[-123.027153,37.645221],[-123.000912,37.63993],[-122.949068,37.663377],[-122.93571,37.697618],[-122.949985,37.732187],[-123.00111,37.772053]]],[[[-119.623559,33.230407],[-119.605282,33.206919],[-119.574192,33.186484],[-119.51025,33.167417],[-119.433717,33.166987],[-119.375937,33.192186],[-119.360926,33.24206],[-119.37973,33.267632],[-119.426177,33.298941],[-119.525313,33.334862],[-119.59252,33.325823],[-119.625944,33.305036],[-119.636302,33.27304],[-119.623559,33.230407]]],[[[-118.328898,32.875037],[-118.448863,32.96175],[-118.495582,33.026336],[-118.542103,33.073788],[-118.578337,33.086243],[-118.641301,33.081882],[-118.675605,33.049349],[-118.670173,33.000034],[-118.644732,33.000033],[-118.635522,32.9656],[-118.608802,32.92769],[-118.59693,32.890466],[-118.538197,32.81277],[-118.50765,32.796877],[-118.473906,32.766824],[-118.436538,32.75004],[-118.408322,32.751914],[-118.378377,32.770232],[-118.323819,32.773514],[-118.289905,32.81053],[-118.29725,32.845094],[-118.328898,32.875037]]],[[[-118.308676,33.416694],[-118.316351,33.434137],[-118.359393,33.464922],[-118.436835,33.489443],[-118.468961,33.512999],[-118.523236,33.530153],[-118.612158,33.53185],[-118.644058,33.521284],[-118.66371,33.500025],[-118.665919,33.467334],[-118.655057,33.44531],[-118.607228,33.398294],[-118.553376,33.380226],[-118.547476,33.34048],[-118.518037,33.29665],[-118.486688,33.276905],[-118.455072,33.269019],[-118.385589,33.265358],[-118.327993,33.248083],[-118.302837,33.251005],[-118.259178,33.272505],[-118.241051,33.313029],[-118.262748,33.37044],[-118.304102,33.403632],[-118.308676,33.416694]]],[[[-119.468064,34.063139],[-119.512106,34.09346],[-119.565522,34.108809],[-119.614268,34.099432],[-119.654931,34.071218],[-119.693007,34.094854],[-119.745222,34.110917],[-119.792601,34.106375],[-119.82922,34.118702],[-119.928795,34.129902],[-119.965471,34.11256],[-120.000963,34.076819],[-120.019503,34.087718],[-120.080285,34.086401],[-120.171128,34.073318],[-120.194114,34.058721],[-120.252846,34.058031],[-120.284651,34.09475],[-120.338562,34.121687],[-120.361978,34.14878],[-120.418787,34.160569],[-120.471654,34.134102],[-120.497378,34.149892],[-120.538118,34.150344],[-120.560998,34.140664],[-120.582264,34.107517],[-120.573808,34.075966],[-120.523387,34.051133],[-120.518784,34.01408],[-120.497726,33.991199],[-120.466777,33.976116],[-120.411766,33.977045],[-120.367073,33.961036],[-120.298973,33.968457],[-120.273096,33.929577],[-120.221128,33.884187],[-120.139927,33.844799],[-120.104088,33.842396],[-120.019339,33.870555],[-119.980842,33.892317],[-119.928799,33.905471],[-119.904762,33.939674],[-119.861952,33.910836],[-119.823208,33.89939],[-119.789372,33.910131],[-119.707491,33.910388],[-119.63673,33.938277],[-119.602085,33.936768],[-119.532825,33.949874],[-119.49229,33.983215],[-119.432961,33.955905],[-119.380456,33.953611],[-119.318443,33.974899],[-119.296238,33.99724],[-119.293112,34.020152],[-119.305153,34.047449],[-119.342462,34.068964],[-119.468064,34.063139]]],[[[-124.328835,41.998334],[-124.306995,41.992906],[-124.284769,41.956834],[-124.289939,41.920057],[-124.27612,41.903052],[-124.285031,41.859487],[-124.311936,41.858788],[-124.365199,41.886568],[-124.411317,41.882056],[-124.435015,41.856507],[-124.438909,41.824504],[-124.405715,41.783141],[-124.330609,41.733793],[-124.211706,41.681193],[-124.211419,41.641688],[-124.190113,41.60819],[-124.188336,41.573996],[-124.160493,41.543409],[-124.157943,41.518962],[-124.136497,41.464265],[-124.139973,41.381821],[-124.188604,41.389267],[-124.235174,41.363833],[-124.236177,41.317977],[-124.202385,41.294721],[-124.160704,41.289293],[-124.195532,41.184582],[-124.244308,41.158319],[-124.252474,41.134075],[-124.24743,41.092224],[-124.204984,41.013638],[-124.182694,41.001313],[-124.197619,40.950529],[-124.232622,40.869286],[-124.299333,40.768481],[-124.368878,40.669706],[-124.447677,40.529709],[-124.45256,40.493084],[-124.475894,40.459762],[-124.478625,40.422102],[-124.429462,40.364906],[-124.418542,40.318753],[-124.42915,40.266915],[-124.421287,40.237102],[-124.392777,40.209799],[-124.287833,40.137683],[-124.272099,40.13365],[-124.231204,40.093382],[-124.156646,40.065885],[-124.144099,40.052401],[-124.141086,40.010971],[-124.108581,39.978845],[-124.078596,39.972263],[-124.035355,39.931073],[-124.009637,39.893749],[-123.982946,39.876778],[-123.973477,39.853562],[-123.938591,39.814944],[-123.910529,39.799575],[-123.895741,39.711406],[-123.857183,39.674693],[-123.849387,39.57829],[-123.834649,39.549194],[-123.867826,39.508305],[-123.887402,39.453036],[-123.893085,39.337494],[-123.850092,39.231396],[-123.83888,39.183339],[-123.80921,39.141043],[-123.761965,39.041091],[-123.763148,39.00869],[-123.797217,38.989863],[-123.80986,38.961219],[-123.784676,38.893728],[-123.737921,38.857441],[-123.717807,38.827921],[-123.632611,38.758173],[-123.591962,38.740227],[-123.578005,38.717583],[-123.51058,38.68089],[-123.456159,38.614788],[-123.394157,38.553942],[-123.360929,38.510202],[-123.32381,38.498614],[-123.306742,38.478681],[-123.269188,38.457569],[-123.22022,38.441954],[-123.188465,38.40286],[-123.164757,38.388337],[-123.134312,38.345786],[-123.134523,38.296263],[-123.051963,38.215855],[-123.014817,38.149132],[-123.063797,38.036487],[-123.0812,38.018971],[-123.08388,37.983366],[-123.067965,37.958669],[-123.022507,37.942065],[-122.953224,37.941093],[-122.889291,37.974109],[-122.85046,37.945585],[-122.838896,37.918914],[-122.789027,37.893763],[-122.752466,37.857699],[-122.715845,37.845625],[-122.675654,37.848517],[-122.612285,37.815224],[-122.588174,37.789362],[-122.581281,37.760203],[-122.567497,37.639185],[-122.586454,37.592529],[-122.574963,37.49994],[-122.546996,37.457857],[-122.509889,37.441554],[-122.503543,37.411902],[-122.465869,37.34124],[-122.484046,37.249944],[-122.464117,37.177848],[-122.445632,37.150037],[-122.396916,37.109888],[-122.376086,37.065648],[-122.313899,37.054764],[-122.276375,37.024834],[-122.251082,36.985833],[-122.172024,36.942681],[-122.076399,36.910199],[-122.068317,36.874953],[-122.001124,36.647704],[-122.042748,36.596627],[-122.037459,36.560381],[-122.017455,36.537405],[-122.021998,36.508908],[-121.999472,36.466451],[-121.994104,36.435063],[-121.970391,36.383368],[-121.971877,36.357831],[-121.959053,36.287108],[-121.911267,36.248756],[-121.880193,36.210314],[-121.850153,36.19234],[-121.770693,36.162014],[-121.727812,36.129009],[-121.674359,36.064661],[-121.64818,35.998617],[-121.620365,35.969109],[-121.561461,35.943818],[-121.519192,35.8634],[-121.501047,35.842745],[-121.456097,35.817713],[-121.399754,35.765342],[-121.379327,35.726959],[-121.366362,35.680419],[-121.347721,35.664984],[-121.323512,35.624969],[-121.286036,35.608436],[-121.201555,35.58369],[-121.177756,35.561255],[-121.151056,35.516412],[-121.041635,35.41999],[-120.989632,35.411139],[-120.933292,35.392748],[-120.923435,35.348413],[-120.951278,35.29014],[-120.960247,35.243622],[-120.917792,35.187378],[-120.8473,35.137869],[-120.800411,35.116577],[-120.746308,35.109297],[-120.725503,35.113576],[-120.689606,35.098828],[-120.694715,35.028839],[-120.71483,34.962631],[-120.725745,34.9465],[-120.73382,34.896041],[-120.712967,34.862902],[-120.673281,34.838624],[-120.702093,34.748329],[-120.664691,34.696344],[-120.68648,34.645023],[-120.707183,34.616211],[-120.712757,34.568297],[-120.684529,34.527131],[-120.64236,34.503765],[-120.594334,34.504072],[-120.559439,34.488466],[-120.535664,34.452695],[-120.523646,34.419505],[-120.500999,34.403136],[-120.450998,34.391184],[-120.367335,34.408486],[-120.287125,34.417609],[-120.13512,34.422061],[-120.09067,34.409286],[-120.044814,34.412454],[-119.927899,34.374995],[-119.898502,34.360358],[-119.837921,34.354318],[-119.797067,34.365331],[-119.732023,34.339696],[-119.707593,34.337324],[-119.651357,34.366708],[-119.584795,34.36166],[-119.555829,34.345019],[-119.500953,34.326922],[-119.466995,34.29366],[-119.401994,34.272258],[-119.375935,34.24988],[-119.335926,34.237523],[-119.285743,34.140805],[-119.250942,34.106201],[-119.217993,34.062491],[-119.158963,34.040253],[-119.027407,34.014919],[-118.952426,33.992959],[-118.887301,33.987223],[-118.824893,33.949042],[-118.779039,33.954527],[-118.727459,33.980307],[-118.677017,33.979268],[-118.625916,33.987233],[-118.557356,33.987673],[-118.523447,33.953049],[-118.447254,33.84876],[-118.443968,33.839057],[-118.484483,33.803154],[-118.485577,33.753664],[-118.466962,33.725524],[-118.428637,33.701266],[-118.385931,33.686712],[-118.345415,33.663427],[-118.274239,33.663429],[-118.237008,33.690595],[-118.16583,33.691563],[-118.1259,33.697151],[-118.096473,33.653651],[-118.033611,33.609354],[-118.000893,33.593166],[-117.934046,33.549254],[-117.816356,33.490872],[-117.741409,33.410118],[-117.713571,33.408777],[-117.62588,33.342109],[-117.571531,33.312302],[-117.520999,33.268872],[-117.500875,33.242151],[-117.435563,33.177512],[-117.375872,33.075217],[-117.323279,32.903064],[-117.342,32.840354],[-117.3303,32.804817],[-117.320939,32.689847],[-117.309239,32.656401],[-117.276916,32.623474],[-117.251994,32.61486],[-117.223302,32.621238],[-117.204917,32.528832],[-117.126062,32.534156],[-116.794753,32.56246],[-116.499184,32.586812],[-116.04662,32.623353],[-115.801995,32.641633],[-115.513918,32.663591],[-115.250813,32.682309],[-114.719633,32.718763],[-114.689618,32.738171],[-114.615976,32.728489],[-114.581784,32.734946],[-114.564447,32.749554],[-114.526856,32.757094],[-114.531669,32.791185],[-114.510327,32.816488],[-114.468971,32.845155],[-114.462929,32.907944],[-114.480925,32.936276],[-114.467624,32.956663],[-114.490129,32.969884],[-114.506362,33.017403],[-114.52013,33.029984],[-114.578287,33.035375],[-114.618788,33.027202],[-114.639552,33.04529],[-114.670803,33.037983],[-114.68902,33.084035],[-114.707896,33.097431],[-114.67935,33.162433],[-114.674479,33.225504],[-114.689421,33.24525],[-114.677032,33.270169],[-114.723259,33.288079],[-114.731223,33.302433],[-114.700938,33.337014],[-114.699056,33.361148],[-114.720065,33.407891],[-114.62964,33.428137],[-114.622918,33.456561],[-114.589246,33.501813],[-114.560963,33.516739],[-114.558023,33.532589],[-114.524215,33.553068],[-114.540617,33.591412],[-114.522071,33.611277],[-114.533215,33.648443],[-114.523959,33.685879],[-114.496489,33.696901],[-114.512348,33.734214],[-114.507089,33.76793],[-114.52805,33.814963],[-114.529597,33.848063],[-114.503887,33.865754],[-114.534146,33.925187],[-114.522002,33.955623],[-114.499883,33.961789],[-114.46012,33.993888],[-114.438266,34.022609],[-114.434181,34.087379],[-114.415908,34.107636],[-114.366517,34.118577],[-114.324576,34.136759],[-114.286724,34.170715],[-114.254141,34.17383],[-114.164476,34.251667],[-114.13545,34.257886],[-114.138282,34.30323],[-114.176909,34.349306],[-114.226107,34.365916],[-114.262909,34.400373],[-114.291154,34.409784],[-114.301016,34.426807],[-114.339627,34.451435],[-114.371217,34.446992],[-114.386699,34.457911],[-114.380838,34.529724],[-114.405228,34.569637],[-114.435671,34.593841],[-114.442363,34.644153],[-114.47162,34.712966],[-114.516619,34.736745],[-114.552682,34.766871],[-114.592339,34.841153],[-114.628276,34.863596],[-114.629392,34.983171],[-114.633487,35.001857],[-114.994666,35.292511],[-115.100957,35.375681],[-115.25972,35.503541],[-115.646336,35.807735],[-115.918743,36.019936],[-116.250818,36.276939],[-116.380347,36.374961],[-116.488233,36.459097],[-116.750926,36.658377],[-117.000895,36.847694],[-117.373451,37.124932],[-117.500117,37.22038],[-117.712358,37.374931],[-117.875927,37.497267],[-118.372572,37.856012],[-118.714201,38.102105],[-118.771867,38.141871],[-119.097159,38.372851],[-119.461282,38.627533],[-119.626004,38.740941],[-120.001014,38.999574],[-120.006473,39.272878],[-120.006127,39.374106],[-120.00082,39.606828],[-120.001133,39.752229],[-119.997903,39.981636],[-119.995705,40.37583],[-119.995976,40.57055],[-119.998785,40.77385],[-119.999875,40.999894],[-119.99976,41.236061],[-119.998287,41.618818],[-119.999168,41.99454],[-120.291374,41.993004],[-120.647178,41.993084],[-121.035247,41.993324],[-121.126131,41.996009],[-121.340061,41.996361],[-121.573199,41.99851],[-121.846712,42.00307],[-122.000319,42.003967],[-122.162708,42.007746],[-122.378226,42.009517],[-122.634738,42.004854],[-122.893866,42.002669],[-123.045343,42.003075],[-123.145962,42.009242],[-123.347562,41.999108],[-123.434976,42.001637],[-123.624554,41.999837],[-123.657,41.995137],[-124.100376,41.996712],[-124.328835,41.998334]]]]},\"properties\":{\"name\":\"California\",\"ns_code\":\"01779778\",\"geoid\":\"06\",\"usps_abbrev\":\"CA\",\"fips_code\":\"06\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-89.485427,36.497491],[-89.466646,36.527041],[-89.47993,36.569087],[-89.530638,36.58171],[-89.570726,36.557656],[-89.560412,36.524406],[-89.539232,36.497934],[-89.485427,36.497491]]],[[[-81.968012,37.538035],[-82.056353,37.536804],[-82.075651,37.555915],[-82.117304,37.559226],[-82.132284,37.593327],[-82.156297,37.592365],[-82.181691,37.627147],[-82.2145,37.6254],[-82.226156,37.652913],[-82.258068,37.656883],[-82.295881,37.686736],[-82.319566,37.734238],[-82.321174,37.756087],[-82.340596,37.786028],[-82.376169,37.80203],[-82.412119,37.843891],[-82.420484,37.885446],[-82.475056,37.910859],[-82.502198,37.933052],[-82.468851,37.97323],[-82.488655,37.998912],[-82.518956,38.002153],[-82.54924,38.067952],[-82.583357,38.090032],[-82.585342,38.106634],[-82.634702,38.136941],[-82.644443,38.166561],[-82.611601,38.171339],[-82.598295,38.217361],[-82.574518,38.264505],[-82.582655,38.298365],[-82.57672,38.328515],[-82.598121,38.345933],[-82.59348,38.421821],[-82.610808,38.471953],[-82.653457,38.493702],[-82.698285,38.54377],[-82.725554,38.558185],[-82.788313,38.55973],[-82.821205,38.573344],[-82.847588,38.595641],[-82.856895,38.647405],[-82.87736,38.694458],[-82.875174,38.746837],[-82.897189,38.756214],[-82.93935,38.745004],[-82.971567,38.727452],[-83.026976,38.727554],[-83.062579,38.689457],[-83.112058,38.671843],[-83.134754,38.632013],[-83.154483,38.62077],[-83.201445,38.61673],[-83.245222,38.628711],[-83.288041,38.598349],[-83.318399,38.610515],[-83.327626,38.637851],[-83.377573,38.661809],[-83.468133,38.675476],[-83.520755,38.703094],[-83.61141,38.684679],[-83.634901,38.670426],[-83.644498,38.637078],[-83.668718,38.626542],[-83.723497,38.647076],[-83.770178,38.655857],[-83.784386,38.696581],[-83.840657,38.721976],[-83.859973,38.757217],[-83.97734,38.787435],[-84.044576,38.770557],[-84.070779,38.770414],[-84.15662,38.795072],[-84.213115,38.805835],[-84.230147,38.82643],[-84.234027,38.891407],[-84.286536,38.952542],[-84.313835,39.016588],[-84.360608,39.041513],[-84.412487,39.046967],[-84.430561,39.05874],[-84.435388,39.100885],[-84.458818,39.121479],[-84.511041,39.093654],[-84.553509,39.098509],[-84.575499,39.081225],[-84.632506,39.076544],[-84.651692,39.092528],[-84.689354,39.103192],[-84.718096,39.136616],[-84.750345,39.147622],[-84.784484,39.117719],[-84.820157,39.105489],[-84.897098,39.057137],[-84.838579,38.989902],[-84.835155,38.957929],[-84.877535,38.920689],[-84.869014,38.899667],[-84.81477,38.895511],[-84.786613,38.882416],[-84.790436,38.861246],[-84.825963,38.83663],[-84.812534,38.786729],[-84.887285,38.794776],[-84.933516,38.777501],[-84.991927,38.778537],[-85.073036,38.74104],[-85.13199,38.702916],[-85.173048,38.688022],[-85.221119,38.701058],[-85.258946,38.73777],[-85.305858,38.741536],[-85.372265,38.73045],[-85.407994,38.737136],[-85.452087,38.709785],[-85.455042,38.681263],[-85.438586,38.658102],[-85.439729,38.611493],[-85.415977,38.563654],[-85.42339,38.531845],[-85.475124,38.503689],[-85.49851,38.468495],[-85.52479,38.457636],[-85.586033,38.450092],[-85.62037,38.423462],[-85.655355,38.323849],[-85.685208,38.294692],[-85.743213,38.267283],[-85.780743,38.288352],[-85.821706,38.281119],[-85.835456,38.266666],[-85.851694,38.222445],[-85.884282,38.19957],[-85.904063,38.173427],[-85.909289,38.142235],[-85.90499,38.089818],[-85.923428,38.025247],[-85.943132,38.006622],[-86.021667,37.995793],[-86.049012,37.959861],[-86.079389,38.000625],[-86.1216,38.016052],[-86.17893,38.01081],[-86.221352,38.028669],[-86.267769,38.057171],[-86.2791,38.100754],[-86.272847,38.140835],[-86.29574,38.164069],[-86.347736,38.195363],[-86.37741,38.188574],[-86.366055,38.161531],[-86.325874,38.154029],[-86.332099,38.129929],[-86.375953,38.130539],[-86.403487,38.104563],[-86.443207,38.127762],[-86.465503,38.104306],[-86.432672,38.08527],[-86.433923,38.065705],[-86.456793,38.049031],[-86.513233,38.044054],[-86.52503,38.028902],[-86.525095,37.968081],[-86.507089,37.929917],[-86.541991,37.917034],[-86.58848,37.921346],[-86.600173,37.9044],[-86.604699,37.858127],[-86.648393,37.841453],[-86.660653,37.864247],[-86.645801,37.907208],[-86.680397,37.915077],[-86.718671,37.893057],[-86.752705,37.915372],[-86.79551,37.989453],[-86.815746,37.998957],[-86.851933,37.989029],[-86.919834,37.93639],[-86.9766,37.930965],[-87.040163,37.901215],[-87.043285,37.868552],[-87.06967,37.801513],[-87.091069,37.786964],[-87.128604,37.78577],[-87.14097,37.815112],[-87.164747,37.841353],[-87.219945,37.84889],[-87.318228,37.90497],[-87.344793,37.911099],[-87.377697,37.934391],[-87.428307,37.945047],[-87.463403,37.93494],[-87.50972,37.906028],[-87.556567,37.928606],[-87.577996,37.97057],[-87.601573,37.971304],[-87.628067,37.922892],[-87.592826,37.889688],[-87.588435,37.861685],[-87.615583,37.831848],[-87.673447,37.829873],[-87.681684,37.856273],[-87.666904,37.876795],[-87.684343,37.903754],[-87.711657,37.893872],[-87.759432,37.891861],[-87.794177,37.875356],[-87.832994,37.877143],[-87.874,37.92272],[-87.897144,37.927756],[-87.938548,37.890769],[-87.936301,37.867873],[-87.910017,37.843018],[-87.906939,37.807593],[-87.931794,37.7982],[-87.945991,37.773578],[-88.015487,37.801948],[-88.028016,37.799188],[-88.049183,37.755073],[-88.072311,37.73352],[-88.12499,37.707223],[-88.160389,37.656131],[-88.13351,37.574417],[-88.0721,37.528811],[-88.061168,37.504269],[-88.082243,37.472665],[-88.136246,37.471471],[-88.197714,37.460155],[-88.279207,37.453292],[-88.311974,37.441001],[-88.369196,37.40207],[-88.412746,37.425071],[-88.454642,37.409953],[-88.47409,37.391863],[-88.487236,37.339107],[-88.512263,37.297096],[-88.507062,37.259689],[-88.473688,37.221652],[-88.448016,37.203403],[-88.424603,37.151764],[-88.449353,37.087615],[-88.482624,37.067257],[-88.52316,37.06579],[-88.572863,37.079569],[-88.616928,37.116041],[-88.703274,37.142935],[-88.735061,37.145247],[-88.802554,37.187861],[-88.931424,37.227378],[-88.973793,37.2298],[-89.029866,37.211065],[-89.092755,37.156763],[-89.111409,37.118988],[-89.154346,37.08896],[-89.177857,37.057796],[-89.17383,37.011443],[-89.132915,36.982057],[-89.1092,36.976451],[-89.098968,36.956067],[-89.117115,36.888685],[-89.138109,36.847323],[-89.170443,36.84184],[-89.178223,36.808614],[-89.116983,36.775595],[-89.125967,36.751809],[-89.169785,36.759641],[-89.197856,36.739772],[-89.200654,36.718752],[-89.169426,36.688771],[-89.159118,36.666354],[-89.197753,36.628971],[-89.202771,36.601665],[-89.227046,36.569537],[-89.269546,36.569716],[-89.31645,36.624044],[-89.332034,36.632881],[-89.365797,36.625126],[-89.41727,36.499011],[-89.30102,36.507233],[-88.959746,36.502076],[-88.77157,36.502729],[-88.216645,36.499827],[-88.053293,36.497058],[-88.032675,36.536985],[-88.044862,36.60307],[-88.070541,36.678255],[-88.012203,36.67719],[-87.849544,36.663695],[-87.853319,36.63316],[-87.474687,36.640415],[-87.070491,36.64273],[-86.734208,36.648898],[-86.589906,36.652443],[-86.564255,36.633583],[-86.507781,36.652379],[-86.332126,36.648701],[-86.032922,36.630736],[-85.832432,36.6221],[-85.50151,36.614855],[-85.276867,36.626827],[-85.109237,36.622885],[-84.991632,36.617041],[-84.830035,36.604536],[-84.540474,36.596304],[-84.271545,36.591703],[-83.999943,36.589857],[-83.690789,36.582596],[-83.675395,36.600784],[-83.613896,36.63485],[-83.567853,36.646051],[-83.532404,36.664689],[-83.492805,36.670187],[-83.418682,36.668729],[-83.347635,36.700199],[-83.194252,36.739519],[-83.13512,36.742629],[-83.128987,36.775784],[-83.09848,36.813517],[-83.099904,36.831147],[-83.072836,36.854457],[-82.969243,36.858019],[-82.87908,36.889255],[-82.857644,36.92885],[-82.869607,36.973343],[-82.830842,36.993556],[-82.82872,37.005798],[-82.782863,37.007821],[-82.742759,37.042796],[-82.726486,37.073411],[-82.715149,37.121848],[-82.633918,37.154179],[-82.550589,37.204483],[-82.439893,37.247005],[-82.355536,37.265147],[-82.316008,37.29483],[-82.142983,37.414842],[-81.968012,37.538035]]]]},\"properties\":{\"name\":\"Kentucky\",\"ns_code\":\"01779786\",\"geoid\":\"21\",\"usps_abbrev\":\"KY\",\"fips_code\":\"21\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-71.088571,41.431315],[-71.033604,41.414085],[-71.026103,41.371693],[-70.990748,41.351289],[-70.901866,41.36177],[-70.897722,41.327411],[-70.870027,41.297068],[-70.893595,41.267994],[-70.877327,41.224144],[-70.847746,41.205814],[-70.80595,41.198742],[-70.767837,41.20649],[-70.744307,41.22603],[-70.734843,41.259231],[-70.68502,41.293176],[-70.606672,41.29941],[-70.512199,41.298313],[-70.459851,41.265253],[-70.408733,41.277294],[-70.374466,41.269458],[-70.344083,41.276146],[-70.285736,41.242346],[-70.178851,41.204531],[-70.109684,41.189803],[-70.012483,41.187053],[-69.973182,41.193545],[-69.910319,41.226849],[-69.892209,41.271323],[-69.910093,41.317263],[-69.974592,41.408922],[-70.006467,41.433898],[-70.065174,41.442687],[-70.094412,41.43081],[-70.11644,41.386165],[-70.097844,41.353584],[-70.148699,41.345216],[-70.174906,41.35729],[-70.215115,41.360275],[-70.238142,41.386205],[-70.267283,41.393426],[-70.323593,41.391607],[-70.383449,41.369933],[-70.385874,41.433721],[-70.405928,41.457013],[-70.436449,41.468919],[-70.489689,41.462977],[-70.508603,41.498965],[-70.466436,41.50616],[-70.423896,41.528732],[-70.395164,41.556722],[-70.311254,41.571062],[-70.267429,41.558373],[-70.225558,41.568561],[-70.20804,41.58415],[-70.160357,41.59937],[-70.077934,41.612528],[-70.063352,41.590173],[-70.080316,41.564454],[-70.126792,41.530878],[-70.114394,41.484083],[-70.084904,41.467493],[-70.040313,41.46596],[-69.999458,41.490527],[-69.946613,41.512152],[-69.928994,41.532152],[-69.915399,41.589243],[-69.898562,41.606096],[-69.862876,41.673153],[-69.860058,41.758503],[-69.866525,41.817011],[-69.909178,41.942062],[-69.929109,41.978476],[-70.011591,42.073378],[-70.067098,42.105327],[-70.120691,42.125101],[-70.172542,42.134515],[-70.229175,42.131189],[-70.27743,42.114345],[-70.608381,42.138479],[-70.642946,42.178185],[-70.640113,42.228372],[-70.684061,42.253836],[-70.690393,42.278999],[-70.60629,42.53122],[-70.582709,42.553004],[-70.545374,42.570119],[-70.502143,42.624579],[-70.513934,42.680594],[-70.549306,42.724164],[-70.625943,42.739724],[-70.70258,42.724162],[-70.714324,42.731911],[-70.737225,42.787956],[-70.734874,42.824477],[-70.74883,42.858831],[-70.735005,42.874685],[-70.820894,42.872076],[-70.848625,42.860939],[-70.88561,42.882496],[-70.930798,42.884588],[-70.9665,42.868989],[-71.031201,42.859087],[-71.064201,42.806289],[-71.132503,42.821388],[-71.186103,42.790674],[-71.181803,42.73759],[-71.245504,42.742589],[-71.294205,42.69699],[-71.745815,42.707287],[-71.981402,42.713295],[-72.458436,42.72685],[-72.809113,42.736581],[-73.264957,42.74594],[-73.356894,42.49836],[-73.508142,42.086257],[-73.487314,42.049638],[-73.432812,42.050587],[-73.030063,42.039197],[-72.813541,42.036494],[-72.816741,41.997595],[-72.766738,42.002995],[-72.751765,42.029792],[-72.700036,42.036895],[-72.573235,42.03014],[-72.52813,42.034295],[-72.143104,42.030586],[-72.073426,42.027562],[-71.800649,42.023569],[-71.799242,42.008065],[-71.381401,42.018798],[-71.3817,41.893199],[-71.338698,41.898399],[-71.333997,41.8623],[-71.347197,41.823101],[-71.326496,41.779801],[-71.260982,41.752118],[-71.195555,41.675152],[-71.132507,41.660356],[-71.140442,41.605298],[-71.131727,41.593762],[-71.121116,41.492787],[-71.088571,41.431315]]]},\"properties\":{\"name\":\"Massachusetts\",\"ns_code\":\"00606926\",\"geoid\":\"25\",\"usps_abbrev\":\"MA\",\"fips_code\":\"25\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-82.902018,24.715678],[-82.932893,24.684668],[-82.964099,24.670596],[-82.985529,24.640485],[-82.986238,24.61065],[-82.974817,24.589605],[-82.928241,24.578138],[-82.900278,24.583272],[-82.849053,24.576748],[-82.80441,24.604296],[-82.766681,24.66794],[-82.766509,24.701764],[-82.800177,24.726282],[-82.86562,24.726225],[-82.902018,24.715678]]],[[[-87.518346,30.229506],[-87.39497,30.250202],[-87.296457,30.27269],[-87.257726,30.264158],[-87.138764,30.278493],[-87.064535,30.294702],[-86.908882,30.320226],[-86.713366,30.343381],[-86.645861,30.346173],[-86.588722,30.34192],[-86.5235,30.330321],[-86.486937,30.331693],[-86.397504,30.325354],[-86.279554,30.304318],[-86.18476,30.277966],[-85.999967,30.213108],[-85.947196,30.191446],[-85.826187,30.125185],[-85.767959,30.076404],[-85.709762,30.043182],[-85.632537,30.014255],[-85.587654,29.962518],[-85.540031,29.928003],[-85.505081,29.911781],[-85.446289,29.901033],[-85.469852,29.868284],[-85.474996,29.831542],[-85.470893,29.789028],[-85.457381,29.735231],[-85.429996,29.669967],[-85.381684,29.612853],[-85.326642,29.605934],[-85.292016,29.632273],[-85.234846,29.624999],[-85.173329,29.590911],[-85.109362,29.569357],[-85.07797,29.541333],[-85.031168,29.539088],[-84.927476,29.569628],[-84.825591,29.623639],[-84.773065,29.637929],[-84.73575,29.656375],[-84.639202,29.735214],[-84.58619,29.748259],[-84.542286,29.769797],[-84.52259,29.794358],[-84.512845,29.832042],[-84.484516,29.833406],[-84.44686,29.858732],[-84.384097,29.845039],[-84.338999,29.847125],[-84.298796,29.867126],[-84.256915,29.942848],[-84.265276,29.979045],[-84.251319,29.995969],[-84.184614,29.983069],[-84.141282,29.993465],[-84.109352,30.028084],[-84.043431,30.039755],[-84.016856,30.0164],[-83.9754,30.007171],[-83.913303,29.958704],[-83.884173,29.955553],[-83.862794,29.930363],[-83.808523,29.904346],[-83.745228,29.89382],[-83.696764,29.858666],[-83.666258,29.811802],[-83.647311,29.797139],[-83.638998,29.740045],[-83.609308,29.698416],[-83.544374,29.671453],[-83.51275,29.634629],[-83.481264,29.630329],[-83.46451,29.613319],[-83.468469,29.569707],[-83.449456,29.483351],[-83.425444,29.455772],[-83.36022,29.431083],[-83.351944,29.408114],[-83.30848,29.383018],[-83.297689,29.346075],[-83.238679,29.30861],[-83.239767,29.25024],[-83.233821,29.235728],[-83.17181,29.185921],[-83.166696,29.125245],[-83.14408,29.07284],[-83.11108,29.049868],[-83.076,29.041819],[-82.99984,29.050786],[-82.978081,29.069227],[-82.950078,29.076059],[-82.887809,29.046217],[-82.866599,29.045524],[-82.851278,29.017389],[-82.844767,28.972084],[-82.857828,28.942006],[-82.833008,28.899435],[-82.808244,28.890227],[-82.794786,28.857729],[-82.81361,28.811492],[-82.813037,28.791293],[-82.787541,28.743457],[-82.788007,28.709302],[-82.765158,28.656481],[-82.727392,28.595587],[-82.738986,28.578838],[-82.748738,28.532054],[-82.74202,28.485834],[-82.746195,28.44931],[-82.776715,28.403235],[-82.800268,28.354732],[-82.795276,28.299764],[-82.837436,28.263066],[-82.89215,28.245247],[-82.908311,28.206626],[-82.896633,28.080295],[-82.878105,28.04282],[-82.886552,28.01366],[-82.888203,27.963713],[-82.908909,27.890082],[-82.901337,27.839107],[-82.875,27.794343],[-82.8057,27.709384],[-82.815377,27.654618],[-82.81123,27.636363],[-82.823669,27.575077],[-82.815377,27.535958],[-82.765195,27.477378],[-82.735158,27.415808],[-82.705438,27.389668],[-82.67828,27.344143],[-82.631787,27.295098],[-82.62287,27.260773],[-82.605696,27.238202],[-82.56474,27.2056],[-82.52755,27.119208],[-82.49982,27.043895],[-82.394753,26.875342],[-82.338319,26.803318],[-82.3222,26.769663],[-82.31793,26.734402],[-82.32191,26.665079],[-82.281846,26.61019],[-82.276193,26.575227],[-82.257975,26.545812],[-82.236453,26.4773],[-82.216196,26.456467],[-82.173229,26.437646],[-82.144669,26.400286],[-82.112221,26.377755],[-82.068898,26.371318],[-82.008175,26.395359],[-81.978126,26.399692],[-81.941949,26.37536],[-81.906149,26.331427],[-81.884599,26.27685],[-81.867882,26.214383],[-81.859223,26.108242],[-81.85034,26.062872],[-81.801041,25.973828],[-81.806218,25.953709],[-81.784875,25.913956],[-81.775286,25.880947],[-81.742219,25.843898],[-81.718139,25.793033],[-81.687613,25.770912],[-81.628669,25.771261],[-81.597969,25.790582],[-81.588095,25.809922],[-81.529215,25.783842],[-81.487718,25.752285],[-81.445143,25.737576],[-81.399194,25.659252],[-81.372998,25.640587],[-81.347231,25.636727],[-81.32334,25.59307],[-81.276004,25.535118],[-81.259117,25.479956],[-81.221797,25.441496],[-81.205709,25.410751],[-81.205941,25.37702],[-81.196077,25.357058],[-81.234044,25.234522],[-81.215507,25.1894],[-81.195329,25.168837],[-81.184409,25.125001],[-81.149525,25.107384],[-81.120119,25.073538],[-81.098489,25.066471],[-81.075376,25.033158],[-81.05511,24.971413],[-81.02013,24.9309],[-80.99398,24.910454],[-80.973068,24.866433],[-80.938324,24.843193],[-80.964288,24.819907],[-81.06447,24.791116],[-81.096655,24.809256],[-81.132429,24.805835],[-81.15924,24.836018],[-81.183065,24.843269],[-81.202322,24.868352],[-81.252946,24.881145],[-81.293697,24.86815],[-81.312571,24.873705],[-81.369161,24.871302],[-81.402473,24.881827],[-81.458887,24.86745],[-81.528173,24.835487],[-81.541962,24.821385],[-81.60277,24.799559],[-81.723528,24.725524],[-81.806387,24.696472],[-81.866423,24.663305],[-81.89157,24.665855],[-81.936739,24.656502],[-81.987851,24.63477],[-82.026643,24.607666],[-82.081731,24.635634],[-82.137572,24.638715],[-82.164692,24.629473],[-82.210239,24.590843],[-82.218964,24.54221],[-82.205536,24.518991],[-82.175098,24.499617],[-82.100333,24.499998],[-82.067171,24.523715],[-82.051074,24.496844],[-82.023479,24.477864],[-81.974409,24.471209],[-81.979768,24.431656],[-81.946441,24.399348],[-81.866905,24.403588],[-81.813522,24.417639],[-81.788985,24.439482],[-81.76287,24.449643],[-81.71183,24.429733],[-81.610259,24.454308],[-81.585826,24.471094],[-81.539924,24.486034],[-81.519086,24.517248],[-81.527852,24.553279],[-81.514615,24.564757],[-81.474733,24.553455],[-81.420636,24.570572],[-81.385432,24.569933],[-81.293455,24.596722],[-81.27169,24.597096],[-81.204259,24.631186],[-81.184044,24.631205],[-81.147686,24.649064],[-81.124795,24.636605],[-81.076521,24.633375],[-81.047648,24.64024],[-81.022583,24.665275],[-80.999792,24.668912],[-80.918911,24.713066],[-80.893295,24.716453],[-80.852228,24.748931],[-80.812466,24.761211],[-80.783375,24.76126],[-80.745115,24.788878],[-80.674811,24.825611],[-80.652342,24.831403],[-80.619896,24.854586],[-80.585451,24.889329],[-80.538164,24.912733],[-80.514343,24.940628],[-80.469498,24.951755],[-80.453365,24.963698],[-80.439101,24.997989],[-80.409327,25.012677],[-80.390435,25.057189],[-80.35778,25.072902],[-80.333321,25.099202],[-80.302899,25.144923],[-80.249773,25.179479],[-80.229264,25.225501],[-80.243168,25.255375],[-80.217862,25.295247],[-80.188586,25.291267],[-80.150013,25.31442],[-80.112612,25.375391],[-80.11175,25.40088],[-80.141757,25.434711],[-80.126347,25.474551],[-80.115671,25.541393],[-80.079787,25.541533],[-80.0567,25.555212],[-80.04351,25.598051],[-80.060238,25.625383],[-80.0965,25.652753],[-80.091223,25.721903],[-80.06687,25.756733],[-80.056056,25.837688],[-80.067685,25.902395],[-80.052257,25.974956],[-80.040925,26.092459],[-80.016904,26.257978],[-80.014362,26.362236],[-80.006149,26.432809],[-79.988543,26.527264],[-79.980648,26.595447],[-79.981303,26.650273],[-79.974734,26.708231],[-79.97965,26.750341],[-79.975425,26.80025],[-79.996463,26.88181],[-80.039102,27.029853],[-80.080955,27.125329],[-80.095411,27.18064],[-80.127097,27.239374],[-80.227962,27.463004],[-80.229299,27.485629],[-80.261092,27.549139],[-80.281354,27.621171],[-80.296456,27.65341],[-80.328113,27.750306],[-80.39944,27.891467],[-80.429011,27.941482],[-80.499773,28.07726],[-80.543999,28.271092],[-80.53417,28.335703],[-80.519428,28.391084],[-80.481209,28.425511],[-80.46939,28.453097],[-80.478054,28.492451],[-80.503585,28.540362],[-80.522973,28.608238],[-80.635573,28.750268],[-80.721834,28.875],[-80.874815,29.105968],[-80.906836,29.144706],[-80.955785,29.250258],[-80.999776,29.33685],[-81.085187,29.528862],[-81.119814,29.59673],[-81.149144,29.666143],[-81.167403,29.723101],[-81.186399,29.76008],[-81.205177,29.822433],[-81.209268,29.875247],[-81.255134,30.000245],[-81.285314,30.125244],[-81.330627,30.299481],[-81.335381,30.361292],[-81.321563,30.378574],[-81.324877,30.424735],[-81.353574,30.44666],[-81.354883,30.492754],[-81.375002,30.532758],[-81.380867,30.627345],[-81.373398,30.662779],[-81.358169,30.675976],[-81.347015,30.712444],[-81.464013,30.710983],[-81.489898,30.726156],[-81.572395,30.721897],[-81.623771,30.736052],[-81.647483,30.727394],[-81.659805,30.751265],[-81.718109,30.744806],[-81.74096,30.765142],[-81.793968,30.787239],[-81.881858,30.799961],[-81.891052,30.822308],[-81.963928,30.8181],[-81.972458,30.779926],[-82.002385,30.789798],[-82.011603,30.761878],[-82.039795,30.747297],[-82.035927,30.706205],[-82.05022,30.674538],[-82.00581,30.565358],[-82.018554,30.528992],[-82.016906,30.475111],[-82.043762,30.414869],[-82.037218,30.371848],[-82.065598,30.358114],[-82.105018,30.368817],[-82.171623,30.359918],[-82.192596,30.378831],[-82.210733,30.42564],[-82.20124,30.485114],[-82.225072,30.50769],[-82.240403,30.53777],[-82.214677,30.568556],[-82.590548,30.592186],[-83.065223,30.620039],[-83.499925,30.645674],[-83.975655,30.67037],[-84.374894,30.689793],[-84.474409,30.692793],[-84.644815,30.701991],[-84.864693,30.711542],[-84.897622,30.751391],[-84.911122,30.751191],[-84.936824,30.818489],[-84.926824,30.846389],[-84.94252,30.888488],[-84.983527,30.935486],[-84.980427,30.962086],[-85.004026,30.973468],[-85.002499,31.000685],[-85.243632,31.000884],[-85.568112,30.996244],[-85.87493,30.993618],[-86.12154,30.992884],[-86.563436,30.995223],[-87.140755,30.999532],[-87.237685,30.996393],[-87.311949,30.998435],[-87.598829,30.997455],[-87.588862,30.96579],[-87.62027,30.887404],[-87.634788,30.866707],[-87.626224,30.846664],[-87.544789,30.778395],[-87.535114,30.749448],[-87.501502,30.721092],[-87.406356,30.674437],[-87.393294,30.627218],[-87.408018,30.583701],[-87.445103,30.528909],[-87.435578,30.480496],[-87.414677,30.457296],[-87.370768,30.446865],[-87.369383,30.431948],[-87.440678,30.391498],[-87.450778,30.346999],[-87.462978,30.334],[-87.49998,30.328957],[-87.50558,30.3111],[-87.452378,30.300201],[-87.51838,30.283901],[-87.518346,30.229506]]]]},\"properties\":{\"name\":\"Florida\",\"ns_code\":\"00294478\",\"geoid\":\"12\",\"usps_abbrev\":\"FL\",\"fips_code\":\"12\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-111.048974,44.474072],[-111.131379,44.499925],[-111.180773,44.569038],[-111.230351,44.579727],[-111.241502,44.614225],[-111.219236,44.622616],[-111.255082,44.651011],[-111.294843,44.70006],[-111.337113,44.733204],[-111.37476,44.750295],[-111.397766,44.72476],[-111.465497,44.713824],[-111.490008,44.699537],[-111.473633,44.664968],[-111.525764,44.604883],[-111.499991,44.54192],[-111.591768,44.561502],[-111.617695,44.551012],[-111.704218,44.560205],[-111.821488,44.509286],[-111.866126,44.562811],[-111.947941,44.556776],[-111.978786,44.53589],[-112.032707,44.546642],[-112.096299,44.523212],[-112.130952,44.537791],[-112.183945,44.533053],[-112.221698,44.543519],[-112.228894,44.564002],[-112.286187,44.568472],[-112.319198,44.53911],[-112.358917,44.528847],[-112.356688,44.492621],[-112.366437,44.467358],[-112.390543,44.450666],[-112.473207,44.480027],[-112.50031,44.463051],[-112.550557,44.484928],[-112.60293,44.490939],[-112.664485,44.48645],[-112.71793,44.504273],[-112.781294,44.484888],[-112.840278,44.424964],[-112.813272,44.398173],[-112.812735,44.377181],[-112.844859,44.358221],[-112.919625,44.409581],[-112.951146,44.416699],[-113.003544,44.450814],[-113.027148,44.495716],[-113.00703,44.525872],[-113.045502,44.543934],[-113.039836,44.566842],[-113.087851,44.599243],[-113.056772,44.618659],[-113.067762,44.679474],[-113.098119,44.697975],[-113.102129,44.729032],[-113.137704,44.760109],[-113.131151,44.772392],[-113.179366,44.787149],[-113.193117,44.801653],[-113.247166,44.82295],[-113.340471,44.784682],[-113.356062,44.819798],[-113.422376,44.842595],[-113.455071,44.865424],[-113.461084,44.886742],[-113.49167,44.924408],[-113.494444,44.948597],[-113.443793,44.959894],[-113.45197,45.059247],[-113.520134,45.093033],[-113.513342,45.115225],[-113.556314,45.115243],[-113.58968,45.15536],[-113.59252,45.184689],[-113.636889,45.212983],[-113.650064,45.23471],[-113.69276,45.264299],[-113.67886,45.276092],[-113.738729,45.329741],[-113.734402,45.392353],[-113.77407,45.406659],[-113.775924,45.470998],[-113.760014,45.480138],[-113.772791,45.514411],[-113.83388,45.520632],[-113.821437,45.565973],[-113.797009,45.582017],[-113.806729,45.602146],[-113.862169,45.623784],[-113.904571,45.624767],[-113.897663,45.646706],[-113.931494,45.672965],[-113.933957,45.693061],[-113.973624,45.702809],[-114.019315,45.692937],[-114.013786,45.658238],[-114.067619,45.627706],[-114.086584,45.59118],[-114.122322,45.58426],[-114.135249,45.557465],[-114.180043,45.551432],[-114.192802,45.536596],[-114.248532,45.545419],[-114.248988,45.520636],[-114.279217,45.480616],[-114.345019,45.459916],[-114.36562,45.490416],[-114.429048,45.516624],[-114.43012,45.529309],[-114.517761,45.568129],[-114.549508,45.56059],[-114.558253,45.585104],[-114.538467,45.605892],[-114.563305,45.631612],[-114.507754,45.657626],[-114.495421,45.703321],[-114.504307,45.72173],[-114.547896,45.743606],[-114.566172,45.773864],[-114.514586,45.82685],[-114.509303,45.845531],[-114.406307,45.869145],[-114.395059,45.901458],[-114.431159,45.935737],[-114.402291,45.957799],[-114.414118,45.979278],[-114.457101,45.997296],[-114.493475,46.052402],[-114.466937,46.065689],[-114.459526,46.093907],[-114.474777,46.113191],[-114.5213,46.125287],[-114.515703,46.165687],[-114.477345,46.160629],[-114.44521,46.167885],[-114.451795,46.239666],[-114.47077,46.264517],[-114.427309,46.283624],[-114.433478,46.305502],[-114.413758,46.335945],[-114.410083,46.365625],[-114.422458,46.387097],[-114.384756,46.411784],[-114.379338,46.460166],[-114.403019,46.498675],[-114.348521,46.510241],[-114.347946,46.551642],[-114.329765,46.575665],[-114.320665,46.646963],[-114.359108,46.670061],[-114.453601,46.648967],[-114.466902,46.631695],[-114.546333,46.64498],[-114.593914,46.633046],[-114.635868,46.65951],[-114.641471,46.679515],[-114.620859,46.707415],[-114.646665,46.730802],[-114.696656,46.740572],[-114.717645,46.713994],[-114.75112,46.697385],[-114.788656,46.714033],[-114.765059,46.758081],[-114.787913,46.780818],[-114.830671,46.783605],[-114.856874,46.801633],[-114.920459,46.827697],[-114.936822,46.89716],[-114.926985,46.919586],[-114.959625,46.929493],[-115.00091,46.967703],[-115.047857,46.969532],[-115.087807,47.045518],[-115.107133,47.04904],[-115.191803,47.132048],[-115.245859,47.151964],[-115.261885,47.181742],[-115.300504,47.188139],[-115.294785,47.220914],[-115.323575,47.256529],[-115.409306,47.263988],[-115.426636,47.280158],[-115.52605,47.299252],[-115.551374,47.350042],[-115.617247,47.38252],[-115.643818,47.377537],[-115.657681,47.40065],[-115.749714,47.440362],[-115.69293,47.457237],[-115.646384,47.456695],[-115.649524,47.477752],[-115.750993,47.541594],[-115.755295,47.551438],[-115.692138,47.596425],[-115.694284,47.62346],[-115.73627,47.654762],[-115.722629,47.695162],[-115.783504,47.729305],[-115.797299,47.75752],[-115.824575,47.752137],[-115.846949,47.784286],[-115.852291,47.827991],[-115.881523,47.849672],[-115.901401,47.843097],[-115.965173,47.910654],[-115.993678,47.926183],[-116.030587,47.972247],[-116.048464,47.976802],[-116.049976,48.237463],[-116.049273,48.714641],[-116.049193,49.000912],[-116.641269,48.999946],[-117.032351,48.999188],[-117.032107,48.874926],[-117.035425,48.499914],[-117.035086,48.38521],[-117.041107,48.124904],[-117.04247,47.839009],[-117.039921,47.492266],[-117.040019,47.259272],[-117.039498,46.931999],[-117.039813,46.425425],[-117.053953,46.344566],[-117.023844,46.335976],[-117.022293,46.31747],[-116.987939,46.298031],[-116.991134,46.276342],[-116.970298,46.261233],[-116.956031,46.225976],[-116.962966,46.19968],[-116.922648,46.160744],[-116.959548,46.099058],[-116.981747,46.092881],[-116.942656,46.061],[-116.915989,45.995413],[-116.87598,45.954775],[-116.866544,45.916958],[-116.814131,45.877641],[-116.763402,45.81658],[-116.734543,45.826527],[-116.698079,45.820852],[-116.665268,45.782059],[-116.59421,45.77908],[-116.535698,45.734231],[-116.535117,45.691277],[-116.488818,45.650799],[-116.463635,45.602785],[-116.524172,45.548565],[-116.557621,45.503495],[-116.55483,45.46293],[-116.588195,45.44292],[-116.597447,45.41277],[-116.623385,45.393963],[-116.674648,45.315082],[-116.67473,45.275844],[-116.703697,45.240099],[-116.728757,45.144381],[-116.774847,45.105536],[-116.797329,45.060267],[-116.848037,45.021728],[-116.857549,44.982567],[-116.83199,44.933007],[-116.852427,44.887578],[-116.896589,44.846545],[-116.932199,44.802781],[-116.9347,44.783881],[-117.000303,44.756082],[-117.044217,44.74514],[-117.063824,44.703623],[-117.095868,44.664737],[-117.098221,44.640689],[-117.120522,44.614658],[-117.124754,44.583834],[-117.146032,44.568603],[-117.144161,44.545647],[-117.181583,44.52296],[-117.200237,44.492027],[-117.224104,44.483734],[-117.215072,44.427162],[-117.242675,44.396548],[-117.235117,44.373853],[-117.189842,44.335007],[-117.22274,44.298326],[-117.170341,44.258889],[-117.141264,44.258515],[-117.119861,44.278272],[-117.05651,44.230874],[-117.031862,44.248635],[-116.975382,44.242392],[-116.967259,44.194581],[-116.935443,44.193962],[-116.903607,44.179985],[-116.895931,44.154295],[-116.933701,44.100043],[-116.977085,44.08582],[-116.973185,44.049425],[-116.934499,44.021218],[-116.958328,43.983495],[-116.961535,43.918388],[-116.980544,43.91483],[-116.982347,43.86884],[-117.032289,43.828767],[-117.026634,43.80811],[-117.026651,43.374877],[-117.026874,43.127005],[-117.026231,42.831194],[-117.026531,42.374871],[-117.026197,41.99989],[-116.606567,41.99739],[-116.368477,41.99628],[-116.243962,41.997651],[-115.870181,41.996766],[-115.305702,41.996121],[-115.031783,41.996008],[-114.806948,42.001842],[-114.598267,41.994511],[-114.467581,41.995492],[-114.041723,41.99372],[-113.893261,41.988057],[-113.496548,41.993305],[-112.979918,41.998441],[-112.624578,42.000353],[-112.192976,42.001167],[-112.173351,41.996568],[-112.01218,41.99835],[-111.491095,41.999522],[-111.046689,42.001567],[-111.047023,42.474064],[-111.043547,42.719502],[-111.044078,42.97598],[-111.04412,43.247071],[-111.045383,43.484566],[-111.046516,43.908369],[-111.049077,44.02013],[-111.048974,44.474072]]]},\"properties\":{\"name\":\"Idaho\",\"ns_code\":\"01779783\",\"geoid\":\"16\",\"usps_abbrev\":\"ID\",\"fips_code\":\"16\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-94.617919,36.499414],[-94.114073,36.498632],[-93.875197,36.498722],[-93.426451,36.498935],[-93.136615,36.49796],[-92.753678,36.497975],[-92.46094,36.498671],[-92.375159,36.497199],[-92.019432,36.498338],[-91.539505,36.499236],[-91.422068,36.497138],[-90.948379,36.498463],[-90.513782,36.49844],[-90.15228,36.49795],[-90.127286,36.411063],[-90.078873,36.399358],[-90.063594,36.384235],[-90.08241,36.321949],[-90.064413,36.302108],[-90.083795,36.271938],[-90.115101,36.265282],[-90.125487,36.231232],[-90.187556,36.20566],[-90.220846,36.184146],[-90.235372,36.13948],[-90.29293,36.114499],[-90.300557,36.097525],[-90.348208,36.041573],[-90.37789,35.995683],[-90.120302,35.997876],[-89.733095,36.000608],[-89.706932,36.000981],[-89.687254,36.034048],[-89.680029,36.082494],[-89.59307,36.129699],[-89.607,36.171179],[-89.69263,36.224959],[-89.695235,36.252766],[-89.651407,36.250683],[-89.611145,36.239271],[-89.534676,36.252771],[-89.536441,36.272827],[-89.578492,36.288317],[-89.611819,36.309088],[-89.610689,36.340442],[-89.531822,36.339246],[-89.509722,36.373626],[-89.54234,36.420103],[-89.520642,36.478668],[-89.539232,36.497934],[-89.560412,36.524406],[-89.570726,36.557656],[-89.530638,36.58171],[-89.47993,36.569087],[-89.466646,36.527041],[-89.485427,36.497491],[-89.494119,36.476268],[-89.476643,36.458393],[-89.453514,36.461789],[-89.41727,36.499011],[-89.365797,36.625126],[-89.332034,36.632881],[-89.31645,36.624044],[-89.269546,36.569716],[-89.227046,36.569537],[-89.202771,36.601665],[-89.197753,36.628971],[-89.159118,36.666354],[-89.169426,36.688771],[-89.200654,36.718752],[-89.197856,36.739772],[-89.169785,36.759641],[-89.125967,36.751809],[-89.116983,36.775595],[-89.178223,36.808614],[-89.170443,36.84184],[-89.138109,36.847323],[-89.117115,36.888685],[-89.098968,36.956067],[-89.1092,36.976451],[-89.132915,36.982057],[-89.170009,36.970298],[-89.192097,36.979995],[-89.203408,37.018856],[-89.259935,37.06407],[-89.307726,37.069654],[-89.301368,37.044982],[-89.257608,37.015496],[-89.266242,36.996302],[-89.29213,36.992189],[-89.317168,37.012767],[-89.37961,37.040669],[-89.37871,37.094586],[-89.42558,37.138235],[-89.461862,37.199517],[-89.457832,37.242594],[-89.489915,37.251315],[-89.518469,37.286256],[-89.509699,37.31426],[-89.474569,37.338165],[-89.43604,37.344441],[-89.420939,37.393952],[-89.439769,37.4372],[-89.471201,37.466473],[-89.516447,37.535558],[-89.519808,37.582747],[-89.481118,37.582973],[-89.478399,37.598869],[-89.517718,37.641217],[-89.512009,37.685525],[-89.52573,37.698441],[-89.583316,37.713261],[-89.618067,37.74982],[-89.64953,37.745496],[-89.667868,37.760257],[-89.66038,37.786296],[-89.739875,37.846921],[-89.797542,37.862128],[-89.799329,37.881516],[-89.844786,37.905572],[-89.898338,37.870931],[-89.953359,37.884614],[-89.97419,37.919509],[-89.959564,37.940158],[-89.931267,37.948277],[-89.940687,37.970874],[-89.988108,37.961749],[-90.065045,38.016875],[-90.092838,38.01726],[-90.126532,38.041665],[-90.130788,38.062341],[-90.17222,38.069636],[-90.242574,38.112125],[-90.274843,38.157559],[-90.334172,38.189857],[-90.358628,38.221981],[-90.372422,38.283725],[-90.370819,38.333554],[-90.341446,38.388298],[-90.289901,38.433136],[-90.264056,38.521025],[-90.22306,38.575732],[-90.192441,38.598619],[-90.179377,38.626657],[-90.187126,38.676404],[-90.21298,38.711988],[-90.166409,38.772649],[-90.123107,38.798048],[-90.113327,38.849306],[-90.19761,38.887648],[-90.250248,38.919344],[-90.309454,38.92412],[-90.392721,38.959071],[-90.450813,38.967769],[-90.472122,38.958838],[-90.487058,38.925862],[-90.546831,38.874009],[-90.577576,38.868384],[-90.623356,38.887149],[-90.663018,38.926283],[-90.679027,38.993867],[-90.712541,39.057064],[-90.68199,39.090066],[-90.68739,39.11991],[-90.709146,39.155111],[-90.726981,39.251173],[-90.751599,39.265432],[-90.793461,39.309498],[-90.8475,39.345272],[-90.934007,39.392127],[-90.939025,39.402744],[-90.993789,39.422959],[-91.03827,39.448435],[-91.062414,39.474122],[-91.064305,39.494643],[-91.100911,39.539001],[-91.148275,39.545798],[-91.168419,39.564928],[-91.185921,39.605119],[-91.223328,39.617603],[-91.27614,39.665759],[-91.347253,39.71048],[-91.367753,39.729029],[-91.363444,39.792804],[-91.376488,39.810033],[-91.433798,39.841865],[-91.44784,39.877951],[-91.420878,39.914865],[-91.494878,40.036453],[-91.489606,40.057435],[-91.510839,40.126137],[-91.513079,40.178537],[-91.506501,40.236304],[-91.492727,40.278217],[-91.46214,40.342414],[-91.419422,40.378264],[-91.441243,40.386255],[-91.483153,40.382492],[-91.524612,40.410765],[-91.526155,40.458625],[-91.567743,40.46229],[-91.594644,40.494997],[-91.616948,40.504794],[-91.621902,40.542292],[-91.681714,40.553035],[-91.68882,40.583409],[-91.729115,40.61364],[-91.8684,40.608056],[-92.096611,40.601831],[-92.485216,40.595085],[-92.659409,40.590341],[-93.0135,40.586297],[-93.260429,40.580814],[-93.547578,40.580407],[-94.07214,40.573026],[-94.282994,40.571469],[-94.594196,40.57096],[-94.99166,40.575692],[-95.488514,40.581735],[-95.765645,40.585208],[-95.773549,40.578205],[-95.75711,40.52599],[-95.697202,40.528475],[-95.694147,40.556942],[-95.671776,40.562628],[-95.652269,40.538108],[-95.661681,40.517312],[-95.699969,40.505275],[-95.69313,40.469396],[-95.659734,40.444484],[-95.66144,40.415917],[-95.642414,40.369829],[-95.622684,40.342323],[-95.653729,40.322582],[-95.553292,40.291158],[-95.556275,40.270761],[-95.516107,40.249187],[-95.478694,40.243661],[-95.4699,40.2227],[-95.47792,40.183844],[-95.440694,40.162282],[-95.431351,40.139163],[-95.396549,40.124701],[-95.419999,40.05044],[-95.391527,40.027058],[-95.363983,40.031498],[-95.30829,39.999998],[-95.250254,39.948644],[-95.205266,39.93975],[-95.206063,39.912061],[-95.189561,39.900145],[-95.146055,39.904183],[-95.139679,39.880592],[-95.085003,39.861883],[-95.035246,39.865921],[-95.015135,39.899643],[-94.95518,39.901288],[-94.934907,39.893784],[-94.939854,39.852044],[-94.891481,39.834644],[-94.876244,39.807394],[-94.89053,39.791425],[-94.91955,39.790291],[-94.912293,39.759338],[-94.869655,39.772899],[-94.859035,39.754116],[-94.875634,39.730492],[-94.902612,39.724202],[-94.947541,39.745337],[-94.970422,39.732121],[-94.969333,39.69073],[-95.024595,39.668485],[-95.055152,39.621657],[-95.056897,39.580567],[-95.107474,39.573743],[-95.108065,39.539919],[-95.050552,39.497514],[-95.033408,39.460876],[-94.990172,39.446192],[-94.972952,39.421705],[-94.919225,39.385174],[-94.888972,39.392432],[-94.879088,39.375703],[-94.910321,39.351295],[-94.898516,39.29845],[-94.84632,39.268481],[-94.827479,39.249896],[-94.831679,39.215938],[-94.782951,39.207162],[-94.762858,39.179991],[-94.735743,39.169191],[-94.688788,39.183228],[-94.659162,39.174126],[-94.650647,39.154092],[-94.606455,39.160827],[-94.588658,39.151163],[-94.607151,39.105582],[-94.608172,38.811095],[-94.613356,38.457967],[-94.612503,38.220379],[-94.614164,37.974007],[-94.617669,37.776193],[-94.616724,37.500123],[-94.618313,37.194687],[-94.617964,36.998905],[-94.618471,36.816277],[-94.617919,36.499414]]]},\"properties\":{\"name\":\"Missouri\",\"ns_code\":\"01779791\",\"geoid\":\"29\",\"usps_abbrev\":\"MO\",\"fips_code\":\"29\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[-167.944593,25],[-167.952449,25.023538],[-167.98894,25.049049],[-168.01596,25.048561],[-168.050607,25.025868],[-168.05839,24.987885],[-168.037751,24.959715],[-167.983537,24.949265],[-167.953685,24.964386],[-167.944593,25]]],[[[-160.5,21.697025],[-160.53174,21.711998],[-160.57826,21.70025],[-160.59977,21.66765],[-160.590748,21.625],[-160.570728,21.606664],[-160.520366,21.60493],[-160.484924,21.63243],[-160.483196,21.665186],[-160.5,21.697025]]],[[[-164.755463,23.601692],[-164.76481,23.581721],[-164.75,23.536146],[-164.7013,23.517002],[-164.654024,23.535221],[-164.640019,23.576111],[-164.654083,23.607069],[-164.68263,23.622806],[-164.709793,23.623234],[-164.755463,23.601692]]],[[[-161.875,23.019242],[-161.858059,23.060806],[-161.863002,23.085183],[-161.887575,23.110384],[-161.91944,23.113988],[-161.965449,23.104987],[-161.982524,23.091275],[-161.985445,23.041131],[-161.955171,23.009169],[-161.894219,23.00943],[-161.875,23.019242]]],[[[-171.67768,25.75],[-171.66792,25.781217],[-171.676628,25.810907],[-171.710623,25.833211],[-171.76812,25.833681],[-171.80015,25.805822],[-171.809629,25.78135],[-171.797027,25.740539],[-171.77015,25.718199],[-171.73749,25.706533],[-171.69984,25.716858],[-171.67768,25.75]]],[[[-173.97704,26],[-173.925968,26.019627],[-173.903358,26.060056],[-173.913424,26.099399],[-173.92909,26.120139],[-173.957916,26.13569],[-174.007114,26.137053],[-174.04962,26.116578],[-174.067374,26.059614],[-174.059183,26.020719],[-174.04609,26.002306],[-174.006404,25.990289],[-173.97704,26]]],[[[-178.25,28.347151],[-178.233018,28.375],[-178.23222,28.427335],[-178.238469,28.45615],[-178.257041,28.476535],[-178.31512,28.516334],[-178.34054,28.517269],[-178.395769,28.5],[-178.42758,28.466102],[-178.438336,28.444088],[-178.440486,28.405172],[-178.415882,28.380086],[-178.39561,28.341901],[-178.301402,28.328336],[-178.25,28.347151]]],[[[-166.125,23.607113],[-166.110682,23.625],[-166.11769,23.664596],[-166.13295,23.684703],[-166.105881,23.713536],[-166.10678,23.742522],[-166.148171,23.785479],[-166.16486,23.835407],[-166.16594,23.875],[-166.182347,23.902325],[-166.233064,23.928491],[-166.307072,23.924883],[-166.380463,23.883978],[-166.38653,23.846363],[-166.378839,23.823635],[-166.35634,23.805945],[-166.322572,23.799052],[-166.315823,23.74012],[-166.28599,23.71812],[-166.25,23.713898],[-166.205415,23.691601],[-166.223964,23.672533],[-166.232285,23.64158],[-166.224238,23.614791],[-166.194409,23.589531],[-166.151155,23.590464],[-166.125,23.607113]]],[[[-160.267683,21.75],[-160.218301,21.728797],[-160.17031,21.739551],[-160.15206,21.762955],[-160.141939,21.799026],[-160.122015,21.82496],[-160.059408,21.845594],[-160.03103,21.869364],[-160.022423,21.911575],[-160.027502,21.931214],[-160.006191,21.956575],[-159.997205,21.99251],[-160.01829,22.03001],[-160.056722,22.068345],[-160.10031,22.082497],[-160.14042,22.073322],[-160.15954,22.041325],[-160.174011,21.991479],[-160.1995,21.981278],[-160.242661,21.943869],[-160.279947,21.920638],[-160.302371,21.861491],[-160.29853,21.7905],[-160.267683,21.75]]],[[[-157.111502,20.875],[-157.075207,20.827299],[-157.048069,20.820545],[-157.045183,20.770467],[-157.027278,20.728731],[-156.994434,20.693575],[-156.962839,20.685459],[-156.935899,20.692222],[-156.875,20.690965],[-156.845415,20.711677],[-156.795363,20.735251],[-156.75,20.807605],[-156.75746,20.850771],[-156.823329,20.930808],[-156.868474,20.964379],[-156.913323,20.977963],[-156.989822,20.985058],[-157.035268,20.983302],[-157.08883,20.958397],[-157.11123,20.93123],[-157.11724,20.891693],[-157.111502,20.875]]],[[[-175.746115,27.732717],[-175.726159,27.75],[-175.676624,27.846765],[-175.666492,27.875],[-175.67013,27.941601],[-175.704354,27.982737],[-175.734928,28],[-175.806733,28.007192],[-175.966335,27.929911],[-175.992144,27.889499],[-175.988874,27.861904],[-175.955759,27.833055],[-176.01083,27.830286],[-176.043354,27.795897],[-176.029292,27.742683],[-175.969598,27.693402],[-175.93949,27.688231],[-175.895192,27.699213],[-175.84505,27.728457],[-175.81752,27.722972],[-175.746115,27.732717]]],[[[-157.015058,21.261194],[-157.034394,21.245709],[-157.158992,21.257896],[-157.183943,21.271466],[-157.220212,21.278496],[-157.272186,21.276564],[-157.305252,21.252881],[-157.311675,21.204128],[-157.349892,21.157085],[-157.36566,21.125],[-157.366549,21.100956],[-157.34912,21.07224],[-157.321244,21.050676],[-157.231438,21.043099],[-157.129544,21.048555],[-157.072149,21.057597],[-157.05024,21.042704],[-156.973894,21.021821],[-156.914535,20.997584],[-156.836954,21],[-156.78529,21.02454],[-156.672586,21.095717],[-156.65052,21.134277],[-156.65956,21.181907],[-156.686507,21.210514],[-156.72208,21.226062],[-156.794843,21.234161],[-156.853514,21.222705],[-156.874362,21.233093],[-156.910279,21.229571],[-156.9274,21.256578],[-156.952167,21.269328],[-156.990343,21.273534],[-157.015058,21.261194]]],[[[-159.503667,21.833462],[-159.443999,21.817789],[-159.4039,21.834953],[-159.357779,21.865692],[-159.307978,21.908673],[-159.284354,21.940637],[-159.279222,21.965296],[-159.28371,22.018501],[-159.25,22.084146],[-159.241827,22.125],[-159.245779,22.166808],[-159.26552,22.203915],[-159.308798,22.25],[-159.40292,22.290473],[-159.43275,22.278035],[-159.48404,22.285916],[-159.524654,22.275494],[-159.574883,22.28295],[-159.612952,22.272235],[-159.677252,22.227108],[-159.75,22.199449],[-159.788826,22.160105],[-159.794006,22.134622],[-159.83772,22.094226],[-159.84812,22.076073],[-159.850045,22.016486],[-159.843609,22],[-159.793601,21.93978],[-159.73584,21.912166],[-159.711714,21.907557],[-159.631259,21.846688],[-159.587907,21.84547],[-159.529638,21.833418],[-159.503667,21.833462]]],[[[-158.31764,21.539148],[-158.298653,21.524718],[-158.301353,21.496818],[-158.276241,21.449319],[-158.240052,21.411531],[-158.217752,21.364532],[-158.183253,21.345935],[-158.164354,21.272335],[-158.14925,21.251737],[-158.110077,21.237071],[-157.974355,21.252039],[-157.89866,21.249939],[-157.85907,21.213135],[-157.810229,21.199238],[-157.771407,21.207833],[-157.74531,21.221671],[-157.708324,21.208202],[-157.67578,21.213801],[-157.60769,21.269134],[-157.59791,21.295134],[-157.60822,21.353054],[-157.62845,21.376616],[-157.652151,21.439107],[-157.640749,21.462695],[-157.66095,21.496789],[-157.712174,21.520814],[-157.750924,21.516458],[-157.78095,21.544607],[-157.798649,21.5852],[-157.84415,21.621804],[-157.88145,21.705183],[-157.94178,21.754579],[-157.997244,21.761591],[-158.05014,21.739881],[-158.095543,21.702995],[-158.114744,21.66721],[-158.148846,21.641999],[-158.278156,21.627842],[-158.324247,21.615675],[-158.34465,21.583177],[-158.342151,21.564286],[-158.31764,21.539148]]],[[[-156.565189,20.738914],[-156.51187,20.735386],[-156.5,20.699087],[-156.531659,20.67922],[-156.549625,20.65803],[-156.597734,20.657879],[-156.655056,20.625],[-156.704442,20.604591],[-156.733645,20.581844],[-156.754144,20.5443],[-156.75,20.454335],[-156.705115,20.454142],[-156.533456,20.46298],[-156.48937,20.5],[-156.479551,20.529573],[-156.488377,20.558546],[-156.459868,20.557666],[-156.42049,20.536609],[-156.36288,20.530908],[-156.290472,20.536895],[-156.25,20.550337],[-156.195608,20.577151],[-156.13527,20.569843],[-156.073271,20.598409],[-156.05427,20.597254],[-156.0163,20.612374],[-155.961458,20.660452],[-155.928101,20.724792],[-155.926785,20.781735],[-155.93913,20.836591],[-155.958991,20.875],[-156,20.875],[-156,20.852123],[-156.032289,20.859858],[-156.076476,20.880751],[-156.111061,20.908338],[-156.136358,20.917437],[-156.1953,20.971502],[-156.274302,21.004236],[-156.33922,21.004195],[-156.404908,20.974181],[-156.45109,20.968758],[-156.467546,20.98795],[-156.475383,21.019208],[-156.490728,21.035435],[-156.55951,21.082338],[-156.59103,21.092411],[-156.670424,21.076693],[-156.70849,21.043231],[-156.735017,21.005729],[-156.751796,20.948548],[-156.75194,20.913681],[-156.737531,20.867918],[-156.721737,20.842436],[-156.65986,20.778419],[-156.625,20.75],[-156.586932,20.75],[-156.565189,20.738914]]],[[[-155.247897,20.059808],[-155.438177,20.148815],[-155.54075,20.180249],[-155.578768,20.180147],[-155.63185,20.214253],[-155.688845,20.244776],[-155.702217,20.261602],[-155.751464,20.29447],[-155.836783,20.321226],[-155.906873,20.306067],[-155.947962,20.261652],[-155.957667,20.234972],[-155.957719,20.167614],[-155.932275,20.083445],[-155.884311,20.018327],[-155.938883,19.961518],[-155.962427,19.909152],[-156.012391,19.889753],[-156.05354,19.843534],[-156.079517,19.831131],[-156.10724,19.790402],[-156.119075,19.719301],[-156.08882,19.661408],[-156.07529,19.620962],[-156.03589,19.597961],[-156.022997,19.563632],[-156.012864,19.5],[-155.999134,19.466181],[-155.978105,19.445809],[-155.963615,19.386519],[-155.943213,19.344227],[-155.946461,19.290445],[-155.973882,19.14257],[-155.974041,19.107789],[-155.95406,19.053622],[-155.925931,19.008154],[-155.875,18.97643],[-155.828233,18.966542],[-155.78173,18.933417],[-155.739971,18.916891],[-155.722895,18.882523],[-155.694257,18.866786],[-155.662995,18.866159],[-155.630753,18.879687],[-155.5656,18.934517],[-155.54535,18.96185],[-155.537366,18.989057],[-155.518785,19.008071],[-155.494878,19.071586],[-155.466097,19.095352],[-155.428934,19.107215],[-155.391434,19.14436],[-155.342151,19.161315],[-155.297856,19.199453],[-155.258015,19.222171],[-155.230051,19.212851],[-155.182098,19.209954],[-155.10551,19.23427],[-155.049153,19.267251],[-154.997442,19.284554],[-154.939663,19.312834],[-154.889028,19.360392],[-154.847913,19.385152],[-154.810311,19.416057],[-154.775751,19.457113],[-154.756605,19.506911],[-154.759333,19.536462],[-154.77829,19.559895],[-154.822221,19.59331],[-154.875,19.616167],[-154.911451,19.662036],[-154.928935,19.673605],[-154.928109,19.704534],[-154.95243,19.756784],[-154.977849,19.781404],[-155.03331,19.793421],[-155.03162,19.875],[-155.086049,19.941743],[-155.14033,19.989481],[-155.247897,20.059808]]]]},\"properties\":{\"name\":\"Hawaii\",\"ns_code\":\"01779782\",\"geoid\":\"15\",\"usps_abbrev\":\"HI\",\"fips_code\":\"15\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-85.605165,34.984678],[-85.828767,34.988069],[-86.248019,34.990421],[-86.467941,34.990486],[-86.836306,34.991899],[-87.084881,34.996462],[-87.429968,35.002791],[-87.662256,35.003537],[-88.000032,35.005942],[-88.202959,35.008028],[-88.200064,34.995636],[-88.152413,34.919741],[-88.097888,34.892202],[-88.118407,34.724292],[-88.135204,34.615878],[-88.165583,34.383944],[-88.210741,34.0292],[-88.252254,33.719586],[-88.294867,33.367103],[-88.322505,33.142358],[-88.354292,32.875131],[-88.392176,32.548377],[-88.428209,32.25103],[-88.473227,31.893856],[-88.464908,31.705476],[-88.451573,31.481531],[-88.425432,30.998323],[-88.411726,30.722702],[-88.400618,30.476084],[-88.384431,30.158543],[-88.347719,30.190088],[-88.310302,30.177816],[-88.27794,30.180071],[-88.162022,30.200093],[-88.13161,30.173621],[-88.074869,30.147149],[-88.028037,30.150381],[-88.003618,30.173417],[-87.925756,30.179561],[-87.807153,30.177378],[-87.718002,30.19176],[-87.647824,30.199211],[-87.518346,30.229506],[-87.51838,30.283901],[-87.452378,30.300201],[-87.50558,30.3111],[-87.49998,30.328957],[-87.462978,30.334],[-87.450778,30.346999],[-87.440678,30.391498],[-87.369383,30.431948],[-87.370768,30.446865],[-87.414677,30.457296],[-87.435578,30.480496],[-87.445103,30.528909],[-87.408018,30.583701],[-87.393294,30.627218],[-87.406356,30.674437],[-87.501502,30.721092],[-87.535114,30.749448],[-87.544789,30.778395],[-87.626224,30.846664],[-87.634788,30.866707],[-87.62027,30.887404],[-87.588862,30.96579],[-87.598829,30.997455],[-87.311949,30.998435],[-87.237685,30.996393],[-87.140755,30.999532],[-86.563436,30.995223],[-86.12154,30.992884],[-85.87493,30.993618],[-85.568112,30.996244],[-85.243632,31.000884],[-85.002499,31.000685],[-85.01139,31.053547],[-85.028573,31.074583],[-85.037062,31.109753],[-85.052867,31.119489],[-85.077801,31.157889],[-85.099647,31.164942],[-85.106963,31.202693],[-85.096843,31.225239],[-85.113261,31.264343],[-85.090334,31.293606],[-85.083828,31.326971],[-85.09217,31.364576],[-85.065639,31.439521],[-85.071621,31.468384],[-85.041428,31.539293],[-85.05796,31.57084],[-85.057473,31.618624],[-85.087029,31.640966],[-85.092429,31.659966],[-85.12593,31.696265],[-85.11913,31.730964],[-85.141831,31.782363],[-85.131231,31.815262],[-85.140731,31.857461],[-85.08213,31.944658],[-85.08683,31.957758],[-85.055075,32.010714],[-85.05883,32.046656],[-85.045063,32.088063],[-85.06206,32.132486],[-85.009224,32.181646],[-84.963728,32.195852],[-84.973328,32.21765],[-84.930727,32.21895],[-84.901191,32.258374],[-84.9338,32.29826],[-85.002791,32.322428],[-85.004582,32.345196],[-84.983866,32.362386],[-84.971831,32.442843],[-84.99353,32.450743],[-85.001532,32.514741],[-85.023645,32.542955],[-85.067199,32.579306],[-85.08224,32.616264],[-85.105437,32.644934],[-85.088483,32.657758],[-85.112337,32.683234],[-85.122738,32.716032],[-85.113167,32.732813],[-85.14298,32.761654],[-85.122326,32.774383],[-85.165635,32.808222],[-85.154855,32.839689],[-85.184737,32.870514],[-85.233108,33.111714],[-85.304439,33.482884],[-85.356921,33.748233],[-85.406748,34.002314],[-85.47045,34.328239],[-85.527261,34.588683],[-85.561416,34.750079],[-85.605165,34.984678]]]},\"properties\":{\"name\":\"Alabama\",\"ns_code\":\"01779775\",\"geoid\":\"01\",\"usps_abbrev\":\"AL\",\"fips_code\":\"01\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-83.108614,35.000659],[-83.120872,34.942039],[-83.158966,34.929138],[-83.205032,34.880329],[-83.242599,34.877808],[-83.246361,34.856533],[-83.32415,34.787479],[-83.326309,34.750839],[-83.349075,34.736633],[-83.352692,34.716904],[-83.3389,34.680039],[-83.304641,34.669561],[-83.293183,34.654296],[-83.255281,34.637696],[-83.23258,34.611597],[-83.168278,34.590998],[-83.122904,34.560117],[-83.086807,34.517555],[-83.034565,34.483571],[-82.990684,34.479968],[-82.901551,34.486764],[-82.8644,34.459785],[-82.833571,34.364092],[-82.798658,34.341777],[-82.782128,34.298278],[-82.754242,34.275787],[-82.743172,34.251598],[-82.740585,34.207268],[-82.717459,34.150546],[-82.675219,34.129779],[-82.627907,34.06419],[-82.593887,34.028109],[-82.579553,33.979688],[-82.556765,33.945324],[-82.524515,33.94336],[-82.494811,33.911712],[-82.42281,33.863757],[-82.408353,33.86632],[-82.346933,33.834298],[-82.300213,33.800627],[-82.299393,33.785037],[-82.247472,33.752591],[-82.23432,33.699836],[-82.200164,33.662012],[-82.196584,33.630583],[-82.133523,33.590535],[-82.10624,33.595637],[-82.092016,33.581862],[-82.046335,33.56383],[-82.00983,33.530313],[-81.985938,33.486536],[-81.934136,33.468337],[-81.913126,33.438333],[-81.93139,33.428474],[-81.931549,33.382156],[-81.94474,33.364041],[-81.918337,33.332842],[-81.847736,33.307243],[-81.863336,33.289844],[-81.835738,33.271045],[-81.852822,33.248542],[-81.789236,33.208247],[-81.756935,33.197848],[-81.768235,33.167949],[-81.743835,33.14145],[-81.704634,33.11645],[-81.645433,33.094051],[-81.614994,33.095551],[-81.565501,33.047888],[-81.51059,33.025805],[-81.4919,33.006694],[-81.502716,32.938688],[-81.483198,32.921802],[-81.457061,32.850389],[-81.426475,32.840773],[-81.423331,32.778512],[-81.406074,32.741625],[-81.427505,32.702242],[-81.405711,32.688727],[-81.393033,32.651542],[-81.41866,32.629392],[-81.389205,32.595416],[-81.319121,32.559596],[-81.299796,32.563049],[-81.279516,32.536226],[-81.251069,32.517805],[-81.200408,32.468314],[-81.205565,32.423929],[-81.178283,32.392986],[-81.155114,32.344341],[-81.122631,32.30699],[-81.123276,32.276975],[-81.141414,32.272039],[-81.15577,32.245793],[-81.123149,32.20132],[-81.128142,32.169097],[-81.117225,32.117604],[-81.066965,32.090384],[-81.032633,32.085476],[-81.01197,32.100178],[-80.954483,32.068616],[-80.926769,32.041663],[-80.89296,32.034951],[-80.751429,32.033468],[-80.741672,32.064374],[-80.748202,32.08449],[-80.70522,32.105187],[-80.66472,32.142607],[-80.63695,32.144272],[-80.608451,32.162294],[-80.584985,32.162589],[-80.548933,32.192053],[-80.523824,32.234592],[-80.459332,32.262221],[-80.431544,32.261384],[-80.392327,32.280317],[-80.37283,32.313408],[-80.374825,32.342453],[-80.35746,32.350534],[-80.336717,32.380753],[-80.306163,32.397097],[-80.294078,32.418521],[-80.263645,32.429579],[-80.243074,32.472449],[-80.198839,32.496241],[-80.166951,32.491086],[-80.121459,32.499192],[-80.094998,32.523866],[-80.088877,32.549029],[-80.03077,32.559961],[-79.979002,32.558074],[-79.918341,32.607264],[-79.855063,32.636389],[-79.828058,32.664778],[-79.798255,32.675632],[-79.766592,32.711537],[-79.760074,32.738257],[-79.691893,32.75924],[-79.662251,32.783119],[-79.637821,32.816377],[-79.612276,32.822971],[-79.58539,32.850856],[-79.534689,32.870012],[-79.495945,32.931572],[-79.459374,32.940504],[-79.440181,32.958916],[-79.371297,32.945717],[-79.322257,32.956078],[-79.273373,33.046559],[-79.215538,33.064248],[-79.180871,33.107528],[-79.140519,33.13962],[-79.091614,33.159056],[-79.077605,33.188653],[-79.086583,33.21531],[-79.120217,33.246932],[-79.093312,33.304952],[-79.087581,33.369287],[-79.034496,33.456834],[-78.993033,33.488992],[-78.956996,33.538722],[-78.887415,33.611975],[-78.822059,33.670239],[-78.772415,33.707967],[-78.691382,33.755288],[-78.635723,33.776679],[-78.499301,33.812852],[-78.54108,33.85112],[-78.874747,34.134395],[-79.201539,34.40864],[-79.358252,34.545579],[-79.6753,34.804744],[-79.874855,34.80541],[-80.164577,34.811656],[-80.558125,34.81744],[-80.797491,34.819752],[-80.782042,34.935785],[-80.93495,35.107409],[-81.041489,35.044703],[-81.057648,35.062433],[-81.057236,35.086129],[-81.03247,35.110033],[-81.047091,35.145157],[-81.069092,35.151242],[-81.264273,35.161062],[-81.749875,35.180019],[-82.181121,35.194067],[-82.27492,35.200071],[-82.315871,35.190678],[-82.362991,35.191037],[-82.390253,35.21554],[-82.419744,35.198613],[-82.431481,35.173187],[-82.486653,35.174463],[-82.525566,35.15652],[-82.598579,35.137788],[-82.641797,35.131817],[-82.697945,35.095359],[-82.72701,35.094142],[-82.763712,35.068209],[-82.784839,35.0857],[-83.108614,35.000659]]]},\"properties\":{\"name\":\"South Carolina\",\"ns_code\":\"01779799\",\"geoid\":\"45\",\"usps_abbrev\":\"SC\",\"fips_code\":\"45\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-71.501088,45.013349],[-71.540931,44.98512],[-71.515001,44.958597],[-71.515868,44.931715],[-71.496093,44.907849],[-71.545923,44.865932],[-71.551114,44.836965],[-71.576865,44.815197],[-71.57864,44.785444],[-71.630755,44.753141],[-71.626093,44.728543],[-71.59457,44.697033],[-71.584606,44.656882],[-71.561962,44.647189],[-71.553237,44.608914],[-71.5363,44.585908],[-71.549836,44.569016],[-71.597723,44.554657],[-71.571337,44.538329],[-71.591733,44.518175],[-71.597994,44.486885],[-71.652127,44.460792],[-71.658906,44.442633],[-71.714279,44.409545],[-71.791609,44.400188],[-71.814141,44.382278],[-71.812473,44.358477],[-71.872472,44.336628],[-71.906909,44.348284],[-71.92911,44.337577],[-71.98112,44.3375],[-72.01914,44.320255],[-72.058851,44.286369],[-72.047799,44.238531],[-72.065918,44.189289],[-72.043896,44.158449],[-72.032525,44.082488],[-72.084762,44.020756],[-72.116977,43.994486],[-72.099093,43.957286],[-72.116807,43.947557],[-72.121554,43.918493],[-72.150518,43.902221],[-72.187329,43.855151],[-72.182511,43.810354],[-72.232069,43.748428],[-72.271839,43.733716],[-72.305277,43.695267],[-72.303936,43.667986],[-72.327395,43.636774],[-72.328514,43.600805],[-72.371051,43.580494],[-72.380383,43.54088],[-72.398376,43.510829],[-72.380428,43.488525],[-72.391526,43.46878],[-72.413377,43.362741],[-72.395525,43.312605],[-72.407856,43.282895],[-72.439187,43.245223],[-72.43909,43.201054],[-72.450416,43.192373],[-72.453048,43.139475],[-72.432944,43.11253],[-72.434385,43.083713],[-72.467255,43.054119],[-72.458386,43.018602],[-72.4736,42.972284],[-72.532226,42.954882],[-72.524188,42.917609],[-72.552702,42.884874],[-72.557114,42.852474],[-72.539317,42.804647],[-72.475938,42.757702],[-72.458436,42.72685],[-71.981402,42.713295],[-71.745815,42.707287],[-71.294205,42.69699],[-71.245504,42.742589],[-71.181803,42.73759],[-71.186103,42.790674],[-71.132503,42.821388],[-71.064201,42.806289],[-71.031201,42.859087],[-70.9665,42.868989],[-70.930798,42.884588],[-70.88561,42.882496],[-70.848625,42.860939],[-70.820894,42.872076],[-70.735005,42.874685],[-70.726024,42.928524],[-70.694395,42.956754],[-70.67286,42.930419],[-70.605724,42.912329],[-70.575094,42.917126],[-70.611152,42.97899],[-70.63218,42.986429],[-70.701197,43.04524],[-70.70631,43.075339],[-70.737969,43.073728],[-70.824584,43.125088],[-70.833614,43.146127],[-70.828057,43.186529],[-70.809714,43.224227],[-70.818264,43.238384],[-70.859837,43.258025],[-70.896742,43.285364],[-70.931188,43.336562],[-70.973462,43.349914],[-70.986852,43.413805],[-70.960826,43.439331],[-70.975311,43.477718],[-70.953874,43.519167],[-70.950776,43.551491],[-70.972743,43.57031],[-70.98904,43.790138],[-70.99478,43.960348],[-71.009964,44.284783],[-71.026162,44.549268],[-71.033524,44.697637],[-71.058301,44.991243],[-71.077227,45.250439],[-71.083924,45.305451],[-71.097125,45.302226],[-71.131953,45.245423],[-71.178576,45.241389],[-71.231122,45.249712],[-71.297092,45.298985],[-71.336392,45.273066],[-71.362831,45.267617],[-71.375299,45.245482],[-71.420335,45.232719],[-71.39758,45.202938],[-71.43578,45.139395],[-71.429489,45.122256],[-71.46952,45.083661],[-71.486822,45.078351],[-71.500953,45.04506],[-71.501088,45.013349]]]},\"properties\":{\"name\":\"New Hampshire\",\"ns_code\":\"01779794\",\"geoid\":\"33\",\"usps_abbrev\":\"NH\",\"fips_code\":\"33\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-104.053028,43.000585],[-103.475638,43.00048],[-102.889187,43.000438],[-102.625701,42.999773],[-102.125427,42.999196],[-101.862762,42.999374],[-101.633007,42.996255],[-101.22811,42.998127],[-100.88752,42.998015],[-100.525978,42.999042],[-100.034201,42.998145],[-99.635934,42.997834],[-99.195658,42.998084],[-98.88551,42.998181],[-98.49855,42.998559],[-98.467356,42.947556],[-98.342408,42.900847],[-98.325864,42.8865],[-98.258276,42.87439],[-98.219826,42.853157],[-98.129223,42.821056],[-98.062999,42.781001],[-98.013072,42.762219],[-97.947189,42.770709],[-97.888647,42.817177],[-97.875531,42.858656],[-97.831393,42.86896],[-97.774381,42.84974],[-97.686477,42.842212],[-97.60376,42.858329],[-97.561928,42.847552],[-97.504847,42.858477],[-97.43911,42.84711],[-97.417066,42.865918],[-97.341181,42.855882],[-97.306677,42.867604],[-97.289859,42.855499],[-97.248556,42.855386],[-97.218046,42.845113],[-97.212659,42.812069],[-97.150763,42.795566],[-97.130257,42.771541],[-97.071849,42.772305],[-96.97912,42.76009],[-96.948902,42.719465],[-96.907922,42.733857],[-96.806213,42.704154],[-96.802177,42.672235],[-96.764061,42.661983],[-96.728027,42.66688],[-96.687667,42.653127],[-96.711,42.608129],[-96.687005,42.579225],[-96.63803,42.551959],[-96.625961,42.513578],[-96.60347,42.50446],[-96.557776,42.52038],[-96.525088,42.510183],[-96.501319,42.482749],[-96.445483,42.49063],[-96.47719,42.491011],[-96.488163,42.511284],[-96.47696,42.555972],[-96.496026,42.580382],[-96.516084,42.630275],[-96.605596,42.70202],[-96.635561,42.740847],[-96.619849,42.783792],[-96.60356,42.783997],[-96.57783,42.829067],[-96.544831,42.850193],[-96.546561,42.874524],[-96.528536,42.897666],[-96.533761,42.928492],[-96.499283,42.958745],[-96.52082,42.979923],[-96.491981,43.010197],[-96.51395,43.030075],[-96.510851,43.04977],[-96.460742,43.064337],[-96.454063,43.097331],[-96.436567,43.119818],[-96.469556,43.158353],[-96.476729,43.221987],[-96.51754,43.217825],[-96.555281,43.226666],[-96.549534,43.248372],[-96.586525,43.273715],[-96.524522,43.312067],[-96.535022,43.336674],[-96.52172,43.386755],[-96.593339,43.433151],[-96.582402,43.466739],[-96.599191,43.500456],[-96.45326,43.50039],[-96.452858,43.822701],[-96.453404,44.025431],[-96.452275,44.302118],[-96.451234,44.718273],[-96.45197,44.962516],[-96.453097,45.300751],[-96.489065,45.357071],[-96.619752,45.408884],[-96.680454,45.410499],[-96.7314,45.45702],[-96.763432,45.520007],[-96.856834,45.605378],[-96.838648,45.647509],[-96.759312,45.688744],[-96.75035,45.698782],[-96.672665,45.732336],[-96.639222,45.772096],[-96.583085,45.820024],[-96.574517,45.844044],[-96.563672,45.935245],[-97.225797,45.93537],[-97.482542,45.935091],[-98.0285,45.935777],[-98.414398,45.936493],[-98.756598,45.938791],[-99.296714,45.940059],[-99.760635,45.940811],[-100.308163,45.943258],[-100.750596,45.943662],[-101.041137,45.943455],[-101.420443,45.943806],[-101.786099,45.944341],[-102.126414,45.944695],[-102.549895,45.944836],[-102.983434,45.945079],[-103.369029,45.945152],[-103.744941,45.945061],[-104.045441,45.94531],[-104.042596,45.749942],[-104.040188,45.374158],[-104.040131,45.011116],[-104.057697,44.99738],[-104.055913,44.874986],[-104.055926,44.517359],[-104.055389,44.249983],[-104.054688,43.999979],[-104.055122,43.740516],[-104.05479,43.41642],[-104.053028,43.000585]]]},\"properties\":{\"name\":\"South Dakota\",\"ns_code\":\"01785534\",\"geoid\":\"46\",\"usps_abbrev\":\"SD\",\"fips_code\":\"46\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-91.419422,40.378264],[-91.46214,40.342414],[-91.492727,40.278217],[-91.506501,40.236304],[-91.513079,40.178537],[-91.510839,40.126137],[-91.489606,40.057435],[-91.494878,40.036453],[-91.420878,39.914865],[-91.44784,39.877951],[-91.433798,39.841865],[-91.376488,39.810033],[-91.363444,39.792804],[-91.367753,39.729029],[-91.347253,39.71048],[-91.27614,39.665759],[-91.223328,39.617603],[-91.185921,39.605119],[-91.168419,39.564928],[-91.148275,39.545798],[-91.100911,39.539001],[-91.064305,39.494643],[-91.062414,39.474122],[-91.03827,39.448435],[-90.993789,39.422959],[-90.939025,39.402744],[-90.934007,39.392127],[-90.8475,39.345272],[-90.793461,39.309498],[-90.751599,39.265432],[-90.726981,39.251173],[-90.709146,39.155111],[-90.68739,39.11991],[-90.68199,39.090066],[-90.712541,39.057064],[-90.679027,38.993867],[-90.663018,38.926283],[-90.623356,38.887149],[-90.577576,38.868384],[-90.546831,38.874009],[-90.487058,38.925862],[-90.472122,38.958838],[-90.450813,38.967769],[-90.392721,38.959071],[-90.309454,38.92412],[-90.250248,38.919344],[-90.19761,38.887648],[-90.113327,38.849306],[-90.123107,38.798048],[-90.166409,38.772649],[-90.21298,38.711988],[-90.187126,38.676404],[-90.179377,38.626657],[-90.192441,38.598619],[-90.22306,38.575732],[-90.264056,38.521025],[-90.289901,38.433136],[-90.341446,38.388298],[-90.370819,38.333554],[-90.372422,38.283725],[-90.358628,38.221981],[-90.334172,38.189857],[-90.274843,38.157559],[-90.242574,38.112125],[-90.17222,38.069636],[-90.130788,38.062341],[-90.126532,38.041665],[-90.092838,38.01726],[-90.065045,38.016875],[-89.988108,37.961749],[-89.940687,37.970874],[-89.931267,37.948277],[-89.959564,37.940158],[-89.97419,37.919509],[-89.953359,37.884614],[-89.898338,37.870931],[-89.844786,37.905572],[-89.799329,37.881516],[-89.797542,37.862128],[-89.739875,37.846921],[-89.66038,37.786296],[-89.667868,37.760257],[-89.64953,37.745496],[-89.618067,37.74982],[-89.583316,37.713261],[-89.52573,37.698441],[-89.512009,37.685525],[-89.517718,37.641217],[-89.478399,37.598869],[-89.481118,37.582973],[-89.519808,37.582747],[-89.516447,37.535558],[-89.471201,37.466473],[-89.439769,37.4372],[-89.420939,37.393952],[-89.43604,37.344441],[-89.474569,37.338165],[-89.509699,37.31426],[-89.518469,37.286256],[-89.489915,37.251315],[-89.457832,37.242594],[-89.461862,37.199517],[-89.42558,37.138235],[-89.37871,37.094586],[-89.37961,37.040669],[-89.317168,37.012767],[-89.29213,36.992189],[-89.266242,36.996302],[-89.257608,37.015496],[-89.301368,37.044982],[-89.307726,37.069654],[-89.259935,37.06407],[-89.203408,37.018856],[-89.192097,36.979995],[-89.170009,36.970298],[-89.132915,36.982057],[-89.17383,37.011443],[-89.177857,37.057796],[-89.154346,37.08896],[-89.111409,37.118988],[-89.092755,37.156763],[-89.029866,37.211065],[-88.973793,37.2298],[-88.931424,37.227378],[-88.802554,37.187861],[-88.735061,37.145247],[-88.703274,37.142935],[-88.616928,37.116041],[-88.572863,37.079569],[-88.52316,37.06579],[-88.482624,37.067257],[-88.449353,37.087615],[-88.424603,37.151764],[-88.448016,37.203403],[-88.473688,37.221652],[-88.507062,37.259689],[-88.512263,37.297096],[-88.487236,37.339107],[-88.47409,37.391863],[-88.454642,37.409953],[-88.412746,37.425071],[-88.369196,37.40207],[-88.311974,37.441001],[-88.279207,37.453292],[-88.197714,37.460155],[-88.136246,37.471471],[-88.082243,37.472665],[-88.061168,37.504269],[-88.0721,37.528811],[-88.13351,37.574417],[-88.160389,37.656131],[-88.12499,37.707223],[-88.072311,37.73352],[-88.049183,37.755073],[-88.028016,37.799188],[-88.094956,37.893689],[-88.013355,37.894854],[-88.037013,37.956276],[-88.012574,37.977062],[-88.034132,38.032045],[-87.967226,38.067105],[-87.960677,38.100191],[-88.004697,38.083965],[-88.013118,38.103527],[-87.974781,38.110515],[-87.944825,38.127272],[-87.910924,38.162557],[-87.969865,38.191786],[-87.984838,38.228674],[-87.937329,38.292431],[-87.85113,38.27512],[-87.830953,38.299365],[-87.832723,38.324853],[-87.806075,38.363143],[-87.784994,38.368011],[-87.743772,38.413115],[-87.73082,38.449228],[-87.744639,38.477271],[-87.685921,38.491843],[-87.656144,38.521668],[-87.670071,38.545237],[-87.621401,38.586118],[-87.627345,38.607237],[-87.597247,38.665314],[-87.534435,38.681754],[-87.51961,38.697198],[-87.495905,38.785434],[-87.526816,38.817369],[-87.525893,38.848795],[-87.552028,38.860318],[-87.51912,38.922603],[-87.513346,38.956],[-87.529496,38.971925],[-87.573466,38.985441],[-87.572588,39.057286],[-87.61838,39.104284],[-87.658745,39.135997],[-87.619603,39.186032],[-87.585725,39.200396],[-87.574615,39.223931],[-87.580652,39.255237],[-87.604947,39.260212],[-87.60592,39.311076],[-87.589084,39.333831],[-87.531646,39.347888],[-87.532707,39.666203],[-87.533269,39.875036],[-87.53153,40.142775],[-87.526168,40.505761],[-87.525661,40.836132],[-87.526675,41.32691],[-87.523661,41.759907],[-87.207774,41.760956],[-87.125042,42.097098],[-87.019935,42.493498],[-87.375058,42.493815],[-87.803218,42.492566],[-88.081066,42.495631],[-88.389994,42.494516],[-88.628997,42.495045],[-88.804148,42.4921],[-88.991611,42.496005],[-89.262669,42.49819],[-89.66982,42.505044],[-89.955237,42.505651],[-90.157406,42.508325],[-90.405927,42.506891],[-90.640927,42.508302],[-90.654027,42.478503],[-90.624328,42.458904],[-90.570736,42.441701],[-90.555018,42.416138],[-90.477941,42.384151],[-90.415937,42.322699],[-90.431039,42.282452],[-90.419326,42.254467],[-90.391108,42.225473],[-90.356964,42.205445],[-90.328273,42.201047],[-90.282173,42.178846],[-90.209479,42.15268],[-90.201404,42.130937],[-90.162895,42.116718],[-90.164485,42.042105],[-90.140061,42.003252],[-90.162141,41.961293],[-90.153584,41.906614],[-90.170041,41.876439],[-90.187969,41.803163],[-90.278633,41.767358],[-90.315549,41.734426],[-90.313052,41.699631],[-90.334525,41.679559],[-90.343452,41.646959],[-90.343228,41.587833],[-90.412825,41.565329],[-90.461432,41.523533],[-90.500633,41.518033],[-90.540935,41.526133],[-90.595237,41.511032],[-90.605937,41.494232],[-90.655839,41.462132],[-90.73754,41.450127],[-90.84746,41.455019],[-90.930691,41.421368],[-90.975168,41.433985],[-91.02779,41.423603],[-91.048259,41.409748],[-91.065058,41.369101],[-91.073553,41.310481],[-91.11419,41.250029],[-91.112348,41.239002],[-91.065899,41.199517],[-91.041875,41.166484],[-90.989662,41.155707],[-90.94626,41.09473],[-90.942253,41.034702],[-90.958142,40.979767],[-90.951967,40.958238],[-90.962916,40.924957],[-90.9985,40.90812],[-91.044653,40.868356],[-91.096946,40.811403],[-91.092476,40.776838],[-91.115735,40.725168],[-91.12082,40.672777],[-91.185295,40.637803],[-91.247851,40.63839],[-91.310048,40.625041],[-91.359873,40.601805],[-91.406202,40.542698],[-91.369059,40.512532],[-91.364915,40.484168],[-91.381045,40.44849],[-91.375712,40.391925],[-91.419422,40.378264]]]},\"properties\":{\"name\":\"Illinois\",\"ns_code\":\"01779784\",\"geoid\":\"17\",\"usps_abbrev\":\"IL\",\"fips_code\":\"17\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-90.309289,34.995694],[-89.878364,34.994327],[-89.617973,34.995217],[-89.486693,34.993978],[-88.925242,34.994842],[-88.620196,34.995465],[-88.200064,34.995636],[-88.202959,35.008028],[-88.000032,35.005942],[-87.662256,35.003537],[-87.429968,35.002791],[-87.084881,34.996462],[-86.836306,34.991899],[-86.467941,34.990486],[-86.248019,34.990421],[-85.828767,34.988069],[-85.605165,34.984678],[-85.466509,34.982971],[-84.952772,34.987943],[-84.809184,34.987569],[-84.321869,34.988408],[-84.29024,35.225572],[-84.211818,35.266078],[-84.199117,35.243679],[-84.107006,35.251409],[-84.055712,35.268182],[-84.02141,35.300983],[-84.03551,35.317783],[-84.038327,35.347813],[-84.0074,35.371643],[-84.014707,35.411984],[-83.992131,35.440402],[-83.937015,35.471511],[-83.911773,35.476028],[-83.882563,35.517182],[-83.82559,35.523829],[-83.771736,35.562118],[-83.707199,35.568533],[-83.640958,35.565712],[-83.601854,35.578228],[-83.56609,35.565993],[-83.498335,35.56298],[-83.445802,35.611803],[-83.421576,35.611186],[-83.366941,35.638728],[-83.334965,35.665471],[-83.31049,35.654452],[-83.254231,35.695807],[-83.24067,35.726759],[-83.185694,35.729888],[-83.159208,35.764892],[-83.120183,35.766234],[-83.078732,35.789472],[-83.036209,35.787405],[-82.995803,35.773128],[-82.974245,35.786967],[-82.94383,35.825637],[-82.916273,35.841603],[-82.920609,35.868472],[-82.896583,35.878471],[-82.914381,35.929683],[-82.898506,35.9451],[-82.851603,35.949452],[-82.820411,35.922077],[-82.787465,35.952163],[-82.775866,36.000803],[-82.715365,36.023804],[-82.684765,36.045004],[-82.636379,36.065903],[-82.590663,36.032106],[-82.612604,35.993488],[-82.610889,35.967409],[-82.549682,35.964275],[-82.505384,35.97768],[-82.460597,36.007897],[-82.410158,36.082709],[-82.355657,36.115309],[-82.336056,36.115009],[-82.288955,36.13571],[-82.245053,36.131011],[-82.213852,36.159112],[-82.176849,36.142214],[-82.148748,36.149615],[-82.130246,36.104517],[-82.079743,36.10652],[-82.054142,36.126821],[-82.033141,36.120422],[-81.908136,36.302013],[-81.879641,36.313654],[-81.855095,36.337186],[-81.797544,36.358428],[-81.764927,36.338672],[-81.717405,36.347779],[-81.741524,36.412653],[-81.720842,36.422454],[-81.715023,36.454899],[-81.694948,36.467449],[-81.696823,36.506112],[-81.708067,36.535705],[-81.679115,36.569047],[-81.677393,36.588156],[-81.6469,36.611918],[-81.922644,36.616213],[-81.93414,36.594213],[-82.210792,36.595861],[-82.622603,36.593797],[-82.887946,36.593461],[-83.261099,36.593887],[-83.2763,36.598187],[-83.499909,36.597234],[-83.675395,36.600784],[-83.690789,36.582596],[-83.999943,36.589857],[-84.271545,36.591703],[-84.540474,36.596304],[-84.830035,36.604536],[-84.991632,36.617041],[-85.109237,36.622885],[-85.276867,36.626827],[-85.50151,36.614855],[-85.832432,36.6221],[-86.032922,36.630736],[-86.332126,36.648701],[-86.507781,36.652379],[-86.564255,36.633583],[-86.589906,36.652443],[-86.734208,36.648898],[-87.070491,36.64273],[-87.474687,36.640415],[-87.853319,36.63316],[-87.849544,36.663695],[-88.012203,36.67719],[-88.070541,36.678255],[-88.044862,36.60307],[-88.032675,36.536985],[-88.053293,36.497058],[-88.216645,36.499827],[-88.77157,36.502729],[-88.959746,36.502076],[-89.30102,36.507233],[-89.41727,36.499011],[-89.453514,36.461789],[-89.476643,36.458393],[-89.494119,36.476268],[-89.485427,36.497491],[-89.539232,36.497934],[-89.520642,36.478668],[-89.54234,36.420103],[-89.509722,36.373626],[-89.531822,36.339246],[-89.610689,36.340442],[-89.611819,36.309088],[-89.578492,36.288317],[-89.536441,36.272827],[-89.534676,36.252771],[-89.611145,36.239271],[-89.651407,36.250683],[-89.695235,36.252766],[-89.69263,36.224959],[-89.607,36.171179],[-89.59307,36.129699],[-89.680029,36.082494],[-89.687254,36.034048],[-89.706932,36.000981],[-89.733095,36.000608],[-89.71304,35.961644],[-89.656147,35.92581],[-89.644395,35.894782],[-89.66599,35.882868],[-89.714934,35.906247],[-89.742606,35.906653],[-89.772855,35.876244],[-89.761524,35.857239],[-89.709261,35.838911],[-89.704387,35.819607],[-89.74164,35.805633],[-89.758396,35.810469],[-89.796324,35.792807],[-89.820876,35.756868],[-89.877256,35.741369],[-89.906847,35.759699],[-89.950277,35.738494],[-89.955753,35.690621],[-89.929435,35.658974],[-89.886979,35.653637],[-89.857942,35.668312],[-89.856619,35.634444],[-89.896998,35.614882],[-89.947815,35.600093],[-89.957898,35.585474],[-89.945651,35.561558],[-89.910408,35.548761],[-89.918001,35.514136],[-89.945026,35.519388],[-89.957113,35.539818],[-89.989499,35.560136],[-90.03955,35.548557],[-90.050437,35.515894],[-90.043462,35.492339],[-90.019108,35.465067],[-90.031584,35.427662],[-90.069434,35.408163],[-90.068156,35.46678],[-90.08365,35.478297],[-90.118928,35.468328],[-90.129447,35.44193],[-90.168882,35.422206],[-90.179265,35.385194],[-90.143475,35.387602],[-90.142624,35.407669],[-90.114373,35.411108],[-90.074992,35.384152],[-90.107312,35.343143],[-90.109093,35.304987],[-90.163812,35.296115],[-90.168794,35.279088],[-90.152094,35.255989],[-90.09949,35.251393],[-90.07934,35.228838],[-90.097408,35.194794],[-90.117393,35.18789],[-90.066591,35.13599],[-90.10059,35.116691],[-90.139024,35.133995],[-90.173603,35.118073],[-90.209397,35.026546],[-90.291996,35.041793],[-90.309289,34.995694]]]},\"properties\":{\"name\":\"Tennessee\",\"ns_code\":\"01325873\",\"geoid\":\"47\",\"usps_abbrev\":\"TN\",\"fips_code\":\"47\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-84.820157,39.105489],[-84.820102,39.25141],[-84.815754,39.478652],[-84.814187,39.813687],[-84.811209,39.99545],[-84.804159,40.275046],[-84.802253,40.63035],[-84.803148,40.958992],[-84.8036,41.275272],[-84.805972,41.696118],[-84.80588,41.760171],[-84.980969,41.759491],[-85.308274,41.759947],[-85.750291,41.758969],[-86.108465,41.760697],[-86.443566,41.759553],[-86.87503,41.760335],[-87.207774,41.760956],[-87.523661,41.759907],[-87.526675,41.32691],[-87.525661,40.836132],[-87.526168,40.505761],[-87.53153,40.142775],[-87.533269,39.875036],[-87.532707,39.666203],[-87.531646,39.347888],[-87.589084,39.333831],[-87.60592,39.311076],[-87.604947,39.260212],[-87.580652,39.255237],[-87.574615,39.223931],[-87.585725,39.200396],[-87.619603,39.186032],[-87.658745,39.135997],[-87.61838,39.104284],[-87.572588,39.057286],[-87.573466,38.985441],[-87.529496,38.971925],[-87.513346,38.956],[-87.51912,38.922603],[-87.552028,38.860318],[-87.525893,38.848795],[-87.526816,38.817369],[-87.495905,38.785434],[-87.51961,38.697198],[-87.534435,38.681754],[-87.597247,38.665314],[-87.627345,38.607237],[-87.621401,38.586118],[-87.670071,38.545237],[-87.656144,38.521668],[-87.685921,38.491843],[-87.744639,38.477271],[-87.73082,38.449228],[-87.743772,38.413115],[-87.784994,38.368011],[-87.806075,38.363143],[-87.832723,38.324853],[-87.830953,38.299365],[-87.85113,38.27512],[-87.937329,38.292431],[-87.984838,38.228674],[-87.969865,38.191786],[-87.910924,38.162557],[-87.944825,38.127272],[-87.974781,38.110515],[-88.013118,38.103527],[-88.004697,38.083965],[-87.960677,38.100191],[-87.967226,38.067105],[-88.034132,38.032045],[-88.012574,37.977062],[-88.037013,37.956276],[-88.013355,37.894854],[-88.094956,37.893689],[-88.028016,37.799188],[-88.015487,37.801948],[-87.945991,37.773578],[-87.931794,37.7982],[-87.906939,37.807593],[-87.910017,37.843018],[-87.936301,37.867873],[-87.938548,37.890769],[-87.897144,37.927756],[-87.874,37.92272],[-87.832994,37.877143],[-87.794177,37.875356],[-87.759432,37.891861],[-87.711657,37.893872],[-87.684343,37.903754],[-87.666904,37.876795],[-87.681684,37.856273],[-87.673447,37.829873],[-87.615583,37.831848],[-87.588435,37.861685],[-87.592826,37.889688],[-87.628067,37.922892],[-87.601573,37.971304],[-87.577996,37.97057],[-87.556567,37.928606],[-87.50972,37.906028],[-87.463403,37.93494],[-87.428307,37.945047],[-87.377697,37.934391],[-87.344793,37.911099],[-87.318228,37.90497],[-87.219945,37.84889],[-87.164747,37.841353],[-87.14097,37.815112],[-87.128604,37.78577],[-87.091069,37.786964],[-87.06967,37.801513],[-87.043285,37.868552],[-87.040163,37.901215],[-86.9766,37.930965],[-86.919834,37.93639],[-86.851933,37.989029],[-86.815746,37.998957],[-86.79551,37.989453],[-86.752705,37.915372],[-86.718671,37.893057],[-86.680397,37.915077],[-86.645801,37.907208],[-86.660653,37.864247],[-86.648393,37.841453],[-86.604699,37.858127],[-86.600173,37.9044],[-86.58848,37.921346],[-86.541991,37.917034],[-86.507089,37.929917],[-86.525095,37.968081],[-86.52503,38.028902],[-86.513233,38.044054],[-86.456793,38.049031],[-86.433923,38.065705],[-86.432672,38.08527],[-86.465503,38.104306],[-86.443207,38.127762],[-86.403487,38.104563],[-86.375953,38.130539],[-86.332099,38.129929],[-86.325874,38.154029],[-86.366055,38.161531],[-86.37741,38.188574],[-86.347736,38.195363],[-86.29574,38.164069],[-86.272847,38.140835],[-86.2791,38.100754],[-86.267769,38.057171],[-86.221352,38.028669],[-86.17893,38.01081],[-86.1216,38.016052],[-86.079389,38.000625],[-86.049012,37.959861],[-86.021667,37.995793],[-85.943132,38.006622],[-85.923428,38.025247],[-85.90499,38.089818],[-85.909289,38.142235],[-85.904063,38.173427],[-85.884282,38.19957],[-85.851694,38.222445],[-85.835456,38.266666],[-85.821706,38.281119],[-85.780743,38.288352],[-85.743213,38.267283],[-85.685208,38.294692],[-85.655355,38.323849],[-85.62037,38.423462],[-85.586033,38.450092],[-85.52479,38.457636],[-85.49851,38.468495],[-85.475124,38.503689],[-85.42339,38.531845],[-85.415977,38.563654],[-85.439729,38.611493],[-85.438586,38.658102],[-85.455042,38.681263],[-85.452087,38.709785],[-85.407994,38.737136],[-85.372265,38.73045],[-85.305858,38.741536],[-85.258946,38.73777],[-85.221119,38.701058],[-85.173048,38.688022],[-85.13199,38.702916],[-85.073036,38.74104],[-84.991927,38.778537],[-84.933516,38.777501],[-84.887285,38.794776],[-84.812534,38.786729],[-84.825963,38.83663],[-84.790436,38.861246],[-84.786613,38.882416],[-84.81477,38.895511],[-84.869014,38.899667],[-84.877535,38.920689],[-84.835155,38.957929],[-84.838579,38.989902],[-84.897098,39.057137],[-84.820157,39.105489]]]},\"properties\":{\"name\":\"Indiana\",\"ns_code\":\"00448508\",\"geoid\":\"18\",\"usps_abbrev\":\"IN\",\"fips_code\":\"18\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-95.765645,40.585208],[-95.488514,40.581735],[-94.99166,40.575692],[-94.594196,40.57096],[-94.282994,40.571469],[-94.07214,40.573026],[-93.547578,40.580407],[-93.260429,40.580814],[-93.0135,40.586297],[-92.659409,40.590341],[-92.485216,40.595085],[-92.096611,40.601831],[-91.8684,40.608056],[-91.729115,40.61364],[-91.68882,40.583409],[-91.681714,40.553035],[-91.621902,40.542292],[-91.616948,40.504794],[-91.594644,40.494997],[-91.567743,40.46229],[-91.526155,40.458625],[-91.524612,40.410765],[-91.483153,40.382492],[-91.441243,40.386255],[-91.419422,40.378264],[-91.375712,40.391925],[-91.381045,40.44849],[-91.364915,40.484168],[-91.369059,40.512532],[-91.406202,40.542698],[-91.359873,40.601805],[-91.310048,40.625041],[-91.247851,40.63839],[-91.185295,40.637803],[-91.12082,40.672777],[-91.115735,40.725168],[-91.092476,40.776838],[-91.096946,40.811403],[-91.044653,40.868356],[-90.9985,40.90812],[-90.962916,40.924957],[-90.951967,40.958238],[-90.958142,40.979767],[-90.942253,41.034702],[-90.94626,41.09473],[-90.989662,41.155707],[-91.041875,41.166484],[-91.065899,41.199517],[-91.112348,41.239002],[-91.11419,41.250029],[-91.073553,41.310481],[-91.065058,41.369101],[-91.048259,41.409748],[-91.02779,41.423603],[-90.975168,41.433985],[-90.930691,41.421368],[-90.84746,41.455019],[-90.73754,41.450127],[-90.655839,41.462132],[-90.605937,41.494232],[-90.595237,41.511032],[-90.540935,41.526133],[-90.500633,41.518033],[-90.461432,41.523533],[-90.412825,41.565329],[-90.343228,41.587833],[-90.343452,41.646959],[-90.334525,41.679559],[-90.313052,41.699631],[-90.315549,41.734426],[-90.278633,41.767358],[-90.187969,41.803163],[-90.170041,41.876439],[-90.153584,41.906614],[-90.162141,41.961293],[-90.140061,42.003252],[-90.164485,42.042105],[-90.162895,42.116718],[-90.201404,42.130937],[-90.209479,42.15268],[-90.282173,42.178846],[-90.328273,42.201047],[-90.356964,42.205445],[-90.391108,42.225473],[-90.419326,42.254467],[-90.431039,42.282452],[-90.415937,42.322699],[-90.477941,42.384151],[-90.555018,42.416138],[-90.570736,42.441701],[-90.624328,42.458904],[-90.654027,42.478503],[-90.640927,42.508302],[-90.645627,42.5441],[-90.686975,42.591774],[-90.702671,42.630756],[-90.720209,42.640758],[-90.952415,42.686778],[-91.051275,42.737001],[-91.065606,42.756766],[-91.10056,42.883078],[-91.143491,42.904698],[-91.14655,42.963345],[-91.174692,43.038713],[-91.179382,43.068624],[-91.175253,43.134665],[-91.1562,43.142945],[-91.119115,43.200366],[-91.079278,43.228259],[-91.05791,43.253968],[-91.085652,43.29187],[-91.129121,43.32635],[-91.201847,43.349103],[-91.21477,43.365874],[-91.19767,43.395334],[-91.203144,43.419805],[-91.232241,43.460018],[-91.217706,43.50055],[-91.632178,43.500441],[-92.029845,43.500621],[-92.464473,43.50029],[-92.830258,43.499555],[-93.298874,43.499459],[-93.919733,43.499766],[-94.274829,43.500141],[-94.914574,43.500872],[-95.433839,43.500113],[-95.97247,43.500063],[-96.45326,43.50039],[-96.599191,43.500456],[-96.582402,43.466739],[-96.593339,43.433151],[-96.52172,43.386755],[-96.535022,43.336674],[-96.524522,43.312067],[-96.586525,43.273715],[-96.549534,43.248372],[-96.555281,43.226666],[-96.51754,43.217825],[-96.476729,43.221987],[-96.469556,43.158353],[-96.436567,43.119818],[-96.454063,43.097331],[-96.460742,43.064337],[-96.510851,43.04977],[-96.51395,43.030075],[-96.491981,43.010197],[-96.52082,42.979923],[-96.499283,42.958745],[-96.533761,42.928492],[-96.528536,42.897666],[-96.546561,42.874524],[-96.544831,42.850193],[-96.57783,42.829067],[-96.60356,42.783997],[-96.619849,42.783792],[-96.635561,42.740847],[-96.605596,42.70202],[-96.516084,42.630275],[-96.496026,42.580382],[-96.47696,42.555972],[-96.488163,42.511284],[-96.47719,42.491011],[-96.445483,42.49063],[-96.401962,42.48644],[-96.380705,42.446393],[-96.41031,42.412965],[-96.407998,42.337408],[-96.369969,42.310878],[-96.367977,42.290681],[-96.331312,42.259408],[-96.323723,42.229887],[-96.35987,42.210553],[-96.346688,42.164686],[-96.309758,42.132101],[-96.267486,42.10978],[-96.279201,42.073544],[-96.268063,42.041705],[-96.234356,42.040712],[-96.222823,42.023817],[-96.184711,42.004972],[-96.191971,41.985347],[-96.129186,41.965136],[-96.142265,41.915379],[-96.162067,41.903047],[-96.136525,41.864151],[-96.111252,41.850229],[-96.109746,41.823992],[-96.064897,41.791673],[-96.105194,41.731195],[-96.075865,41.714785],[-96.121048,41.677002],[-96.095131,41.647767],[-96.115995,41.622024],[-96.11364,41.601955],[-96.081188,41.574296],[-96.096617,41.545626],[-96.057331,41.511051],[-96.03429,41.513036],[-96.026657,41.540366],[-95.999966,41.53948],[-95.992777,41.514596],[-96.01738,41.492139],[-96.004047,41.472146],[-95.93634,41.465304],[-95.919989,41.452604],[-95.937694,41.39477],[-95.928774,41.37008],[-95.95713,41.349593],[-95.925185,41.321355],[-95.885554,41.318097],[-95.87748,41.284583],[-95.928646,41.281332],[-95.911296,41.239442],[-95.926117,41.211619],[-95.915428,41.185145],[-95.867624,41.188416],[-95.882944,41.154572],[-95.862427,41.089687],[-95.882225,41.063539],[-95.860862,41.038547],[-95.863492,40.99734],[-95.832055,40.98114],[-95.836438,40.921642],[-95.812083,40.884239],[-95.847785,40.864328],[-95.837186,40.835347],[-95.843745,40.803783],[-95.836903,40.776477],[-95.881529,40.750611],[-95.883178,40.717579],[-95.852615,40.702262],[-95.842801,40.677496],[-95.789485,40.659388],[-95.769759,40.622884],[-95.751271,40.609057],[-95.765645,40.585208]]]},\"properties\":{\"name\":\"Iowa\",\"ns_code\":\"01779785\",\"geoid\":\"19\",\"usps_abbrev\":\"IA\",\"fips_code\":\"19\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-109.050044,31.332502],[-109.049186,31.797381],[-109.048142,32.160736],[-109.047612,32.426377],[-109.047117,32.777569],[-109.047483,33.067912],[-109.046564,33.375059],[-109.047145,33.74001],[-109.046765,34.17382],[-109.046175,34.520102],[-109.045866,34.949127],[-109.046084,35.249986],[-109.046523,35.523433],[-109.046348,35.75724],[-109.045726,36.116908],[-109.045946,36.375002],[-109.045554,36.644972],[-109.045223,36.999084],[-109.495338,36.999105],[-109.750669,36.99816],[-110.023043,36.998601],[-110.47019,36.997997],[-110.509004,37.003985],[-110.75723,37.003273],[-111.278221,37.000467],[-111.751401,37.001234],[-112.000735,37.000959],[-112.302044,37.001053],[-112.750761,37.000483],[-113.052912,36.999983],[-113.572079,36.999981],[-114.0506,37.000396],[-114.049935,36.709521],[-114.043135,36.380478],[-114.045559,36.288837],[-114.043944,36.19335],[-114.066798,36.179087],[-114.1534,36.02317],[-114.174683,36.02667],[-114.211932,36.014834],[-114.243865,36.015266],[-114.270862,36.045523],[-114.314427,36.060523],[-114.304171,36.07558],[-114.323458,36.101186],[-114.370181,36.142624],[-114.412491,36.146511],[-114.443736,36.125593],[-114.456487,36.138032],[-114.504715,36.127188],[-114.508104,36.149713],[-114.57109,36.151099],[-114.620605,36.131759],[-114.627079,36.140761],[-114.681847,36.109192],[-114.734085,36.104672],[-114.754508,36.086171],[-114.723324,36.026588],[-114.740866,36.012928],[-114.740536,35.975545],[-114.707603,35.92795],[-114.708112,35.909933],[-114.663214,35.873692],[-114.706076,35.845873],[-114.695757,35.833387],[-114.712026,35.805529],[-114.694267,35.756633],[-114.705447,35.711757],[-114.682657,35.688571],[-114.689001,35.65028],[-114.653927,35.611739],[-114.670191,35.583471],[-114.65677,35.534964],[-114.678892,35.501276],[-114.663541,35.447797],[-114.627325,35.410193],[-114.595163,35.321883],[-114.569529,35.162317],[-114.578263,35.12881],[-114.626316,35.120423],[-114.645152,35.104995],[-114.604182,35.073715],[-114.63819,35.022069],[-114.633487,35.001857],[-114.629392,34.983171],[-114.628276,34.863596],[-114.592339,34.841153],[-114.552682,34.766871],[-114.516619,34.736745],[-114.47162,34.712966],[-114.442363,34.644153],[-114.435671,34.593841],[-114.405228,34.569637],[-114.380838,34.529724],[-114.386699,34.457911],[-114.371217,34.446992],[-114.339627,34.451435],[-114.301016,34.426807],[-114.291154,34.409784],[-114.262909,34.400373],[-114.226107,34.365916],[-114.176909,34.349306],[-114.138282,34.30323],[-114.13545,34.257886],[-114.164476,34.251667],[-114.254141,34.17383],[-114.286724,34.170715],[-114.324576,34.136759],[-114.366517,34.118577],[-114.415908,34.107636],[-114.434181,34.087379],[-114.438266,34.022609],[-114.46012,33.993888],[-114.499883,33.961789],[-114.522002,33.955623],[-114.534146,33.925187],[-114.503887,33.865754],[-114.529597,33.848063],[-114.52805,33.814963],[-114.507089,33.76793],[-114.512348,33.734214],[-114.496489,33.696901],[-114.523959,33.685879],[-114.533215,33.648443],[-114.522071,33.611277],[-114.540617,33.591412],[-114.524215,33.553068],[-114.558023,33.532589],[-114.560963,33.516739],[-114.589246,33.501813],[-114.622918,33.456561],[-114.62964,33.428137],[-114.720065,33.407891],[-114.699056,33.361148],[-114.700938,33.337014],[-114.731223,33.302433],[-114.723259,33.288079],[-114.677032,33.270169],[-114.689421,33.24525],[-114.674479,33.225504],[-114.67935,33.162433],[-114.707896,33.097431],[-114.68902,33.084035],[-114.670803,33.037983],[-114.639552,33.04529],[-114.618788,33.027202],[-114.578287,33.035375],[-114.52013,33.029984],[-114.506362,33.017403],[-114.490129,32.969884],[-114.467624,32.956663],[-114.480925,32.936276],[-114.462929,32.907944],[-114.468971,32.845155],[-114.510327,32.816488],[-114.531669,32.791185],[-114.526856,32.757094],[-114.564447,32.749554],[-114.581784,32.734946],[-114.615976,32.728489],[-114.689618,32.738171],[-114.719633,32.718763],[-114.751079,32.659789],[-114.781896,32.624702],[-114.809555,32.616203],[-114.799683,32.593621],[-114.813644,32.567515],[-114.802559,32.535521],[-114.813613,32.494276],[-114.383941,32.36449],[-114.125773,32.285325],[-113.750756,32.169005],[-113.461183,32.078832],[-113.125961,31.97278],[-112.871505,31.896838],[-112.503812,31.783912],[-112.200717,31.690033],[-111.659381,31.519336],[-111.285239,31.400178],[-111.074825,31.332239],[-110.750638,31.333636],[-110.460172,31.332827],[-109.875628,31.33405],[-109.500621,31.333911],[-109.050044,31.332502]]]},\"properties\":{\"name\":\"Arizona\",\"ns_code\":\"01779777\",\"geoid\":\"04\",\"usps_abbrev\":\"AZ\",\"fips_code\":\"04\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-96.563672,45.935245],[-96.574517,45.844044],[-96.583085,45.820024],[-96.639222,45.772096],[-96.672665,45.732336],[-96.75035,45.698782],[-96.759312,45.688744],[-96.838648,45.647509],[-96.856834,45.605378],[-96.763432,45.520007],[-96.7314,45.45702],[-96.680454,45.410499],[-96.619752,45.408884],[-96.489065,45.357071],[-96.453097,45.300751],[-96.45197,44.962516],[-96.451234,44.718273],[-96.452275,44.302118],[-96.453404,44.025431],[-96.452858,43.822701],[-96.45326,43.50039],[-95.97247,43.500063],[-95.433839,43.500113],[-94.914574,43.500872],[-94.274829,43.500141],[-93.919733,43.499766],[-93.298874,43.499459],[-92.830258,43.499555],[-92.464473,43.50029],[-92.029845,43.500621],[-91.632178,43.500441],[-91.217706,43.50055],[-91.24382,43.54913],[-91.236666,43.587656],[-91.268748,43.615348],[-91.26845,43.709824],[-91.244135,43.774667],[-91.262433,43.79217],[-91.284138,43.847065],[-91.35674,43.916564],[-91.36472,43.93488],[-91.406011,43.963929],[-91.440541,44.001518],[-91.507121,44.01898],[-91.5826,44.027381],[-91.646446,44.063337],[-91.710597,44.12048],[-91.817302,44.164235],[-91.875158,44.200575],[-91.892963,44.235149],[-91.887189,44.252513],[-91.928224,44.335473],[-91.959996,44.359797],[-92.038147,44.388731],[-92.053549,44.401375],[-92.123679,44.42184],[-92.221083,44.440386],[-92.282364,44.477707],[-92.298484,44.494621],[-92.317357,44.542512],[-92.347567,44.557149],[-92.432527,44.565705],[-92.539909,44.567456],[-92.568946,44.603372],[-92.622537,44.616054],[-92.621847,44.639018],[-92.664715,44.663379],[-92.732043,44.714345],[-92.808415,44.751604],[-92.765668,44.84101],[-92.773946,44.889997],[-92.750645,44.937299],[-92.769445,44.97215],[-92.761953,45.0221],[-92.802911,45.065403],[-92.744938,45.108309],[-92.766808,45.185466],[-92.751708,45.218666],[-92.762004,45.288213],[-92.737121,45.300459],[-92.698967,45.336374],[-92.704054,45.35366],[-92.650284,45.398769],[-92.646602,45.441635],[-92.680234,45.464344],[-92.726677,45.514462],[-92.726082,45.541112],[-92.773412,45.568235],[-92.823309,45.560934],[-92.883749,45.575483],[-92.888114,45.628377],[-92.869193,45.717568],[-92.809837,45.744172],[-92.784617,45.764199],[-92.757947,45.811216],[-92.765681,45.827252],[-92.712503,45.891705],[-92.637841,45.934469],[-92.551186,45.95224],[-92.525186,45.983862],[-92.472761,45.972952],[-92.442259,46.016177],[-92.410014,46.027251],[-92.350407,46.016368],[-92.335335,46.059422],[-92.294033,46.074377],[-92.292683,46.441283],[-92.292192,46.666042],[-92.265993,46.651041],[-92.212392,46.649941],[-92.176259,46.690473],[-92.189091,46.717541],[-92.146292,46.716032],[-92.13789,46.73954],[-92.089493,46.749237],[-92.020294,46.704041],[-91.981341,46.723091],[-91.875186,46.763303],[-91.59214,46.874935],[-91.500181,46.91332],[-91.319638,46.999929],[-91.000198,47.149758],[-90.654661,47.309822],[-90.264218,47.30027],[-89.957102,47.291103],[-89.820034,47.49991],[-89.625167,47.799549],[-89.483385,48.013716],[-89.581485,47.995925],[-89.617867,48.010947],[-89.715906,48.009246],[-89.734025,48.021871],[-89.819802,48.015099],[-89.871245,47.985945],[-89.89908,47.988057],[-89.99305,48.028404],[-89.993822,48.049027],[-90.023595,48.084708],[-90.132645,48.111768],[-90.224692,48.108148],[-90.28934,48.098993],[-90.305634,48.105117],[-90.375817,48.091214],[-90.403219,48.105114],[-90.438449,48.098747],[-90.556838,48.096008],[-90.579897,48.123922],[-90.641595,48.103515],[-90.74152,48.094583],[-90.761629,48.09828],[-90.797088,48.13926],[-90.804207,48.177834],[-90.832589,48.173765],[-90.839176,48.239511],[-90.88548,48.245784],[-90.97694,48.21943],[-91.031588,48.188456],[-91.082734,48.180764],[-91.250112,48.084087],[-91.29021,48.073947],[-91.370875,48.069417],[-91.429642,48.048608],[-91.450331,48.068805],[-91.48865,48.068073],[-91.575658,48.048792],[-91.569735,48.093345],[-91.588718,48.102217],[-91.640173,48.096931],[-91.653563,48.109568],[-91.711844,48.114605],[-91.698162,48.141645],[-91.715233,48.199297],[-91.781174,48.200433],[-91.816209,48.21175],[-91.864382,48.207031],[-91.89347,48.237699],[-91.945155,48.230442],[-91.954466,48.251838],[-92.006574,48.265423],[-92.012973,48.297392],[-92.000126,48.321354],[-92.083489,48.353868],[-92.161983,48.363306],[-92.206803,48.345596],[-92.262275,48.354931],[-92.288994,48.34299],[-92.301451,48.28861],[-92.28073,48.244275],[-92.314665,48.240527],[-92.370103,48.220299],[-92.415111,48.293845],[-92.469949,48.351837],[-92.47675,48.37176],[-92.45639,48.401134],[-92.507285,48.447876],[-92.656027,48.436709],[-92.712562,48.463013],[-92.698814,48.494885],[-92.636685,48.499434],[-92.634931,48.542873],[-92.728046,48.539289],[-92.929609,48.606884],[-92.950119,48.630419],[-92.984963,48.623731],[-93.089596,48.627705],[-93.17847,48.623279],[-93.207426,48.642419],[-93.25487,48.642746],[-93.347527,48.626619],[-93.371152,48.605069],[-93.404225,48.609337],[-93.465982,48.587287],[-93.467502,48.545661],[-93.547184,48.528674],[-93.632324,48.53009],[-93.645392,48.517274],[-93.794455,48.515986],[-93.81827,48.530028],[-93.805316,48.570298],[-93.821173,48.60629],[-93.844013,48.629384],[-93.926975,48.631203],[-94.006921,48.643124],[-94.157378,48.645712],[-94.244398,48.653421],[-94.264503,48.698861],[-94.308585,48.710176],[-94.342767,48.703247],[-94.416183,48.710917],[-94.438701,48.694878],[-94.538589,48.702641],[-94.592099,48.71912],[-94.694347,48.782184],[-94.70438,48.824263],[-94.684347,48.883958],[-94.718941,48.999991],[-94.750218,48.999992],[-94.750278,49.099273],[-94.773233,49.120732],[-94.798083,49.197871],[-94.79746,49.214353],[-94.825555,49.294389],[-94.816501,49.320997],[-94.854283,49.324108],[-94.957439,49.370066],[-95.058375,49.35313],[-95.115861,49.366505],[-95.153314,49.384358],[-95.153711,48.998903],[-95.368698,48.998729],[-95.922542,48.999734],[-96.625311,48.999982],[-96.929983,48.999984],[-97.228722,49.000562],[-97.234128,48.947898],[-97.199891,48.88259],[-97.17302,48.851568],[-97.183988,48.834085],[-97.150559,48.782562],[-97.136403,48.725733],[-97.105442,48.689648],[-97.09991,48.658522],[-97.12424,48.633973],[-97.164405,48.557047],[-97.152765,48.530285],[-97.14208,48.463347],[-97.149236,48.443358],[-97.131479,48.406586],[-97.15721,48.385732],[-97.155481,48.363768],[-97.126745,48.34214],[-97.13125,48.319543],[-97.11657,48.279661],[-97.135835,48.236919],[-97.13165,48.20711],[-97.149755,48.185824],[-97.146531,48.144055],[-97.098829,48.071477],[-97.071288,48.045797],[-97.0556,47.949085],[-97.018107,47.920228],[-97.02544,47.885935],[-97.005447,47.87024],[-96.899237,47.689579],[-96.888092,47.639648],[-96.849908,47.590754],[-96.86175,47.541398],[-96.849853,47.497311],[-96.855814,47.439723],[-96.865852,47.418975],[-96.837974,47.38273],[-96.852871,47.375009],[-96.830618,47.338014],[-96.843857,47.292939],[-96.829126,47.234723],[-96.839359,47.196357],[-96.822062,47.185175],[-96.83472,47.15639],[-96.81619,47.108481],[-96.826147,47.076497],[-96.817737,47.051118],[-96.834839,47.007254],[-96.791758,46.928465],[-96.759599,46.915708],[-96.776713,46.895619],[-96.775059,46.840363],[-96.802544,46.811521],[-96.787136,46.785651],[-96.7811,46.722048],[-96.79951,46.675245],[-96.796041,46.631209],[-96.745697,46.571027],[-96.750448,46.544048],[-96.735054,46.48057],[-96.713672,46.465012],[-96.7162,46.436679],[-96.693927,46.422081],[-96.667422,46.376117],[-96.59966,46.330481],[-96.597792,46.22888],[-96.584378,46.203539],[-96.588387,46.179084],[-96.554436,46.084186],[-96.577875,46.027034],[-96.563672,45.935245]]]},\"properties\":{\"name\":\"Minnesota\",\"ns_code\":\"00662849\",\"geoid\":\"27\",\"usps_abbrev\":\"MN\",\"fips_code\":\"27\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-93.814351,29.596576],[-93.779317,29.611689],[-93.766521,29.634305],[-93.770147,29.670825],[-93.686728,29.694812],[-93.487754,29.717811],[-93.387246,29.713771],[-93.349444,29.695406],[-93.303627,29.706102],[-93.287354,29.724331],[-93.223168,29.726396],[-93.12515,29.706209],[-92.977,29.659582],[-92.806757,29.586386],[-92.750712,29.564883],[-92.632538,29.529718],[-92.55982,29.518179],[-92.40502,29.500289],[-92.29367,29.481496],[-92.223462,29.494713],[-92.146666,29.532037],[-92.07805,29.531489],[-92.045616,29.505806],[-92.02985,29.475961],[-91.98387,29.460713],[-91.964831,29.439392],[-91.913207,29.420207],[-91.897652,29.375219],[-91.875032,29.361992],[-91.840934,29.36019],[-91.77749,29.396084],[-91.723408,29.363418],[-91.699993,29.360089],[-91.644044,29.375555],[-91.56832,29.348068],[-91.52974,29.344455],[-91.421128,29.302828],[-91.318698,29.208357],[-91.292681,29.196177],[-91.207845,29.171159],[-91.149359,29.165084],[-91.118387,29.14277],[-91.006054,29.119994],[-91.000899,29.100481],[-91.02303,29.067398],[-91.008759,29.026902],[-90.970287,29.005027],[-90.90708,28.991132],[-90.822648,28.985911],[-90.720765,28.99263],[-90.552547,29.036806],[-90.479847,29.008612],[-90.40938,28.99534],[-90.344319,29.003178],[-90.239062,29.033104],[-90.200696,29.036184],[-90.151252,29.056345],[-90.092594,29.09199],[-90.032325,29.135044],[-89.610427,29.198635],[-89.553649,29.185976],[-89.502319,29.158376],[-89.468673,29.107461],[-89.46342,29.08282],[-89.465736,28.974437],[-89.491844,28.919406],[-89.48764,28.887371],[-89.456527,28.860609],[-89.425697,28.855127],[-89.384727,28.871503],[-89.279604,28.977783],[-89.234903,28.987152],[-89.209579,28.970199],[-89.196536,28.939964],[-89.169067,28.922574],[-89.13328,28.919728],[-89.096055,28.942493],[-89.088826,28.961147],[-89.045638,29.006942],[-89.012688,29.02768],[-88.996902,29.085304],[-88.927809,29.117275],[-88.91324,29.154574],[-88.909151,29.200161],[-88.91831,29.236276],[-88.942054,29.253742],[-89.022615,29.274463],[-89.069426,29.294297],[-89.094528,29.328932],[-89.112159,29.339672],[-89.137612,29.381429],[-89.142073,29.429465],[-89.119451,29.458065],[-89.072295,29.470838],[-89.038874,29.492229],[-88.960204,29.577104],[-88.912657,29.61178],[-88.869937,29.657099],[-88.82343,29.714992],[-88.801817,29.750197],[-88.767255,29.840771],[-88.758388,29.918271],[-88.764495,29.971944],[-88.778234,30.014066],[-88.811647,30.06749],[-88.839407,30.091158],[-88.875025,30.102495],[-88.90033,30.16998],[-88.94161,30.159419],[-89.000032,30.1611],[-89.029181,30.177874],[-89.074653,30.154294],[-89.183669,30.212155],[-89.25004,30.185519],[-89.288394,30.17847],[-89.416711,30.177639],[-89.504735,30.157923],[-89.538635,30.19589],[-89.571907,30.180721],[-89.616828,30.225772],[-89.631614,30.257013],[-89.630622,30.337491],[-89.657287,30.357183],[-89.683708,30.405586],[-89.683631,30.451787],[-89.721405,30.488883],[-89.752807,30.502962],[-89.779159,30.544029],[-89.78968,30.544656],[-89.818771,30.59288],[-89.820068,30.632808],[-89.841521,30.686568],[-89.842525,30.719942],[-89.822969,30.740075],[-89.831537,30.76761],[-89.804632,30.802511],[-89.770269,30.89939],[-89.750073,30.91293],[-89.75595,30.944119],[-89.735686,30.966573],[-89.728147,31.002431],[-90.210877,31.000654],[-90.7335,30.999218],[-91.122154,30.998908],[-91.636942,30.999416],[-91.590463,31.01727],[-91.564397,31.038965],[-91.56415,31.06683],[-91.625199,31.115963],[-91.624217,31.133729],[-91.589046,31.178586],[-91.595029,31.201969],[-91.644356,31.234414],[-91.641253,31.266917],[-91.558684,31.262176],[-91.518578,31.275283],[-91.510049,31.316822],[-91.548213,31.346084],[-91.54638,31.382763],[-91.568953,31.377629],[-91.578334,31.399067],[-91.548465,31.432668],[-91.532336,31.390275],[-91.504756,31.36498],[-91.472542,31.371069],[-91.472067,31.397076],[-91.499812,31.4201],[-91.513366,31.444396],[-91.515919,31.530729],[-91.475917,31.531266],[-91.443916,31.542466],[-91.407915,31.569366],[-91.422716,31.597065],[-91.486518,31.586566],[-91.517233,31.61346],[-91.497665,31.645371],[-91.463817,31.620365],[-91.423016,31.611265],[-91.400815,31.620465],[-91.395715,31.644165],[-91.397115,31.711364],[-91.369375,31.746903],[-91.320913,31.750064],[-91.279789,31.744751],[-91.28742,31.771964],[-91.348114,31.758163],[-91.363514,31.783363],[-91.345214,31.843861],[-91.294713,31.86046],[-91.283212,31.815762],[-91.262011,31.809362],[-91.245624,31.833165],[-91.265812,31.86486],[-91.233903,31.877607],[-91.20101,31.909159],[-91.18811,31.961358],[-91.16441,31.982557],[-91.106908,31.989357],[-91.075908,32.016827],[-91.087408,32.034056],[-91.141109,32.045654],[-91.16201,32.061855],[-91.139309,32.081753],[-91.098708,32.048355],[-91.080008,32.079154],[-91.038607,32.098253],[-91.030891,32.120558],[-91.005006,32.142851],[-91.002906,32.162052],[-91.049707,32.157352],[-91.052754,32.125777],[-91.08163,32.133992],[-91.11099,32.124644],[-91.162822,32.132694],[-91.174577,32.157311],[-91.16488,32.195743],[-91.133587,32.213431],[-91.11727,32.206668],[-91.083708,32.22645],[-91.061408,32.21865],[-91.034302,32.241548],[-90.983381,32.211767],[-90.969515,32.252053],[-90.984077,32.279953],[-90.947834,32.283486],[-90.916157,32.303582],[-90.912363,32.339454],[-90.986672,32.35176],[-91.004706,32.367244],[-90.99408,32.403861],[-90.965987,32.420654],[-90.993863,32.450851],[-91.027406,32.433942],[-91.093508,32.457441],[-91.115708,32.48234],[-91.116708,32.500138],[-91.093742,32.549127],[-91.049307,32.498539],[-91.000106,32.48229],[-90.987669,32.498481],[-91.018469,32.521019],[-91.057706,32.533777],[-91.080411,32.556468],[-91.030991,32.583347],[-91.003545,32.612271],[-91.025769,32.646572],[-91.049796,32.607188],[-91.121157,32.584806],[-91.142782,32.598899],[-91.152699,32.640757],[-91.126201,32.667135],[-91.063946,32.702925],[-91.060766,32.727494],[-91.125107,32.743195],[-91.1495,32.741931],[-91.165814,32.757841],[-91.16167,32.812465],[-91.143559,32.844738],[-91.105631,32.858396],[-91.070602,32.888658],[-91.064804,32.926464],[-91.080431,32.943206],[-91.086802,32.976266],[-91.106581,32.988937],[-91.135095,32.980108],[-91.131304,32.926918],[-91.163877,32.899476],[-91.196785,32.906784],[-91.212837,32.922103],[-91.201842,32.961212],[-91.166073,33.004106],[-91.587251,33.00631],[-92.041143,33.007784],[-92.219778,33.009166],[-92.55941,33.012734],[-93.000211,33.017625],[-93.49052,33.018442],[-94.042964,33.019219],[-94.043212,32.776803],[-94.042946,32.340572],[-94.04272,31.999263],[-94.006795,31.97343],[-93.970826,31.919968],[-93.90308,31.893922],[-93.901883,31.875168],[-93.872988,31.836696],[-93.874836,31.822314],[-93.839343,31.80033],[-93.822598,31.773559],[-93.836884,31.750171],[-93.794548,31.702076],[-93.823169,31.672893],[-93.8177,31.621638],[-93.838057,31.606795],[-93.820764,31.558221],[-93.785028,31.525962],[-93.742586,31.526229],[-93.719708,31.50584],[-93.749476,31.46869],[-93.692631,31.437192],[-93.704879,31.410881],[-93.674117,31.397681],[-93.667273,31.365413],[-93.67384,31.323855],[-93.687511,31.310813],[-93.659285,31.282106],[-93.613942,31.259375],[-93.618255,31.233105],[-93.589119,31.165889],[-93.54893,31.186601],[-93.539366,31.115207],[-93.563269,31.093592],[-93.52717,31.073189],[-93.524739,31.039007],[-93.539184,31.008212],[-93.56543,30.990579],[-93.531234,30.924388],[-93.558502,30.867905],[-93.553626,30.83514],[-93.592101,30.763675],[-93.61478,30.756064],[-93.608875,30.719117],[-93.631345,30.677872],[-93.6831,30.640763],[-93.678662,30.594097],[-93.713782,30.587584],[-93.738788,30.540975],[-93.706507,30.50991],[-93.716602,30.494424],[-93.698135,30.43888],[-93.757409,30.390813],[-93.764252,30.330229],[-93.729046,30.296705],[-93.704584,30.289949],[-93.703544,30.259321],[-93.720931,30.208461],[-93.692849,30.135213],[-93.724101,30.09301],[-93.720805,30.053043],[-93.739734,30.023987],[-93.790087,29.986564],[-93.806551,29.957445],[-93.830374,29.894358],[-93.859896,29.859921],[-93.922744,29.818808],[-93.92881,29.79708],[-93.89847,29.771577],[-93.888821,29.742234],[-93.87002,29.735482],[-93.837412,29.689885],[-93.814351,29.596576]]]},\"properties\":{\"name\":\"Louisiana\",\"ns_code\":\"01629543\",\"geoid\":\"22\",\"usps_abbrev\":\"LA\",\"fips_code\":\"22\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-77.039006,38.791645],[-76.909393,38.892852],[-77.041018,38.995548],[-77.119759,38.934343],[-77.093216,38.905767],[-77.067742,38.899535],[-77.032143,38.850287],[-77.039006,38.791645]]]},\"properties\":{\"name\":\"District of Columbia\",\"ns_code\":\"01702382\",\"geoid\":\"11\",\"usps_abbrev\":\"DC\",\"fips_code\":\"11\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-75.166435,38.027834],[-75.249609,38.026715],[-75.624449,37.994195],[-75.648228,37.966771],[-75.647606,37.947025],[-75.691321,37.955496],[-75.761642,37.941366],[-75.80124,37.912174],[-75.952672,37.906827],[-75.943693,37.946133],[-75.993338,37.953487],[-76.052021,37.953578],[-76.233415,37.887638],[-76.257527,37.905738],[-76.338617,37.945286],[-76.412934,37.966332],[-76.465008,38.01322],[-76.516609,38.026783],[-76.535402,38.073626],[-76.596128,38.106892],[-76.610384,38.148516],[-76.700899,38.161359],[-76.800199,38.16888],[-76.839368,38.163609],[-76.872716,38.171686],[-76.915867,38.199603],[-76.94902,38.208418],[-76.962086,38.256964],[-77.00115,38.280174],[-77.030683,38.311622],[-77.014796,38.332367],[-77.018498,38.381941],[-77.05342,38.398813],[-77.086678,38.367928],[-77.137734,38.368023],[-77.163219,38.345823],[-77.235208,38.332536],[-77.283796,38.342855],[-77.304938,38.375271],[-77.3204,38.435221],[-77.318542,38.474437],[-77.255703,38.563318],[-77.246362,38.593441],[-77.203696,38.618172],[-77.129682,38.634704],[-77.136293,38.647231],[-77.122632,38.685256],[-77.081614,38.707926],[-77.043338,38.718468],[-77.039006,38.791645],[-77.032143,38.850287],[-77.067742,38.899535],[-77.093216,38.905767],[-77.119759,38.934343],[-77.145081,38.963146],[-77.197774,38.96674],[-77.244911,38.982614],[-77.255759,39.001991],[-77.245093,39.022146],[-77.340144,39.062909],[-77.386818,39.062241],[-77.460602,39.074843],[-77.481015,39.105689],[-77.518447,39.119782],[-77.527322,39.146961],[-77.507558,39.181367],[-77.481202,39.187909],[-77.45771,39.224979],[-77.484731,39.246052],[-77.54084,39.265269],[-77.568753,39.306447],[-77.588777,39.301591],[-77.644084,39.30875],[-77.675467,39.32436],[-77.719519,39.321314],[-77.747587,39.294834],[-77.771365,39.236493],[-77.798157,39.200703],[-77.828806,39.132773],[-78.158193,39.343391],[-78.262815,39.41434],[-78.347087,39.466012],[-78.346718,39.427618],[-78.359918,39.409028],[-78.343214,39.388807],[-78.366867,39.35929],[-78.340415,39.353628],[-78.360571,39.317597],[-78.419422,39.257476],[-78.399669,39.243874],[-78.438695,39.198093],[-78.403907,39.167738],[-78.427294,39.152726],[-78.459311,39.114273],[-78.479597,39.109505],[-78.571901,39.031995],[-78.550467,39.018065],[-78.601655,38.964603],[-78.629553,38.980866],[-78.680456,38.925313],[-78.719914,38.906076],[-78.739147,38.927134],[-78.759085,38.900529],[-78.78803,38.885123],[-78.869276,38.762991],[-78.993761,38.850021],[-79.047349,38.79181],[-79.092955,38.702002],[-79.092655,38.660417],[-79.131056,38.653218],[-79.158257,38.593119],[-79.205859,38.524521],[-79.210591,38.492913],[-79.254482,38.45593],[-79.279692,38.424211],[-79.312276,38.411876],[-79.476638,38.457228],[-79.521469,38.533918],[-79.53687,38.550917],[-79.649075,38.591515],[-79.662875,38.570416],[-79.680074,38.510617],[-79.699473,38.474527],[-79.689667,38.431462],[-79.706634,38.41573],[-79.742771,38.353367],[-79.809934,38.304681],[-79.788945,38.268703],[-79.850326,38.233324],[-79.897335,38.193381],[-79.929031,38.139771],[-79.92633,38.107147],[-79.953511,38.08148],[-79.98029,38.027596],[-80.008888,37.99083],[-80.069431,37.945463],[-80.140609,37.883932],[-80.162202,37.875122],[-80.206482,37.81597],[-80.248389,37.76825],[-80.262765,37.738336],[-80.258143,37.720612],[-80.296078,37.691731],[-80.267504,37.646156],[-80.220984,37.627767],[-80.258919,37.595499],[-80.328504,37.564315],[-80.31237,37.546225],[-80.299789,37.508271],[-80.425629,37.449885],[-80.475601,37.422949],[-80.49486,37.435072],[-80.492981,37.457749],[-80.511391,37.481672],[-80.552036,37.473563],[-80.645893,37.422147],[-80.705203,37.394618],[-80.770082,37.372363],[-80.784188,37.394587],[-80.837678,37.425658],[-80.863003,37.411514],[-80.883247,37.383933],[-80.849451,37.346909],[-80.89805,37.316779],[-80.951018,37.29497],[-80.987319,37.301359],[-81.104147,37.280605],[-81.167029,37.262881],[-81.225104,37.234874],[-81.320105,37.299323],[-81.362156,37.337687],[-81.394287,37.316411],[-81.416663,37.273214],[-81.437158,37.274065],[-81.560631,37.206663],[-81.678221,37.20154],[-81.740124,37.237752],[-81.746039,37.263398],[-81.789294,37.284416],[-81.825851,37.279311],[-81.853553,37.287706],[-81.874059,37.327674],[-81.897936,37.33367],[-81.928463,37.360871],[-81.93587,37.382856],[-81.928154,37.4145],[-81.945765,37.440214],[-81.99227,37.460916],[-81.996578,37.476705],[-81.939989,37.512591],[-81.968012,37.538035],[-82.142983,37.414842],[-82.316008,37.29483],[-82.355536,37.265147],[-82.439893,37.247005],[-82.550589,37.204483],[-82.633918,37.154179],[-82.715149,37.121848],[-82.726486,37.073411],[-82.742759,37.042796],[-82.782863,37.007821],[-82.82872,37.005798],[-82.830842,36.993556],[-82.869607,36.973343],[-82.857644,36.92885],[-82.87908,36.889255],[-82.969243,36.858019],[-83.072836,36.854457],[-83.099904,36.831147],[-83.09848,36.813517],[-83.128987,36.775784],[-83.13512,36.742629],[-83.194252,36.739519],[-83.347635,36.700199],[-83.418682,36.668729],[-83.492805,36.670187],[-83.532404,36.664689],[-83.567853,36.646051],[-83.613896,36.63485],[-83.675395,36.600784],[-83.499909,36.597234],[-83.2763,36.598187],[-83.261099,36.593887],[-82.887946,36.593461],[-82.622603,36.593797],[-82.210792,36.595861],[-81.93414,36.594213],[-81.922644,36.616213],[-81.6469,36.611918],[-81.677393,36.588156],[-81.600101,36.586851],[-81.520119,36.580449],[-81.374773,36.574736],[-81.286844,36.57509],[-81.008527,36.563959],[-80.840334,36.559199],[-80.730385,36.562348],[-80.294792,36.543957],[-80.11334,36.542606],[-79.887248,36.542865],[-79.563478,36.541009],[-79.24983,36.541139],[-79.070096,36.542034],[-78.511317,36.540951],[-78.249794,36.544459],[-78.14287,36.543679],[-77.749905,36.545518],[-77.372744,36.544639],[-76.919464,36.543863],[-76.915897,36.552093],[-76.802631,36.55055],[-76.292968,36.550551],[-75.797497,36.550916],[-75.819825,36.625153],[-75.874647,36.744305],[-75.92685,36.936457],[-75.889676,37.054515],[-75.824147,37.110122],[-75.781587,37.137116],[-75.744639,37.182349],[-75.729271,37.216901],[-75.732284,37.245694],[-75.715515,37.275687],[-75.644386,37.34227],[-75.643456,37.372864],[-75.604026,37.422024],[-75.595642,37.465584],[-75.528423,37.561308],[-75.541779,37.606145],[-75.515962,37.671538],[-75.460933,37.771356],[-75.417073,37.82105],[-75.366857,37.809789],[-75.337979,37.815232],[-75.296004,37.845752],[-75.238073,37.93539],[-75.166435,38.027834]]]},\"properties\":{\"name\":\"Virginia\",\"ns_code\":\"01779803\",\"geoid\":\"51\",\"usps_abbrev\":\"VA\",\"fips_code\":\"51\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-103.002434,36.500397],[-103.041923,36.50035],[-103.040824,36.000037],[-103.041666,35.625043],[-103.042258,35.394355],[-103.04264,35.109974],[-103.042691,34.739882],[-103.043762,34.31275],[-103.044173,33.974557],[-103.049608,33.737766],[-103.053903,33.506965],[-103.064639,33.000106],[-103.064968,32.754674],[-103.064676,32.500115],[-103.06478,32.226978],[-103.064422,32.000517],[-103.375458,32.000334],[-103.748317,32.000197],[-104.096927,31.999997],[-104.466406,32.000116],[-105.000507,32.00042],[-105.429383,32.000577],[-105.731353,32.001565],[-106.125534,32.002445],[-106.618486,32.000495],[-106.639536,31.980343],[-106.622163,31.936007],[-106.645295,31.894858],[-106.635922,31.866237],[-106.577244,31.810406],[-106.54714,31.807299],[-106.528242,31.783148],[-106.490549,31.748924],[-106.45143,31.764427],[-106.381074,31.732082],[-106.349565,31.696712],[-106.33472,31.664109],[-106.307944,31.629519],[-106.280142,31.561462],[-106.244854,31.53796],[-106.21962,31.481561],[-106.200119,31.462411],[-106.172389,31.453077],[-106.132291,31.425081],[-106.110112,31.423017],[-106.075307,31.397615],[-106.006924,31.392824],[-105.969281,31.365696],[-105.953544,31.364503],[-105.931585,31.312781],[-105.908629,31.312745],[-105.89511,31.291309],[-105.869294,31.288955],[-105.794287,31.202286],[-105.774028,31.168474],[-105.741284,31.164204],[-105.7084,31.13593],[-105.648839,31.115877],[-105.642684,31.099583],[-105.606438,31.085278],[-105.584994,31.056964],[-105.577388,31.019672],[-105.557226,30.989673],[-105.532676,30.984697],[-105.49898,30.950826],[-105.451421,30.924681],[-105.44256,30.909723],[-105.399415,30.888305],[-105.386724,30.853306],[-105.339166,30.840405],[-105.250212,30.799028],[-105.217783,30.805946],[-105.116809,30.743059],[-105.062486,30.686532],[-105.026618,30.679428],[-105.007796,30.686221],[-104.98611,30.662767],[-104.971411,30.609846],[-104.933675,30.599572],[-104.899052,30.570769],[-104.873928,30.495572],[-104.861446,30.427123],[-104.847632,30.413979],[-104.859397,30.391125],[-104.810904,30.363171],[-104.811805,30.333953],[-104.761442,30.300816],[-104.760964,30.276632],[-104.713316,30.237805],[-104.687076,30.179342],[-104.694381,30.101888],[-104.685829,30.082063],[-104.707249,30.050134],[-104.703953,30.023748],[-104.682387,29.97185],[-104.675861,29.915537],[-104.633929,29.871206],[-104.629084,29.852013],[-104.578726,29.782621],[-104.569113,29.751656],[-104.549796,29.740641],[-104.544198,29.681271],[-104.52498,29.665974],[-104.514031,29.63863],[-104.477326,29.628619],[-104.466752,29.609387],[-104.398921,29.571897],[-104.394881,29.556282],[-104.338201,29.520182],[-104.296789,29.524128],[-104.262175,29.513501],[-104.209153,29.481468],[-104.211166,29.449755],[-104.180792,29.425861],[-104.16838,29.396136],[-104.141158,29.377823],[-104.106874,29.37344],[-104.082256,29.346064],[-104.020218,29.312048],[-103.972059,29.296055],[-103.92572,29.294372],[-103.863335,29.27922],[-103.783626,29.265324],[-103.756362,29.219937],[-103.718561,29.180826],[-103.703735,29.184415],[-103.633657,29.15858],[-103.596281,29.151915],[-103.556868,29.155502],[-103.524835,29.137123],[-103.450019,29.065734],[-103.386817,29.021735],[-103.338751,29.020277],[-103.265735,28.99567],[-103.250416,28.98043],[-103.203834,28.98723],[-103.153896,28.971606],[-103.117478,28.997774],[-103.097848,29.027036],[-103.100134,29.060675],[-103.053887,29.100749],[-103.033879,29.100972],[-103.014259,29.124978],[-102.97874,29.186433],[-102.950888,29.178162],[-102.917234,29.191788],[-102.867474,29.223883],[-102.871032,29.240706],[-102.906382,29.262055],[-102.888178,29.293359],[-102.892971,29.309151],[-102.877726,29.3548],[-102.845303,29.357699],[-102.825612,29.396946],[-102.83248,29.432431],[-102.807213,29.49405],[-102.808195,29.523115],[-102.778382,29.543592],[-102.768641,29.59468],[-102.740154,29.598174],[-102.73451,29.642143],[-102.693539,29.676957],[-102.698551,29.695545],[-102.677219,29.741052],[-102.645758,29.733874],[-102.572095,29.757474],[-102.547978,29.744644],[-102.513,29.766569],[-102.514939,29.784299],[-102.473233,29.776018],[-102.434228,29.776946],[-102.404537,29.765181],[-102.34161,29.86905],[-102.299997,29.877369],[-102.261495,29.853241],[-102.221853,29.840365],[-102.188942,29.848809],[-102.159796,29.814685],[-102.116723,29.793038],[-102.084379,29.795064],[-102.049023,29.785672],[-101.981567,29.81502],[-101.95548,29.795333],[-101.875772,29.793796],[-101.849827,29.807279],[-101.837595,29.793364],[-101.788531,29.779272],[-101.782767,29.789321],[-101.712056,29.762777],[-101.662994,29.77106],[-101.646936,29.754565],[-101.606648,29.774113],[-101.57754,29.769074],[-101.567749,29.798727],[-101.53501,29.797679],[-101.539191,29.761774],[-101.500389,29.765868],[-101.455799,29.788049],[-101.448881,29.750699],[-101.415584,29.746534],[-101.398362,29.717],[-101.375386,29.701807],[-101.363218,29.652638],[-101.347863,29.662209],[-101.314135,29.659054],[-101.300027,29.640702],[-101.311706,29.615372],[-101.311636,29.585127],[-101.277878,29.59081],[-101.280494,29.615181],[-101.262233,29.630607],[-101.24103,29.565028],[-101.261176,29.536776],[-101.235273,29.524854],[-101.173824,29.514568],[-101.151877,29.477005],[-101.087149,29.469414],[-101.060151,29.458661],[-101.036604,29.406108],[-101.003953,29.364703],[-100.950727,29.347711],[-100.941265,29.333926],[-100.886842,29.307848],[-100.878126,29.28086],[-100.80006,29.248797],[-100.766004,29.185847],[-100.775905,29.173344],[-100.737795,29.139079],[-100.6752,29.100741],[-100.664021,29.074164],[-100.660208,29.031497],[-100.648664,29.000243],[-100.647268,28.955457],[-100.631611,28.902839],[-100.602595,28.902214],[-100.576866,28.835157],[-100.546576,28.824923],[-100.537772,28.780776],[-100.507613,28.740599],[-100.510534,28.692669],[-100.500354,28.66196],[-100.462069,28.641108],[-100.447598,28.610066],[-100.398462,28.585169],[-100.411388,28.55196],[-100.386963,28.514023],[-100.33474,28.500261],[-100.367765,28.475019],[-100.341534,28.449571],[-100.336187,28.430181],[-100.349306,28.401398],[-100.314199,28.345859],[-100.288639,28.316976],[-100.294296,28.28438],[-100.257789,28.24034],[-100.223629,28.235223],[-100.211313,28.193899],[-100.086898,28.146783],[-100.053122,28.084731],[-100.018951,28.066441],[-100.006148,28.018419],[-99.989762,27.992876],[-99.931811,27.980968],[-99.938541,27.954059],[-99.91746,27.917973],[-99.893456,27.899208],[-99.904385,27.875284],[-99.881329,27.849551],[-99.872294,27.795258],[-99.84966,27.79246],[-99.842967,27.768518],[-99.81312,27.773997],[-99.801651,27.741771],[-99.77074,27.732134],[-99.721519,27.666155],[-99.704601,27.654954],[-99.668942,27.659974],[-99.666108,27.636088],[-99.638929,27.626758],[-99.603533,27.641992],[-99.577636,27.618597],[-99.541889,27.606001],[-99.511049,27.564507],[-99.521919,27.544094],[-99.528392,27.49855],[-99.496826,27.500106],[-99.479251,27.478635],[-99.495104,27.451518],[-99.487765,27.413399],[-99.504397,27.339896],[-99.538035,27.31709],[-99.495021,27.303937],[-99.496504,27.273389],[-99.465825,27.26797],[-99.441545,27.249915],[-99.443286,27.21941],[-99.426418,27.178287],[-99.439844,27.151581],[-99.430672,27.125307],[-99.442043,27.105967],[-99.43447,27.078517],[-99.451035,27.066765],[-99.446123,27.023048],[-99.415476,27.01724],[-99.37731,26.973819],[-99.388253,26.944217],[-99.337297,26.922759],[-99.321819,26.906846],[-99.3289,26.88022],[-99.295146,26.86544],[-99.268613,26.843213],[-99.262208,26.815668],[-99.242444,26.788262],[-99.240023,26.745851],[-99.208907,26.724761],[-99.209948,26.693938],[-99.20024,26.655824],[-99.178064,26.620547],[-99.166742,26.536079],[-99.128379,26.525501],[-99.104851,26.500383],[-99.091976,26.472376],[-99.11085,26.426282],[-99.082006,26.396667],[-99.037058,26.413603],[-99.01064,26.392135],[-98.974164,26.401148],[-98.955846,26.376982],[-98.901677,26.371229],[-98.895694,26.353491],[-98.86191,26.36599],[-98.841562,26.358943],[-98.82045,26.371002],[-98.798211,26.360166],[-98.777028,26.325729],[-98.749469,26.329988],[-98.73225,26.299108],[-98.678958,26.260064],[-98.664633,26.235351],[-98.599154,26.257612],[-98.580817,26.254741],[-98.503766,26.2088],[-98.462714,26.225802],[-98.44228,26.198911],[-98.386694,26.157872],[-98.3666,26.168782],[-98.332948,26.16128],[-98.324868,26.122305],[-98.284291,26.10109],[-98.248737,26.072042],[-98.221217,26.076611],[-98.198466,26.055397],[-98.171246,26.069681],[-98.137302,26.060845],[-98.103811,26.067094],[-98.038367,26.042107],[-98.022703,26.06042],[-97.987302,26.065722],[-97.966069,26.051937],[-97.878755,26.065269],[-97.849594,26.052109],[-97.804259,26.059654],[-97.790246,26.033359],[-97.72647,26.022656],[-97.667422,26.029073],[-97.62289,25.989519],[-97.582563,25.937857],[-97.530977,25.920357],[-97.520812,25.886],[-97.49643,25.895805],[-97.470819,25.888354],[-97.44403,25.848614],[-97.373187,25.839956],[-97.358754,25.878808],[-97.374173,25.905923],[-97.350394,25.925237],[-97.326003,25.917689],[-97.2767,25.952146],[-97.24701,25.948234],[-97.209961,25.963458],[-97.091121,25.97384],[-97.097858,26.03862],[-97.08999,26.07534],[-97.108301,26.10451],[-97.110147,26.140583],[-97.126121,26.250351],[-97.156496,26.375348],[-97.214056,26.576766],[-97.251705,26.661866],[-97.282725,26.763423],[-97.300423,26.834613],[-97.31595,26.929289],[-97.323219,27.010256],[-97.319824,27.110023],[-97.309363,27.196109],[-97.296567,27.250323],[-97.250264,27.392308],[-97.199816,27.500319],[-97.125265,27.640597],[-97.050221,27.750308],[-97.021855,27.783364],[-96.984281,27.803783],[-96.959495,27.875302],[-96.875264,27.96992],[-96.844196,28.000296],[-96.746251,28.079622],[-96.618785,28.169522],[-96.531,28.221164],[-96.37525,28.29076],[-96.352686,28.312923],[-96.34181,28.342381],[-96.320762,28.362868],[-96.271767,28.382934],[-96.258191,28.407872],[-96.182242,28.452036],[-96.078331,28.500001],[-95.87524,28.582788],[-95.750238,28.638419],[-95.532019,28.750256],[-95.493888,28.76534],[-95.420071,28.805625],[-95.353471,28.817863],[-95.322113,28.834442],[-95.308121,28.85883],[-95.227228,28.910379],[-95.180507,28.949243],[-95.139469,28.991697],[-95.105447,29.016048],[-95.086398,29.06824],[-95.000202,29.104452],[-94.912171,29.159318],[-94.772686,29.231718],[-94.713311,29.282521],[-94.66622,29.281047],[-94.642421,29.300136],[-94.623898,29.343143],[-94.650311,29.38282],[-94.625192,29.397662],[-94.499452,29.452337],[-94.370351,29.50023],[-94.12518,29.595559],[-94.033565,29.625002],[-93.906043,29.625001],[-93.875006,29.621426],[-93.848588,29.60026],[-93.814351,29.596576],[-93.837412,29.689885],[-93.87002,29.735482],[-93.888821,29.742234],[-93.89847,29.771577],[-93.92881,29.79708],[-93.922744,29.818808],[-93.859896,29.859921],[-93.830374,29.894358],[-93.806551,29.957445],[-93.790087,29.986564],[-93.739734,30.023987],[-93.720805,30.053043],[-93.724101,30.09301],[-93.692849,30.135213],[-93.720931,30.208461],[-93.703544,30.259321],[-93.704584,30.289949],[-93.729046,30.296705],[-93.764252,30.330229],[-93.757409,30.390813],[-93.698135,30.43888],[-93.716602,30.494424],[-93.706507,30.50991],[-93.738788,30.540975],[-93.713782,30.587584],[-93.678662,30.594097],[-93.6831,30.640763],[-93.631345,30.677872],[-93.608875,30.719117],[-93.61478,30.756064],[-93.592101,30.763675],[-93.553626,30.83514],[-93.558502,30.867905],[-93.531234,30.924388],[-93.56543,30.990579],[-93.539184,31.008212],[-93.524739,31.039007],[-93.52717,31.073189],[-93.563269,31.093592],[-93.539366,31.115207],[-93.54893,31.186601],[-93.589119,31.165889],[-93.618255,31.233105],[-93.613942,31.259375],[-93.659285,31.282106],[-93.687511,31.310813],[-93.67384,31.323855],[-93.667273,31.365413],[-93.674117,31.397681],[-93.704879,31.410881],[-93.692631,31.437192],[-93.749476,31.46869],[-93.719708,31.50584],[-93.742586,31.526229],[-93.785028,31.525962],[-93.820764,31.558221],[-93.838057,31.606795],[-93.8177,31.621638],[-93.823169,31.672893],[-93.794548,31.702076],[-93.836884,31.750171],[-93.822598,31.773559],[-93.839343,31.80033],[-93.874836,31.822314],[-93.872988,31.836696],[-93.901883,31.875168],[-93.90308,31.893922],[-93.970826,31.919968],[-94.006795,31.97343],[-94.04272,31.999263],[-94.042946,32.340572],[-94.043212,32.776803],[-94.042964,33.019219],[-94.04273,33.241822],[-94.04345,33.552253],[-94.073833,33.556006],[-94.082352,33.57572],[-94.147428,33.565183],[-94.162266,33.588906],[-94.184308,33.594578],[-94.203594,33.566534],[-94.226188,33.552976],[-94.238866,33.576748],[-94.291212,33.581478],[-94.309582,33.551673],[-94.352643,33.56063],[-94.35359,33.544005],[-94.38953,33.546739],[-94.430041,33.591124],[-94.4532,33.59178],[-94.452701,33.617373],[-94.485878,33.637865],[-94.505655,33.620668],[-94.564076,33.626265],[-94.593294,33.665258],[-94.626844,33.682035],[-94.639198,33.663737],[-94.684796,33.684353],[-94.736814,33.692332],[-94.773126,33.727187],[-94.827948,33.740882],[-94.860687,33.741945],[-94.886934,33.769067],[-94.91143,33.778383],[-94.923289,33.808742],[-94.942217,33.80578],[-94.964401,33.837021],[-94.998019,33.860504],[-95.049025,33.86409],[-95.157145,33.936727],[-95.21668,33.962652],[-95.250885,33.936063],[-95.253814,33.893977],[-95.306397,33.874906],[-95.325535,33.885734],[-95.363209,33.866812],[-95.442746,33.866506],[-95.533798,33.881153],[-95.551216,33.889713],[-95.552799,33.924311],[-95.597522,33.942342],[-95.627318,33.907806],[-95.665478,33.909004],[-95.688876,33.888486],[-95.733183,33.896447],[-95.756612,33.892028],[-95.75142,33.860683],[-95.77024,33.845202],[-95.815723,33.859815],[-95.830406,33.834785],[-95.92478,33.884843],[-95.94603,33.859338],[-95.998351,33.85105],[-96.048834,33.836468],[-96.084626,33.846656],[-96.149227,33.837091],[-96.165199,33.78013],[-96.179846,33.759618],[-96.220522,33.74739],[-96.263797,33.767522],[-96.290359,33.770831],[-96.306078,33.744307],[-96.318759,33.696753],[-96.359374,33.689432],[-96.370847,33.717996],[-96.430216,33.778654],[-96.500984,33.772801],[-96.532651,33.823143],[-96.573151,33.819182],[-96.62315,33.841496],[-96.597348,33.875101],[-96.59217,33.895513],[-96.630117,33.895422],[-96.673449,33.912278],[-96.690708,33.849959],[-96.713734,33.831328],[-96.761587,33.824407],[-96.794276,33.868886],[-96.832157,33.874835],[-96.841592,33.852894],[-96.866438,33.853149],[-96.895728,33.896414],[-96.905253,33.947219],[-96.924268,33.959159],[-96.973807,33.935697],[-96.996251,33.942664],[-96.98374,33.89209],[-97.02144,33.846379],[-97.057599,33.836506],[-97.048324,33.817269],[-97.092646,33.804198],[-97.085191,33.764738],[-97.095644,33.729222],[-97.123578,33.717044],[-97.154367,33.724094],[-97.192969,33.760784],[-97.205705,33.802908],[-97.194678,33.831192],[-97.166824,33.840395],[-97.185458,33.9007],[-97.212601,33.9159],[-97.245049,33.903216],[-97.255639,33.863702],[-97.313983,33.867535],[-97.340052,33.859841],[-97.37469,33.818552],[-97.426799,33.818641],[-97.463202,33.84309],[-97.452182,33.86896],[-97.458343,33.901887],[-97.500929,33.919538],[-97.55827,33.897099],[-97.589254,33.903922],[-97.59798,33.955247],[-97.609091,33.968093],[-97.66149,33.990818],[-97.687694,33.98718],[-97.732224,33.936681],[-97.763043,33.934145],[-97.805972,33.876571],[-97.833505,33.858096],[-97.877387,33.850236],[-97.984373,33.898024],[-97.957155,33.914454],[-97.956917,33.958502],[-97.946802,33.990893],[-97.97167,34.005434],[-98.041117,33.993456],[-98.084435,34.002893],[-98.103617,34.029207],[-98.10063,34.049984],[-98.120208,34.072127],[-98.09055,34.122484],[-98.109462,34.154111],[-98.130816,34.150532],[-98.16879,34.114262],[-98.200075,34.116783],[-98.232474,34.134641],[-98.300209,34.134579],[-98.32258,34.14972],[-98.36402,34.157109],[-98.398441,34.128456],[-98.398389,34.104566],[-98.418805,34.082655],[-98.442808,34.083144],[-98.486328,34.062598],[-98.504149,34.072287],[-98.577356,34.1491],[-98.603096,34.160501],[-98.643223,34.164531],[-98.690072,34.133155],[-98.766671,34.136834],[-98.831115,34.162154],[-98.887112,34.16826],[-98.920704,34.183435],[-98.951284,34.212189],[-99.05635,34.200014],[-99.080577,34.211483],[-99.121004,34.20185],[-99.189776,34.214357],[-99.213161,34.308476],[-99.208832,34.339172],[-99.234298,34.340451],[-99.237562,34.366701],[-99.269796,34.380843],[-99.261275,34.403508],[-99.28791,34.41403],[-99.320073,34.409267],[-99.37555,34.458789],[-99.395187,34.44203],[-99.387181,34.410842],[-99.398728,34.375832],[-99.517624,34.414494],[-99.569696,34.418418],[-99.600026,34.374688],[-99.648142,34.382312],[-99.694528,34.378218],[-99.727031,34.410554],[-99.753447,34.420853],[-99.814313,34.476204],[-99.821839,34.497848],[-99.85305,34.511585],[-99.888741,34.550452],[-99.921801,34.570253],[-99.953817,34.578527],[-99.971013,34.562501],[-100.000277,34.56045],[-100.00038,34.769239],[-100.000384,35.095109],[-100.000395,35.306413],[-100.000393,35.692515],[-100.000398,35.967746],[-100.000406,36.499702],[-100.522227,36.499291],[-100.824105,36.499619],[-101.219265,36.499463],[-101.496598,36.499455],[-101.834063,36.499556],[-102.22058,36.500333],[-102.625467,36.500364],[-103.002434,36.500397]]]},\"properties\":{\"name\":\"Texas\",\"ns_code\":\"01779801\",\"geoid\":\"48\",\"usps_abbrev\":\"TX\",\"fips_code\":\"48\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-71.501088,45.013349],[-71.798683,45.010812],[-72.103058,45.005598],[-72.270869,45.004186],[-72.481033,45.00887],[-72.58988,45.013237],[-72.845633,45.016659],[-72.968092,45.014092],[-73.085972,45.015494],[-73.343124,45.01084],[-73.354633,44.987352],[-73.338244,44.964751],[-73.338622,44.919366],[-73.356218,44.904492],[-73.379452,44.83801],[-73.335044,44.804109],[-73.333152,44.789588],[-73.363792,44.745254],[-73.370064,44.666071],[-73.38982,44.617211],[-73.381849,44.589316],[-73.342933,44.551907],[-73.299896,44.476651],[-73.296052,44.428334],[-73.31504,44.388526],[-73.333613,44.3723],[-73.324195,44.310033],[-73.310914,44.274298],[-73.319603,44.249909],[-73.343376,44.23819],[-73.363393,44.207737],[-73.390625,44.191068],[-73.411225,44.113767],[-73.437905,44.045125],[-73.410385,44.026503],[-73.408859,43.934505],[-73.3741,43.875304],[-73.372589,43.845354],[-73.391014,43.818411],[-73.351265,43.769823],[-73.370612,43.725329],[-73.405243,43.688368],[-73.42791,43.634428],[-73.41832,43.623325],[-73.428637,43.583994],[-73.398126,43.568065],[-73.365563,43.623441],[-73.302553,43.625709],[-73.284912,43.579272],[-73.258631,43.56495],[-73.241486,43.532621],[-73.246844,43.517252],[-73.256475,43.260093],[-73.27417,42.949028],[-73.278673,42.83341],[-73.290944,42.80192],[-73.276421,42.746019],[-73.264957,42.74594],[-72.809113,42.736581],[-72.458436,42.72685],[-72.475938,42.757702],[-72.539317,42.804647],[-72.557114,42.852474],[-72.552702,42.884874],[-72.524188,42.917609],[-72.532226,42.954882],[-72.4736,42.972284],[-72.458386,43.018602],[-72.467255,43.054119],[-72.434385,43.083713],[-72.432944,43.11253],[-72.453048,43.139475],[-72.450416,43.192373],[-72.43909,43.201054],[-72.439187,43.245223],[-72.407856,43.282895],[-72.395525,43.312605],[-72.413377,43.362741],[-72.391526,43.46878],[-72.380428,43.488525],[-72.398376,43.510829],[-72.380383,43.54088],[-72.371051,43.580494],[-72.328514,43.600805],[-72.327395,43.636774],[-72.303936,43.667986],[-72.305277,43.695267],[-72.271839,43.733716],[-72.232069,43.748428],[-72.182511,43.810354],[-72.187329,43.855151],[-72.150518,43.902221],[-72.121554,43.918493],[-72.116807,43.947557],[-72.099093,43.957286],[-72.116977,43.994486],[-72.084762,44.020756],[-72.032525,44.082488],[-72.043896,44.158449],[-72.065918,44.189289],[-72.047799,44.238531],[-72.058851,44.286369],[-72.01914,44.320255],[-71.98112,44.3375],[-71.92911,44.337577],[-71.906909,44.348284],[-71.872472,44.336628],[-71.812473,44.358477],[-71.814141,44.382278],[-71.791609,44.400188],[-71.714279,44.409545],[-71.658906,44.442633],[-71.652127,44.460792],[-71.597994,44.486885],[-71.591733,44.518175],[-71.571337,44.538329],[-71.597723,44.554657],[-71.549836,44.569016],[-71.5363,44.585908],[-71.553237,44.608914],[-71.561962,44.647189],[-71.584606,44.656882],[-71.59457,44.697033],[-71.626093,44.728543],[-71.630755,44.753141],[-71.57864,44.785444],[-71.576865,44.815197],[-71.551114,44.836965],[-71.545923,44.865932],[-71.496093,44.907849],[-71.515868,44.931715],[-71.515001,44.958597],[-71.540931,44.98512],[-71.501088,45.013349]]]},\"properties\":{\"name\":\"Vermont\",\"ns_code\":\"01779802\",\"geoid\":\"50\",\"usps_abbrev\":\"VT\",\"fips_code\":\"50\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-70.575094,42.917126],[-70.537146,42.951423],[-70.528882,43.005769],[-70.558353,43.047264],[-70.599658,43.056939],[-70.582163,43.095487],[-70.555762,43.121505],[-70.52508,43.089278],[-70.493014,43.073043],[-70.430571,43.080218],[-70.406243,43.102092],[-70.403396,43.12509],[-70.419644,43.152551],[-70.44621,43.168149],[-70.493265,43.169151],[-70.519666,43.191015],[-70.506324,43.21883],[-70.519554,43.25162],[-70.49323,43.286829],[-70.421808,43.297495],[-70.369219,43.32222],[-70.318948,43.390138],[-70.287601,43.403522],[-70.268433,43.426351],[-70.155986,43.524444],[-69.855212,43.646947],[-69.808826,43.64734],[-69.786576,43.658131],[-69.749488,43.653985],[-69.719209,43.661745],[-69.664745,43.711748],[-69.631537,43.700885],[-69.566618,43.70283],[-69.51909,43.732122],[-69.501617,43.779686],[-69.40927,43.810817],[-69.387279,43.799016],[-69.397041,43.772959],[-69.384361,43.731756],[-69.356378,43.71088],[-69.297855,43.707694],[-69.262027,43.725426],[-69.23338,43.762316],[-69.242535,43.799761],[-69.226563,43.812203],[-69.18584,43.793786],[-69.141338,43.798421],[-69.083005,43.825647],[-69.055894,43.858911],[-69.022784,43.842507],[-69.024295,43.819842],[-68.990327,43.786891],[-68.949836,43.78017],[-68.925518,43.750088],[-68.874478,43.734179],[-68.826916,43.735711],[-68.797778,43.752882],[-68.782244,43.777537],[-68.785324,43.804339],[-68.749473,43.832531],[-68.724315,43.834592],[-68.68466,43.854647],[-68.663789,43.893926],[-68.679258,43.923455],[-68.716495,43.941984],[-68.721478,43.961938],[-68.659586,43.947991],[-68.585076,43.954312],[-68.547277,43.96862],[-68.436843,44.053233],[-68.368797,44.04522],[-68.311393,44.053429],[-68.263136,44.090617],[-68.20336,44.099587],[-68.175858,44.125083],[-68.17075,44.190114],[-68.122626,44.226041],[-68.12828,44.264253],[-68.060959,44.278256],[-68.018178,44.275107],[-67.973647,44.295159],[-67.960779,44.319265],[-67.925178,44.341434],[-67.898151,44.322126],[-67.858336,44.317024],[-67.80969,44.33651],[-67.795262,44.361165],[-67.805569,44.391015],[-67.77475,44.411314],[-67.741744,44.390831],[-67.674642,44.394634],[-67.636544,44.383592],[-67.567585,44.397209],[-67.492616,44.43222],[-67.455241,44.464873],[-67.439695,44.505384],[-67.3882,44.520399],[-67.356776,44.518094],[-67.317841,44.531544],[-67.301151,44.549591],[-67.248052,44.554898],[-67.187845,44.581782],[-67.096711,44.639192],[-67.010765,44.717322],[-66.977521,44.731241],[-66.941841,44.765625],[-66.902228,44.776429],[-66.885444,44.794208],[-66.932563,44.825508],[-66.965317,44.828905],[-66.982165,44.86722],[-66.968843,44.910419],[-67.021641,44.953925],[-67.094597,45.074793],[-67.112587,45.11257],[-67.158871,45.162035],[-67.202548,45.171404],[-67.227194,45.163462],[-67.272065,45.191223],[-67.291015,45.187471],[-67.295881,45.147968],[-67.340924,45.125092],[-67.380558,45.152108],[-67.461888,45.242668],[-67.476372,45.275371],[-67.431258,45.34107],[-67.427325,45.390721],[-67.473883,45.424739],[-67.500072,45.490849],[-67.434955,45.528217],[-67.420732,45.549695],[-67.431055,45.584114],[-67.456667,45.60431],[-67.499006,45.586643],[-67.589773,45.606765],[-67.675156,45.630933],[-67.713036,45.681213],[-67.754303,45.667575],[-67.803503,45.677696],[-67.809622,45.729574],[-67.781796,45.731117],[-67.809157,45.767372],[-67.801989,45.803066],[-67.763437,45.828676],[-67.804096,45.869747],[-67.803778,45.882741],[-67.750733,45.91759],[-67.78121,45.943589],[-67.781854,46.259293],[-67.788234,46.599908],[-67.78984,46.805877],[-67.790328,47.067202],[-67.882895,47.104317],[-67.890709,47.126395],[-67.927419,47.154955],[-67.951986,47.194628],[-67.989778,47.209995],[-68.025184,47.239871],[-68.073659,47.259085],[-68.15228,47.309594],[-68.152405,47.322021],[-68.223718,47.34473],[-68.234513,47.355295],[-68.327559,47.360013],[-68.380225,47.339578],[-68.383972,47.301171],[-68.429882,47.281293],[-68.474812,47.29716],[-68.517699,47.29594],[-68.549931,47.282101],[-68.577816,47.287629],[-68.607429,47.247057],[-68.663256,47.236221],[-68.687236,47.244432],[-68.811529,47.215131],[-68.857423,47.190325],[-68.904981,47.180436],[-68.941029,47.206101],[-69.040918,47.244973],[-69.054932,47.315298],[-69.054509,47.376563],[-69.035803,47.407249],[-69.062257,47.432631],[-69.081384,47.423742],[-69.121862,47.442598],[-69.176045,47.456771],[-69.224496,47.459854],[-69.458347,47.231322],[-69.769522,46.923483],[-69.997213,46.695402],[-70.056539,46.416815],[-70.094507,46.408241],[-70.148089,46.359],[-70.175244,46.358148],[-70.208989,46.329527],[-70.20687,46.299139],[-70.231688,46.291026],[-70.260763,46.229716],[-70.290405,46.185087],[-70.237081,46.144879],[-70.2569,46.099499],[-70.28248,46.100475],[-70.30714,46.061107],[-70.279076,46.060849],[-70.313699,46.022205],[-70.302773,45.998987],[-70.311671,45.966533],[-70.265401,45.962829],[-70.239536,45.93968],[-70.263039,45.924041],[-70.25409,45.902286],[-70.284473,45.871693],[-70.34192,45.852311],[-70.415233,45.795663],[-70.407096,45.762555],[-70.38807,45.748247],[-70.400494,45.719743],[-70.439176,45.703889],[-70.465552,45.706474],[-70.527065,45.666577],[-70.552855,45.667904],[-70.619255,45.612071],[-70.645283,45.606294],[-70.689045,45.562099],[-70.684789,45.550919],[-70.722427,45.513867],[-70.717814,45.490169],[-70.681156,45.452176],[-70.651032,45.445054],[-70.624516,45.406148],[-70.650432,45.377274],[-70.678185,45.394283],[-70.712112,45.390574],[-70.744021,45.421124],[-70.794133,45.428385],[-70.825605,45.400244],[-70.802608,45.366116],[-70.81989,45.34007],[-70.806162,45.321079],[-70.84877,45.263161],[-70.858122,45.229155],[-70.884709,45.234808],[-70.921246,45.278874],[-70.917949,45.311695],[-70.989163,45.334168],[-71.008487,45.319243],[-71.083924,45.305451],[-71.077227,45.250439],[-71.058301,44.991243],[-71.033524,44.697637],[-71.026162,44.549268],[-71.009964,44.284783],[-70.99478,43.960348],[-70.98904,43.790138],[-70.972743,43.57031],[-70.950776,43.551491],[-70.953874,43.519167],[-70.975311,43.477718],[-70.960826,43.439331],[-70.986852,43.413805],[-70.973462,43.349914],[-70.931188,43.336562],[-70.896742,43.285364],[-70.859837,43.258025],[-70.818264,43.238384],[-70.809714,43.224227],[-70.828057,43.186529],[-70.833614,43.146127],[-70.824584,43.125088],[-70.737969,43.073728],[-70.70631,43.075339],[-70.701197,43.04524],[-70.63218,42.986429],[-70.611152,42.97899],[-70.575094,42.917126]]]},\"properties\":{\"name\":\"Maine\",\"ns_code\":\"01779787\",\"geoid\":\"23\",\"usps_abbrev\":\"ME\",\"fips_code\":\"23\"}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-81.677393,36.588156],[-81.679115,36.569047],[-81.708067,36.535705],[-81.696823,36.506112],[-81.694948,36.467449],[-81.715023,36.454899],[-81.720842,36.422454],[-81.741524,36.412653],[-81.717405,36.347779],[-81.764927,36.338672],[-81.797544,36.358428],[-81.855095,36.337186],[-81.879641,36.313654],[-81.908136,36.302013],[-82.033141,36.120422],[-82.054142,36.126821],[-82.079743,36.10652],[-82.130246,36.104517],[-82.148748,36.149615],[-82.176849,36.142214],[-82.213852,36.159112],[-82.245053,36.131011],[-82.288955,36.13571],[-82.336056,36.115009],[-82.355657,36.115309],[-82.410158,36.082709],[-82.460597,36.007897],[-82.505384,35.97768],[-82.549682,35.964275],[-82.610889,35.967409],[-82.612604,35.993488],[-82.590663,36.032106],[-82.636379,36.065903],[-82.684765,36.045004],[-82.715365,36.023804],[-82.775866,36.000803],[-82.787465,35.952163],[-82.820411,35.922077],[-82.851603,35.949452],[-82.898506,35.9451],[-82.914381,35.929683],[-82.896583,35.878471],[-82.920609,35.868472],[-82.916273,35.841603],[-82.94383,35.825637],[-82.974245,35.786967],[-82.995803,35.773128],[-83.036209,35.787405],[-83.078732,35.789472],[-83.120183,35.766234],[-83.159208,35.764892],[-83.185694,35.729888],[-83.24067,35.726759],[-83.254231,35.695807],[-83.31049,35.654452],[-83.334965,35.665471],[-83.366941,35.638728],[-83.421576,35.611186],[-83.445802,35.611803],[-83.498335,35.56298],[-83.56609,35.565993],[-83.601854,35.578228],[-83.640958,35.565712],[-83.707199,35.568533],[-83.771736,35.562118],[-83.82559,35.523829],[-83.882563,35.517182],[-83.911773,35.476028],[-83.937015,35.471511],[-83.992131,35.440402],[-84.014707,35.411984],[-84.0074,35.371643],[-84.038327,35.347813],[-84.03551,35.317783],[-84.02141,35.300983],[-84.055712,35.268182],[-84.107006,35.251409],[-84.199117,35.243679],[-84.211818,35.266078],[-84.29024,35.225572],[-84.321869,34.988408],[-84.039375,34.986973],[-83.749894,34.987691],[-83.619985,34.986592],[-83.620185,34.992091],[-83.501536,34.992862],[-83.108614,35.000659],[-82.784839,35.0857],[-82.763712,35.068209],[-82.72701,35.094142],[-82.697945,35.095359],[-82.641797,35.131817],[-82.598579,35.137788],[-82.525566,35.15652],[-82.486653,35.174463],[-82.431481,35.173187],[-82.419744,35.198613],[-82.390253,35.21554],[-82.362991,35.191037],[-82.315871,35.190678],[-82.27492,35.200071],[-82.181121,35.194067],[-81.749875,35.180019],[-81.264273,35.161062],[-81.069092,35.151242],[-81.047091,35.145157],[-81.03247,35.110033],[-81.057236,35.086129],[-81.057648,35.062433],[-81.041489,35.044703],[-80.93495,35.107409],[-80.782042,34.935785],[-80.797491,34.819752],[-80.558125,34.81744],[-80.164577,34.811656],[-79.874855,34.80541],[-79.6753,34.804744],[-79.358252,34.545579],[-79.201539,34.40864],[-78.874747,34.134395],[-78.54108,33.85112],[-78.499301,33.812852],[-78.399002,33.843262],[-78.341208,33.855293],[-78.262335,33.863163],[-78.145353,33.862734],[-78.066581,33.847985],[-78.052895,33.827432],[-78.01286,33.803863],[-77.99971,33.76713],[-77.964052,33.752899],[-77.917505,33.76382],[-77.893686,33.804702],[-77.89897,33.854639],[-77.884519,33.90367],[-77.863534,33.93467],[-77.830206,34.030283],[-77.785555,34.125171],[-77.753791,34.155023],[-77.74742,34.175594],[-77.691093,34.245744],[-77.668453,34.258302],[-77.623579,34.303162],[-77.516162,34.382443],[-77.418832,34.435923],[-77.295077,34.488968],[-77.219176,34.53966],[-77.124671,34.583147],[-76.999666,34.617009],[-76.818937,34.643253],[-76.749655,34.647665],[-76.6928,34.64295],[-76.614722,34.618278],[-76.612344,34.600128],[-76.58372,34.552625],[-76.562509,34.536912],[-76.524378,34.532155],[-76.491991,34.546532],[-76.444196,34.633155],[-76.374638,34.715991],[-76.315731,34.77603],[-76.249632,34.834631],[-76.124626,34.933081],[-76.02443,35.005364],[-75.998411,35.007389],[-75.908588,35.071522],[-75.807724,35.115906],[-75.70737,35.146111],[-75.6246,35.175824],[-75.59022,35.183757],[-75.548003,35.168184],[-75.499593,35.177172],[-75.4746,35.202029],[-75.425581,35.390009],[-75.415089,35.50018],[-75.402422,35.54421],[-75.40039,35.593512],[-75.41782,35.686082],[-75.437617,35.74696],[-75.480501,35.814207],[-75.566047,35.975614],[-75.670103,36.156074],[-75.711319,36.250163],[-75.749635,36.353606],[-75.790558,36.500156],[-75.797497,36.550916],[-76.292968,36.550551],[-76.802631,36.55055],[-76.915897,36.552093],[-76.919464,36.543863],[-77.372744,36.544639],[-77.749905,36.545518],[-78.14287,36.543679],[-78.249794,36.544459],[-78.511317,36.540951],[-79.070096,36.542034],[-79.24983,36.541139],[-79.563478,36.541009],[-79.887248,36.542865],[-80.11334,36.542606],[-80.294792,36.543957],[-80.730385,36.562348],[-80.840334,36.559199],[-81.008527,36.563959],[-81.286844,36.57509],[-81.374773,36.574736],[-81.520119,36.580449],[-81.600101,36.586851],[-81.677393,36.588156]]]},\"properties\":{\"name\":\"North Carolina\",\"ns_code\":\"01027616\",\"geoid\":\"37\",\"usps_abbrev\":\"NC\",\"fips_code\":\"37\"}}]}\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/Cornelius.tsx",
    "content": "/*!\n * React port of Cornelius library (based on v0.1 released under the MIT license)\n * Original library: http://restorando.github.io/cornelius\n */\n\nimport { isNil, isFinite, map, extend, min, max } from \"lodash\";\nimport moment from \"moment\";\nimport chroma from \"chroma-js\";\nimport React, { useMemo } from \"react\";\nimport Tooltip from \"antd/lib/tooltip\";\nimport { createNumberFormatter, formatSimpleTemplate } from \"@/lib/value-format\";\nimport chooseTextColorForBackground from \"@/lib/chooseTextColorForBackground\";\n\nimport \"./cornelius.less\";\n\nconst momentInterval = {\n  daily: \"days\",\n  weekly: \"weeks\",\n  monthly: \"months\",\n  yearly: \"years\",\n};\n\nconst timeLabelFormats = {\n  daily: \"MMMM D, YYYY\",\n  weekly: \"[Week of] MMM D, YYYY\",\n  monthly: \"MMMM YYYY\",\n  yearly: \"YYYY\",\n};\n\nconst defaultOptions = {\n  initialDate: null,\n  timeInterval: \"monthly\",\n  noValuePlaceholder: \"-\",\n  rawNumberOnHover: true,\n  displayAbsoluteValues: false,\n  initialIntervalNumber: 1,\n  maxColumns: Infinity,\n\n  title: null,\n  timeColumnTitle: \"Time\",\n  peopleColumnTitle: \"People\",\n  stageColumnTitle: \"{{ @ }}\",\n  numberFormat: \"0,0[.]00\",\n  percentFormat: \"0.00%\",\n  timeLabelFormat: timeLabelFormats.monthly,\n\n  colors: {\n    min: \"#ffffff\",\n    max: \"#041d66\",\n    steps: 7,\n  },\n};\n\nfunction prepareOptions(options: any) {\n  options = extend({}, defaultOptions, options, {\n    initialDate: moment(options.initialDate),\n    colors: extend({}, defaultOptions.colors, options.colors),\n  });\n\n  return extend(options, {\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    timeLabelFormat: timeLabelFormats[options.timeInterval],\n    formatNumber: createNumberFormatter(options.numberFormat),\n    formatPercent: createNumberFormatter(options.percentFormat),\n    getColorForValue: chroma\n      .scale([options.colors.min, options.colors.max])\n      .mode(\"hsl\")\n      .domain([0, 100])\n      .classes(options.colors.steps),\n  });\n}\n\nfunction isLightColor(backgroundColor: any) {\n  backgroundColor = chroma(backgroundColor);\n  const white = \"#ffffff\";\n  const black = \"#000000\";\n  return chroma.contrast(backgroundColor, white) < chroma.contrast(backgroundColor, black);\n}\n\nfunction formatStageTitle(options: any, index: any) {\n  return formatSimpleTemplate(options.stageColumnTitle, { \"@\": options.initialIntervalNumber - 1 + index });\n}\n\nfunction formatTimeLabel(options: any, offset: any) {\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  const interval = momentInterval[options.timeInterval];\n  return options.initialDate\n    .clone()\n    .add(offset, interval)\n    .format(options.timeLabelFormat);\n}\n\nfunction CorneliusHeader({ options, maxRowLength }: any) {\n  // eslint-disable-line react/prop-types\n  const cells = [];\n  for (let i = 1; i < maxRowLength; i += 1) {\n    cells.push(\n      <th key={`col${i}`} className=\"cornelius-stage\">\n        {formatStageTitle(options, i)}\n      </th>\n    );\n  }\n\n  return (\n    <tr>\n      <th className=\"cornelius-time\">{options.timeColumnTitle}</th>\n      <th className=\"cornelius-people\">{options.peopleColumnTitle}</th>\n      {cells}\n    </tr>\n  );\n}\n\nfunction CorneliusRow({ options, data, index, maxRowLength }: any) {\n  // eslint-disable-line react/prop-types\n  const baseValue = data[0] || 0;\n\n  const cells = [];\n  for (let i = 1; i < maxRowLength; i += 1) {\n    const value = data[i];\n    const percentageValue = isFinite(value / baseValue) ? (value / baseValue) * 100 : null;\n    const cellProps = { key: `col${i}` };\n\n    if (isNil(percentageValue)) {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'className' does not exist on type '{ key... Remove this comment to see the full error message\n      cellProps.className = \"cornelius-empty\";\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'children' does not exist on type '{ key:... Remove this comment to see the full error message\n      cellProps.children = options.noValuePlaceholder;\n    } else {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'className' does not exist on type '{ key... Remove this comment to see the full error message\n      cellProps.className = options.displayAbsoluteValues ? \"cornelius-absolute\" : \"cornelius-percentage\";\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'children' does not exist on type '{ key:... Remove this comment to see the full error message\n      cellProps.children = options.displayAbsoluteValues\n        ? options.formatNumber(value)\n        : options.formatPercent(percentageValue);\n\n      const backgroundColor = options.getColorForValue(percentageValue);\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'style' does not exist on type '{ key: st... Remove this comment to see the full error message\n      cellProps.style = {\n        backgroundColor,\n        color: chooseTextColorForBackground(backgroundColor),\n      };\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'style' does not exist on type '{ key: st... Remove this comment to see the full error message\n      if (isLightColor(cellProps.style.color)) {\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'className' does not exist on type '{ key... Remove this comment to see the full error message\n        cellProps.className += \" cornelius-white-text\";\n      }\n\n      if (options.rawNumberOnHover && !options.displayAbsoluteValues) {\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'children' does not exist on type '{ key:... Remove this comment to see the full error message\n        cellProps.children = (\n          <Tooltip title={options.formatNumber(value)} mouseEnterDelay={0} mouseLeaveDelay={0}>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'children' does not exist on type '{ key:... Remove this comment to see the full error message */}\n            <div>{cellProps.children}</div>\n          </Tooltip>\n        );\n      }\n    }\n\n    cells.push(<td {...cellProps} />);\n  }\n\n  return (\n    <tr>\n      <td className=\"cornelius-label\">{formatTimeLabel(options, index)}</td>\n      <td className=\"cornelius-people\">{options.formatNumber(baseValue)}</td>\n      {cells}\n    </tr>\n  );\n}\n\ntype OwnCorneliusProps = {\n  data?: number[][];\n  options?: {\n    initialDate: any; // TODO: PropTypes.instanceOf(Date)\n    timeInterval?: \"daily\" | \"weekly\" | \"monthly\" | \"yearly\";\n    noValuePlaceholder?: string;\n    rawNumberOnHover?: boolean;\n    displayAbsoluteValues?: boolean;\n    initialIntervalNumber?: number;\n    maxColumns?: number;\n    title?: string;\n    timeColumnTitle?: string;\n    peopleColumnTitle?: string;\n    stageColumnTitle?: string;\n    numberFormat?: string;\n    percentFormat?: string;\n    timeLabelFormat?: string;\n    colors?: {\n      min?: string;\n      max?: string;\n      steps?: number;\n    };\n  };\n};\n\nconst corneliusDefaultProps = {\n  data: [],\n  options: {},\n};\n\ntype CorneliusProps = OwnCorneliusProps & typeof corneliusDefaultProps;\n\nexport default function Cornelius({ data, options }: CorneliusProps) {\n  options = useMemo(() => prepareOptions(options), [options]);\n\n  const maxRowLength = useMemo(\n    () =>\n      min([\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'length' does not exist on type 'number'.\n        max(map(data, d => d.length)) || 0,\n        // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.\n        options.maxColumns + 1, // each row includes totals, but `maxColumns` is only for stage columns\n      ]),\n    [data, options.maxColumns]\n  );\n\n  if (data.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"cornelius-container\">\n      {options.title && <div className=\"cornelius-title\">{options.title}</div>}\n\n      <table className=\"cornelius-table\">\n        <thead>\n          <CorneliusHeader options={options} maxRowLength={maxRowLength} />\n        </thead>\n        <tbody>\n          {map(data, (row, index) => (\n            <CorneliusRow key={`row${index}`} options={options} data={row} index={index} maxRowLength={maxRowLength} />\n          ))}\n        </tbody>\n      </table>\n    </div>\n  );\n}\n\nCornelius.defaultProps = corneliusDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/Editor/AppearanceSettings.tsx",
    "content": "import React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Input, Checkbox, ContextHelp } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function AppearanceSettings({ options, onOptionsChange }: any) {\n  const [debouncedOnOptionsChange] = useDebouncedCallback(onOptionsChange, 200);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label=\"Time Column Title\"\n          defaultValue={options.timeColumnTitle}\n          onChange={(e: any) => debouncedOnOptionsChange({ timeColumnTitle: e.target.value })}\n        />\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label=\"People Column Title\"\n          defaultValue={options.peopleColumnTitle}\n          onChange={(e: any) => debouncedOnOptionsChange({ peopleColumnTitle: e.target.value })}\n        />\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label={\n            <React.Fragment>\n              Stage Column Title\n              <ContextHelp placement=\"topRight\" arrowPointAtCenter>\n                {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message */}\n                <div>\n                  Use <code>{\"{{ @ }}\"}</code> to insert a stage number\n                </div>\n              </ContextHelp>\n            </React.Fragment>\n          }\n          defaultValue={options.stageColumnTitle}\n          onChange={(e: any) => debouncedOnOptionsChange({ stageColumnTitle: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label={\n            <React.Fragment>\n              Number Values Format\n              <ContextHelp.NumberFormatSpecs />\n            </React.Fragment>\n          }\n          defaultValue={options.numberFormat}\n          onChange={(e: any) => debouncedOnOptionsChange({ numberFormat: e.target.value })}\n        />\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label={\n            <React.Fragment>\n              Percent Values Format\n              <ContextHelp.NumberFormatSpecs />\n            </React.Fragment>\n          }\n          defaultValue={options.percentFormat}\n          onChange={(e: any) => debouncedOnOptionsChange({ percentFormat: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label=\"No Value Placeholder\"\n          defaultValue={options.noValuePlaceholder}\n          onChange={(e: any) => debouncedOnOptionsChange({ noValuePlaceholder: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          defaultChecked={options.showTooltips}\n          onChange={event => onOptionsChange({ showTooltips: event.target.checked })}>\n          Show Tooltips\n        </Checkbox>\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          defaultChecked={options.percentValues}\n          onChange={event => onOptionsChange({ percentValues: event.target.checked })}>\n          Normalize Values to Percentage\n        </Checkbox>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nAppearanceSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/Editor/ColorsSettings.tsx",
    "content": "import { isFinite } from \"lodash\";\nimport React from \"react\";\nimport { Section, ColorPicker, InputNumber } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport DefaultColorPalette from \"@/visualizations/ColorPalette\";\n\nconst ColorPalette = {\n  White: \"#FFFFFF\",\n  ...DefaultColorPalette,\n};\n\nconst minSteps = 3;\nconst maxSteps = 20;\n\nfunction validateSteps(value: any) {\n  value = isFinite(value) ? value : parseInt(value, 10);\n  value = isFinite(value) ? value : 0;\n  return Math.max(minSteps, Math.min(value, maxSteps));\n}\n\nexport default function ColorsSettings({ options, onOptionsChange }: any) {\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <ColorPicker\n          layout=\"horizontal\"\n          label=\"Min Color\"\n          presetColors={ColorPalette}\n          interactive\n          color={options.colors.min}\n          onChange={(min: any) => onOptionsChange({ colors: { min } })}\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n          addonAfter={<ColorPicker.Label color={options.colors.min} presetColors={ColorPalette} />}\n        />\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <ColorPicker\n          layout=\"horizontal\"\n          label=\"Max Color\"\n          presetColors={ColorPalette}\n          interactive\n          color={options.colors.max}\n          onChange={(max: any) => onOptionsChange({ colors: { max } })}\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n          addonAfter={<ColorPicker.Label color={options.colors.max} presetColors={ColorPalette} />}\n        />\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <InputNumber\n          layout=\"horizontal\"\n          label=\"Steps\"\n          min={minSteps}\n          max={maxSteps}\n          value={options.colors.steps}\n          onChange={(value: any) => onOptionsChange({ colors: { steps: validateSteps(value) } })}\n        />\n      </Section>\n    </React.Fragment>\n  );\n}\n\nColorsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/Editor/ColumnsSettings.tsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport { Section, Select } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function ColumnsSettings({ options, data, onOptionsChange }: any) {\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Date (Bucket)\"\n          data-test=\"Cohort.DateColumn\"\n          value={options.dateColumn}\n          onChange={(dateColumn: any) => onOptionsChange({ dateColumn })}>\n          {map(data.columns, ({ name }) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={name} data-test={\"Cohort.DateColumn.\" + name}>\n              {name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Stage\"\n          data-test=\"Cohort.StageColumn\"\n          value={options.stageColumn}\n          onChange={(stageColumn: any) => onOptionsChange({ stageColumn })}>\n          {map(data.columns, ({ name }) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={name} data-test={\"Cohort.StageColumn.\" + name}>\n              {name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Bucket Population Size\"\n          data-test=\"Cohort.TotalColumn\"\n          value={options.totalColumn}\n          onChange={(totalColumn: any) => onOptionsChange({ totalColumn })}>\n          {map(data.columns, ({ name }) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={name} data-test={\"Cohort.TotalColumn.\" + name}>\n              {name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Stage Value\"\n          data-test=\"Cohort.ValueColumn\"\n          value={options.valueColumn}\n          onChange={(valueColumn: any) => onOptionsChange({ valueColumn })}>\n          {map(data.columns, ({ name }) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={name} data-test={\"Cohort.ValueColumn.\" + name}>\n              {name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nColumnsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/Editor/OptionsSettings.tsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport { Section, Select } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nconst CohortTimeIntervals = {\n  daily: \"Daily\",\n  weekly: \"Weekly\",\n  monthly: \"Monthly\",\n};\n\nconst CohortModes = {\n  diagonal: \"Fill gaps with zeros\",\n  simple: \"Show data as is\",\n};\n\nexport default function OptionsSettings({ options, onOptionsChange }: any) {\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Time Interval\"\n          data-test=\"Cohort.TimeInterval\"\n          value={options.timeInterval}\n          onChange={(timeInterval: any) => onOptionsChange({ timeInterval })}>\n          {map(CohortTimeIntervals, (name, value) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={value} data-test={\"Cohort.TimeInterval.\" + value}>\n              {name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Mode\"\n          data-test=\"Cohort.Mode\"\n          value={options.mode}\n          onChange={(mode: any) => onOptionsChange({ mode })}>\n          {map(CohortModes, (name, value) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={value} data-test={\"Cohort.Mode.\" + value}>\n              {name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nOptionsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/Editor/index.ts",
    "content": "import createTabbedEditor from \"@/components/visualizations/editor/createTabbedEditor\";\n\nimport ColumnsSettings from \"./ColumnsSettings\";\nimport OptionsSettings from \"./OptionsSettings\";\nimport ColorsSettings from \"./ColorsSettings\";\nimport AppearanceSettings from \"./AppearanceSettings\";\n\nexport default createTabbedEditor([\n  { key: \"Columns\", title: \"Columns\", component: ColumnsSettings },\n  { key: \"Options\", title: \"Options\", component: OptionsSettings },\n  { key: \"Colors\", title: \"Colors\", component: ColorsSettings },\n  { key: \"Appearance\", title: \"Appearance\", component: AppearanceSettings },\n]);\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/Renderer.tsx",
    "content": "import React, { useMemo } from \"react\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\n\nimport prepareData from \"./prepareData\";\nimport \"./renderer.less\";\n\nimport Cornelius from \"./Cornelius\";\n\nexport default function Renderer({ data, options }: any) {\n  const { data: cohortData, initialDate } = useMemo(() => prepareData(data, options), [data, options]);\n\n  const corneliusOptions = useMemo(\n    () => ({\n      initialDate,\n      timeInterval: options.timeInterval,\n\n      noValuePlaceholder: options.noValuePlaceholder,\n      rawNumberOnHover: options.showTooltips,\n      displayAbsoluteValues: !options.percentValues,\n\n      timeColumnTitle: options.timeColumnTitle,\n      peopleColumnTitle: options.peopleColumnTitle,\n      stageColumnTitle: options.stageColumnTitle,\n\n      numberFormat: options.numberFormat,\n      percentFormat: options.percentFormat,\n\n      colors: options.colors,\n    }),\n    [options, initialDate]\n  );\n\n  if (cohortData.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"cohort-visualization-container\">\n      <Cornelius data={cohortData} options={corneliusOptions} />\n    </div>\n  );\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/cornelius.less",
    "content": "@import \"../variables\";\n\n.cornelius-container {\n  .cornelius-title {\n    text-align: center;\n    padding-bottom: 10px;\n    font-weight: bold;\n    font-size: 14pt;\n    color: @table-header-color;\n    border-collapse: collapse;\n  }\n\n  .cornelius-table {\n    font-size: 9pt;\n    border-spacing: 0;\n    border: 1px solid #e4e4e4;\n    border-collapse: collapse;\n\n    th,\n    td {\n      text-align: center;\n      padding: 10px;\n      border: 1px solid #e4e4e4;\n      color: @table-header-color;\n      font-weight: bold;\n    }\n\n    .cornelius-label {\n      text-align: left;\n    }\n\n    .cornelius-empty,\n    .cornelius-percentage,\n    .cornelius-absolute {\n      font-weight: normal;\n      border: none;\n    }\n\n    .cornelius-label,\n    .cornelius-people,\n    .cornelius-stage {\n      font-weight: bold;\n      color: @table-header-color;\n    }\n\n    .cornelius-percentage,\n    .cornelius-absolute {\n      &.cornelius-white-text {\n        text-shadow: 1px 1px 1px #000000;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/getOptions.ts",
    "content": "import { merge } from \"lodash\";\nimport ColorPalette from \"@/visualizations/ColorPalette\";\n\nconst DEFAULT_OPTIONS = {\n  timeInterval: \"daily\",\n  mode: \"diagonal\",\n  dateColumn: \"date\",\n  stageColumn: \"day_number\",\n  totalColumn: \"total\",\n  valueColumn: \"value\",\n\n  showTooltips: true,\n  percentValues: true,\n\n  timeColumnTitle: \"Time\",\n  peopleColumnTitle: \"Users\",\n  stageColumnTitle: \"{{ @ }}\",\n\n  numberFormat: \"0,0[.]00\",\n  percentFormat: \"0.00%\",\n  noValuePlaceholder: \"-\",\n\n  colors: {\n    min: \"#ffffff\",\n    max: ColorPalette[\"Dark Blue\"],\n    steps: 7,\n  },\n};\n\nexport default function getOptions(options: any) {\n  return merge({}, DEFAULT_OPTIONS, options);\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/index.ts",
    "content": "import getOptions from \"./getOptions\";\nimport Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nexport default {\n  type: \"COHORT\",\n  name: \"Cohort\",\n  getOptions,\n  Renderer,\n  Editor,\n\n  autoHeight: true,\n  defaultRows: 8,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/prepareData.ts",
    "content": "import _ from \"lodash\";\nimport moment from \"moment\";\n\nconst momentInterval = {\n  weekly: \"weeks\",\n  daily: \"days\",\n  monthly: \"months\",\n};\n\nfunction groupData(sortedData: any) {\n  const result = {};\n\n  _.each(sortedData, item => {\n    const date = moment(item.date);\n    const groupKey = date.valueOf();\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    result[groupKey] = result[groupKey] || {\n      date,\n      total: parseInt(item.total, 10) || 0,\n      values: {},\n    };\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    result[groupKey].values[item.stage] = parseInt(item.value, 10) || null;\n  });\n\n  return _.values(result);\n}\n\nfunction prepareDiagonalData(sortedData: any, options: any) {\n  const timeInterval = options.timeInterval;\n  const grouped = groupData(sortedData);\n  const firstStage = _.min(_.map(sortedData, i => i.stage));\n  // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n  const stageCount = moment(_.last(grouped).date).diff(_.first(grouped).date, momentInterval[timeInterval]);\n  let lastStage = firstStage + stageCount;\n\n  let previousDate: any = null;\n\n  const data: any = [];\n  _.each(grouped, group => {\n    if (previousDate !== null) {\n      // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n      let diff = Math.abs(previousDate.diff(group.date, momentInterval[timeInterval]));\n      while (diff > 1) {\n        const row = [0];\n        for (let stage = firstStage; stage <= lastStage; stage += 1) {\n          // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n          row.push(group.values[stage] || 0);\n        }\n        data.push(row);\n        // It should be diagonal, so decrease count of stages for each next row\n        lastStage -= 1;\n        diff -= 1;\n      }\n    }\n\n    // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n    previousDate = group.date;\n\n    // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n    const row = [group.total];\n    for (let stage = firstStage; stage <= lastStage; stage += 1) {\n      // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n      row.push(group.values[stage] || 0);\n    }\n    // It should be diagonal, so decrease count of stages for each next row\n    lastStage -= 1;\n\n    data.push(row);\n  });\n\n  return data;\n}\n\nfunction prepareSimpleData(sortedData: any, options: any) {\n  const timeInterval = options.timeInterval;\n  const grouped = groupData(sortedData);\n  const stages = _.map(sortedData, i => i.stage);\n  const firstStage = _.min(stages);\n  const lastStage = _.max(stages);\n\n  let previousDate: any = null;\n\n  const data: any = [];\n  _.each(grouped, group => {\n    if (previousDate !== null) {\n      // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n      let diff = Math.abs(previousDate.diff(group.date, momentInterval[timeInterval]));\n      while (diff > 1) {\n        data.push([0]);\n        diff -= 1;\n      }\n    }\n\n    // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n    previousDate = group.date;\n\n    // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n    const row = [group.total];\n    for (let stage = firstStage; stage <= lastStage; stage += 1) {\n      // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n      row.push(group.values[stage]);\n    }\n\n    data.push(row);\n  });\n\n  return data;\n}\n\nfunction isDataValid(rawData: any, options: any) {\n  const columnNames = _.map(rawData.columns, c => c.name);\n  return (\n    rawData.rows.length > 0 &&\n    _.includes(columnNames, options.dateColumn) &&\n    _.includes(columnNames, options.stageColumn) &&\n    _.includes(columnNames, options.totalColumn) &&\n    _.includes(columnNames, options.valueColumn)\n  );\n}\n\nexport default function prepareData(rawData: any, options: any) {\n  if (!isDataValid(rawData, options)) {\n    return { data: [], initialDate: null };\n  }\n\n  rawData = _.map(rawData.rows, item => ({\n    date: item[options.dateColumn],\n    stage: parseInt(item[options.stageColumn], 10),\n    total: parseFloat(item[options.totalColumn]),\n    value: parseFloat(item[options.valueColumn]),\n  }));\n  const sortedData = _.sortBy(rawData, r => r.date + r.stage);\n  const initialDate = moment(sortedData[0].date).toDate();\n\n  let data;\n  switch (options.mode) {\n    case \"simple\":\n      data = prepareSimpleData(sortedData, options);\n      break;\n    default:\n      data = prepareDiagonalData(sortedData, options);\n      break;\n  }\n\n  return { data, initialDate };\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/cohort/renderer.less",
    "content": "@import \"../variables\";\n\n.cohort-visualization-container {\n  .cornelius-table {\n    width: 100%;\n\n    &,\n    tr,\n    th,\n    td {\n      border-color: #f0f0f0;\n    }\n\n    .cornelius-time,\n    .cornelius-label,\n    .cornelius-stage,\n    .cornelius-people {\n      background-color: fade(@visualizations-gray, 3%);\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/counter/Editor/FormatSettings.tsx",
    "content": "import React from \"react\";\nimport { Section, Input, InputNumber, Switch } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nimport { isValueNumber } from \"../utils\";\n\nexport default function FormatSettings({ options, data, onOptionsChange }: any) {\n  const inputsEnabled = isValueNumber(data.rows, options);\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <InputNumber\n          layout=\"horizontal\"\n          label=\"Formatting Decimal Place\"\n          data-test=\"Counter.Formatting.DecimalPlace\"\n          defaultValue={options.stringDecimal}\n          disabled={!inputsEnabled}\n          onChange={(stringDecimal: any) => onOptionsChange({ stringDecimal })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label=\"Formatting Decimal Character\"\n          data-test=\"Counter.Formatting.DecimalCharacter\"\n          defaultValue={options.stringDecChar}\n          disabled={!inputsEnabled}\n          onChange={(e: any) => onOptionsChange({ stringDecChar: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label=\"Formatting Thousands Separator\"\n          data-test=\"Counter.Formatting.ThousandsSeparator\"\n          defaultValue={options.stringThouSep}\n          disabled={!inputsEnabled}\n          onChange={(e: any) => onOptionsChange({ stringThouSep: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label=\"Formatting String Prefix\"\n          data-test=\"Counter.Formatting.StringPrefix\"\n          defaultValue={options.stringPrefix}\n          disabled={!inputsEnabled}\n          onChange={(e: any) => onOptionsChange({ stringPrefix: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label=\"Formatting String Suffix\"\n          data-test=\"Counter.Formatting.StringSuffix\"\n          defaultValue={options.stringSuffix}\n          disabled={!inputsEnabled}\n          onChange={(e: any) => onOptionsChange({ stringSuffix: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n        <Switch\n          data-test=\"Counter.Formatting.FormatTargetValue\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          defaultChecked={options.formatTargetValue}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(formatTargetValue: any) => any' is not assi... Remove this comment to see the full error message\n          onChange={(formatTargetValue: any) => onOptionsChange({ formatTargetValue })}>\n          Format Target Value\n        </Switch>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nFormatSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/counter/Editor/GeneralSettings.tsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport { Section, Select, Input, InputNumber, Switch } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function GeneralSettings({ options, data, visualizationName, onOptionsChange }: any) {\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label=\"Counter Label\"\n          data-test=\"Counter.General.Label\"\n          defaultValue={options.counterLabel}\n          placeholder={visualizationName}\n          onChange={(e: any) => onOptionsChange({ counterLabel: e.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Counter Value Column Name\"\n          data-test=\"Counter.General.ValueColumn\"\n          defaultValue={options.counterColName}\n          disabled={options.countRow}\n          onChange={(counterColName: any) => onOptionsChange({ counterColName })}>\n          {map(data.columns, col => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={col.name} data-test={\"Counter.General.ValueColumn.\" + col.name}>\n              {col.name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <InputNumber\n          layout=\"horizontal\"\n          label=\"Counter Value Row Number\"\n          data-test=\"Counter.General.ValueRowNumber\"\n          defaultValue={options.rowNumber}\n          disabled={options.countRow}\n          onChange={(rowNumber: any) => onOptionsChange({ rowNumber })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Target Value Column Name\"\n          data-test=\"Counter.General.TargetValueColumn\"\n          defaultValue={options.targetColName}\n          onChange={(targetColName: any) => onOptionsChange({ targetColName })}>\n          {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          <Select.Option value=\"\">No target value</Select.Option>\n          {map(data.columns, col => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={col.name} data-test={\"Counter.General.TargetValueColumn.\" + col.name}>\n              {col.name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <InputNumber\n          layout=\"horizontal\"\n          label=\"Target Value Row Number\"\n          data-test=\"Counter.General.TargetValueRowNumber\"\n          defaultValue={options.targetRowNumber}\n          onChange={(targetRowNumber: any) => onOptionsChange({ targetRowNumber })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n        <Switch\n          data-test=\"Counter.General.CountRows\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          defaultChecked={options.countRow}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(countRow: any) => any' is not assignable to... Remove this comment to see the full error message\n          onChange={(countRow: any) => onOptionsChange({ countRow })}>\n          Count Rows\n        </Switch>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nGeneralSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/counter/Editor/index.ts",
    "content": "import createTabbedEditor from \"@/components/visualizations/editor/createTabbedEditor\";\n\nimport GeneralSettings from \"./GeneralSettings\";\nimport FormatSettings from \"./FormatSettings\";\n\nexport default createTabbedEditor([\n  { key: \"General\", title: \"General\", component: GeneralSettings },\n  { key: \"Format\", title: \"Format\", component: FormatSettings },\n]);\n"
  },
  {
    "path": "viz-lib/src/visualizations/counter/Renderer.tsx",
    "content": "import { isFinite } from \"lodash\";\nimport React, { useState, useEffect } from \"react\";\nimport cx from \"classnames\";\nimport resizeObserver from \"@/services/resizeObserver\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\n\nimport { getCounterData } from \"./utils\";\n\nimport \"./render.less\";\n\nfunction getCounterStyles(scale: any) {\n  return {\n    msTransform: `scale(${scale})`,\n    MozTransform: `scale(${scale})`,\n    WebkitTransform: `scale(${scale})`,\n    transform: `scale(${scale})`,\n  };\n}\n\nfunction getCounterScale(container: any) {\n  const inner = container.firstChild;\n  const scale = Math.min(container.offsetWidth / inner.offsetWidth, container.offsetHeight / inner.offsetHeight);\n  return Number(isFinite(scale) ? scale : 1).toFixed(2); // keep only two decimal places\n}\n\nexport default function Renderer({ data, options, visualizationName }: any) {\n  const [scale, setScale] = useState(\"1.00\");\n  const [container, setContainer] = useState(null);\n\n  useEffect(() => {\n    if (container) {\n      const unwatch = resizeObserver(container, () => {\n        setScale(getCounterScale(container));\n      });\n      return unwatch;\n    }\n  }, [container]);\n\n  useEffect(() => {\n    if (container) {\n      // update scaling when options or data change (new formatting, values, etc.\n      // may change inner container dimensions which will not be tracked by `resizeObserver`);\n      setScale(getCounterScale(container));\n    }\n  }, [data, options, container]);\n\n  const {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'showTrend' does not exist on type '{}'.\n    showTrend,\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'trendPositive' does not exist on type '{... Remove this comment to see the full error message\n    trendPositive,\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'counterValue' does not exist on type '{}... Remove this comment to see the full error message\n    counterValue,\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'counterValueTooltip' does not exist on t... Remove this comment to see the full error message\n    counterValueTooltip,\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'targetValue' does not exist on type '{}'... Remove this comment to see the full error message\n    targetValue,\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'targetValueTooltip' does not exist on ty... Remove this comment to see the full error message\n    targetValueTooltip,\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'counterLabel' does not exist on type '{}... Remove this comment to see the full error message\n    counterLabel,\n  } = getCounterData(data.rows, options, visualizationName);\n  return (\n    <div\n      className={cx(\"counter-visualization-container\", {\n        \"trend-positive\": showTrend && trendPositive,\n        \"trend-negative\": showTrend && !trendPositive,\n      })}>\n      {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'Dispatch<SetStateAction<null>>' is not assig... Remove this comment to see the full error message */}\n      <div className=\"counter-visualization-content\" ref={setContainer}>\n        <div style={getCounterStyles(scale)}>\n          <div className=\"counter-visualization-value\" title={counterValueTooltip}>\n            {counterValue}\n          </div>\n          {targetValue && (\n            <div className=\"counter-visualization-target\" title={targetValueTooltip}>\n              ({targetValue})\n            </div>\n          )}\n          <div className=\"counter-visualization-label\">{counterLabel}</div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/counter/index.ts",
    "content": "import Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nconst DEFAULT_OPTIONS = {\n  counterLabel: \"\",\n  counterColName: \"counter\",\n  rowNumber: 1,\n  targetRowNumber: 1,\n  stringDecimal: 0,\n  stringDecChar: \".\",\n  stringThouSep: \",\",\n  tooltipFormat: \"0,0.000\", // TODO: Show in editor\n};\n\nexport default {\n  type: \"COUNTER\",\n  name: \"Counter\",\n  getOptions: (options: any) => ({\n    ...DEFAULT_OPTIONS,\n    ...options,\n  }),\n  Renderer,\n  Editor,\n\n  defaultColumns: 4,\n  defaultRows: 5,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/counter/render.less",
    "content": ".counter-visualization-container {\n  display: block;\n  text-align: center;\n  padding: 15px 10px;\n  overflow: hidden;\n  position: relative;\n\n  .counter-visualization-content {\n    margin: 0;\n    padding: 0;\n    font-size: 80px;\n    line-height: normal;\n    overflow: hidden;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n\n    .counter-visualization-value,\n    .counter-visualization-target {\n      font-size: 1em;\n      display: block;\n    }\n\n    .counter-visualization-label {\n      font-size: 0.5em;\n      display: block;\n    }\n\n    .counter-visualization-target {\n      color: #ccc;\n    }\n\n    .counter-visualization-label {\n      font-size: 0.5em;\n      display: block;\n    }\n  }\n\n  &.trend-positive .counter-visualization-value {\n    color: #5cb85c;\n  }\n\n  &.trend-negative .counter-visualization-value {\n    color: #d9534f;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/counter/utils.test.ts",
    "content": "import { getCounterData } from \"./utils\";\n\nlet dummy: any;\n\ndescribe(\"Visualizations -> Counter -> Utils\", () => {\n  beforeEach(() => {\n    dummy = {\n      rows: [\n        { city: \"New York City\", population: 18604000 },\n        { city: \"Shangai\", population: 24484000 },\n        { city: \"Tokyo\", population: 38140000 },\n      ],\n      options: {},\n      visualisationName: \"Visualisation Name\",\n      result: {\n        counterLabel: \"Visualisation Name\",\n        counterValue: \"\",\n        targetValue: null,\n        counterValueTooltip: \"\",\n        targetValueTooltip: \"\",\n      },\n    };\n  });\n\n  describe(\"getCounterData()\", () => {\n    describe('\"Count rows\" option is disabled', () => {\n      test(\"No target and counter values return empty result\", () => {\n        const result = getCounterData(dummy.rows, dummy.options, dummy.visualisationName);\n        expect(result).toEqual({\n          ...dummy.result,\n          showTrend: false,\n        });\n      });\n\n      test('\"Counter label\" overrides vizualization name', () => {\n        const result = getCounterData(dummy.rows, { counterLabel: \"Counter Label\" }, dummy.visualisationName);\n        expect(result).toEqual({\n          ...dummy.result,\n          counterLabel: \"Counter Label\",\n          showTrend: false,\n        });\n      });\n\n      test('\"Counter Value Column Name\" must be set to a correct non empty value', () => {\n        const result = getCounterData(dummy.rows, { rowNumber: 3 }, dummy.visualisationName);\n        expect(result).toEqual({\n          ...dummy.result,\n          showTrend: false,\n        });\n\n        const result2 = getCounterData(dummy.rows, { counterColName: \"missingColumn\" }, dummy.visualisationName);\n        expect(result2).toEqual({\n          ...dummy.result,\n          showTrend: false,\n        });\n      });\n\n      test('\"Counter Value Column Name\" uses correct column', () => {\n        const result = getCounterData(dummy.rows, { counterColName: \"population\" }, dummy.visualisationName);\n        expect(result).toEqual({\n          ...dummy.result,\n          counterValue: \"18,604,000.000\",\n          counterValueTooltip: \"18,604,000\",\n          showTrend: false,\n        });\n      });\n\n      test(\"Counter and target values return correct result including trend\", () => {\n        const result = getCounterData(\n          dummy.rows,\n          {\n            rowNumber: 1,\n            counterColName: \"population\",\n            targetRowNumber: 2,\n            targetColName: \"population\",\n          },\n          dummy.visualisationName\n        );\n        expect(result).toEqual({\n          ...dummy.result,\n          counterValue: \"18,604,000.000\",\n          counterValueTooltip: \"18,604,000\",\n          targetValue: \"24484000\",\n          targetValueTooltip: \"24,484,000\",\n          showTrend: true,\n          trendPositive: false,\n        });\n\n        const result2 = getCounterData(\n          dummy.rows,\n          {\n            rowNumber: 2,\n            counterColName: \"population\",\n            targetRowNumber: 1,\n            targetColName: \"population\",\n          },\n          dummy.visualisationName\n        );\n        expect(result2).toEqual({\n          ...dummy.result,\n          counterValue: \"24,484,000.000\",\n          counterValueTooltip: \"24,484,000\",\n          targetValue: \"18604000\",\n          targetValueTooltip: \"18,604,000\",\n          showTrend: true,\n          trendPositive: true,\n        });\n      });\n    });\n\n    describe('\"Count rows\" option is enabled', () => {\n      beforeEach(() => {\n        dummy.result = {\n          ...dummy.result,\n          counterValue: \"3.000\",\n          counterValueTooltip: \"3\",\n          showTrend: false,\n        };\n      });\n\n      test(\"Rows are counted correctly\", () => {\n        const result = getCounterData(dummy.rows, { countRow: true }, dummy.visualisationName);\n        expect(result).toEqual(dummy.result);\n      });\n\n      test(\"Counter value is ignored\", () => {\n        const result = getCounterData(\n          dummy.rows,\n          {\n            countRow: true,\n            rowNumber: 3,\n            counterColName: \"population\",\n          },\n          dummy.visualisationName\n        );\n        expect(result).toEqual(dummy.result);\n      });\n\n      test(\"Target value and trend are computed correctly\", () => {\n        const result = getCounterData(\n          dummy.rows,\n          {\n            countRow: true,\n            targetRowNumber: 2,\n            targetColName: \"population\",\n          },\n          dummy.visualisationName\n        );\n        expect(result).toEqual({\n          ...dummy.result,\n          targetValue: \"24484000\",\n          targetValueTooltip: \"24,484,000\",\n          showTrend: true,\n          trendPositive: false,\n        });\n      });\n\n      test(\"Empty rows return counter value 0\", () => {\n        const result = getCounterData([], { countRow: true }, dummy.visualisationName);\n        expect(result).toEqual({\n          ...dummy.result,\n          counterValue: \"0.000\",\n          counterValueTooltip: \"0\",\n        });\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/counter/utils.ts",
    "content": "import { isNumber, isFinite, toString } from \"lodash\";\nimport numeral from \"numeral\";\n\n// TODO: allow user to specify number format string instead of delimiters only\n// It will allow to remove this function (move all that weird formatting logic to a migration\n// that will set number format for all existing counter visualization)\nfunction numberFormat(value: any, decimalPoints: any, decimalDelimiter: any, thousandsDelimiter: any) {\n  // Temporarily update locale data (restore defaults after formatting)\n  const locale = numeral.localeData();\n  const savedDelimiters = locale.delimiters;\n\n  // Mimic old behavior - AngularJS `number` filter defaults:\n  // - `,` as thousands delimiter\n  // - `.` as decimal delimiter\n  // - three decimal points\n  locale.delimiters = {\n    thousands: \",\",\n    decimal: \".\",\n  };\n  let formatString = \"0,0.000\";\n  if ((Number.isFinite(decimalPoints) && decimalPoints >= 0) || decimalDelimiter || thousandsDelimiter) {\n    locale.delimiters = {\n      thousands: thousandsDelimiter,\n      decimal: decimalDelimiter || \".\",\n    };\n\n    formatString = \"0,0\";\n    if (decimalPoints > 0) {\n      formatString += \".\";\n      while (decimalPoints > 0) {\n        formatString += \"0\";\n        decimalPoints -= 1;\n      }\n    }\n  }\n  const result = numeral(value).format(formatString);\n\n  locale.delimiters = savedDelimiters;\n  return result;\n}\n\n// 0 - special case, use first record\n// 1..N - 1-based record number from beginning (wraps if greater than dataset size)\n// -1..-N - 1-based record number from end (wraps if greater than dataset size)\nfunction getRowNumber(index: any, rowsCount: any) {\n  index = parseInt(index, 10) || 0;\n  if (index === 0) {\n    return index;\n  }\n  const wrappedIndex = (Math.abs(index) - 1) % rowsCount;\n  return index > 0 ? wrappedIndex : rowsCount - wrappedIndex - 1;\n}\n\nfunction formatValue(value: any, { stringPrefix, stringSuffix, stringDecimal, stringDecChar, stringThouSep }: any) {\n  if (isNumber(value)) {\n    value = numberFormat(value, stringDecimal, stringDecChar, stringThouSep);\n    return toString(stringPrefix) + value + toString(stringSuffix);\n  }\n  return toString(value);\n}\n\nfunction formatTooltip(value: any, formatString: any) {\n  if (isNumber(value)) {\n    return numeral(value).format(formatString);\n  }\n  return toString(value);\n}\n\nexport function getCounterData(rows: any, options: any, visualizationName: any) {\n  const result = {};\n  const rowsCount = rows.length;\n\n  if (rowsCount > 0 || options.countRow) {\n    const counterColName = options.counterColName;\n    const targetColName = options.targetColName;\n\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'counterLabel' does not exist on type '{}... Remove this comment to see the full error message\n    result.counterLabel = options.counterLabel || visualizationName;\n\n    if (options.countRow) {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'counterValue' does not exist on type '{}... Remove this comment to see the full error message\n      result.counterValue = rowsCount;\n    } else if (counterColName) {\n      const rowNumber = getRowNumber(options.rowNumber, rowsCount);\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'counterValue' does not exist on type '{}... Remove this comment to see the full error message\n      result.counterValue = rows[rowNumber][counterColName];\n    }\n\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'showTrend' does not exist on type '{}'.\n    result.showTrend = false;\n\n    if (targetColName) {\n      const targetRowNumber = getRowNumber(options.targetRowNumber, rowsCount);\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'targetValue' does not exist on type '{}'... Remove this comment to see the full error message\n      result.targetValue = rows[targetRowNumber][targetColName];\n\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'counterValue' does not exist on type '{}... Remove this comment to see the full error message\n      if (Number.isFinite(result.counterValue) && isFinite(result.targetValue)) {\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'counterValue' does not exist on type '{}... Remove this comment to see the full error message\n        const delta = result.counterValue - result.targetValue;\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'showTrend' does not exist on type '{}'.\n        result.showTrend = true;\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'trendPositive' does not exist on type '{... Remove this comment to see the full error message\n        result.trendPositive = delta >= 0;\n      }\n    } else {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'targetValue' does not exist on type '{}'... Remove this comment to see the full error message\n      result.targetValue = null;\n    }\n\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'counterValueTooltip' does not exist on t... Remove this comment to see the full error message\n    result.counterValueTooltip = formatTooltip(result.counterValue, options.tooltipFormat);\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'targetValueTooltip' does not exist on ty... Remove this comment to see the full error message\n    result.targetValueTooltip = formatTooltip(result.targetValue, options.tooltipFormat);\n\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'counterValue' does not exist on type '{}... Remove this comment to see the full error message\n    result.counterValue = formatValue(result.counterValue, options);\n\n    if (options.formatTargetValue) {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'targetValue' does not exist on type '{}'... Remove this comment to see the full error message\n      result.targetValue = formatValue(result.targetValue, options);\n    } else {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'targetValue' does not exist on type '{}'... Remove this comment to see the full error message\n      if (isFinite(result.targetValue)) {\n        // @ts-expect-error ts-migrate(2339) FIXME: Property 'targetValue' does not exist on type '{}'... Remove this comment to see the full error message\n        result.targetValue = numeral(result.targetValue).format(\"0[.]00[0]\");\n      }\n    }\n  }\n\n  return result;\n}\n\nexport function isValueNumber(rows: any, options: any) {\n  if (options.countRow) {\n    return true; // array length is always a number\n  }\n\n  const rowsCount = rows.length;\n  if (rowsCount > 0) {\n    const rowNumber = getRowNumber(options.rowNumber, rowsCount);\n    const counterColName = options.counterColName;\n    if (counterColName) {\n      return isNumber(rows[rowNumber][counterColName]);\n    }\n  }\n\n  return false;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/Editor/ColumnEditor.tsx",
    "content": "import React from \"react\";\nimport SharedColumnEditor from \"../../shared/components/ColumnEditor\";\n\ntype OwnProps = {\n  column: {\n    name: string;\n    title?: string;\n    visible?: boolean;\n    alignContent?: \"left\" | \"center\" | \"right\";\n    displayAs?: any;\n    description?: string;\n  };\n  onChange?: (...args: any[]) => any;\n};\n\nconst columnEditorDefaultProps = {\n  onChange: (...args: any[]) => {},\n};\n\ntype Props = OwnProps & typeof columnEditorDefaultProps;\n\nexport default function ColumnEditor({ column, onChange }: Props) {\n  return (\n    <SharedColumnEditor\n      column={column}\n      onChange={onChange}\n      variant=\"details\"\n      showSearch={false}\n    />\n  );\n}\n\nColumnEditor.defaultProps = columnEditorDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/Editor/ColumnsSettings.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport getOptions from \"../getOptions\";\nimport ColumnsSettings from \"./ColumnsSettings\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(options: any, done: any) {\n  const data = {\n    columns: [\n      { name: \"id\", type: \"integer\" },\n      { name: \"name\", type: \"string\" },\n      { name: \"created_at\", type: \"datetime\" },\n    ],\n    rows: [{ id: 1, name: \"test\", created_at: \"2023-01-01T00:00:00Z\" }],\n  };\n  options = getOptions(options, data);\n  return enzyme.mount(\n    <ColumnsSettings\n      visualizationName=\"Details\"\n      data={data}\n      options={options}\n      onOptionsChange={(changedOptions: any) => {\n        expect(changedOptions).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Details -> Editor -> Columns Settings\", () => {\n  test(\"Toggles column visibility\", done => {\n    const el = mount({}, done);\n\n    findByTestID(el, \"Details.Column.id.Visibility\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Changes column title\", done => {\n    const el = mount({}, done);\n    findByTestID(el, \"Details.Column.name.Name\")\n      .last()\n      .simulate(\"click\"); // expand settings\n\n    findByTestID(el, \"Details.Column.name.Title\")\n      .last()\n      .simulate(\"change\", { target: { value: \"Full Name\" } });\n  });\n\n  test(\"Changes column alignment\", done => {\n    const el = mount({}, done);\n    findByTestID(el, \"Details.Column.id.Name\")\n      .last()\n      .simulate(\"click\"); // expand settings\n\n    findByTestID(el, \"Details.Column.id.TextAlignment\")\n      .last()\n      .find('[data-test=\"TextAlignmentSelect.Center\"] input')\n      .simulate(\"change\", { target: { checked: true } });\n  });\n\n  test(\"Changes column description\", done => {\n    const el = mount({}, done);\n    findByTestID(el, \"Details.Column.name.Name\")\n      .last()\n      .simulate(\"click\"); // expand settings\n\n    findByTestID(el, \"Details.Column.name.Description\")\n      .last()\n      .simulate(\"change\", { target: { value: \"User full name\" } });\n  });\n\n  test(\"Changes column display type\", done => {\n    const el = mount({}, done);\n    findByTestID(el, \"Details.Column.created_at.Name\")\n      .last()\n      .simulate(\"click\"); // expand settings\n\n    findByTestID(el, \"Details.Column.created_at.DisplayAs\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Details.Column.created_at.DisplayAs.string\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Hides multiple columns\", done => {\n    const el = mount({}, done);\n\n    findByTestID(el, \"Details.Column.id.Visibility\")\n      .last()\n      .simulate(\"click\");\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/Editor/ColumnsSettings.tsx",
    "content": "import React from \"react\";\nimport SharedColumnsSettings from \"../../shared/components/ColumnsSettings\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function ColumnsSettings({ options, onOptionsChange, data }: any) {\n  return (\n    <SharedColumnsSettings\n      options={options}\n      onOptionsChange={onOptionsChange}\n      variant=\"details\"\n    />\n  );\n}\n\nColumnsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/Editor/__snapshots__/ColumnsSettings.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Details -> Editor -> Columns Settings Changes column alignment 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"center\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"number\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"id\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": \"0,0\",\n      \"order\": 100000,\n      \"title\": \"id\",\n      \"type\": \"integer\",\n      \"visible\": true,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"name\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100001,\n      \"title\": \"name\",\n      \"type\": \"string\",\n      \"visible\": true,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": \"DD/MM/YYYY HH:mm\",\n      \"description\": \"\",\n      \"displayAs\": \"datetime\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"created_at\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100002,\n      \"title\": \"created_at\",\n      \"type\": \"datetime\",\n      \"visible\": true,\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Details -> Editor -> Columns Settings Changes column description 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"number\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"id\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": \"0,0\",\n      \"order\": 100000,\n      \"title\": \"id\",\n      \"type\": \"integer\",\n      \"visible\": true,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"User full name\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"name\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100001,\n      \"title\": \"name\",\n      \"type\": \"string\",\n      \"visible\": true,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": \"DD/MM/YYYY HH:mm\",\n      \"description\": \"\",\n      \"displayAs\": \"datetime\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"created_at\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100002,\n      \"title\": \"created_at\",\n      \"type\": \"datetime\",\n      \"visible\": true,\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Details -> Editor -> Columns Settings Changes column display type 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"number\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"id\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": \"0,0\",\n      \"order\": 100000,\n      \"title\": \"id\",\n      \"type\": \"integer\",\n      \"visible\": true,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"name\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100001,\n      \"title\": \"name\",\n      \"type\": \"string\",\n      \"visible\": true,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": \"DD/MM/YYYY HH:mm\",\n      \"description\": \"\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"created_at\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100002,\n      \"title\": \"created_at\",\n      \"type\": \"datetime\",\n      \"visible\": true,\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Details -> Editor -> Columns Settings Changes column title 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"number\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"id\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": \"0,0\",\n      \"order\": 100000,\n      \"title\": \"id\",\n      \"type\": \"integer\",\n      \"visible\": true,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"name\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100001,\n      \"title\": \"Full Name\",\n      \"type\": \"string\",\n      \"visible\": true,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": \"DD/MM/YYYY HH:mm\",\n      \"description\": \"\",\n      \"displayAs\": \"datetime\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"created_at\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100002,\n      \"title\": \"created_at\",\n      \"type\": \"datetime\",\n      \"visible\": true,\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Details -> Editor -> Columns Settings Hides multiple columns 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"number\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"id\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": \"0,0\",\n      \"order\": 100000,\n      \"title\": \"id\",\n      \"type\": \"integer\",\n      \"visible\": false,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"name\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100001,\n      \"title\": \"name\",\n      \"type\": \"string\",\n      \"visible\": true,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": \"DD/MM/YYYY HH:mm\",\n      \"description\": \"\",\n      \"displayAs\": \"datetime\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"created_at\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100002,\n      \"title\": \"created_at\",\n      \"type\": \"datetime\",\n      \"visible\": true,\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Details -> Editor -> Columns Settings Toggles column visibility 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"number\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"id\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": \"0,0\",\n      \"order\": 100000,\n      \"title\": \"id\",\n      \"type\": \"integer\",\n      \"visible\": false,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"name\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100001,\n      \"title\": \"name\",\n      \"type\": \"string\",\n      \"visible\": true,\n    },\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": \"DD/MM/YYYY HH:mm\",\n      \"description\": \"\",\n      \"displayAs\": \"datetime\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"created_at\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100002,\n      \"title\": \"created_at\",\n      \"type\": \"datetime\",\n      \"visible\": true,\n    },\n  ],\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/Editor/editor.less",
    "content": ".details-visualization-editor-columns {\n  .ant-collapse {\n    background: transparent;\n  }\n\n  .ant-collapse-item {\n    background: #ffffff;\n\n    .drag-handle {\n      height: 20px;\n      margin-left: -16px;\n      padding: 0 16px;\n    }\n  }\n\n  .details-editor-columns-dragged-item {\n    z-index: 1;\n  }\n}\n\n.details-visualization-editor-column {\n  padding-left: 6px;\n\n  .image-dimension-selector {\n    display: flex;\n    align-items: center;\n\n    .image-dimension-selector-spacer {\n      padding-left: 5px;\n      padding-right: 5px;\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/Editor/index.tsx",
    "content": "import createTabbedEditor from \"@/components/visualizations/editor/createTabbedEditor\";\n\nimport ColumnsSettings from \"./ColumnsSettings\";\n\nimport \"./editor.less\";\n\nexport default createTabbedEditor([\n  { key: \"Columns\", title: \"Columns\", component: ColumnsSettings },\n]);\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/Renderer.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\nimport moment from \"moment\";\n\nimport Renderer from \"./Renderer\";\nimport getOptions from \"./getOptions\";\n\nfunction mount(data: any, options: any = {}) {\n  options = getOptions(options, data);\n  return enzyme.mount(<Renderer data={data} options={options} />);\n}\n\ndescribe(\"Visualizations -> Details -> Renderer\", () => {\n  const sampleData = {\n    columns: [\n      { name: \"id\", type: \"integer\" },\n      { name: \"name\", type: \"string\" },\n      { name: \"created_at\", type: \"datetime\" },\n      { name: \"active\", type: \"boolean\" },\n    ],\n    rows: [\n      {\n        id: 1,\n        name: \"John Doe\",\n        created_at: moment(\"2023-01-01T12:00:00Z\"),\n        active: true,\n      },\n      {\n        id: 2,\n        name: \"Jane Smith\",\n        created_at: moment(\"2023-02-01T12:00:00Z\"),\n        active: false,\n      },\n    ],\n  };\n\n  test(\"Renders all columns when no options provided\", () => {\n    const el = mount(sampleData);\n    \n    // Check that the component renders with expected data\n    expect(el.text()).toContain(\"id\");\n    expect(el.text()).toContain(\"name\");\n    expect(el.text()).toContain(\"created_at\");\n    expect(el.text()).toContain(\"active\");\n    expect(el.text()).toContain(\"1\"); // id value\n    expect(el.text()).toContain(\"John Doe\"); // name value\n  });\n\n  test(\"Renders only visible columns\", () => {\n    const options = {\n      columns: [\n        { name: \"id\", visible: true, order: 0 },\n        { name: \"name\", visible: false, order: 1 },\n        { name: \"created_at\", visible: true, order: 2 },\n        { name: \"active\", visible: false, order: 3 },\n      ],\n    };\n    \n    const el = mount(sampleData, options);\n    \n    // Should show id and created_at, but not name and active\n    expect(el.text()).toContain(\"id\");\n    expect(el.text()).toContain(\"created_at\");\n    expect(el.text()).not.toContain(\"name\");\n    expect(el.text()).not.toContain(\"active\");\n  });\n\n  test(\"Respects column order\", () => {\n    const options = {\n      columns: [\n        { name: \"active\", visible: true, order: 0 },\n        { name: \"name\", visible: true, order: 1 },\n        { name: \"created_at\", visible: true, order: 2 },\n        { name: \"id\", visible: true, order: 3 },\n      ],\n    };\n\n    const el = mount(sampleData, options);\n\n    // Get all description item labels in order\n    const labels = el.find('.ant-descriptions-item-label').map(node => node.text());\n\n    // Should appear in order: active (0), name (1), created_at (2), id (3)\n    expect(labels).toEqual(['active', 'name', 'created_at', 'id']);\n  });\n\n  test(\"Uses custom column titles\", () => {\n    const options = {\n      columns: [\n        { name: \"id\", visible: true, title: \"User ID\", order: 0 },\n        { name: \"name\", visible: true, title: \"Full Name\", order: 1 },\n      ],\n    };\n    \n    const el = mount(sampleData, options);\n    \n    expect(el.text()).toContain(\"User ID\");\n    expect(el.text()).toContain(\"Full Name\");\n  });\n\n  test(\"Applies text alignment\", () => {\n    const options = {\n      columns: [\n        { name: \"id\", visible: true, alignContent: \"center\", order: 0 },\n        { name: \"name\", visible: true, alignContent: \"right\", order: 1 },\n      ],\n    };\n    \n    const el = mount(sampleData, options);\n    \n    // Check that alignment styles are applied\n    const alignedDivs = el.find('div[style]');\n    expect(alignedDivs.length).toBeGreaterThan(0);\n  });\n\n  test(\"Shows pagination for multiple rows\", () => {\n    const el = mount(sampleData);\n    \n    // Check that pagination is present - look for pagination elements\n    const paginationElements = el.find('[className*=\"paginator\"]');\n    expect(paginationElements.length).toBeGreaterThan(0);\n  });\n\n  test(\"Hides pagination for single row\", () => {\n    const singleRowData = {\n      ...sampleData,\n      rows: [sampleData.rows[0]],\n    };\n    \n    const el = mount(singleRowData);\n    \n    // Check that pagination is not present for single row\n    const paginationElements = el.find('[className*=\"paginator\"]');\n    expect(paginationElements.length).toBe(0);\n  });\n\n  test(\"Handles empty data\", () => {\n    const emptyData = {\n      columns: [],\n      rows: [],\n    };\n    \n    const el = mount(emptyData);\n    \n    expect(el.html()).toBeNull();\n  });\n\n  test(\"Handles null data\", () => {\n    // Suppress PropTypes warning for this test\n    const originalError = console.error;\n    console.error = jest.fn();\n\n    // Test the component directly with null data instead of using mount helper\n    const el = enzyme.mount(<Renderer data={null as any} options={{}} />);\n    \n    expect(el.html()).toBeNull();\n\n    // Restore console.error\n    console.error = originalError;\n  });\n\n  test(\"Navigates between rows with pagination\", () => {\n    const el = mount(sampleData);\n    \n    // Check first row is displayed\n    expect(el.text()).toContain(\"John Doe\");\n    expect(el.text()).not.toContain(\"Jane Smith\");\n    \n    // Find and click next button\n    const nextButton = el.find('button').filterWhere(n => n.text().includes('Next') || n.prop('aria-label') === 'Next Page');\n    if (nextButton.length > 0) {\n      nextButton.first().simulate(\"click\");\n\n      // Check second row is displayed after state update\n      el.update();\n      expect(el.text()).toContain(\"Jane Smith\");\n    }\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/Renderer.tsx",
    "content": "import React, { useState, useMemo } from \"react\";\nimport { map, filter, sortBy } from \"lodash\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\nimport Descriptions from \"antd/lib/descriptions\";\nimport Pagination from \"antd/lib/pagination\";\nimport Tooltip from \"antd/lib/tooltip\";\n\nimport ColumnTypes from \"../shared/columns\";\nimport \"./details.less\";\n\n\nexport default function Renderer({ data, options }: any) {\n  const [page, setPage] = useState(0);\n\n  const visibleColumns = useMemo(() => {\n    if (!options?.columns) return [];\n\n    const columns = sortBy(filter(options.columns, \"visible\"), \"order\");\n\n    return columns.map((column: any) => {\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      const ColumnType = ColumnTypes[column.displayAs] || ColumnTypes.string;\n      const Component = ColumnType(column);\n\n      return {\n        ...column,\n        Component,\n      };\n    });\n  }, [options?.columns]);\n\n  if (!data || !data.rows || data.rows.length === 0) {\n    return null;\n  }\n\n  const row = data.rows[page];\n\n  return (\n    <div className=\"details-viz\">\n      <Descriptions size=\"small\" column={1} bordered>\n        {map(visibleColumns, column => {\n          const { Component } = column;\n\n          return (\n            <Descriptions.Item\n              key={column.name}\n              label={\n                <React.Fragment>\n                  {column.description && (\n                    <span style={{ paddingRight: 5 }}>\n                      <Tooltip placement=\"top\" title={column.description}>\n                        <i className=\"fa fa-info-circle\" aria-hidden=\"true\"></i>\n                      </Tooltip>\n                    </span>\n                  )}\n                  {column.title || column.name}\n                </React.Fragment>\n              }\n            >\n              <div style={{ textAlign: column.alignContent || \"left\" }}>\n                <Component row={row} />\n              </div>\n            </Descriptions.Item>\n          );\n        })}\n      </Descriptions>\n      {data.rows.length > 1 && (\n        <div className=\"paginator-container\">\n          <Pagination\n            showSizeChanger={false}\n            current={page + 1}\n            defaultPageSize={1}\n            total={data.rows.length}\n            onChange={p => setPage(p - 1)}\n          />\n        </div>\n      )}\n    </div>\n  );\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/details.less",
    "content": ".details-viz {\n  .ant-descriptions-item-label {\n    width: 1px;\n    white-space: nowrap;\n  }\n\n  .paginator-container {\n    margin-top: 10px;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/getOptions.test.ts",
    "content": "import getOptions from \"./getOptions\";\n\ndescribe(\"Visualizations -> Details -> getOptions\", () => {\n  const sampleData = {\n    columns: [\n      { name: \"id\", type: \"integer\" },\n      { name: \"name\", type: \"string\" },\n      { name: \"created_at\", type: \"datetime\" },\n      { name: \"is_active\", type: \"boolean\" },\n      { name: \"score\", type: \"float\" },\n    ],\n  };\n\n  test(\"Returns default options when no options provided\", () => {\n    const result = getOptions({}, sampleData);\n\n    expect(result.columns).toHaveLength(5);\n    expect(result.columns[0]).toEqual(\n      expect.objectContaining({\n        name: \"id\",\n        type: \"integer\",\n        displayAs: \"number\",\n        visible: true,\n        alignContent: \"left\",\n        title: \"id\",\n        description: \"\",\n        allowHTML: false,\n        highlightLinks: false,\n      })\n    );\n  });\n\n  test(\"Preserves existing column options\", () => {\n    const existingOptions = {\n      columns: [\n        {\n          name: \"id\",\n          visible: false,\n          title: \"User ID\",\n          alignContent: \"center\",\n        },\n      ],\n    };\n\n    const result = getOptions(existingOptions, sampleData);\n\n    const idColumn = result.columns.find((col: any) => col.name === \"id\");\n    expect(idColumn).toEqual(\n      expect.objectContaining({\n        visible: false,\n        title: \"User ID\",\n        alignContent: \"center\",\n      })\n    );\n  });\n\n  test(\"Sets correct default display types\", () => {\n    const result = getOptions({}, sampleData);\n\n    const columnsByName = result.columns.reduce((acc: any, col: any) => {\n      acc[col.name] = col;\n      return acc;\n    }, {} as any);\n\n    expect(columnsByName.id.displayAs).toBe(\"number\");\n    expect(columnsByName.name.displayAs).toBe(\"string\");\n    expect(columnsByName.created_at.displayAs).toBe(\"datetime\");\n    expect(columnsByName.is_active.displayAs).toBe(\"boolean\");\n    expect(columnsByName.score.displayAs).toBe(\"number\");\n  });\n\n  test(\"Sets correct default alignments\", () => {\n    const result = getOptions({}, sampleData);\n\n    const columnsByName = result.columns.reduce((acc: any, col: any) => {\n      acc[col.name] = col;\n      return acc;\n    }, {} as any);\n\n    expect(columnsByName.id.alignContent).toBe(\"left\");\n    expect(columnsByName.name.alignContent).toBe(\"left\");\n    expect(columnsByName.created_at.alignContent).toBe(\"left\");\n    expect(columnsByName.is_active.alignContent).toBe(\"left\");\n    expect(columnsByName.score.alignContent).toBe(\"left\");\n  });\n\n  test(\"Handles column name type suffixes\", () => {\n    const dataWithTypeSuffixes = {\n      columns: [\n        { name: \"user::filter\", type: \"string\" },\n        { name: \"amount__multiFilter\", type: \"float\" },\n        { name: \"::date_field\", type: \"date\" },\n      ],\n    };\n\n    const result = getOptions({}, dataWithTypeSuffixes);\n\n    expect(result.columns[0].title).toBe(\"user\");\n    expect(result.columns[1].title).toBe(\"amount\");\n    expect(result.columns[2].title).toBe(\"date_field\");\n  });\n\n  test(\"Maintains column order from existing options\", () => {\n    const existingOptions = {\n      columns: [\n        { name: \"name\", order: 0 },\n        { name: \"id\", order: 1 },\n      ],\n    };\n\n    const result = getOptions(existingOptions, sampleData);\n\n    expect(result.columns[0].name).toBe(\"name\");\n    expect(result.columns[1].name).toBe(\"id\");\n  });\n\n  test(\"Handles missing columns in existing options\", () => {\n    const existingOptions = {\n      columns: [\n        { name: \"id\", visible: false },\n        { name: \"nonexistent\", visible: true },\n      ],\n    };\n\n    const result = getOptions(existingOptions, sampleData);\n\n    // Should include all data columns\n    expect(result.columns).toHaveLength(5);\n    \n    // Should preserve settings for existing columns\n    const idColumn = result.columns.find((col: any) => col.name === \"id\");\n    expect(idColumn.visible).toBe(false);\n  });\n\n  test(\"Includes default format options\", () => {\n    const result = getOptions({}, sampleData);\n\n    const column = result.columns[0];\n    expect(column).toEqual(\n      expect.objectContaining({\n        booleanValues: [\"false\", \"true\"],\n        imageUrlTemplate: \"{{ @ }}\",\n        imageTitleTemplate: \"{{ @ }}\",\n        imageWidth: \"\",\n        imageHeight: \"\",\n        linkUrlTemplate: \"{{ @ }}\",\n        linkTextTemplate: \"{{ @ }}\",\n        linkTitleTemplate: \"{{ @ }}\",\n        linkOpenInNewTab: true,\n      })\n    );\n  });\n\n  test(\"Handles empty data\", () => {\n    const emptyData = { columns: [] };\n    const result = getOptions({}, emptyData);\n\n    expect(result.columns).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/getOptions.ts",
    "content": "import _ from \"lodash\";\nimport {\n  getDefaultFormatOptions,\n  getColumnsOptions,\n} from \"@/visualizations/shared/columnUtils\";\n\nconst DEFAULT_OPTIONS = {};\n\n\nexport default function getOptions(options: any, { columns }: any) {\n  options = { ...DEFAULT_OPTIONS, ...options };\n  options.columns = _.map(getColumnsOptions(columns, options.columns, { alignContent: \"left\" }), col => ({\n    ...getDefaultFormatOptions(col),\n    ...col,\n  }));\n  return options;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/details/index.ts",
    "content": "import getOptions from \"./getOptions\";\nimport Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nexport default {\n  type: \"DETAILS\",\n  name: \"Details View\",\n  getOptions,\n  Renderer,\n  Editor,\n  defaultColumns: 4,\n  defaultRows: 2,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/funnel/Editor/AppearanceSettings.tsx",
    "content": "import React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Input, InputNumber, ContextHelp } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function AppearanceSettings({ options, onOptionsChange }: any) {\n  const [onOptionsChangeDebounced] = useDebouncedCallback(onOptionsChange, 200);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label={\n            <React.Fragment>\n              Number Values Format\n              <ContextHelp.NumberFormatSpecs />\n            </React.Fragment>\n          }\n          data-test=\"Funnel.NumberFormat\"\n          defaultValue={options.numberFormat}\n          onChange={(event: any) => onOptionsChangeDebounced({ numberFormat: event.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label={\n            <React.Fragment>\n              Percent Values Format\n              <ContextHelp.NumberFormatSpecs />\n            </React.Fragment>\n          }\n          data-test=\"Funnel.PercentFormat\"\n          defaultValue={options.percentFormat}\n          onChange={(event: any) => onOptionsChangeDebounced({ percentFormat: event.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <InputNumber\n          layout=\"horizontal\"\n          label=\"Items Count Limit\"\n          data-test=\"Funnel.ItemsLimit\"\n          min={2}\n          defaultValue={options.itemsLimit}\n          onChange={(itemsLimit: any) => onOptionsChangeDebounced({ itemsLimit })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <InputNumber\n          layout=\"horizontal\"\n          label=\"Min Percent Value\"\n          data-test=\"Funnel.PercentRangeMin\"\n          min={0}\n          defaultValue={options.percentValuesRange.min}\n          onChange={(min: any) => onOptionsChangeDebounced({ percentValuesRange: { min } })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <InputNumber\n          layout=\"horizontal\"\n          label=\"Max Percent Value\"\n          data-test=\"Funnel.PercentRangeMax\"\n          min={0}\n          defaultValue={options.percentValuesRange.max}\n          onChange={(max: any) => onOptionsChangeDebounced({ percentValuesRange: { max } })}\n        />\n      </Section>\n    </React.Fragment>\n  );\n}\n\nAppearanceSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/funnel/Editor/GeneralSettings.tsx",
    "content": "import { map } from \"lodash\";\nimport React, { useMemo } from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Select, Input, Checkbox } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function GeneralSettings({ options, data, onOptionsChange }: any) {\n  const columnNames = useMemo(() => map(data.columns, c => c.name), [data]);\n\n  const [onOptionsChangeDebounced] = useDebouncedCallback(onOptionsChange, 200);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Step Column\"\n          data-test=\"Funnel.StepColumn\"\n          placeholder=\"Choose column...\"\n          defaultValue={options.stepCol.colName || undefined}\n          onChange={(colName: any) => onOptionsChange({ stepCol: { colName: colName || null } })}>\n          {map(columnNames, col => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={col} data-test={`Funnel.StepColumn.${col}`}>\n              {col}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label=\"Step Column Title\"\n          data-test=\"Funnel.StepColumnTitle\"\n          defaultValue={options.stepCol.displayAs}\n          onChange={(event: any) => onOptionsChangeDebounced({ stepCol: { displayAs: event.target.value } })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          layout=\"horizontal\"\n          label=\"Value Column\"\n          data-test=\"Funnel.ValueColumn\"\n          placeholder=\"Choose column...\"\n          defaultValue={options.valueCol.colName || undefined}\n          onChange={(colName: any) => onOptionsChange({ valueCol: { colName: colName || null } })}>\n          {map(columnNames, col => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={col} data-test={`Funnel.ValueColumn.${col}`}>\n              {col}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          layout=\"horizontal\"\n          label=\"Value Column Title\"\n          data-test=\"Funnel.ValueColumnTitle\"\n          defaultValue={options.valueCol.displayAs}\n          onChange={(event: any) => onOptionsChangeDebounced({ valueCol: { displayAs: event.target.value } })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Funnel.CustomSort\"\n          checked={!options.autoSort}\n          onChange={event => onOptionsChange({ autoSort: !event.target.checked })}>\n          Custom Sorting\n        </Checkbox>\n      </Section>\n\n      {!options.autoSort && (\n        <React.Fragment>\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            <Select\n              layout=\"horizontal\"\n              label=\"Sort Column\"\n              data-test=\"Funnel.SortColumn\"\n              allowClear\n              placeholder=\"Choose column...\"\n              defaultValue={options.sortKeyCol.colName || undefined}\n              onChange={(colName: any) => onOptionsChange({ sortKeyCol: { colName: colName || null } })}>\n              {map(columnNames, col => (\n                // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n                <Select.Option key={col} data-test={`Funnel.SortColumn.${col}`}>\n                  {col}\n                  {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n                </Select.Option>\n              ))}\n            </Select>\n          </Section>\n\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            <Select\n              layout=\"horizontal\"\n              label=\"Sort Order\"\n              data-test=\"Funnel.SortDirection\"\n              disabled={!options.sortKeyCol.colName}\n              defaultValue={options.sortKeyCol.reverse ? \"desc\" : \"asc\"}\n              onChange={(order: any) => onOptionsChange({ sortKeyCol: { reverse: order === \"desc\" } })}>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"asc\" data-test=\"Funnel.SortDirection.Ascending\">\n                ascending\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option value=\"desc\" data-test=\"Funnel.SortDirection.Descending\">\n                descending\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n            </Select>\n          </Section>\n        </React.Fragment>\n      )}\n    </React.Fragment>\n  );\n}\n\nGeneralSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/funnel/Editor/index.ts",
    "content": "import createTabbedEditor from \"@/components/visualizations/editor/createTabbedEditor\";\n\nimport GeneralSettings from \"./GeneralSettings\";\nimport AppearanceSettings from \"./AppearanceSettings\";\n\nexport default createTabbedEditor([\n  { key: \"General\", title: \"General\", component: GeneralSettings },\n  { key: \"Appearance\", title: \"Appearance\", component: AppearanceSettings },\n]);\n"
  },
  {
    "path": "viz-lib/src/visualizations/funnel/Renderer/FunnelBar.tsx",
    "content": "import React from \"react\";\nimport cx from \"classnames\";\n\nimport \"./funnel-bar.less\";\n\ntype OwnProps = {\n  color?: string;\n  value?: number;\n  align?: \"left\" | \"center\" | \"right\";\n  className?: string;\n  children?: React.ReactNode;\n};\n\nconst funnelBarDefaultProps = {\n  color: \"#dadada\",\n  value: 0.0,\n  align: \"left\",\n  className: null,\n  children: null,\n};\n\ntype Props = OwnProps & typeof funnelBarDefaultProps;\n\nexport default function FunnelBar({ color, value, align, className, children }: Props) {\n  return (\n    <div className={cx(\"funnel-bar\", `funnel-bar-${align}`, className)}>\n      <div className=\"funnel-bar-value\" style={{ backgroundColor: color, width: value + \"%\" }} />\n      <div className=\"funnel-bar-label\">{children}</div>\n    </div>\n  );\n}\n\nFunnelBar.defaultProps = funnelBarDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/visualizations/funnel/Renderer/funnel-bar.less",
    "content": ".funnel-bar {\n  @height: 30px;\n\n  position: relative;\n  height: @height;\n  line-height: @height;\n\n  &-left {\n    text-align: left;\n  }\n  &-center {\n    text-align: center;\n  }\n  &-right {\n    text-align: right;\n  }\n\n  .funnel-bar-value {\n    display: inline-block;\n    vertical-align: top;\n    height: @height;\n  }\n\n  .funnel-bar-label {\n    display: inline-block;\n    text-align: center;\n    vertical-align: middle;\n    position: absolute;\n    left: 0;\n    right: 0;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/funnel/Renderer/index.less",
    "content": ".funnel-visualization-container {\n  table {\n    min-width: 450px;\n    table-layout: fixed;\n\n    tbody tr td {\n      border: none;\n\n      &.text-ellipsis {\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n      }\n    }\n  }\n  .step {\n    max-width: 0;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n  .step .step-name {\n    visibility: hidden;\n    width: inherit;\n    padding: 3px 5px;\n    background-color: white;\n    border: 1px solid;\n    border-radius: 3px;\n    position: absolute;\n    z-index: 1;\n    white-space: initial;\n    word-wrap: break-word;\n  }\n\n  .step:hover .step-name {\n    visibility: visible;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/funnel/Renderer/index.tsx",
    "content": "import { maxBy } from \"lodash\";\nimport React, { useMemo } from \"react\";\nimport Table from \"antd/lib/table\";\nimport Tooltip from \"antd/lib/tooltip\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\nimport ColorPalette from \"@/visualizations/ColorPalette\";\nimport { createNumberFormatter } from \"@/lib/value-format\";\n\nimport prepareData from \"./prepareData\";\nimport FunnelBar from \"./FunnelBar\";\nimport \"./index.less\";\n\nfunction generateRowKeyPrefix() {\n  return Math.trunc(Math.random() * Number.MAX_SAFE_INTEGER).toString(36) + \":\";\n}\n\nexport default function Renderer({ data, options }: any) {\n  const funnelData = useMemo(() => prepareData(data.rows, options), [data, options]);\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  const rowKeyPrefix = useMemo(() => generateRowKeyPrefix(), [funnelData]);\n\n  const formatValue = useMemo(() => createNumberFormatter(options.numberFormat), [options.numberFormat]);\n\n  const formatPercentValue = useMemo(() => {\n    const format = createNumberFormatter(options.percentFormat);\n    return (value: any) => {\n      if (value < options.percentValuesRange.min) {\n        return `<${format(options.percentValuesRange.min)}`;\n      }\n      if (value > options.percentValuesRange.max) {\n        return `>${format(options.percentValuesRange.max)}`;\n      }\n      return format(value);\n    };\n  }, [options.percentFormat, options.percentValuesRange]);\n\n  const columns = useMemo(() => {\n    if (funnelData.length === 0) {\n      return [];\n    }\n\n    // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.\n    const maxToPrevious = maxBy(funnelData, d => (isFinite(d.pctPrevious) ? d.pctPrevious : 0)).pctPrevious;\n    return [\n      {\n        title: options.stepCol.displayAs,\n        dataIndex: \"step\",\n        width: \"25%\",\n        className: \"text-ellipsis\",\n        render: (text: any) => (\n          <Tooltip title={text} mouseEnterDelay={0} mouseLeaveDelay={0}>\n            {text}\n          </Tooltip>\n        ),\n      },\n      {\n        title: options.valueCol.displayAs,\n        dataIndex: \"value\",\n        width: \"45%\",\n        align: \"center\",\n        render: (value: any, item: any) => (\n          // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n          <FunnelBar align=\"center\" color={ColorPalette.Cyan} value={item.pctMax}>\n            {formatValue(value)}\n          </FunnelBar>\n        ),\n      },\n      {\n        title: \"% Max\",\n        dataIndex: \"pctMax\",\n        width: \"15%\",\n        align: \"center\",\n        render: (value: any) => formatPercentValue(value),\n      },\n      {\n        title: \"% Previous\",\n        dataIndex: \"pctPrevious\",\n        width: \"15%\",\n        align: \"center\",\n        render: (value: any) => (\n          // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n          <FunnelBar className=\"funnel-percent-column\" value={(value / maxToPrevious) * 100.0}>\n            {formatPercentValue(value)}\n          </FunnelBar>\n        ),\n      },\n    ];\n  }, [options.stepCol.displayAs, options.valueCol.displayAs, funnelData, formatValue, formatPercentValue]);\n\n  if (funnelData.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"funnel-visualization-container\">\n      <Table\n        // @ts-expect-error ts-migrate(2322) FIXME: Type '({ title: any; dataIndex: string; width: str... Remove this comment to see the full error message\n        columns={columns}\n        dataSource={funnelData}\n        rowKey={(record, index) => rowKeyPrefix + index}\n        pagination={false}\n      />\n    </div>\n  );\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/funnel/Renderer/prepareData.ts",
    "content": "import { map, maxBy, sortBy, toString } from \"lodash\";\nimport moment from \"moment\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\n\nfunction stepValueToString(value: any) {\n  if (moment.isMoment(value)) {\n    const format = visualizationsSettings.dateTimeFormat || \"DD/MM/YYYY HH:mm\";\n    return value.format(format);\n  }\n  return toString(value);\n}\n\nexport default function prepareData(rows: any, options: any) {\n  if (rows.length === 0 || !options.stepCol.colName || !options.valueCol.colName) {\n    return [];\n  }\n\n  rows = [...rows];\n  if (options.sortKeyCol.colName) {\n    rows = sortBy(rows, options.sortKeyCol.colName);\n  }\n  if (options.sortKeyCol.reverse) {\n    rows = rows.reverse();\n  }\n\n  const data = map(rows, row => ({\n    step: stepValueToString(row[options.stepCol.colName]),\n    value: parseFloat(row[options.valueCol.colName]) || 0.0,\n  }));\n\n  // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.\n  const maxVal = maxBy(data, d => d.value).value;\n  data.forEach((d, i) => {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'pctMax' does not exist on type '{ step: ... Remove this comment to see the full error message\n    d.pctMax = (d.value / maxVal) * 100.0;\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'pctPrevious' does not exist on type '{ s... Remove this comment to see the full error message\n    d.pctPrevious = i === 0 || d.value === data[i - 1].value ? 100.0 : (d.value / data[i - 1].value) * 100.0;\n  });\n\n  return data.slice(0, options.itemsLimit);\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/funnel/getOptions.ts",
    "content": "import { isFinite, map, merge, includes } from \"lodash\";\n\nconst DEFAULT_OPTIONS = {\n  stepCol: { colName: null, displayAs: \"Steps\" },\n  valueCol: { colName: null, displayAs: \"Value\" },\n  autoSort: true,\n  sortKeyCol: { colName: null, reverse: false },\n  itemsLimit: 100,\n  percentValuesRange: { min: 0.01, max: 1000.0 },\n  numberFormat: \"0,0[.]00\",\n  percentFormat: \"0[.]00%\",\n};\n\nexport default function getOptions(options: any, { columns }: any) {\n  options = merge({}, DEFAULT_OPTIONS, options);\n\n  // Validate\n  const availableColumns = map(columns, c => c.name);\n  if (!includes(availableColumns, options.stepCol.colName)) {\n    options.stepCol.colName = null;\n  }\n  if (!includes(availableColumns, options.valueCol.colName)) {\n    options.valueCol.colName = null;\n  }\n  if (!includes(availableColumns, options.sortKeyCol.colName)) {\n    options.sortKeyCol.colName = null;\n  }\n\n  if (!isFinite(options.itemsLimit)) {\n    options.itemsLimit = DEFAULT_OPTIONS.itemsLimit;\n  }\n  if (options.itemsLimit < 2) {\n    options.itemsLimit = 2;\n  }\n\n  if (options.autoSort) {\n    options.sortKeyCol.colName = options.valueCol.colName;\n    options.sortKeyCol.reverse = true;\n  }\n\n  return options;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/funnel/index.ts",
    "content": "import getOptions from \"./getOptions\";\nimport Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nexport default {\n  type: \"FUNNEL\",\n  name: \"Funnel\",\n  getOptions,\n  Renderer,\n  Editor,\n\n  defaultRows: 10,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/index.ts",
    "content": "import Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nexport { Renderer, Editor };\n"
  },
  {
    "path": "viz-lib/src/visualizations/map/Editor/FormatSettings.tsx",
    "content": "import React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Input, Checkbox, TextArea, ContextHelp } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nfunction TemplateFormatHint() {\n  // eslint-disable-line react/prop-types\n  return (\n    // @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message\n    <ContextHelp placement=\"topLeft\" arrowPointAtCenter>\n      <div style={{ paddingBottom: 5 }}>\n        All query result columns can be referenced using <code>{\"{{ column_name }}\"}</code> syntax.\n      </div>\n      <div style={{ paddingBottom: 5 }}>Leave this field empty to use default template.</div>\n    </ContextHelp>\n  );\n}\n\nexport default function FormatSettings({ options, onOptionsChange }: any) {\n  const [onOptionsChangeDebounced] = useDebouncedCallback(onOptionsChange, 200);\n\n  const templateFormatHint = <TemplateFormatHint />;\n\n  return (\n    <div className=\"map-visualization-editor-format-settings\">\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Map.Editor.TooltipEnabled\"\n          checked={options.tooltip.enabled}\n          onChange={event => onOptionsChange({ tooltip: { enabled: event.target.checked } })}>\n          Show tooltip\n        </Checkbox>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label={<React.Fragment>Tooltip template {templateFormatHint}</React.Fragment>}\n          data-test=\"Map.Editor.TooltipTemplate\"\n          disabled={!options.tooltip.enabled}\n          placeholder=\"Default template\"\n          defaultValue={options.tooltip.template}\n          onChange={(event: any) => onOptionsChangeDebounced({ tooltip: { template: event.target.value } })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Map.Editor.PopupEnabled\"\n          checked={options.popup.enabled}\n          onChange={event => onOptionsChange({ popup: { enabled: event.target.checked } })}>\n          Show popup\n        </Checkbox>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <TextArea\n          label={<React.Fragment>Popup template {templateFormatHint}</React.Fragment>}\n          data-test=\"Map.Editor.PopupTemplate\"\n          disabled={!options.popup.enabled}\n          rows={4}\n          placeholder=\"Default template\"\n          defaultValue={options.popup.template}\n          onChange={(event: any) => onOptionsChangeDebounced({ popup: { template: event.target.value } })}\n        />\n      </Section>\n    </div>\n  );\n}\n\nFormatSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/map/Editor/GeneralSettings.tsx",
    "content": "import { isNil, map, filter, difference } from \"lodash\";\nimport React, { useMemo } from \"react\";\nimport { Section, Select } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nfunction getColumns(column: any, unusedColumns: any) {\n  return filter([column, ...unusedColumns], v => !isNil(v));\n}\n\nexport default function GeneralSettings({ options, data, onOptionsChange }: any) {\n  const unusedColumns = useMemo(\n    () =>\n      difference(\n        map(data.columns, c => c.name),\n        [options.latColName, options.lonColName, options.classify]\n      ),\n    [data, options.latColName, options.lonColName, options.classify]\n  );\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Latitude Column Name\"\n          data-test=\"Map.Editor.LatitudeColumnName\"\n          value={options.latColName}\n          onChange={(latColName: any) => onOptionsChange({ latColName })}>\n          {map(getColumns(options.latColName, unusedColumns), col => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={col} data-test={\"Map.Editor.LatitudeColumnName.\" + col}>\n              {col}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Longitude Column Name\"\n          data-test=\"Map.Editor.LongitudeColumnName\"\n          value={options.lonColName}\n          onChange={(lonColName: any) => onOptionsChange({ lonColName })}>\n          {map(getColumns(options.lonColName, unusedColumns), col => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={col} data-test={\"Map.Editor.LongitudeColumnName.\" + col}>\n              {col}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Group By\"\n          data-test=\"Map.Editor.GroupBy\"\n          allowClear\n          placeholder=\"none\"\n          value={options.classify || undefined}\n          onChange={(column: any) => onOptionsChange({ classify: column || null })}>\n          {map(getColumns(options.classify, unusedColumns), col => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={col} data-test={\"Map.Editor.GroupBy.\" + col}>\n              {col}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nGeneralSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/map/Editor/GroupsSettings.tsx",
    "content": "import { map } from \"lodash\";\nimport React, { useMemo, useCallback } from \"react\";\nimport Table from \"antd/lib/table\";\nimport ColorPicker from \"@/components/ColorPicker\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport ColorPalette from \"@/visualizations/ColorPalette\";\n\nimport prepareData from \"../prepareData\";\n\nexport default function GroupsSettings({ options, data, onOptionsChange }: any) {\n  const groups = useMemo(\n    () => map(prepareData(data, options), ({ name }) => ({ name, color: (options.groups[name] || {}).color || null })),\n    [data, options]\n  );\n\n  const colors = useMemo(\n    () => ({\n      Automatic: null,\n      ...ColorPalette,\n    }),\n    []\n  );\n\n  const updateGroupOption = useCallback(\n    (name, prop, value) => {\n      onOptionsChange({\n        groups: {\n          [name]: {\n            [prop]: value,\n          },\n        },\n      });\n    },\n    [onOptionsChange]\n  );\n\n  const columns = [\n    {\n      title: \"Group\",\n      dataIndex: \"name\",\n    },\n    {\n      title: \"Color\",\n      dataIndex: \"color\",\n      width: \"1%\",\n      render: (unused: any, item: any) => (\n        <ColorPicker\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'never'.\n          interactive\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '{ \"Indian Red\": string; \"Green 2\": string; \"... Remove this comment to see the full error message\n          presetColors={colors}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          placement=\"topRight\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          color={item.color}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          triggerProps={{ \"data-test\": `Map.Editor.Groups.${item.name}.Color` }}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(value: any) => void' is not assignable to t... Remove this comment to see the full error message\n          onChange={(value: any) => updateGroupOption(item.name, \"color\", value)}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'never'.\n          addonAfter={<ColorPicker.Label color={item.color} presetColors={colors} />}\n        />\n      ),\n    },\n  ];\n\n  return <Table columns={columns} dataSource={groups} rowKey=\"name\" showHeader={false} pagination={false} />;\n}\n\nGroupsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/map/Editor/StyleSettings.tsx",
    "content": "import { isNil, map } from \"lodash\";\nimport React, { useMemo } from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Select, Checkbox, Input, ColorPicker, ContextHelp } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\nimport ColorPalette from \"@/visualizations/ColorPalette\";\n\nconst mapTiles = [\n  {\n    name: \"OpenStreetMap\",\n    url: \"//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\",\n  },\n  {\n    name: \"OpenStreetMap BW\",\n    url: \"//{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png\",\n  },\n  {\n    name: \"OpenStreetMap DE\",\n    url: \"//{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png\",\n  },\n  {\n    name: \"OpenStreetMap FR\",\n    url: \"//{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png\",\n  },\n  {\n    name: \"OpenStreetMap Hot\",\n    url: \"//{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png\",\n  },\n  {\n    name: \"Thunderforest\",\n    url: \"//{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png\",\n  },\n  {\n    name: \"Thunderforest Spinal\",\n    url: \"//{s}.tile.thunderforest.com/spinal-map/{z}/{x}/{y}.png\",\n  },\n  {\n    name: \"OpenMapSurfer\",\n    url: \"//korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}\",\n  },\n  {\n    name: \"Stamen Toner\",\n    url: \"//stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png\",\n  },\n  {\n    name: \"Stamen Toner Background\",\n    url: \"//stamen-tiles-{s}.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png\",\n  },\n  {\n    name: \"Stamen Toner Lite\",\n    url: \"//stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png\",\n  },\n  {\n    name: \"OpenTopoMap\",\n    url: \"//{s}.tile.opentopomap.org/{z}/{x}/{y}.png\",\n  },\n];\n\nconst CustomColorPalette = {\n  White: \"#ffffff\",\n  ...ColorPalette,\n};\n\nfunction getCustomIconOptionFields(iconShape: any) {\n  switch (iconShape) {\n    case \"doughnut\":\n      return { showIcon: false, showBackgroundColor: true, showBorderColor: true };\n    case \"circle-dot\":\n    case \"rectangle-dot\":\n      return { showIcon: false, showBackgroundColor: false, showBorderColor: true };\n    default:\n      return { showIcon: true, showBackgroundColor: true, showBorderColor: true };\n  }\n}\n\nexport default function StyleSettings({ options, onOptionsChange }: any) {\n  const [debouncedOnOptionsChange] = useDebouncedCallback(onOptionsChange, 200);\n\n  const { showIcon, showBackgroundColor, showBorderColor } = useMemo(\n    () => getCustomIconOptionFields(options.iconShape),\n    [options.iconShape]\n  );\n\n  const isCustomMarkersStyleAllowed = isNil(options.classify);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Map Tiles\"\n          data-test=\"Map.Editor.Tiles\"\n          value={options.mapTileUrl}\n          onChange={(mapTileUrl: any) => onOptionsChange({ mapTileUrl })}>\n          {map(mapTiles, ({ name, url }) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={url} data-test={\"Map.Editor.Tiles.\" + name}>\n              {name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section.Title>Markers</Section.Title>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Map.Editor.ClusterMarkers\"\n          defaultChecked={options.clusterMarkers}\n          onChange={event => onOptionsChange({ clusterMarkers: event.target.checked })}>\n          Cluster Markers\n        </Checkbox>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Map.Editor.CustomizeMarkers\"\n          disabled={!isCustomMarkersStyleAllowed}\n          defaultChecked={options.customizeMarkers}\n          onChange={event => onOptionsChange({ customizeMarkers: event.target.checked })}>\n          Override default style\n        </Checkbox>\n        {!isCustomMarkersStyleAllowed && (\n          // @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message\n          <ContextHelp placement=\"topLeft\" arrowPointAtCenter>\n            Custom marker styles are not available\n            <br />\n            when <b>Group By</b> column selected.\n          </ContextHelp>\n        )}\n      </Section>\n\n      {isCustomMarkersStyleAllowed && options.customizeMarkers && (\n        <React.Fragment>\n          {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n          <Section>\n            <Select\n              layout=\"horizontal\"\n              label=\"Shape\"\n              data-test=\"Map.Editor.MarkerShape\"\n              value={options.iconShape}\n              onChange={(iconShape: any) => onOptionsChange({ iconShape })}>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option key=\"marker\" data-test=\"Map.Editor.MarkerShape.marker\">\n                Marker + Icon\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option key=\"doughnut\" data-test=\"Map.Editor.MarkerShape.doughnut\">\n                Circle\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option key=\"circle-dot\" data-test=\"Map.Editor.MarkerShape.circle-dot\">\n                Circle Dot\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option key=\"circle\" data-test=\"Map.Editor.MarkerShape.circle\">\n                Circle + Icon\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option key=\"rectangle-dot\" data-test=\"Map.Editor.MarkerShape.rectangle-dot\">\n                Square Dot\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              <Select.Option key=\"rectangle\" data-test=\"Map.Editor.MarkerShape.rectangle\">\n                Square + Icon\n                {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n              </Select.Option>\n            </Select>\n          </Section>\n\n          {showIcon && (\n            // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n            <Section>\n              <Input\n                layout=\"horizontal\"\n                label={\n                  <React.Fragment>\n                    Icon\n                    {/* @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message */}\n                    <ContextHelp placement=\"topLeft\" arrowPointAtCenter>\n                      <div style={{ marginBottom: 5 }}>\n                        Enter an icon name from{\" \"}\n                        <a href=\"https://fontawesome.com/v4.7.0/icons/\" target=\"_blank\" rel=\"noopener noreferrer\">\n                          Font-Awesome 4.7\n                        </a>\n                      </div>\n                      <div style={{ marginBottom: 5 }}>\n                        Examples: <code>check</code>, <code>times-circle</code>, <code>flag</code>\n                      </div>\n                      <div>Leave blank to remove.</div>\n                    </ContextHelp>\n                  </React.Fragment>\n                }\n                data-test=\"Map.Editor.MarkerIcon\"\n                defaultValue={options.iconFont}\n                onChange={(event: any) => debouncedOnOptionsChange({ iconFont: event.target.value })}\n              />\n            </Section>\n          )}\n\n          {showIcon && (\n            // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n            <Section>\n              <ColorPicker\n                layout=\"horizontal\"\n                label=\"Icon Color\"\n                interactive\n                presetColors={CustomColorPalette}\n                placement=\"topRight\"\n                color={options.foregroundColor}\n                triggerProps={{ \"data-test\": \"Map.Editor.MarkerIconColor\" }}\n                onChange={(foregroundColor: any) => onOptionsChange({ foregroundColor })}\n                // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n                addonAfter={<ColorPicker.Label color={options.foregroundColor} presetColors={CustomColorPalette} />}\n              />\n            </Section>\n          )}\n\n          {showBackgroundColor && (\n            // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n            <Section>\n              <ColorPicker\n                layout=\"horizontal\"\n                label=\"Background Color\"\n                interactive\n                presetColors={CustomColorPalette}\n                placement=\"topRight\"\n                color={options.backgroundColor}\n                triggerProps={{ \"data-test\": \"Map.Editor.MarkerBackgroundColor\" }}\n                onChange={(backgroundColor: any) => onOptionsChange({ backgroundColor })}\n                // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n                addonAfter={<ColorPicker.Label color={options.backgroundColor} presetColors={CustomColorPalette} />}\n              />\n            </Section>\n          )}\n\n          {showBorderColor && (\n            // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n            <Section>\n              <ColorPicker\n                layout=\"horizontal\"\n                label=\"Border Color\"\n                interactive\n                presetColors={CustomColorPalette}\n                placement=\"topRight\"\n                color={options.borderColor}\n                triggerProps={{ \"data-test\": \"Map.Editor.MarkerBorderColor\" }}\n                onChange={(borderColor: any) => onOptionsChange({ borderColor })}\n                // @ts-expect-error ts-migrate(2339) FIXME: Property 'Label' does not exist on type '({ classN... Remove this comment to see the full error message\n                addonAfter={<ColorPicker.Label color={options.borderColor} presetColors={CustomColorPalette} />}\n              />\n            </Section>\n          )}\n        </React.Fragment>\n      )}\n    </React.Fragment>\n  );\n}\n\nStyleSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/map/Editor/index.ts",
    "content": "import createTabbedEditor from \"@/components/visualizations/editor/createTabbedEditor\";\n\nimport GeneralSettings from \"./GeneralSettings\";\nimport GroupsSettings from \"./GroupsSettings\";\nimport FormatSettings from \"./FormatSettings\";\nimport StyleSettings from \"./StyleSettings\";\n\nexport default createTabbedEditor([\n  { key: \"General\", title: \"General\", component: GeneralSettings },\n  { key: \"Groups\", title: \"Groups\", component: GroupsSettings },\n  { key: \"Format\", title: \"Format\", component: FormatSettings },\n  { key: \"Style\", title: \"Style\", component: StyleSettings },\n]);\n"
  },
  {
    "path": "viz-lib/src/visualizations/map/Renderer.tsx",
    "content": "import { isEqual, omit, merge } from \"lodash\";\nimport React, { useState, useEffect, useRef, useMemo } from \"react\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\n\nimport prepareData from \"./prepareData\";\nimport initMap from \"./initMap\";\n\nfunction useMemoWithDeepCompare(create: any, inputs: any) {\n  const valueRef = useRef();\n  const value = useMemo(create, inputs);\n  if (!isEqual(value, valueRef.current)) {\n    // @ts-expect-error ts-migrate(2322) FIXME: Type 'unknown' is not assignable to type 'undefine... Remove this comment to see the full error message\n    valueRef.current = value;\n  }\n  return valueRef.current;\n}\n\nexport default function Renderer({ data, options, onOptionsChange }: any) {\n  const [container, setContainer] = useState(null);\n\n  const optionsWithoutBounds = useMemoWithDeepCompare(() => omit(options, [\"bounds\"]), [options]);\n\n  const groups = useMemo(() => prepareData(data, optionsWithoutBounds), [data, optionsWithoutBounds]);\n\n  const [map, setMap] = useState(null);\n\n  useEffect(() => {\n    if (container) {\n      const _map = initMap(container);\n      // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ onBoundsChange: () => void; up... Remove this comment to see the full error message\n      setMap(_map);\n      return () => {\n        _map.destroy();\n      };\n    }\n  }, [container]);\n\n  useEffect(() => {\n    if (map) {\n      // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.\n      map.updateLayers(groups, optionsWithoutBounds);\n    }\n  }, [map, groups, optionsWithoutBounds]);\n\n  useEffect(() => {\n    if (map) {\n      // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.\n      map.updateBounds(options.bounds);\n    }\n  }, [map, options.bounds]);\n\n  useEffect(() => {\n    if (map && onOptionsChange) {\n      // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.\n      map.onBoundsChange = (bounds: any) => {\n        onOptionsChange(merge({}, options, { bounds }));\n      };\n    }\n  }, [map, options, onOptionsChange]);\n\n  // @ts-expect-error ts-migrate(2322) FIXME: Type 'Dispatch<SetStateAction<null>>' is not assig... Remove this comment to see the full error message\n  return <div className=\"map-visualization-container\" ref={setContainer} />;\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/map/getOptions.ts",
    "content": "import { merge } from \"lodash\";\n\nexport type LeafletBaseIconType = \"marker\" | \"rectangle\" | \"circle\" | \"rectangle-dot\" | \"circle-dot\" | \"doughnut\";\nexport interface MapOptionsType {\n  latColName: string;\n  lonColName: string;\n  classify: any;\n  groups: Record<string, any>;\n  mapTileUrl: string;\n  clusterMarkers: boolean;\n  customizeMarkers: boolean;\n  iconShape: LeafletBaseIconType;\n  iconFont: LeafletBaseIconType;\n  foregroundColor: string;\n  backgroundColor: string;\n  borderColor: string;\n  bounds: any;\n  tooltip: {\n    enabled: boolean;\n    template: string;\n  };\n  popup: {\n    enabled: boolean;\n    template: string;\n  };\n}\n\nconst DEFAULT_OPTIONS: MapOptionsType = {\n  latColName: \"lat\",\n  lonColName: \"lon\",\n  classify: null,\n  groups: {},\n  mapTileUrl: \"//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\",\n  clusterMarkers: true,\n  customizeMarkers: false,\n  iconShape: \"marker\",\n  iconFont: \"circle\",\n  foregroundColor: \"#ffffff\",\n  backgroundColor: \"#356AFF\",\n  borderColor: \"#356AFF\",\n  bounds: null,\n  tooltip: {\n    enabled: false,\n    template: \"\",\n  },\n  popup: {\n    enabled: true,\n    template: \"\",\n  },\n};\n\nexport default function getOptions(options: MapOptionsType) {\n  options = merge({}, DEFAULT_OPTIONS, options);\n  options.mapTileUrl = options.mapTileUrl || DEFAULT_OPTIONS.mapTileUrl;\n\n  // Backward compatibility\n  if (options.classify === \"none\") {\n    options.classify = null;\n  }\n\n  return options;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/map/index.ts",
    "content": "import getOptions from \"./getOptions\";\nimport Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nexport default {\n  type: \"MAP\",\n  name: \"Map (Markers)\",\n  getOptions,\n  Renderer,\n  Editor,\n\n  defaultColumns: 6,\n  defaultRows: 8,\n  minColumns: 2,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/map/initMap.ts",
    "content": "import { isFunction, each, map, toString, clone } from \"lodash\";\nimport chroma from \"chroma-js\";\nimport L from \"leaflet\";\nimport \"leaflet.markercluster\";\nimport \"leaflet/dist/leaflet.css\";\nimport \"leaflet.markercluster/dist/MarkerCluster.css\";\nimport \"leaflet.markercluster/dist/MarkerCluster.Default.css\";\nimport \"beautifymarker\";\nimport \"beautifymarker/leaflet-beautify-marker-icon.css\";\n// @ts-expect-error ts-migrate(2307) FIXME: Cannot find module 'leaflet/dist/images/marker-ico... Remove this comment to see the full error message\nimport markerIcon from \"leaflet/dist/images/marker-icon.png\";\n// @ts-expect-error ts-migrate(2307) FIXME: Cannot find module 'leaflet/dist/images/marker-ico... Remove this comment to see the full error message\nimport markerIconRetina from \"leaflet/dist/images/marker-icon-2x.png\";\n// @ts-expect-error ts-migrate(2307) FIXME: Cannot find module 'leaflet/dist/images/marker-sha... Remove this comment to see the full error message\nimport markerShadow from \"leaflet/dist/images/marker-shadow.png\";\nimport \"leaflet-fullscreen\";\nimport \"leaflet-fullscreen/dist/leaflet.fullscreen.css\";\nimport { formatSimpleTemplate } from \"@/lib/value-format\";\nimport sanitize from \"@/services/sanitize\";\nimport resizeObserver from \"@/services/resizeObserver\";\nimport chooseTextColorForBackground from \"@/lib/chooseTextColorForBackground\";\n\n// This is a workaround for an issue with giving Leaflet load the icon on its own.\nL.Icon.Default.mergeOptions({\n  iconUrl: markerIcon,\n  iconRetinaUrl: markerIconRetina,\n  shadowUrl: markerShadow,\n});\n\n// @ts-expect-error ts-migrate(2339) FIXME: Property '_getIconUrl' does not exist on type 'Def... Remove this comment to see the full error message\ndelete L.Icon.Default.prototype._getIconUrl;\n\nconst iconAnchors = {\n  marker: [14, 32],\n  circle: [10, 10],\n  rectangle: [11, 11],\n  \"circle-dot\": [1, 2],\n  \"rectangle-dot\": [1, 2],\n  doughnut: [8, 8],\n};\n\nconst popupAnchors = {\n  rectangle: [0, -3],\n  circle: [1, -3],\n};\n\nconst createHeatpointMarker = (lat: any, lon: any, color: any) =>\n  L.circleMarker([lat, lon], { fillColor: color, fillOpacity: 0.9, stroke: false });\n\n// @ts-expect-error ts-migrate(2339) FIXME: Property 'MarkerClusterIcon' does not exist on typ... Remove this comment to see the full error message\nL.MarkerClusterIcon = L.DivIcon.extend({\n  options: {\n    color: null,\n    className: \"marker-cluster\",\n    iconSize: new L.Point(40, 40),\n  },\n  // @ts-expect-error ts-migrate(7019) FIXME: Rest parameter 'args' implicitly has an 'any[]' ty... Remove this comment to see the full error message\n  createIcon(...args) {\n    const color = chroma(this.options.color);\n    const textColor = chooseTextColorForBackground(color);\n    const borderColor = color.alpha(0.4).css();\n    const backgroundColor = color.alpha(0.8).css();\n\n    const icon = L.DivIcon.prototype.createIcon.call(this, ...args);\n    icon.innerHTML = `\n      <div style=\"background: ${backgroundColor}\">\n        <span style=\"color: ${textColor}\">${toString(this.options.html)}</span>\n      </div>\n    `;\n    icon.style.background = borderColor;\n    return icon;\n  },\n});\n// @ts-expect-error ts-migrate(2339) FIXME: Property 'markerClusterIcon' does not exist on typ... Remove this comment to see the full error message\nL.markerClusterIcon = (...args) => new L.MarkerClusterIcon(...args);\n\nfunction createIconMarker(\n  lat: any,\n  lon: any,\n  { iconShape, iconFont, foregroundColor, backgroundColor, borderColor }: any\n) {\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'BeautifyIcon' does not exist on type 'ty... Remove this comment to see the full error message\n  const icon = L.BeautifyIcon.icon({\n    iconShape,\n    icon: iconFont,\n    iconSize: iconShape === \"rectangle\" ? [22, 22] : false,\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    iconAnchor: iconAnchors[iconShape],\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    popupAnchor: popupAnchors[iconShape],\n    prefix: \"fa\",\n    textColor: foregroundColor,\n    backgroundColor,\n    borderColor,\n  });\n\n  return L.marker([lat, lon], { icon });\n}\n\nfunction createMarkerClusterGroup(color: any) {\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'markerClusterGroup' does not exist on ty... Remove this comment to see the full error message\n  return L.markerClusterGroup({\n    iconCreateFunction(cluster: any) {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'markerClusterIcon' does not exist on typ... Remove this comment to see the full error message\n      return L.markerClusterIcon({ color, html: cluster.getChildCount() });\n    },\n  });\n}\n\nfunction createMarkersLayer(options: any, { color, points }: any) {\n  const { classify, clusterMarkers, customizeMarkers } = options;\n\n  const result = clusterMarkers ? createMarkerClusterGroup(color) : L.featureGroup();\n\n  // create markers\n  each(points, ({ lat, lon, row }) => {\n    const rowCopy = clone(row);\n    rowCopy[options.latColName] = lat;\n    rowCopy[options.lonColName] = lon;\n\n    let marker;\n    if (classify) {\n      marker = createHeatpointMarker(lat, lon, color);\n    } else {\n      if (customizeMarkers) {\n        marker = createIconMarker(lat, lon, options);\n      } else {\n        marker = L.marker([lat, lon]);\n      }\n    }\n\n    if (options.tooltip.enabled) {\n      if (options.tooltip.template !== \"\") {\n        marker.bindTooltip(sanitize(formatSimpleTemplate(options.tooltip.template, rowCopy)));\n      } else {\n        marker.bindTooltip(`\n          <strong>${lat}, ${lon}</strong>\n        `);\n      }\n    }\n\n    if (options.popup.enabled) {\n      if (options.popup.template !== \"\") {\n        marker.bindPopup(sanitize(formatSimpleTemplate(options.popup.template, rowCopy)));\n      } else {\n        marker.bindPopup(`\n          <ul style=\"list-style-type: none; padding-left: 0\">\n            <li><strong>${lat}, ${lon}</strong>\n            ${map(row, (v, k) => `<li>${k}: ${v}</li>`).join(\"\")}\n          </ul>\n        `);\n      }\n    }\n    result.addLayer(marker);\n  });\n\n  return result;\n}\n\nexport default function initMap(container: any) {\n  const _map = L.map(container, {\n    center: [0.0, 0.0],\n    zoom: 1,\n    scrollWheelZoom: false,\n    // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ center: [number, number]; zoom... Remove this comment to see the full error message\n    fullscreenControl: true,\n  });\n  const _tileLayer = L.tileLayer(\"//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\", {\n    attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors',\n  }).addTo(_map);\n  const _markerLayers = L.featureGroup().addTo(_map);\n  const _layersControls = L.control.layers().addTo(_map);\n\n  let onBoundsChange = () => {};\n\n  let boundsChangedFromMap = false;\n  const onMapMoveEnd = () => {\n    // @ts-expect-error ts-migrate(2554) FIXME: Expected 0 arguments, but got 1.\n    onBoundsChange(_map.getBounds());\n  };\n  _map.on(\"focus\", () => {\n    boundsChangedFromMap = true;\n    _map.on(\"moveend\", onMapMoveEnd);\n  });\n  _map.on(\"blur\", () => {\n    _map.off(\"moveend\", onMapMoveEnd);\n    boundsChangedFromMap = false;\n  });\n\n  function updateLayers(groups: any, options: any) {\n    _tileLayer.setUrl(options.mapTileUrl);\n\n    _markerLayers.eachLayer(layer => {\n      _markerLayers.removeLayer(layer);\n      _layersControls.removeLayer(layer);\n    });\n\n    each(groups, group => {\n      const layer = createMarkersLayer(options, group);\n      _markerLayers.addLayer(layer);\n      _layersControls.addOverlay(layer, group.name);\n    });\n\n    // hide layers control if it is empty\n    if (groups.length > 0) {\n      _layersControls.addTo(_map);\n    } else {\n      _layersControls.remove();\n    }\n  }\n\n  function updateBounds(bounds: any) {\n    if (!boundsChangedFromMap) {\n      bounds = bounds\n        ? L.latLngBounds([bounds._southWest.lat, bounds._southWest.lng], [bounds._northEast.lat, bounds._northEast.lng])\n        : _markerLayers.getBounds();\n      if (bounds.isValid()) {\n        _map.fitBounds(bounds, { animate: false, duration: 0 });\n      }\n    }\n  }\n\n  const unwatchResize = resizeObserver(container, () => {\n    _map.invalidateSize(false);\n  });\n\n  return {\n    get onBoundsChange() {\n      return onBoundsChange;\n    },\n    set onBoundsChange(value) {\n      onBoundsChange = isFunction(value) ? value : () => {};\n    },\n    updateLayers,\n    updateBounds,\n    destroy() {\n      unwatchResize();\n      _map.remove();\n    },\n  };\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/map/prepareData.ts",
    "content": "import d3 from \"d3\";\nimport { isNil, extend, map, filter, groupBy, omit } from \"lodash\";\n\nexport default function prepareData(data: any, options: any) {\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'scale' does not exist on type 'typeof im... Remove this comment to see the full error message\n  const colorScale = d3.scale.category10();\n\n  const { classify, latColName, lonColName } = options;\n\n  const pointGroups = classify ? groupBy(data.rows, classify) : { All: data.rows };\n\n  return filter(\n    map(pointGroups, (rows, name) => {\n      const points = filter(\n        map(rows, row => {\n          const lat = row[latColName];\n          const lon = row[lonColName];\n          if (isNil(lat) || isNil(lon)) {\n            return null;\n          }\n          return { lat, lon, row: omit(row, [latColName, lonColName]) };\n        })\n      );\n      if (points.length === 0) {\n        return null;\n      }\n\n      const result = extend({}, options.groups[name], { name, points });\n      if (isNil(result.color)) {\n        result.color = colorScale(name);\n      }\n\n      return result;\n    })\n  );\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/pivot/Editor.tsx",
    "content": "import { merge } from \"lodash\";\nimport React from \"react\";\nimport { Section, Switch } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function Editor({ options, onOptionsChange }: any) {\n  const updateOptions = (updates: any) => {\n    onOptionsChange(merge({}, options, updates));\n  };\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n        <Switch\n          data-test=\"PivotEditor.HideControls\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          id=\"pivot-show-controls\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'never'.\n          defaultChecked={!options.controls.enabled}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(enabled: any) => void' is not assignable to... Remove this comment to see the full error message\n          onChange={(enabled: any) => updateOptions({ controls: { enabled: !enabled } })}>\n          Show Pivot Controls\n        </Switch>\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n        <Switch\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          id=\"pivot-show-row-totals\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          defaultChecked={options.rendererOptions.table.rowTotals}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(rowTotals: any) => void' is not assignable ... Remove this comment to see the full error message\n          onChange={(rowTotals: any) => updateOptions({ rendererOptions: { table: { rowTotals } } })}>\n          Show Row Totals\n        </Switch>\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n        <Switch\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'never'.\n          id=\"pivot-show-column-totals\"\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'any' is not assignable to type 'never'.\n          defaultChecked={options.rendererOptions.table.colTotals}\n          // @ts-expect-error ts-migrate(2322) FIXME: Type '(colTotals: any) => void' is not assignable ... Remove this comment to see the full error message\n          onChange={(colTotals: any) => updateOptions({ rendererOptions: { table: { colTotals } } })}>\n          Show Column Totals\n        </Switch>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nEditor.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/pivot/Renderer.tsx",
    "content": "import React, { useState, useEffect, useMemo } from \"react\";\nimport { get, find, pick, map, mapValues } from \"lodash\";\nimport PivotTableUI from \"react-pivottable/PivotTableUI\";\nimport TableRenderers from \"react-pivottable/TableRenderers\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\nimport { formatColumnValue } from \"@/lib/utils\";\nimport createPlotlyComponent from \"react-plotly.js/factory\";\nimport createPlotlyRenderers from \"react-pivottable/PlotlyRenderers\";\nimport { Plotly } from \"@/visualizations/chart/plotly\";\n\nimport \"react-pivottable/pivottable.css\";\nimport \"./renderer.less\";\n\nconst Plot = createPlotlyComponent(Plotly);\nconst PlotlyRenderers = createPlotlyRenderers(Plot);\n\nconst VALID_OPTIONS = [\n  \"rows\",\n  \"cols\",\n  \"vals\",\n  \"aggregatorName\",\n  \"valueFilter\",\n  \"sorters\",\n  \"rowOrder\",\n  \"colOrder\",\n  \"derivedAttributes\",\n  \"rendererName\",\n  \"hiddenAttributes\",\n  \"hiddenFromAggregators\",\n  \"hiddenFromDragDrop\",\n  \"menuLimit\",\n  \"unusedOrientationCutoff\",\n  \"controls\",\n  \"rendererOptions\",\n];\n\nfunction formatRows({ rows, columns }: any) {\n  return map(rows, (row) =>\n    mapValues(row, (value, key) => formatColumnValue(value, find(columns, { name: key }).type))\n  );\n}\n\nexport default function Renderer({ data, options, onOptionsChange }: any) {\n  const [config, setConfig] = useState({ ...options });\n  const dataRows = useMemo(() => formatRows(data), [data]);\n\n  useEffect(() => {\n    setConfig({ ...options });\n  }, [options]);\n\n  const onChange = (updatedOptions: any) => {\n    const validOptions = pick(updatedOptions, VALID_OPTIONS);\n    setConfig({ ...validOptions });\n    onOptionsChange(validOptions);\n  };\n\n  // Legacy behavior: hideControls when controls.enabled is true\n  const hideControls = get(options, \"controls.enabled\");\n  const hideRowTotals = !get(options, \"rendererOptions.table.rowTotals\");\n  const hideColumnTotals = !get(options, \"rendererOptions.table.colTotals\");\n  return (\n    <div\n      className=\"pivot-table-visualization-container\"\n      data-hide-controls={hideControls || null}\n      data-hide-row-totals={hideRowTotals || null}\n      data-hide-column-totals={hideColumnTotals || null}\n      data-test=\"PivotTableVisualization\"\n    >\n      {/* @ts-ignore PivotTableUI types may mismatch with @types/react */}\n      <PivotTableUI\n        {...pick(config, VALID_OPTIONS)}\n        data={dataRows}\n        onChange={onChange}\n        renderers={Object.assign({}, TableRenderers, PlotlyRenderers)}\n      />\n    </div>\n  );\n}\n\nRenderer.propTypes = RendererPropTypes;\nRenderer.defaultProps = { onOptionsChange: () => {} };\n"
  },
  {
    "path": "viz-lib/src/visualizations/pivot/index.ts",
    "content": "import { merge } from \"lodash\";\n\nimport Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nconst DEFAULT_OPTIONS = {\n  controls: {\n    enabled: false, // `false` means \"show controls\" o_O\n  },\n  rendererOptions: {\n    table: {\n      colTotals: true,\n      rowTotals: true,\n    },\n  },\n};\n\nexport default {\n  type: \"PIVOT\",\n  name: \"Pivot Table\",\n  getOptions: (options: any) => merge({}, DEFAULT_OPTIONS, options),\n  Renderer,\n  Editor,\n\n  defaultRows: 10,\n  defaultColumns: 6,\n  minColumns: 2,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/pivot/renderer.less",
    "content": "@import \"../variables.less\";\n\n.pivot-table-visualization-container {\n  &[data-hide-controls] {\n    .pvtAxisContainer,\n    .pvtRenderers,\n    .pvtVals {\n      display: none;\n    }\n  }\n\n  &[data-hide-row-totals] {\n    td:last-child,\n    th:last-child {\n      &.pvtTotalLabel:not(:empty),\n      &.pvtTotal,\n      &.pvtGrandTotal {\n        display: none;\n      }\n    }\n  }\n\n  &[data-hide-column-totals] {\n    tbody > tr:last-child {\n      & > .pvtTotalLabel,\n      & > .pvtTotal,\n      & > .pvtGrandTotal {\n        display: none;\n      }\n    }\n  }\n}\n\n.pvtAxisContainer,\n.pvtVals {\n  border: 1px solid fade(@visualizations-gray, 15%);\n  background: #fff;\n}\n\n.pvtAxisContainer li span.pvtAttr {\n  background: fade(@visualizations-gray, 10%);\n  border: 1px solid fade(@visualizations-gray, 15%);\n  border-radius: 3px;\n}\n\n.pvtCheckContainer {\n  border-top: 1px solid fade(@visualizations-gray, 15%);\n  border-bottom: 1px solid fade(@visualizations-gray, 15%);\n}\n\n.pvtCheckContainer p {\n  margin: 2px;\n  line-height: 1;\n}\n\n.pvtTriangle {\n  color: fade(@visualizations-gray, 90%);\n}\n\ntable.pvtTable thead tr th,\ntable.pvtTable tbody tr th {\n  background-color: fade(@visualizations-gray, 10%);\n  border: 1px solid #ced8dc;\n}\n\ntable.pvtTable tbody tr td {\n  border: 1px solid #ced8dc;\n}\n\n.pvtFilterBox {\n  border: 1px solid fade(@visualizations-gray, 15%);\n  border-radius: 3px;\n  box-shadow: fade(@visualizations-gray, 15%) 0px 4px 9px -3px;\n\n  button {\n    background-color: rgba(102, 136, 153, 0.15);\n    margin-right: 5px;\n    border: 1px solid transparent;\n    padding: 3px 6px;\n    font-size: 13px;\n    line-height: 1.42857143;\n    border-radius: 3px;\n\n    &:hover {\n      background-color: rgba(102, 136, 153, 0.25);\n    }\n  }\n\n  input[type=\"text\"] {\n    width: 90%;\n    margin: 0 auto 10px;\n    height: 35px;\n    padding: 6px 12px;\n    border: 1px solid #e8e8e8;\n    border-radius: 3px;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/prop-types.ts",
    "content": "import PropTypes from \"prop-types\";\n\nconst VisualizationOptions = PropTypes.object;\ntype VisualizationOptions = any;\n\ntype Data = {\n  columns: any[];\n  rows: any[];\n}; // eslint-disable-line react/forbid-prop-types\n\nconst Data: PropTypes.Requireable<Data> = PropTypes.shape({\n  columns: PropTypes.arrayOf(PropTypes.object).isRequired,\n  rows: PropTypes.arrayOf(PropTypes.object).isRequired,\n});\n\ntype VisualizationType = {\n  id?: number;\n  type: string;\n  name: string;\n  options: VisualizationOptions;\n};\n\n// @ts-expect-error ts-migrate(2322) FIXME: Type 'Requireable<InferProps<{ id: Requireable<num... Remove this comment to see the full error message\nconst VisualizationType: PropTypes.Requireable<VisualizationType> = PropTypes.shape({\n  id: PropTypes.number,\n  type: PropTypes.string.isRequired,\n  name: PropTypes.string.isRequired,\n  options: VisualizationOptions.isRequired,\n});\nexport { VisualizationType };\n\n// For each visualization's renderer\nexport const RendererPropTypes = {\n  visualizationName: PropTypes.string,\n  data: Data.isRequired,\n  options: VisualizationOptions.isRequired,\n  onOptionsChange: PropTypes.func, // (newOptions) => void\n};\n\n// For each visualization's editor\nexport const EditorPropTypes = {\n  visualizationName: PropTypes.string,\n  data: Data.isRequired,\n  options: VisualizationOptions.isRequired,\n  onOptionsChange: PropTypes.func.isRequired, // (newOptions) => void\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/registeredVisualizations.ts",
    "content": "import { find, flatten, each } from \"lodash\";\nimport PropTypes from \"prop-types\";\n\nimport boxPlotVisualization from \"./box-plot\";\nimport chartVisualization from \"./chart\";\nimport choroplethVisualization from \"./choropleth\";\nimport cohortVisualization from \"./cohort\";\nimport counterVisualization from \"./counter\";\nimport detailsVisualization from \"./details\";\nimport funnelVisualization from \"./funnel\";\nimport mapVisualization from \"./map\";\nimport pivotVisualization from \"./pivot\";\nimport sankeyVisualization from \"./sankey\";\nimport sunburstVisualization from \"./sunburst\";\nimport tableVisualization from \"./table\";\nimport wordCloudVisualization from \"./word-cloud\";\n\ntype VisualizationConfig = {\n  type: string;\n  name: string;\n  getOptions: (...args: any[]) => any;\n  isDefault?: boolean;\n  isDeprecated?: boolean;\n  Renderer: (...args: any[]) => any;\n  Editor?: (...args: any[]) => any;\n  autoHeight?: boolean;\n  defaultRows?: number;\n  defaultColumns?: number;\n  minRows?: number;\n  maxRows?: number;\n  minColumns?: number;\n  maxColumns?: number;\n};\n\n// @ts-expect-error ts-migrate(2322) FIXME: Type 'Requireable<InferProps<{ type: Validator<str... Remove this comment to see the full error message\nconst VisualizationConfig: PropTypes.Requireable<VisualizationConfig> = PropTypes.shape({\n  type: PropTypes.string.isRequired,\n  name: PropTypes.string.isRequired,\n  getOptions: PropTypes.func.isRequired,\n  isDefault: PropTypes.bool,\n  isDeprecated: PropTypes.bool,\n  Renderer: PropTypes.func.isRequired,\n  Editor: PropTypes.func,\n  // other config options\n  autoHeight: PropTypes.bool,\n  defaultRows: PropTypes.number,\n  defaultColumns: PropTypes.number,\n  minRows: PropTypes.number,\n  maxRows: PropTypes.number,\n  minColumns: PropTypes.number,\n  maxColumns: PropTypes.number,\n});\n\nconst registeredVisualizations = {};\n\nfunction validateVisualizationConfig(config: any) {\n  const typeSpecs = { config: VisualizationConfig };\n  const values = { config };\n  PropTypes.checkPropTypes(typeSpecs, values, \"prop\", \"registerVisualization\");\n}\n\nfunction registerVisualization(config: any) {\n  validateVisualizationConfig(config);\n  config = {\n    Editor: () => null,\n    ...config,\n    isDefault: config.isDefault && !config.isDeprecated,\n  };\n\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  if (registeredVisualizations[config.type]) {\n    throw new Error(`Visualization ${config.type} already registered.`);\n  }\n\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  registeredVisualizations[config.type] = config;\n}\n\neach(\n  flatten([\n    boxPlotVisualization,\n    chartVisualization,\n    choroplethVisualization,\n    cohortVisualization,\n    counterVisualization,\n    detailsVisualization,\n    funnelVisualization,\n    mapVisualization,\n    pivotVisualization,\n    sankeyVisualization,\n    sunburstVisualization,\n    tableVisualization,\n    wordCloudVisualization,\n  ]),\n  registerVisualization\n);\n\nexport default registeredVisualizations;\n\nexport function getDefaultVisualization() {\n  // return any visualization explicitly marked as default, or any non-deprecated otherwise\n  return (\n    find(registeredVisualizations, (visualization: any) => visualization.isDefault) ||\n    find(registeredVisualizations, (visualization: any) => !visualization.isDeprecated)\n  );\n}\n\nexport function newVisualization(type = null, options = {}) {\n  const visualization: any = type ? registeredVisualizations[type] : getDefaultVisualization();\n  return {\n    type: visualization.type,\n    name: visualization.name,\n    description: \"\",\n    options,\n  };\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/sankey/Editor.tsx",
    "content": "import React from \"react\";\n\nexport default function Editor() {\n  return (\n    <React.Fragment>\n      <p>This visualization expects the query result to have rows in the following format:</p>\n      <ul>\n        <li>\n          <strong>stage1</strong> - stage 1 value\n        </li>\n        <li>\n          <strong>stage2</strong> - stage 2 value (or null)\n        </li>\n        <li>\n          <strong>stage3</strong> - stage 3 value (or null)\n        </li>\n        <li>\n          <strong>stage4</strong> - stage 4 value (or null)\n        </li>\n        <li>\n          <strong>stage5</strong> - stage 5 value (or null)\n        </li>\n        <li>\n          <strong>value</strong> - number of times this sequence occurred\n        </li>\n      </ul>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/sankey/Renderer.tsx",
    "content": "import React, { useState, useEffect, useMemo } from \"react\";\nimport resizeObserver from \"@/services/resizeObserver\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\n\nimport { SankeyDataType } from \"./index\";\nimport initSankey, { ExtendedSankeyDataType } from \"./initSankey\";\nimport \"./renderer.less\";\n\nexport default function Renderer({ data }: { data: SankeyDataType }) {\n  const [container, setContainer] = useState<null | HTMLDivElement>(null);\n\n  const render = useMemo(() => initSankey(data as ExtendedSankeyDataType), [data]);\n\n  useEffect(() => {\n    if (container) {\n      render(container);\n      const unwatch = resizeObserver(container, () => {\n        render(container);\n      });\n      return unwatch;\n    }\n  }, [container, render]);\n\n  return <div className=\"sankey-visualization-container\" ref={setContainer} />;\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/sankey/d3sankey.ts",
    "content": "/* eslint-disable */\n\nimport d3 from \"d3\";\n\nexport interface LinkType {\n  id: number;\n  name: string;\n  color: string;\n  x: number;\n  y: number;\n  dx: number;\n  dy: number;\n  source: SourceTargetType;\n  target: SourceTargetType;\n}\n\nexport type SourceTargetType = {\n  sourceLinks: Array<LinkType>;\n  targetLinks: Array<LinkType>;\n};\n\nexport type NodeType = LinkType & SourceTargetType;\nexport interface D3SankeyType {\n  nodeWidth: (...args: any[]) => any;\n  nodeHeight: (...args: any[]) => any;\n  nodePadding: (...args: any[]) => any;\n  nodes: (...args: any[]) => any[];\n  link: (...args: any[]) => any;\n  links: (...args: any[]) => any[];\n  size: (...args: any[]) => any;\n  layout: (...args: any[]) => any;\n  relayout: (...args: any[]) => any;\n}\n\nexport type DType = { sy: number; ty: number; value: number; source: LinkType; target: LinkType } & LinkType;\n\nfunction center(node: any) {\n  return node.y + node.dy / 2;\n}\n\nfunction value(link: any) {\n  return link.value;\n}\n\nfunction Sankey(): D3SankeyType {\n  const sankey = {};\n  let nodeWidth = 24;\n  let nodePadding = 8;\n  let size = [1, 1];\n  let nodes: any[] = [];\n  let links: any[] = [];\n\n  // Populate the sourceLinks and targetLinks for each node.\n  // Also, if the source and target are not objects, assume they are indices.\n  function computeNodeLinks() {\n    nodes.forEach(node => {\n      node.sourceLinks = [];\n      node.targetLinks = [];\n    });\n    links.forEach(link => {\n      let source = link.source;\n      let target = link.target;\n      if (typeof source === \"number\") source = link.source = nodes[link.source];\n      if (typeof target === \"number\") target = link.target = nodes[link.target];\n      source.sourceLinks.push(link);\n      target.targetLinks.push(link);\n    });\n  }\n\n  // Compute the value (size) of each node by summing the associated links.\n  function computeNodeValues() {\n    nodes.forEach(node => {\n      node.value = Math.max(d3.sum(node.sourceLinks, value), d3.sum(node.targetLinks, value));\n    });\n  }\n\n  function moveSinksRight(x: any) {\n    nodes.forEach(node => {\n      if (!node.sourceLinks.length) {\n        node.x = x - 1;\n      }\n    });\n  }\n\n  function scaleNodeBreadths(kx: any) {\n    nodes.forEach(node => {\n      node.x *= kx;\n    });\n  }\n\n  // Iteratively assign the breadth (x-position) for each node.\n  // Nodes are assigned the maximum breadth of incoming neighbors plus one;\n  // nodes with no incoming links are assigned breadth zero, while\n  // nodes with no outgoing links are assigned the maximum breadth.\n  function computeNodeBreadths() {\n    let remainingNodes = nodes;\n    let nextNodes: any;\n    let x = 0;\n\n    function assignBreadth(node: any) {\n      node.x = x;\n      node.dx = nodeWidth;\n      node.sourceLinks.forEach((link: any) => {\n        if (nextNodes.indexOf(link.target) < 0) {\n          nextNodes.push(link.target);\n        }\n      });\n    }\n\n    while (remainingNodes.length) {\n      nextNodes = [];\n      remainingNodes.forEach(assignBreadth);\n      remainingNodes = nextNodes;\n      x += 1;\n    }\n\n    moveSinksRight(x);\n    x = Math.max(\n      d3.max(nodes, n => n.x),\n      2\n    ); // get new maximum x value (min 2)\n    scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));\n  }\n\n  function computeNodeDepths(iterations: any) {\n    const nodesByBreadth = d3\n      // @ts-expect-error\n      .nest()\n      .key((d: any) => d.x)\n      .sortKeys(d3.ascending)\n      .entries(nodes)\n      .map((d: any) => d.values);\n\n    function initializeNodeDepth() {\n      // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n      const ky = d3.min(nodesByBreadth, n => (size[1] - (n.length - 1) * nodePadding) / d3.sum(n, value));\n\n      nodesByBreadth.forEach((n: any) => {\n        n.forEach((node: any, i: any) => {\n          node.y = i;\n          // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.\n          node.dy = node.value * ky;\n        });\n      });\n\n      links.forEach(link => {\n        // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.\n        link.dy = link.value * ky;\n      });\n    }\n\n    function relaxLeftToRight(alpha: any) {\n      function weightedSource(link: any) {\n        return center(link.source) * link.value;\n      }\n\n      nodesByBreadth.forEach((n: any) => {\n        n.forEach((node: any) => {\n          if (node.targetLinks.length) {\n            const y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);\n            node.y += (y - center(node)) * alpha;\n          }\n        });\n      });\n    }\n\n    function resolveCollisions() {\n      nodesByBreadth.forEach((nodes: any) => {\n        const n = nodes.length;\n        let node;\n        let dy;\n        let y0 = 0;\n        let i;\n\n        // Push any overlapping nodes down.\n        nodes.sort(ascendingDepth);\n        for (i = 0; i < n; ++i) {\n          node = nodes[i];\n          dy = y0 - node.y;\n          if (dy > 0) node.y += dy;\n          y0 = node.y + node.dy + nodePadding;\n        }\n\n        // If the bottommost node goes outside the bounds, push it back up.\n        dy = y0 - nodePadding - size[1];\n        if (dy > 0) {\n          y0 = node.y -= dy;\n\n          // Push any overlapping nodes back up.\n          for (i = n - 2; i >= 0; --i) {\n            node = nodes[i];\n            dy = node.y + node.dy + nodePadding - y0;\n            if (dy > 0) node.y -= dy;\n            y0 = node.y;\n          }\n        }\n      });\n    }\n\n    initializeNodeDepth();\n    resolveCollisions();\n\n    for (let alpha = 1; iterations > 0; iterations -= 1) {\n      relaxRightToLeft((alpha *= 0.99));\n      resolveCollisions();\n      relaxLeftToRight(alpha);\n      resolveCollisions();\n    }\n\n    function relaxRightToLeft(alpha: any) {\n      nodesByBreadth\n        .slice()\n        .reverse()\n        .forEach((nodes: any) => {\n          nodes.forEach((node: any) => {\n            if (node.sourceLinks.length) {\n              const y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);\n              node.y += (y - center(node)) * alpha;\n            }\n          });\n        });\n\n      function weightedTarget(link: any) {\n        return center(link.target) * link.value;\n      }\n    }\n\n    function ascendingDepth(a: any, b: any) {\n      return a.y - b.y;\n    }\n  }\n\n  function computeLinkDepths() {\n    nodes.forEach(node => {\n      node.sourceLinks.sort(ascendingTargetDepth);\n      node.targetLinks.sort(ascendingSourceDepth);\n    });\n    nodes.forEach(node => {\n      let sy = 0,\n        ty = 0;\n      node.sourceLinks.forEach((link: any) => {\n        link.sy = sy;\n        sy += link.dy;\n      });\n      node.targetLinks.forEach((link: any) => {\n        link.ty = ty;\n        ty += link.dy;\n      });\n    });\n\n    function ascendingSourceDepth(a: any, b: any) {\n      return a.source.y - b.source.y;\n    }\n\n    function ascendingTargetDepth(a: any, b: any) {\n      return a.target.y - b.target.y;\n    }\n  }\n\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'nodeWidth' does not exist on type '{}'.\n  sankey.nodeWidth = function(_: any) {\n    if (!arguments.length) return nodeWidth;\n    nodeWidth = +_;\n    return sankey;\n  };\n\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'nodePadding' does not exist on type '{}'... Remove this comment to see the full error message\n  sankey.nodePadding = function(_: any) {\n    if (!arguments.length) return nodePadding;\n    nodePadding = +_;\n    return sankey;\n  };\n\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'nodes' does not exist on type '{}'.\n  sankey.nodes = function(_: any) {\n    if (!arguments.length) return nodes;\n    nodes = _;\n    return sankey;\n  };\n\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'links' does not exist on type '{}'.\n  sankey.links = function(_: any) {\n    if (!arguments.length) return links;\n    links = _;\n    return sankey;\n  };\n\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'size' does not exist on type '{}'.\n  sankey.size = function(_: any) {\n    if (!arguments.length) return size;\n    size = _;\n    return sankey;\n  };\n\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'layout' does not exist on type '{}'.\n  sankey.layout = function(iterations: any) {\n    computeNodeLinks();\n    computeNodeValues();\n    computeNodeBreadths();\n    computeNodeDepths(iterations);\n    computeLinkDepths();\n    return sankey;\n  };\n\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'relayout' does not exist on type '{}'.\n  sankey.relayout = function() {\n    computeLinkDepths();\n    return sankey;\n  };\n\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'link' does not exist on type '{}'.\n  sankey.link = function() {\n    let curvature = 0.5;\n\n    function link(d: DType) {\n      const x0 = d.source.x + d.source.dx;\n      const x1 = d.target.x;\n      const xi = d3.interpolateNumber(x0, x1);\n      const x2 = xi(curvature);\n      const x3 = xi(1 - curvature);\n      const y0 = d.source.y + d.sy + d.dy / 2;\n      const y1 = d.target.y + d.ty + d.dy / 2;\n\n      return `M${x0},${y0}C${x2},${y0} ${x3},${y1} ${x1},${y1}`;\n    }\n\n    link.curvature = (_: any) => {\n      if (!arguments.length) return curvature;\n      curvature = +_;\n      return link;\n    };\n\n    return link;\n  };\n\n  return sankey as D3SankeyType;\n}\n\nexport default Sankey;\n"
  },
  {
    "path": "viz-lib/src/visualizations/sankey/index.ts",
    "content": "import Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\nexport interface SankeyDataType {\n  columns: {\n    name: string;\n    friendly_name: string;\n    type: \"integer\";\n  }[];\n\n  rows: {\n    value: number;\n    [name: string]: number | string | null;\n  }[];\n}\n\nexport default {\n  type: \"SANKEY\",\n  name: \"Sankey\",\n  getOptions: (options: {}) => ({\n    ...options,\n  }),\n  Renderer,\n  Editor,\n\n  defaultRows: 7,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/sankey/initSankey.ts",
    "content": "import {\n  isNil,\n  map,\n  extend,\n  sortBy,\n  includes,\n  filter,\n  reduce,\n  find,\n  keys,\n  values,\n  identity,\n  mapValues,\n  every,\n  isNaN,\n  isNumber,\n  isString,\n} from \"lodash\";\nimport d3 from \"d3\";\nimport d3sankey, { NodeType, LinkType, SourceTargetType, DType } from \"./d3sankey\";\nimport { SankeyDataType } from \".\";\n\nexport type ExtendedSankeyDataType = Partial<SankeyDataType> & { nodes: any[]; links: any[] };\n\nfunction getConnectedNodes(node: NodeType) {\n  console.log(node);\n  // source link = this node is the source, I need the targets\n  const nodes: any = [];\n  node.sourceLinks.forEach((link: LinkType) => {\n    nodes.push(link.target);\n  });\n  node.targetLinks.forEach((link: LinkType) => {\n    nodes.push(link.source);\n  });\n\n  return nodes;\n}\n\nfunction graph(data: ExtendedSankeyDataType[\"rows\"]) {\n  const nodesDict = {};\n  const links = {};\n  const nodes: any[] = [];\n\n  const validKey = (key: any) => key !== \"value\";\n  // @ts-expect-error\n  const dataKeys = sortBy(filter(keys(data[0]), validKey), identity);\n\n  function normalizeName(name: any) {\n    if (!isNil(name)) {\n      return \"\" + name;\n    }\n\n    return \"Exit\";\n  }\n\n  function getNode(name: string, level: any) {\n    name = normalizeName(name);\n    const key = `${name}:${String(level)}`;\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    let node = nodesDict[key];\n    if (!node) {\n      node = { name };\n      node.id = nodes.push(node) - 1;\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      nodesDict[key] = node;\n    }\n    return node;\n  }\n\n  function getLink(source: SourceTargetType, target: SourceTargetType) {\n    // @ts-expect-error ts-migrate(2538) FIXME: Type 'any[]' cannot be used as an index type.\n    let link = links[[source, target]];\n    if (!link) {\n      link = { target, source, value: 0 };\n      // @ts-expect-error ts-migrate(2538) FIXME: Type 'any[]' cannot be used as an index type.\n      links[[source, target]] = link;\n    }\n\n    return link;\n  }\n\n  function addLink(sourceName: any, targetName: any, value: any, depth: any) {\n    if ((sourceName === \"\" || !sourceName) && depth > 1) {\n      return;\n    }\n\n    const source = getNode(sourceName, depth);\n    const target = getNode(targetName, depth + 1);\n    const link = getLink(source.id, target.id);\n    link.value += parseInt(value, 10);\n  }\n\n  // @ts-expect-error\n  data.forEach((row: any) => {\n    addLink(row[dataKeys[0]], row[dataKeys[1]], row.value || 0, 1);\n    addLink(row[dataKeys[1]], row[dataKeys[2]], row.value || 0, 2);\n    addLink(row[dataKeys[2]], row[dataKeys[3]], row.value || 0, 3);\n    addLink(row[dataKeys[3]], row[dataKeys[4]], row.value || 0, 4);\n    addLink(row[dataKeys[4]], null, row.value || 0, 5); // this line ensures that the last stage has a corresponding exit node\n  });\n\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'scale' does not exist on type 'typeof im... Remove this comment to see the full error message\n  const color = d3.scale.category20();\n\n  return {\n    nodes: map(nodes, d => extend(d, { color: color(d.name.replace(/ .*/, \"\")) })),\n    links: values(links),\n  };\n}\n\nfunction spreadNodes(height: any, data: ExtendedSankeyDataType) {\n  const nodesByBreadth = d3\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'nest' does not exist on type 'typeof imp... Remove this comment to see the full error message\n    .nest()\n    .key((d: DType) => d.x)\n    .entries(data.nodes)\n    // @ts-expect-error\n    .map((d: DType) => d.values);\n\n  nodesByBreadth.forEach((nodes: any) => {\n    nodes = filter(\n      sortBy(nodes, node => -node.value),\n      node => node.name !== \"Exit\"\n    );\n\n    // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n    const sum = d3.sum(nodes, o => o.dy);\n    const padding = (height - sum) / nodes.length;\n\n    reduce(\n      nodes,\n      (y0, node) => {\n        node.y = y0;\n        return y0 + node.dy + padding;\n      },\n      0\n    );\n  });\n}\n\nfunction isDataValid(data: ExtendedSankeyDataType) {\n  // data should contain column named 'value', otherwise no reason to render anything at all\n  if (!data || !find(data.columns, c => c.name === \"value\")) {\n    return false;\n  }\n  // prepareData will have coerced any invalid data rows into NaN, which is verified below\n  return every(data.rows, row =>\n    every(row, v => {\n      if (!v || isString(v)) {\n        return true;\n      }\n      return isFinite(v);\n    })\n  );\n}\n\n// will coerce number strings into valid numbers\nfunction prepareDataRows(rows: ExtendedSankeyDataType[\"rows\"]) {\n  return map(rows, row =>\n    mapValues(row, v => {\n      if (!v || isNumber(v)) {\n        return v;\n      }\n      return isNaN(parseFloat(v)) ? v : parseFloat(v);\n    })\n  );\n}\n\nexport default function initSankey(data: ExtendedSankeyDataType) {\n  data.rows = prepareDataRows(data.rows) as ExtendedSankeyDataType[\"rows\"];\n\n  if (!isDataValid(data)) {\n    return (element: HTMLDivElement) => {\n      d3.select(element)\n        .selectAll(\"*\")\n        .remove();\n    };\n  }\n\n  data = graph(data.rows);\n  // @ts-expect-error\n  const format = (d: DType) => d3.format(\",.0f\")(d); // TODO: editor option ?\n\n  return (element: HTMLDivElement) => {\n    d3.select(element)\n      .selectAll(\"*\")\n      .remove();\n\n    const margin = {\n      top: 10,\n      right: 10,\n      bottom: 10,\n      left: 10,\n    };\n    const width = element.offsetWidth - margin.left - margin.right;\n    const height = element.offsetHeight - margin.top - margin.bottom;\n\n    if (width <= 0 || height <= 0) {\n      return;\n    }\n\n    // append the svg canvas to the page\n    const svg: d3.Selection<SVGGElement, any, any, any> = d3\n      .select(element)\n      .append(\"svg\")\n      .attr(\"class\", \"sankey\")\n      .attr(\"width\", width + margin.left + margin.right)\n      .attr(\"height\", height + margin.top + margin.bottom)\n      .append(\"g\")\n      .attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n    // Set the sankey diagram properties\n    const sankey = d3sankey()\n      .nodeWidth(15)\n      .nodePadding(10)\n      .size([width, height]);\n\n    const path = sankey.link();\n\n    sankey\n      .nodes(data.nodes)\n      .links(data.links)\n      .layout(0);\n\n    spreadNodes(height, data);\n    sankey.relayout();\n\n    // add in the links\n    const link = svg\n      .append(\"g\")\n      .selectAll(\".link\")\n      .data(data.links)\n      .enter()\n      .append(\"path\")\n      .filter(l => l.target.name !== \"Exit\")\n      .attr(\"class\", \"link\")\n      .attr(\"d\", path)\n      .style(\"stroke-width\", d => Math.max(1, d.dy))\n      .sort((a, b) => b.dy - a.dy);\n\n    // add the link titles\n    link.append(\"title\").text(d => `${d.source.name} → ${d.target.name}\\n${format(d.value)}`);\n\n    const node = svg\n      .append(\"g\")\n      .selectAll(\".node\")\n      .data(data.nodes)\n      .enter()\n      .append(\"g\")\n      .filter(n => n.name !== \"Exit\")\n      .attr(\"class\", \"node\")\n      .attr(\"transform\", (d: DType) => `translate(${d.x},${d.y})`);\n\n    function nodeMouseOver(currentNode: NodeType) {\n      let nodes = getConnectedNodes(currentNode);\n      nodes = map(nodes, i => i.id);\n      node\n        .filter(d => {\n          if (d === currentNode) {\n            return false;\n          }\n          return !includes(nodes, d.id);\n        })\n        .style(\"opacity\", 0.2);\n      link\n        .filter(l => !(includes(currentNode.sourceLinks, l) || includes(currentNode.targetLinks, l)))\n        .style(\"opacity\", 0.2);\n    }\n\n    function nodeMouseOut() {\n      node.style(\"opacity\", 1);\n      link.style(\"opacity\", 1);\n    }\n\n    // add in the nodes\n    node.on(\"mouseover\", nodeMouseOver).on(\"mouseout\", nodeMouseOut);\n\n    // add the rectangles for the nodes\n    // FIXME: d is DType, but d3 will not accept a nonstandard function\n    node\n      .append(\"rect\")\n      .attr(\"height\", (d: any) => d.dy)\n      .attr(\"width\", sankey.nodeWidth())\n      .style(\"fill\", (d: any) => d.color)\n      // @ts-expect-error\n      .style(\"stroke\", (d: any) => d3.rgb(d.color).darker(2))\n      .append(\"title\")\n      .text((d: any) => `${d.name}\\n${format(d.value)}`);\n\n    // add in the title for the nodes\n    node\n      .append(\"text\")\n      .attr(\"x\", -6)\n      .attr(\"y\", (d: any) => d.dy / 2)\n      .attr(\"dy\", \".35em\")\n      .attr(\"text-anchor\", \"end\")\n      .attr(\"transform\", null)\n      .text((d: any) => d.name)\n      .filter((d: any) => d.x < width / 2)\n      .attr(\"x\", 6 + sankey.nodeWidth())\n      .attr(\"text-anchor\", \"start\");\n  };\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/sankey/renderer.less",
    "content": "/* Sankey Visualization */\n.sankey .node rect {\n  fill-opacity: .9;\n  shape-rendering: crispEdges;\n  stroke-width: 0;\n}\n.sankey .node text {\n  text-shadow: 0 1px 0 #fff;\n}\n.sankey .link {\n  fill: none;\n  stroke: #000;\n  stroke-opacity: .2;\n}\n\n.sankey-visualization-container {\n  height: 500px;\n  overflow: hidden;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columnUtils.ts",
    "content": "import _ from \"lodash\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\n\nconst filterTypes = [\"filter\", \"multi-filter\", \"multiFilter\"];\n\nexport function getColumnNameWithoutType(column: any) {\n  let typeSplit;\n  if (column.indexOf(\"::\") !== -1) {\n    typeSplit = \"::\";\n  } else if (column.indexOf(\"__\") !== -1) {\n    typeSplit = \"__\";\n  } else {\n    return column;\n  }\n\n  const parts = column.split(typeSplit);\n  if (parts[0] === \"\" && parts.length === 2) {\n    return parts[1];\n  }\n\n  if (!_.includes(filterTypes, parts[1])) {\n    return column;\n  }\n\n  return parts[0];\n}\n\nexport function getColumnContentAlignment(type: any) {\n  return [\"integer\", \"float\", \"boolean\", \"date\", \"datetime\"].indexOf(type) >= 0 ? \"right\" : \"left\";\n}\n\nexport function getDefaultColumnsOptions(columns: any, extraFields = {}) {\n  const displayAs = {\n    integer: \"number\",\n    float: \"number\",\n    boolean: \"boolean\",\n    date: \"datetime\",\n    datetime: \"datetime\",\n  };\n\n  const defaultFields = {\n    // `string` cell options\n    allowHTML: false,\n    highlightLinks: false,\n  };\n\n  return _.map(columns, (col, index) => ({\n    name: col.name,\n    type: col.type,\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    displayAs: displayAs[col.type] || \"string\",\n    visible: true,\n    order: 100000 + index,\n    title: getColumnNameWithoutType(col.name),\n    alignContent: getColumnContentAlignment(col.type),\n    description: \"\",\n    ...defaultFields,\n    ...extraFields,\n  }));\n}\n\nexport function getDefaultFormatOptions(column: any) {\n  const dateTimeFormat = {\n    date: visualizationsSettings.dateFormat || \"DD/MM/YYYY\",\n    datetime: visualizationsSettings.dateTimeFormat || \"DD/MM/YYYY HH:mm\",\n  };\n  const numberFormat = {\n    integer: visualizationsSettings.integerFormat || \"0,0\",\n    float: visualizationsSettings.floatFormat || \"0,0.00\",\n  };\n  return {\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    dateTimeFormat: dateTimeFormat[column.type],\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    numberFormat: numberFormat[column.type],\n    nullValue: visualizationsSettings.nullValue,\n    booleanValues: visualizationsSettings.booleanValues || [\"false\", \"true\"],\n    // `image` cell options\n    imageUrlTemplate: \"{{ @ }}\",\n    imageTitleTemplate: \"{{ @ }}\",\n    imageWidth: \"\",\n    imageHeight: \"\",\n    // `link` cell options\n    linkUrlTemplate: \"{{ @ }}\",\n    linkTextTemplate: \"{{ @ }}\",\n    linkTitleTemplate: \"{{ @ }}\",\n    linkOpenInNewTab: true,\n  };\n}\n\nexport function wereColumnsReordered(queryColumns: any, visualizationColumns: any) {\n  queryColumns = _.map(queryColumns, col => col.name);\n  visualizationColumns = _.map(visualizationColumns, col => col.name);\n\n  // Some columns may be removed - so skip them (but keep original order)\n  visualizationColumns = _.filter(visualizationColumns, col => _.includes(queryColumns, col));\n  // Pick query columns that were previously saved with viz (but keep order too)\n  queryColumns = _.filter(queryColumns, col => _.includes(visualizationColumns, col));\n\n  // Both array now have the same size as they both contains only common columns\n  // (in fact, it was an intersection, that kept order of items on both arrays).\n  // Now check for equality item-by-item; if common columns are in the same order -\n  // they were not reordered in editor\n  for (let i = 0; i < queryColumns.length; i += 1) {\n    if (visualizationColumns[i] !== queryColumns[i]) {\n      return true;\n    }\n  }\n  return false;\n}\n\nexport function getColumnsOptions(columns: any, visualizationColumns: any, extraFields = {}) {\n  const options = getDefaultColumnsOptions(columns, extraFields);\n\n  if (wereColumnsReordered(columns, visualizationColumns)) {\n    visualizationColumns = _.fromPairs(\n      _.map(visualizationColumns, (col, index) => [col.name, _.extend({}, col, { order: index })])\n    );\n  } else {\n    visualizationColumns = _.fromPairs(_.map(visualizationColumns, col => [col.name, _.omit(col, \"order\")]));\n  }\n\n  _.each(options, col => _.extend(col, visualizationColumns[col.name]));\n\n  return _.sortBy(options, \"order\");\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/__snapshots__/boolean.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Table -> Columns -> Boolean Editor Changes value for FALSE 1`] = `\n{\n  \"booleanValues\": [\n    \"no\",\n    \"true\",\n  ],\n}\n`;\n\nexports[`Visualizations -> Table -> Columns -> Boolean Editor Changes value for TRUE 1`] = `\n{\n  \"booleanValues\": [\n    \"false\",\n    \"yes\",\n  ],\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/__snapshots__/datetime.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Table -> Columns -> Date/Time Editor Changes format 1`] = `\n{\n  \"dateTimeFormat\": \"YYYY/MM/DD HH:ss\",\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/__snapshots__/image.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Table -> Columns -> Image Editor Changes URL template 1`] = `\n{\n  \"imageUrlTemplate\": \"http://{{ @ }}.jpeg\",\n}\n`;\n\nexports[`Visualizations -> Table -> Columns -> Image Editor Changes height 1`] = `\n{\n  \"imageHeight\": \"300\",\n}\n`;\n\nexports[`Visualizations -> Table -> Columns -> Image Editor Changes title template 1`] = `\n{\n  \"imageTitleTemplate\": \"Image {{ @ }}\",\n}\n`;\n\nexports[`Visualizations -> Table -> Columns -> Image Editor Changes width 1`] = `\n{\n  \"imageWidth\": \"400\",\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/__snapshots__/link.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Table -> Columns -> Link Editor Changes URL template 1`] = `\n{\n  \"linkUrlTemplate\": \"http://{{ @ }}/index.html\",\n}\n`;\n\nexports[`Visualizations -> Table -> Columns -> Link Editor Changes text template 1`] = `\n{\n  \"linkTextTemplate\": \"Text of {{ @ }}\",\n}\n`;\n\nexports[`Visualizations -> Table -> Columns -> Link Editor Changes title template 1`] = `\n{\n  \"linkTitleTemplate\": \"Title of {{ @ }}\",\n}\n`;\n\nexports[`Visualizations -> Table -> Columns -> Link Editor Makes link open in new tab  1`] = `\n{\n  \"linkOpenInNewTab\": true,\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/__snapshots__/number.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Table -> Columns -> Number Editor Changes format 1`] = `\n{\n  \"numberFormat\": \"0.00%\",\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/__snapshots__/text.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Table -> Columns -> Text Editor Enables HTML content 1`] = `\n{\n  \"allowHTML\": true,\n}\n`;\n\nexports[`Visualizations -> Table -> Columns -> Text Editor Enables highlight links option 1`] = `\n{\n  \"highlightLinks\": true,\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/boolean.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport Column from \"./boolean\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(column: any, done: any) {\n  return enzyme.mount(\n    <Column.Editor\n      // @ts-expect-error ts-migrate(2322) FIXME: Type '{ visualizationName: string; column: any; on... Remove this comment to see the full error message\n      visualizationName=\"Test\"\n      column={column}\n      onChange={changedColumn => {\n        expect(changedColumn).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Table -> Columns -> Boolean\", () => {\n  describe(\"Editor\", () => {\n    test(\"Changes value for FALSE\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          booleanValues: [\"false\", \"true\"],\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Boolean.False\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"no\" } });\n    });\n\n    test(\"Changes value for TRUE\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          booleanValues: [\"false\", \"true\"],\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Boolean.True\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"yes\" } });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/boolean.tsx",
    "content": "import React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Input } from \"@/components/visualizations/editor\";\nimport { createBooleanFormatter } from \"@/lib/value-format\";\n\ntype Props = {\n  column: {\n    name: string;\n    booleanValues?: string[];\n  };\n  onChange: (...args: any[]) => any;\n};\n\nfunction Editor({ column, onChange }: Props) {\n  function handleChange(index: any, value: any) {\n    // @ts-expect-error ts-migrate(2488) FIXME: Type 'string[] | undefined' must have a '[Symbol.i... Remove this comment to see the full error message\n    const booleanValues = [...column.booleanValues];\n    booleanValues.splice(index, 1, value);\n    onChange({ booleanValues });\n  }\n\n  const [handleChangeDebounced] = useDebouncedCallback(handleChange, 200);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label={\n            <React.Fragment>\n              Value for <code>false</code>\n            </React.Fragment>\n          }\n          data-test=\"Table.ColumnEditor.Boolean.False\"\n          // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.\n          defaultValue={column.booleanValues[0]}\n          onChange={(event: any) => handleChangeDebounced(0, event.target.value)}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label={\n            <React.Fragment>\n              Value for <code>true</code>\n            </React.Fragment>\n          }\n          data-test=\"Table.ColumnEditor.Boolean.True\"\n          // @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.\n          defaultValue={column.booleanValues[1]}\n          onChange={(event: any) => handleChangeDebounced(1, event.target.value)}\n        />\n      </Section>\n    </React.Fragment>\n  );\n}\n\nexport default function initBooleanColumn(column: any) {\n  const format = createBooleanFormatter(column.booleanValues);\n\n  function prepareData(row: any) {\n    return {\n      text: format(row[column.name]),\n    };\n  }\n\n  function BooleanColumn({ row }: any) {\n    // eslint-disable-line react/prop-types\n    const { text } = prepareData(row);\n    return text;\n  }\n\n  BooleanColumn.prepareData = prepareData;\n\n  return BooleanColumn;\n}\n\ninitBooleanColumn.friendlyName = \"Boolean\";\ninitBooleanColumn.Editor = Editor;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/datetime.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport Column from \"./datetime\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(column: any, done: any) {\n  return enzyme.mount(\n    <Column.Editor\n      // @ts-expect-error ts-migrate(2322) FIXME: Type '{ visualizationName: string; column: any; on... Remove this comment to see the full error message\n      visualizationName=\"Test\"\n      column={column}\n      onChange={changedColumn => {\n        expect(changedColumn).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Table -> Columns -> Date/Time\", () => {\n  describe(\"Editor\", () => {\n    test(\"Changes format\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          dateTimeFormat: \"YYYY-MM-DD HH:mm:ss\",\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.DateTime.Format\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"YYYY/MM/DD HH:ss\" } });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/datetime.tsx",
    "content": "import React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Input, ContextHelp } from \"@/components/visualizations/editor\";\nimport { createDateTimeFormatter } from \"@/lib/value-format\";\n\ntype Props = {\n  column: {\n    name: string;\n    dateTimeFormat?: string;\n  };\n  onChange: (...args: any[]) => any;\n};\n\nfunction Editor({ column, onChange }: Props) {\n  const [onChangeDebounced] = useDebouncedCallback(onChange, 200);\n\n  return (\n    // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n    <Section>\n      <Input\n        label={\n          <React.Fragment>\n            Date/Time format\n            <ContextHelp.DateTimeFormatSpecs />\n          </React.Fragment>\n        }\n        data-test=\"Table.ColumnEditor.DateTime.Format\"\n        defaultValue={column.dateTimeFormat}\n        onChange={(event: any) => onChangeDebounced({ dateTimeFormat: event.target.value })}\n      />\n    </Section>\n  );\n}\n\nexport default function initDateTimeColumn(column: any) {\n  const format = createDateTimeFormatter(column.dateTimeFormat);\n\n  function prepareData(row: any) {\n    return {\n      text: format(row[column.name]),\n    };\n  }\n\n  function DateTimeColumn({ row }: any) {\n    // eslint-disable-line react/prop-types\n    const { text } = prepareData(row);\n    return text;\n  }\n\n  DateTimeColumn.prepareData = prepareData;\n\n  return DateTimeColumn;\n}\n\ninitDateTimeColumn.friendlyName = \"Date/Time\";\ninitDateTimeColumn.Editor = Editor;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/image.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport Column from \"./image\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(column: any, done: any) {\n  return enzyme.mount(\n    <Column.Editor\n      // @ts-expect-error ts-migrate(2322) FIXME: Type '{ visualizationName: string; column: any; on... Remove this comment to see the full error message\n      visualizationName=\"Test\"\n      column={column}\n      onChange={changedColumn => {\n        expect(changedColumn).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Table -> Columns -> Image\", () => {\n  describe(\"Editor\", () => {\n    test(\"Changes URL template\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          imageUrlTemplate: \"{{ @ }}\",\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Image.UrlTemplate\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"http://{{ @ }}.jpeg\" } });\n    });\n\n    test(\"Changes width\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          imageWidth: null,\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Image.Width\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"400\" } });\n    });\n\n    test(\"Changes height\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          imageHeight: null,\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Image.Height\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"300\" } });\n    });\n\n    test(\"Changes title template\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          imageUrlTemplate: \"{{ @ }}\",\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Image.TitleTemplate\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"Image {{ @ }}\" } });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/image.tsx",
    "content": "import { extend, trim } from \"lodash\";\nimport React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Input, ControlLabel, ContextHelp } from \"@/components/visualizations/editor\";\nimport { formatSimpleTemplate } from \"@/lib/value-format\";\n\ntype Props = {\n  column: {\n    name: string;\n    imageUrlTemplate?: string;\n    imageWidth?: string;\n    imageHeight?: string;\n    imageTitleTemplate?: string;\n  };\n  onChange: (...args: any[]) => any;\n};\n\nfunction Editor({ column, onChange }: Props) {\n  const [onChangeDebounced] = useDebouncedCallback(onChange, 200);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label=\"URL template\"\n          data-test=\"Table.ColumnEditor.Image.UrlTemplate\"\n          defaultValue={column.imageUrlTemplate}\n          onChange={(event: any) => onChangeDebounced({ imageUrlTemplate: event.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <ControlLabel\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message\n          label={\n            <React.Fragment>\n              Size\n              {/* @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message */}\n              <ContextHelp placement=\"topLeft\" arrowPointAtCenter>\n                <div style={{ marginBottom: 5 }}>Any positive integer value that specifies size in pixels.</div>\n                <div>Leave empty to use default value.</div>\n              </ContextHelp>\n            </React.Fragment>\n          }>\n          {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message */}\n          <div className=\"image-dimension-selector\">\n            <Input\n              data-test=\"Table.ColumnEditor.Image.Width\"\n              placeholder=\"Width\"\n              defaultValue={column.imageWidth}\n              onChange={(event: any) => onChangeDebounced({ imageWidth: event.target.value })}\n            />\n            <span className=\"image-dimension-selector-spacer\">&times;</span>\n            <Input\n              data-test=\"Table.ColumnEditor.Image.Height\"\n              placeholder=\"Height\"\n              defaultValue={column.imageHeight}\n              onChange={(event: any) => onChangeDebounced({ imageHeight: event.target.value })}\n            />\n          </div>\n        </ControlLabel>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label=\"Title template\"\n          data-test=\"Table.ColumnEditor.Image.TitleTemplate\"\n          defaultValue={column.imageTitleTemplate}\n          onChange={(event: any) => onChangeDebounced({ imageTitleTemplate: event.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message */}\n        <ContextHelp\n          placement=\"topLeft\"\n          arrowPointAtCenter\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message\n          icon={<span style={{ cursor: \"default\" }}>Format specs {ContextHelp.defaultIcon}</span>}>\n          <div>\n            All columns can be referenced using <code>{\"{{ column_name }}\"}</code> syntax.\n          </div>\n          <div>\n            Use <code>{\"{{ @ }}\"}</code> to reference current (this) column.\n          </div>\n          <div>This syntax is applicable to URL, Title and Size options.</div>\n        </ContextHelp>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nexport default function initImageColumn(column: any) {\n  function prepareData(row: any) {\n    row = extend({ \"@\": row[column.name] }, row);\n\n    const src = trim(formatSimpleTemplate(column.imageUrlTemplate, row));\n    if (src === \"\") {\n      return {};\n    }\n\n    const width = parseInt(formatSimpleTemplate(column.imageWidth, row), 10);\n    const height = parseInt(formatSimpleTemplate(column.imageHeight, row), 10);\n    const title = trim(formatSimpleTemplate(column.imageTitleTemplate, row));\n\n    const result = { src };\n\n    if (Number.isFinite(width) && width > 0) {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'width' does not exist on type '{ src: st... Remove this comment to see the full error message\n      result.width = width;\n    }\n    if (Number.isFinite(height) && height > 0) {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'height' does not exist on type '{ src: s... Remove this comment to see the full error message\n      result.height = height;\n    }\n    if (title !== \"\") {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type '{ src: str... Remove this comment to see the full error message\n      result.text = title; // `text` is used for search\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'title' does not exist on type '{ src: st... Remove this comment to see the full error message\n      result.title = title;\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'alt' does not exist on type '{ src: stri... Remove this comment to see the full error message\n      result.alt = title;\n    }\n\n    return result;\n  }\n\n  function ImageColumn({ row }: any) {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type '{}'.\n    // eslint-disable-line react/prop-types\n    const { text, ...props } = prepareData(row);\n    return <img alt=\"\" {...props} />;\n  }\n\n  ImageColumn.prepareData = prepareData;\n\n  return ImageColumn;\n}\n\ninitImageColumn.friendlyName = \"Image\";\ninitImageColumn.Editor = Editor;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/index.ts",
    "content": "import initTextColumn from \"./text\";\nimport initNumberColumn from \"./number\";\nimport initDateTimeColumn from \"./datetime\";\nimport initBooleanColumn from \"./boolean\";\nimport initLinkColumn from \"./link\";\nimport initImageColumn from \"./image\";\nimport initJsonColumn from \"./json\";\n\n// this map should contain all possible values for `column.displayAs` property\nexport default {\n  string: initTextColumn,\n  number: initNumberColumn,\n  datetime: initDateTimeColumn,\n  boolean: initBooleanColumn,\n  link: initLinkColumn,\n  image: initImageColumn,\n  json: initJsonColumn,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/json.tsx",
    "content": "import { isString, isUndefined } from \"lodash\";\nimport React from \"react\";\nimport JsonViewInteractive from \"@/components/json-view-interactive/JsonViewInteractive\";\nimport { visualizationsSettings } from \"@/visualizations/visualizationsSettings\";\n\nexport default function initJsonColumn(column: any) {\n  function prepareData(row: any) {\n    const text = row[column.name];\n    if (isString(text) && text.length <= visualizationsSettings.tableCellMaxJSONSize) {\n      try {\n        return { text, value: JSON.parse(text) };\n      } catch (e) {\n        // ignore `JSON.parse` error and return default value\n      }\n    }\n    return { text, value: undefined };\n  }\n\n  function JsonColumn({ row }: any) {\n    // eslint-disable-line react/prop-types\n    const { text, value } = prepareData(row);\n    if (isUndefined(value)) {\n      return <div className=\"json-cell-invalid\">{\"\" + text}</div>;\n    }\n\n    return (\n      <div className=\"json-cell-valid\">\n        <JsonViewInteractive value={value} />\n      </div>\n    );\n  }\n\n  JsonColumn.prepareData = prepareData;\n\n  return JsonColumn;\n}\n\ninitJsonColumn.friendlyName = \"JSON\";\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/link.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport Column from \"./link\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(column: any, done: any) {\n  return enzyme.mount(\n    <Column.Editor\n      // @ts-expect-error ts-migrate(2322) FIXME: Type '{ visualizationName: string; column: any; on... Remove this comment to see the full error message\n      visualizationName=\"Test\"\n      column={column}\n      onChange={changedColumn => {\n        expect(changedColumn).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Table -> Columns -> Link\", () => {\n  describe(\"Editor\", () => {\n    test(\"Changes URL template\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          linkUrlTemplate: \"{{ @ }}\",\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Link.UrlTemplate\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"http://{{ @ }}/index.html\" } });\n    });\n\n    test(\"Changes text template\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          linkTextTemplate: \"{{ @ }}\",\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Link.TextTemplate\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"Text of {{ @ }}\" } });\n    });\n\n    test(\"Changes title template\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          linkTitleTemplate: \"{{ @ }}\",\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Link.TitleTemplate\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"Title of {{ @ }}\" } });\n    });\n\n    test(\"Makes link open in new tab \", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          linkOpenInNewTab: false,\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Link.OpenInNewTab\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { checked: true } });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/link.tsx",
    "content": "import { extend, trim } from \"lodash\";\nimport React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Input, Checkbox, ContextHelp } from \"@/components/visualizations/editor\";\nimport { formatSimpleTemplate } from \"@/lib/value-format\";\n\ntype Props = {\n  column: {\n    name: string;\n    linkUrlTemplate?: string;\n    linkTextTemplate?: string;\n    linkTitleTemplate?: string;\n    linkOpenInNewTab?: boolean;\n  };\n  onChange: (...args: any[]) => any;\n};\n\nfunction Editor({ column, onChange }: Props) {\n  const [onChangeDebounced] = useDebouncedCallback(onChange, 200);\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label=\"URL template\"\n          data-test=\"Table.ColumnEditor.Link.UrlTemplate\"\n          defaultValue={column.linkUrlTemplate}\n          onChange={(event: any) => onChangeDebounced({ linkUrlTemplate: event.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label=\"Text template\"\n          data-test=\"Table.ColumnEditor.Link.TextTemplate\"\n          defaultValue={column.linkTextTemplate}\n          onChange={(event: any) => onChangeDebounced({ linkTextTemplate: event.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label=\"Title template\"\n          data-test=\"Table.ColumnEditor.Link.TitleTemplate\"\n          defaultValue={column.linkTitleTemplate}\n          onChange={(event: any) => onChangeDebounced({ linkTitleTemplate: event.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Table.ColumnEditor.Link.OpenInNewTab\"\n          checked={column.linkOpenInNewTab}\n          onChange={event => onChange({ linkOpenInNewTab: event.target.checked })}>\n          Open in new tab\n        </Checkbox>\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message */}\n        <ContextHelp\n          placement=\"topLeft\"\n          arrowPointAtCenter\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message\n          icon={<span style={{ cursor: \"default\" }}>Format specs {ContextHelp.defaultIcon}</span>}>\n          <div>\n            All columns can be referenced using <code>{\"{{ column_name }}\"}</code> syntax.\n          </div>\n          <div>\n            Use <code>{\"{{ @ }}\"}</code> to reference current (this) column.\n          </div>\n          <div>This syntax is applicable to URL, Text and Title options.</div>\n        </ContextHelp>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nexport default function initLinkColumn(column: any) {\n  function prepareData(row: any) {\n    row = extend({ \"@\": row[column.name] }, row);\n\n    const href = trim(formatSimpleTemplate(column.linkUrlTemplate, row));\n    if (href === \"\") {\n      return {};\n    }\n\n    const title = trim(formatSimpleTemplate(column.linkTitleTemplate, row));\n    const text = trim(formatSimpleTemplate(column.linkTextTemplate, row));\n\n    const result = {\n      href,\n      text: text !== \"\" ? text : href,\n    };\n\n    if (title !== \"\") {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'title' does not exist on type '{ href: s... Remove this comment to see the full error message\n      result.title = title;\n    }\n    if (column.linkOpenInNewTab) {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'target' does not exist on type '{ href: ... Remove this comment to see the full error message\n      result.target = \"_blank\";\n    }\n\n    return result;\n  }\n\n  function LinkColumn({ row }: any) {\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type '{}'.\n    // eslint-disable-line react/prop-types\n    const { text, ...props } = prepareData(row);\n    return <a {...props}>{text}</a>;\n  }\n\n  LinkColumn.prepareData = prepareData;\n\n  return LinkColumn;\n}\n\ninitLinkColumn.friendlyName = \"Link\";\ninitLinkColumn.Editor = Editor;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/number.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport Column from \"./number\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(column: any, done: any) {\n  return enzyme.mount(\n    <Column.Editor\n      // @ts-expect-error ts-migrate(2322) FIXME: Type '{ visualizationName: string; column: any; on... Remove this comment to see the full error message\n      visualizationName=\"Test\"\n      column={column}\n      onChange={changedColumn => {\n        expect(changedColumn).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Table -> Columns -> Number\", () => {\n  describe(\"Editor\", () => {\n    test(\"Changes format\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          numberFormat: \"0[.]0000\",\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Number.Format\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { value: \"0.00%\" } });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/number.tsx",
    "content": "import React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { Section, Input, ContextHelp } from \"@/components/visualizations/editor\";\nimport { createNumberFormatter } from \"@/lib/value-format\";\n\ntype Props = {\n  column: {\n    name: string;\n    numberFormat?: string;\n  };\n  onChange: (...args: any[]) => any;\n};\n\nfunction Editor({ column, onChange }: Props) {\n  const [onChangeDebounced] = useDebouncedCallback(onChange, 200);\n\n  return (\n    // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n    <Section>\n      <Input\n        label={\n          <React.Fragment>\n            Number format\n            <ContextHelp.NumberFormatSpecs />\n          </React.Fragment>\n        }\n        data-test=\"Table.ColumnEditor.Number.Format\"\n        defaultValue={column.numberFormat}\n        onChange={(event: any) => onChangeDebounced({ numberFormat: event.target.value })}\n      />\n    </Section>\n  );\n}\n\nexport default function initNumberColumn(column: any) {\n  const format = createNumberFormatter(column.numberFormat, true);\n\n  function prepareData(row: any) {\n    return {\n      text: format(row[column.name]),\n    };\n  }\n\n  function NumberColumn({ row }: any) {\n    // eslint-disable-line react/prop-types\n    const { text } = prepareData(row);\n    return text;\n  }\n\n  NumberColumn.prepareData = prepareData;\n\n  return NumberColumn;\n}\n\ninitNumberColumn.friendlyName = \"Number\";\ninitNumberColumn.Editor = Editor;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/text.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport Column from \"./text\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(column: any, done: any) {\n  return enzyme.mount(\n    <Column.Editor\n      // @ts-expect-error ts-migrate(2322) FIXME: Type '{ visualizationName: string; column: any; on... Remove this comment to see the full error message\n      visualizationName=\"Test\"\n      column={column}\n      onChange={changedColumn => {\n        expect(changedColumn).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Table -> Columns -> Text\", () => {\n  describe(\"Editor\", () => {\n    test(\"Enables HTML content\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          allowHTML: false,\n          highlightLinks: false,\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Text.AllowHTML\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { checked: true } });\n    });\n\n    test(\"Enables highlight links option\", done => {\n      const el = mount(\n        {\n          name: \"a\",\n          allowHTML: true,\n          highlightLinks: false,\n        },\n        done\n      );\n\n      findByTestID(el, \"Table.ColumnEditor.Text.HighlightLinks\")\n        .last()\n        .find(\"input\")\n        .simulate(\"change\", { target: { checked: true } });\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/columns/text.tsx",
    "content": "import React from \"react\";\nimport HtmlContent from \"@/components/HtmlContent\";\nimport { Section, Checkbox } from \"@/components/visualizations/editor\";\nimport { createTextFormatter } from \"@/lib/value-format\";\n\ntype Props = {\n  column: {\n    name: string;\n    allowHTML?: boolean;\n    highlightLinks?: boolean;\n  };\n  onChange: (...args: any[]) => any;\n};\n\nfunction Editor({ column, onChange }: Props) {\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Checkbox\n          data-test=\"Table.ColumnEditor.Text.AllowHTML\"\n          checked={column.allowHTML}\n          onChange={event => onChange({ allowHTML: event.target.checked })}>\n          Allow HTML content\n        </Checkbox>\n      </Section>\n\n      {column.allowHTML && (\n        // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n        <Section>\n          <Checkbox\n            data-test=\"Table.ColumnEditor.Text.HighlightLinks\"\n            checked={column.highlightLinks}\n            onChange={event => onChange({ highlightLinks: event.target.checked })}>\n            Highlight links\n          </Checkbox>\n        </Section>\n      )}\n    </React.Fragment>\n  );\n}\n\nexport default function initTextColumn(column: any) {\n  const format = createTextFormatter(column.allowHTML && column.highlightLinks);\n\n  function prepareData(row: any) {\n    return {\n      text: format(row[column.name]),\n    };\n  }\n\n  function TextColumn({ row }: any) {\n    // eslint-disable-line react/prop-types\n    const { text } = prepareData(row);\n    return (column.allowHTML && typeof text === 'string') ? <HtmlContent>{text}</HtmlContent> : text;\n  }\n\n  TextColumn.prepareData = prepareData;\n\n  return TextColumn;\n}\n\ninitTextColumn.friendlyName = \"Text\";\ninitTextColumn.Editor = Editor;\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/components/ColumnEditor.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport ColumnEditor from \"./ColumnEditor\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(column: any, variant: \"table\" | \"details\", onChange: any = jest.fn()) {\n  return enzyme.mount(\n    <ColumnEditor\n      column={column}\n      variant={variant}\n      onChange={onChange}\n    />\n  );\n}\n\nconst mockColumn = {\n  name: \"user_id\",\n  title: \"user_id\",\n  visible: true,\n  alignContent: \"left\" as const,\n  displayAs: \"string\",\n  description: \"\",\n  allowSearch: false,\n};\n\ndescribe(\"Shared ColumnEditor\", () => {\n  describe(\"Common functionality\", () => {\n    test.each([\"table\", \"details\"] as const)(\"Changes column title - %s variant\", async (variant) => {\n      return new Promise<void>((resolve) => {\n        const onChange = jest.fn((changes) => {\n          expect(changes).toEqual({\n            ...mockColumn,\n            title: \"User ID\",\n          });\n          resolve();\n        });\n        const el = mount(mockColumn, variant, onChange);\n\n        const testPrefix = variant === \"table\" ? \"Table\" : \"Details\";\n        findByTestID(el, `${testPrefix}.Column.user_id.Title`)\n          .find(\"input\")\n          .simulate(\"change\", { target: { value: \"User ID\" } });\n      });\n    });\n\n    test.each([\"table\", \"details\"] as const)(\"Changes column alignment - %s variant\", (variant) => {\n      const onChange = jest.fn();\n      const el = mount({\n        ...mockColumn,\n        name: \"amount\",\n        displayAs: \"number\",\n      }, variant, onChange);\n\n      const testPrefix = variant === \"table\" ? \"Table\" : \"Details\";\n      findByTestID(el, `${testPrefix}.Column.amount.TextAlignment`)\n        .find('input[value=\"right\"]')\n        .simulate(\"change\", { target: { value: \"right\" } });\n\n      expect(onChange).toHaveBeenCalledWith({\n        ...mockColumn,\n        name: \"amount\",\n        displayAs: \"number\",\n        alignContent: \"right\",\n      });\n    });\n\n    test.each([\"table\", \"details\"] as const)(\"Changes column description - %s variant\", async (variant) => {\n      return new Promise<void>((resolve) => {\n        const onChange = jest.fn((changes) => {\n          expect(changes).toEqual({\n            ...mockColumn,\n            name: \"status\",\n            title: \"Status\",\n            description: \"Current order status\",\n          });\n          resolve();\n        });\n        const el = mount({\n          ...mockColumn,\n          name: \"status\",\n          title: \"Status\",\n        }, variant, onChange);\n\n        const testPrefix = variant === \"table\" ? \"Table\" : \"Details\";\n        findByTestID(el, `${testPrefix}.Column.status.Description`)\n          .find(\"input\")\n          .simulate(\"change\", { target: { value: \"Current order status\" } });\n      });\n    });\n\n    test.each([\"table\", \"details\"] as const)(\"Changes display type - %s variant\", (variant) => {\n      const onChange = jest.fn();\n      const el = mount({\n        ...mockColumn,\n        name: \"created_at\",\n        title: \"Created At\",\n        displayAs: \"datetime\",\n      }, variant, onChange);\n\n      const testPrefix = variant === \"table\" ? \"Table\" : \"Details\";\n      findByTestID(el, `${testPrefix}.Column.created_at.DisplayAs`)\n        .find(\".ant-select-selector\")\n        .simulate(\"mouseDown\");\n      findByTestID(el, `${testPrefix}.Column.created_at.DisplayAs.string`)\n        .simulate(\"click\");\n\n      expect(onChange).toHaveBeenCalledWith({\n        ...mockColumn,\n        name: \"created_at\",\n        title: \"Created At\",\n        displayAs: \"string\",\n      });\n    });\n  });\n\n  describe(\"Table variant specific\", () => {\n    test(\"Shows search checkbox\", () => {\n      const el = mount(mockColumn, \"table\");\n\n      const searchCheckbox = findByTestID(el, \"Table.Column.user_id.UseForSearch\");\n      expect(searchCheckbox.find(\"input[type='checkbox']\")).toHaveLength(1);\n    });\n\n    test(\"Changes search setting\", () => {\n      const onChange = jest.fn();\n      const el = mount({\n        ...mockColumn,\n        allowSearch: false,\n      }, \"table\", onChange);\n\n      findByTestID(el, \"Table.Column.user_id.UseForSearch\")\n        .find(\"input[type='checkbox']\")\n        .simulate(\"change\", { target: { checked: true } });\n\n      expect(onChange).toHaveBeenCalledWith({\n        ...mockColumn,\n        allowSearch: true,\n      });\n    });\n\n    test(\"Uses correct CSS class\", () => {\n      const el = mount(mockColumn, \"table\");\n      expect(el.find(\".table-visualization-editor-column\")).toHaveLength(1);\n    });\n  });\n\n  describe(\"Details variant specific\", () => {\n    test(\"Hides search checkbox\", () => {\n      const el = mount(mockColumn, \"details\");\n\n      const searchCheckbox = findByTestID(el, \"Details.Column.user_id.UseForSearch\");\n      expect(searchCheckbox).toHaveLength(0);\n    });\n\n    test(\"Uses correct CSS class\", () => {\n      const el = mount(mockColumn, \"details\");\n      expect(el.find(\".details-visualization-editor-column\")).toHaveLength(1);\n    });\n  });\n\n  describe(\"Props and defaults\", () => {\n    test(\"Uses default showSearch based on variant\", () => {\n      const tableEl = mount(mockColumn, \"table\");\n      const detailsEl = mount(mockColumn, \"details\");\n\n      expect(findByTestID(tableEl, \"Table.Column.user_id.UseForSearch\").find(\"input[type='checkbox']\")).toHaveLength(1);\n      expect(findByTestID(detailsEl, \"Details.Column.user_id.UseForSearch\")).toHaveLength(0);\n    });\n\n    test(\"Allows custom testPrefix\", () => {\n      const el = mount(mockColumn, \"table\");\n      el.setProps({ testPrefix: \"Custom.Prefix\" });\n      el.update();\n\n      expect(findByTestID(el, \"Custom.Prefix.Title\").find(\"input\")).toHaveLength(1);\n    });\n\n    test(\"Handles missing onChange gracefully\", () => {\n      const el = mount(mockColumn, \"table\", undefined);\n\n      expect(() => {\n        findByTestID(el, \"Table.Column.user_id.Title\")\n          .find(\"input\")\n          .simulate(\"change\", { target: { value: \"New Title\" } });\n      }).not.toThrow();\n    });\n  });\n\n  describe(\"Rendering\", () => {\n    test(\"Table variant renders with correct structure\", () => {\n      const el = mount({\n        ...mockColumn,\n        allowSearch: true,\n        description: \"Sample description\",\n      }, \"table\");\n\n      // Verify key elements are present\n      expect(el.find('.table-visualization-editor-column')).toHaveLength(1);\n      expect(findByTestID(el, \"Table.Column.user_id.Title\").find(\"input\")).toHaveLength(1);\n      expect(findByTestID(el, \"Table.Column.user_id.TextAlignment\").find(\"input[type='radio']\")).toHaveLength(3);\n      expect(findByTestID(el, \"Table.Column.user_id.UseForSearch\").find(\"input[type='checkbox']\")).toHaveLength(1);\n      expect(findByTestID(el, \"Table.Column.user_id.Description\").find(\"input\")).toHaveLength(1);\n      expect(findByTestID(el, \"Table.Column.user_id.DisplayAs\")).toHaveLength(7); // Expected count based on current behavior\n    });\n\n    test(\"Details variant renders with correct structure\", () => {\n      const el = mount({\n        ...mockColumn,\n        description: \"Sample description\",\n      }, \"details\");\n\n      // Verify key elements are present\n      expect(el.find('.details-visualization-editor-column')).toHaveLength(1);\n      expect(findByTestID(el, \"Details.Column.user_id.Title\").find(\"input\")).toHaveLength(1);\n      expect(findByTestID(el, \"Details.Column.user_id.TextAlignment\").find(\"input[type='radio']\")).toHaveLength(3);\n      expect(findByTestID(el, \"Details.Column.user_id.UseForSearch\")).toHaveLength(0); // Should not exist\n      expect(findByTestID(el, \"Details.Column.user_id.Description\").find(\"input\")).toHaveLength(1);\n      expect(findByTestID(el, \"Details.Column.user_id.DisplayAs\")).toHaveLength(7); // Expected count based on current behavior\n    });\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/components/ColumnEditor.tsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport * as Grid from \"antd/lib/grid\";\nimport { Section, Select, Input, Checkbox, TextAlignmentSelect } from \"@/components/visualizations/editor\";\n\nimport ColumnTypes from \"../columns\";\n\ntype Column = {\n  name: string;\n  title?: string;\n  visible?: boolean;\n  alignContent?: \"left\" | \"center\" | \"right\";\n  displayAs?: any;\n  description?: string;\n  allowSearch?: boolean;\n};\n\ntype ColumnEditorProps = {\n  column: Column;\n  onChange?: (changes: any) => any;\n  variant: \"table\" | \"details\";\n  showSearch?: boolean;\n  testPrefix?: string;\n};\n\nexport default function ColumnEditor({\n  column,\n  onChange,\n  variant,\n  showSearch = variant === \"table\",\n  testPrefix,\n}: ColumnEditorProps) {\n  function handleChange(changes: any) {\n    if (onChange) {\n      onChange({ ...column, ...changes });\n    }\n  }\n\n  const [handleChangeDebounced] = useDebouncedCallback(handleChange, 200);\n\n  // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n  const AdditionalOptions = ColumnTypes[column.displayAs].Editor || null;\n\n  const cssClass = `${variant}-visualization-editor-column`;\n  const dataTestPrefix = testPrefix || `${variant === \"table\" ? \"Table\" : \"Details\"}.Column.${column.name}`;\n\n  return (\n    <div className={cssClass}>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: Element[]; gutter: number; type:... Remove this comment to see the full error message */}\n        <Grid.Row gutter={15} type=\"flex\" align=\"middle\">\n          <Grid.Col span={16}>\n            <Input\n              data-test={`${dataTestPrefix}.Title`}\n              defaultValue={column.title}\n              onChange={(event: any) => handleChangeDebounced({ title: event.target.value })}\n            />\n          </Grid.Col>\n          <Grid.Col span={8}>\n            <TextAlignmentSelect\n              data-test={`${dataTestPrefix}.TextAlignment`}\n              defaultValue={column.alignContent}\n              onChange={(event: any) => handleChange({ alignContent: event.target.value })}\n            />\n          </Grid.Col>\n        </Grid.Row>\n      </Section>\n\n      {showSearch && (\n        /* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */\n        <Section>\n          <Checkbox\n            data-test={`${dataTestPrefix}.UseForSearch`}\n            defaultChecked={column.allowSearch}\n            onChange={event => handleChange({ allowSearch: event.target.checked })}>\n            Use for search\n          </Checkbox>\n        </Section>\n      )}\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Input\n          label=\"Description\"\n          data-test={`${dataTestPrefix}.Description`}\n          defaultValue={column.description}\n          onChange={(event: any) => handleChangeDebounced({ description: event.target.value })}\n        />\n      </Section>\n\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Display as:\"\n          data-test={`${dataTestPrefix}.DisplayAs`}\n          defaultValue={column.displayAs}\n          onChange={(displayAs: any) => handleChange({ displayAs })}>\n          {map(ColumnTypes, ({ friendlyName }, key) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={key} data-test={`${dataTestPrefix}.DisplayAs.${key}`}>\n              {friendlyName}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n\n      {AdditionalOptions && <AdditionalOptions column={column} onChange={handleChange} />}\n    </div>\n  );\n}\n\nColumnEditor.defaultProps = {\n  onChange: () => {},\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/shared/components/ColumnsSettings.tsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport Collapse from \"antd/lib/collapse\";\nimport Tooltip from \"antd/lib/tooltip\";\nimport Typography from \"antd/lib/typography\";\n// @ts-expect-error ts-migrate(2724) FIXME: Module '\"../../../../node_modules/react-sortable-h... Remove this comment to see the full error message\nimport { sortableElement } from \"react-sortable-hoc\";\nimport { SortableContainer, DragHandle } from \"@/components/sortable\";\nimport PropTypes from \"prop-types\";\n\nimport EyeOutlinedIcon from \"@ant-design/icons/EyeOutlined\";\nimport EyeInvisibleOutlinedIcon from \"@ant-design/icons/EyeInvisibleOutlined\";\n\nimport ColumnEditor from \"./ColumnEditor\";\n\nconst { Text } = Typography;\n\nconst SortableItem = sortableElement(Collapse.Panel);\n\ntype ColumnsSettingsProps = {\n  options: any;\n  onOptionsChange: any;\n  variant: \"table\" | \"details\";\n};\n\nexport default function ColumnsSettings({ options, onOptionsChange, variant }: ColumnsSettingsProps) {\n  function handleColumnChange(newColumn: any, event: any) {\n    if (event) {\n      event.stopPropagation();\n    }\n    const columns = map(options.columns, c => (c.name === newColumn.name ? newColumn : c));\n    onOptionsChange({ columns });\n  }\n\n  function handleColumnsReorder({ oldIndex, newIndex }: any) {\n    const columns = [...options.columns];\n    columns.splice(newIndex, 0, ...columns.splice(oldIndex, 1));\n    onOptionsChange({ columns });\n  }\n\n  const helperClass = `${variant}-editor-columns-dragged-item`;\n  const containerClass = `${variant}-visualization-editor-columns`;\n  const testPrefix = variant === \"table\" ? \"Table\" : \"Details\";\n\n  return (\n    <SortableContainer\n      axis=\"y\"\n      lockAxis=\"y\"\n      useDragHandle\n      helperClass={helperClass}\n      helperContainer={(container: any) => container.firstChild}\n      onSortEnd={handleColumnsReorder}\n      containerProps={{\n        className: containerClass,\n      }}>\n      {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message */}\n      <Collapse bordered={false} defaultActiveKey={[]} expandIconPosition=\"right\">\n        {map(options.columns, (column, index) => (\n          <SortableItem\n            key={column.name}\n            index={index}\n            header={\n              <React.Fragment>\n                <DragHandle />\n                <span data-test={`${testPrefix}.Column.${column.name}.Name`}>\n                  {column.name}\n                  {column.title !== \"\" && column.title !== column.name && (\n                    <Text type=\"secondary\" style={{ marginLeft: 5 }}>\n                      <i>({column.title})</i>\n                    </Text>\n                  )}\n                </span>\n              </React.Fragment>\n            }\n            extra={\n              <Tooltip title=\"Toggle visibility\" mouseEnterDelay={0} mouseLeaveDelay={0}>\n                {column.visible ? (\n                  <EyeOutlinedIcon\n                    data-test={`${testPrefix}.Column.${column.name}.Visibility`}\n                    onClick={event => handleColumnChange({ ...column, visible: !column.visible }, event)}\n                  />\n                ) : (\n                  <EyeInvisibleOutlinedIcon\n                    data-test={`${testPrefix}.Column.${column.name}.Visibility`}\n                    onClick={event => handleColumnChange({ ...column, visible: !column.visible }, event)}\n                  />\n                )}\n              </Tooltip>\n            }>\n            <ColumnEditor column={column} variant={variant} onChange={(changes) => handleColumnChange(changes, undefined)} />\n          </SortableItem>\n        ))}\n      </Collapse>\n    </SortableContainer>\n  );\n}\n\nColumnsSettings.propTypes = {\n  options: PropTypes.object.isRequired,\n  onOptionsChange: PropTypes.func.isRequired,\n  variant: PropTypes.oneOf([\"table\", \"details\"]).isRequired,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/sunburst/Editor.tsx",
    "content": "import React from \"react\";\nimport { Section } from \"@/components/visualizations/editor\";\n\nexport default function Editor() {\n  return (\n    <React.Fragment>\n      <p>This visualization expects the query result to have rows in one of the following formats:</p>\n      {/* @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message */}\n      <Section>\n        <p>\n          <strong>Option 1:</strong>\n        </p>\n        <ul>\n          <li>\n            <strong>sequence</strong> - sequence id\n          </li>\n          <li>\n            <strong>stage</strong> - what stage in sequence this is (1, 2, ...)\n          </li>\n          <li>\n            <strong>node</strong> - stage name\n          </li>\n          <li>\n            <strong>value</strong> - number of times this sequence occurred\n          </li>\n        </ul>\n      </Section>\n      {/* @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message */}\n      <Section>\n        <p>\n          <strong>Option 2:</strong>\n        </p>\n        <ul>\n          <li>\n            <strong>stage1</strong> - stage 1 value\n          </li>\n          <li>\n            <strong>stage2</strong> - stage 2 value (or null)\n          </li>\n          <li>\n            <strong>stage3</strong> - stage 3 value (or null)\n          </li>\n          <li>\n            <strong>stage4</strong> - stage 4 value (or null)\n          </li>\n          <li>\n            <strong>stage5</strong> - stage 5 value (or null)\n          </li>\n          <li>\n            <strong>value</strong> - number of times this sequence occurred\n          </li>\n        </ul>\n      </Section>\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/sunburst/Renderer.tsx",
    "content": "import React, { useState, useEffect, useMemo } from \"react\";\nimport resizeObserver from \"@/services/resizeObserver\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\n\nimport initSunburst from \"./initSunburst\";\nimport \"./renderer.less\";\n\nexport default function Renderer({ data }: any) {\n  const [container, setContainer] = useState(null);\n\n  const render = useMemo(() => initSunburst(data), [data]);\n\n  useEffect(() => {\n    if (container) {\n      render(container);\n      const unwatch = resizeObserver(container, () => {\n        render(container);\n      });\n      return unwatch;\n    }\n  }, [container, render]);\n\n  // @ts-expect-error ts-migrate(2322) FIXME: Type 'Dispatch<SetStateAction<null>>' is not assig... Remove this comment to see the full error message\n  return <div className=\"sunburst-visualization-container\" ref={setContainer} />;\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/sunburst/index.ts",
    "content": "import Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nexport default {\n  type: \"SUNBURST_SEQUENCE\",\n  name: \"Sunburst Sequence\",\n  getOptions: (options: any) => ({\n    ...options,\n  }),\n  Renderer,\n  Editor,\n\n  defaultRows: 7,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/sunburst/initSunburst.ts",
    "content": "/**\n * The following is based on @chrisrzhou's example from: http://bl.ocks.org/chrisrzhou/d5bdd8546f64ca0e4366.\n */\n\nimport * as d3 from \"d3\";\nimport { has, map, keys, groupBy, sortBy, filter, find, compact, first, every, identity } from \"lodash\";\n\nconst exitNode = \"<<<Exit>>>\";\n// @ts-expect-error ts-migrate(2339) FIXME: Property 'scale' does not exist on type 'typeof im... Remove this comment to see the full error message\nconst colors = d3.scale.category10();\n\n// helper function colorMap - color gray if \"end\" is detected\nfunction colorMap(d: any) {\n  return colors(d.name);\n}\n\n// Return array of ancestors of nodes, highest first, but excluding the root.\nfunction getAncestors(node: any) {\n  const path = [];\n  let current = node;\n\n  while (current.parent) {\n    path.unshift(current);\n    current = current.parent;\n  }\n  return path;\n}\n\nfunction buildNodesFromHierarchyData(data: any) {\n  const grouped = groupBy(data, \"sequence\");\n\n  return map(grouped, value => {\n    const sorted = sortBy(value, \"stage\");\n    return {\n      size: value[0].value || 0,\n      sequence: value[0].sequence,\n      nodes: map(sorted, i => i.node),\n    };\n  });\n}\n\nfunction buildNodesFromTableData(data: any) {\n  const validKey = (key: any) => key !== \"value\";\n  const dataKeys = sortBy(filter(keys(data[0]), validKey), identity);\n\n  return map(data, (row, sequence) => ({\n    size: row.value || 0,\n    sequence,\n    nodes: compact(map(dataKeys, key => row[key])),\n  }));\n}\n\nfunction isDataInHierarchyFormat(data: any) {\n  const firstRow = first(data);\n  return every([\"sequence\", \"stage\", \"node\", \"value\"], field => has(firstRow, field));\n}\n\nfunction buildHierarchy(data: any) {\n  data = isDataInHierarchyFormat(data) ? buildNodesFromHierarchyData(data) : buildNodesFromTableData(data);\n\n  // build tree\n  const root: any = {\n    name: \"root\",\n    children: [] as any[],\n  };\n\n  data.forEach((d: any) => {\n    const nodes = d.nodes;\n    const size = parseInt(d.size, 10);\n\n    // build graph, nodes, and child nodes\n    let currentNode = root;\n    for (let j = 0; j < nodes.length; j += 1) {\n      let children = currentNode.children;\n      const nodeName = nodes[j];\n      const isLeaf = j + 1 === nodes.length;\n\n      if (!children) {\n        currentNode.children = children = [];\n        children.push({\n          name: exitNode,\n          size: currentNode.size,\n        });\n      }\n\n      let childNode = find(children, child => child.name === nodeName);\n\n      if (isLeaf && childNode) {\n        childNode.children = childNode.children || [];\n        childNode.children.push({\n          name: exitNode,\n          size,\n        });\n      } else if (isLeaf) {\n        children.push({\n          name: nodeName,\n          size,\n        });\n      } else {\n        if (!childNode) {\n          childNode = {\n            name: nodeName,\n            children: [],\n          };\n          children.push(childNode);\n        }\n\n        currentNode = childNode;\n      }\n    }\n  });\n\n  return root;\n}\n\nfunction isDataValid(data: any) {\n  return data && data.rows.length > 0;\n}\n\nexport default function initSunburst(data: any) {\n  if (!isDataValid(data)) {\n    return (element: any) => {\n      d3.select(element)\n        .selectAll(\"*\")\n        .remove();\n    };\n  }\n\n  data = buildHierarchy(data.rows);\n\n  return (element: any) => {\n    d3.select(element)\n      .selectAll(\"*\")\n      .remove();\n\n    // svg dimensions\n    const width = element.clientWidth;\n    const height = element.offsetHeight;\n\n    // Breadcrumb dimensions: width, height, spacing, width of tip/tail.\n    const b = {\n      w: width / 6,\n      h: 30,\n      s: 3,\n      t: 10,\n    };\n\n    const radius = Math.min(width - b.h, height - b.h) / 2 - 5;\n    if (radius <= 0) {\n      return;\n    }\n\n    // margins\n    const margin = {\n      top: radius,\n      bottom: 50,\n      left: radius,\n      right: 0,\n    };\n\n    // Drawing variables: e.g. colors, totalSize, partitions, arcs\n\n    // Total size of all nodes, to be used later when data is loaded\n    let totalSize = 0;\n\n    // create d3.layout.partition\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'layout' does not exist on type 'typeof i... Remove this comment to see the full error message\n    const partition = d3.layout\n      .partition()\n      .size([2 * Math.PI, radius * radius])\n      .value((d: any) => d.size);\n\n    // create arcs for drawing D3 paths\n    const arc = d3.svg\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'arc' does not exist on type '(url: strin... Remove this comment to see the full error message\n      .arc()\n      .startAngle((d: any) => d.x)\n      .endAngle((d: any) => d.x + d.dx)\n      .innerRadius((d: any) => Math.sqrt(d.y))\n      .outerRadius((d: any) => Math.sqrt(d.y + d.dy));\n\n    /**\n     * Define and initialize D3 select references and div-containers\n     *\n     * e.g. vis, breadcrumbs, lastCrumb, summary, sunburst, legend\n     */\n    const vis = d3.select(element);\n\n    // create and position breadcrumbs container and svg\n    const breadcrumbs = vis\n      .append(\"div\")\n      .classed(\"breadcrumbs-container\", true)\n      .append(\"svg\")\n      .attr(\"width\", width)\n      .attr(\"height\", b.h)\n      .attr(\"fill\", \"white\")\n      .attr(\"font-weight\", 600);\n\n    // create and position SVG\n    const container = vis.append(\"div\");\n\n    // create and position summary container\n    const summary = container.append(\"div\").classed(\"summary-container\", true);\n\n    const sunburst = container\n      .append(\"div\")\n      .classed(\"sunburst-container\", true)\n      .append(\"svg\")\n      .attr(\"width\", radius * 2)\n      .attr(\"height\", radius * 2)\n      .append(\"g\")\n      .attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n    // create last breadcrumb element\n    const lastCrumb = breadcrumbs.append(\"text\").classed(\"lastCrumb\", true);\n\n    // Generate a string representation for drawing a breadcrumb polygon.\n    function breadcrumbPoints(d: any, i: any) {\n      const points = [];\n      points.push(\"0,0\");\n      points.push(`${b.w},0`);\n      points.push(`${b.w + b.t},${b.h / 2}`);\n      points.push(`${b.w},${b.h}`);\n      points.push(`0,${b.h}`);\n\n      if (i > 0) {\n        // Leftmost breadcrumb; don't include 6th vertex.\n        points.push(`${b.t},${b.h / 2}`);\n      }\n      return points.join(\" \");\n    }\n\n    // Update the breadcrumb breadcrumbs to show the current sequence and percentage.\n    function updateBreadcrumbs(ancestors: any, percentageString: any) {\n      // Data join, where primary key = name + depth.\n      // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n      const g = breadcrumbs.selectAll(\"g\").data(ancestors, d => d.name + d.depth);\n\n      // Add breadcrumb and label for entering nodes.\n      const breadcrumb = g.enter().append(\"g\");\n\n      breadcrumb\n        .append(\"polygon\")\n        .classed(\"breadcrumbs-shape\", true)\n        .attr(\"points\", breadcrumbPoints)\n        .attr(\"fill\", colorMap);\n\n      breadcrumb\n        .append(\"text\")\n        .classed(\"breadcrumbs-text\", true)\n        .attr(\"x\", (b.w + b.t) / 2)\n        .attr(\"y\", b.h / 2)\n        .attr(\"dy\", \"0.35em\")\n        .attr(\"font-size\", \"10px\")\n        .attr(\"text-anchor\", \"middle\")\n        // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n        .text(d => d.name);\n\n      // Set position for entering and updating nodes.\n      g.attr(\"transform\", (d, i) => `translate(${i * (b.w + b.s)}, 0)`);\n\n      // Remove exiting nodes.\n      g.exit().remove();\n\n      // Update percentage at the lastCrumb.\n      lastCrumb\n        .attr(\"x\", (ancestors.length + 0.5) * (b.w + b.s))\n        .attr(\"y\", b.h / 2)\n        .attr(\"dy\", \"0.35em\")\n        .attr(\"text-anchor\", \"middle\")\n        .attr(\"fill\", \"black\")\n        .attr(\"font-weight\", 600)\n        .text(percentageString);\n    }\n\n    // helper function mouseover to handle mouseover events/animations and calculation\n    // of ancestor nodes etc\n    function mouseover(d: any) {\n      // build percentage string\n      const percentage = ((100 * d.value) / totalSize).toPrecision(3);\n      let percentageString = `${percentage}%`;\n      // @ts-expect-error ts-migrate(2365) FIXME: Operator '<' cannot be applied to types 'string' a... Remove this comment to see the full error message\n      if (percentage < 1) {\n        percentageString = \"< 1.0%\";\n      }\n\n      // update breadcrumbs (get all ancestors)\n      const ancestors = getAncestors(d);\n      updateBreadcrumbs(ancestors, percentageString);\n\n      // update sunburst (Fade all the segments and highlight only ancestors of current segment)\n      sunburst.selectAll(\"path\").attr(\"opacity\", 0.3);\n      sunburst\n        .selectAll(\"path\")\n        .filter(node => ancestors.indexOf(node) >= 0)\n        .attr(\"opacity\", 1);\n\n      // update summary\n      summary.html(`\n      <span>Stage: ${d.depth}</span>\n      <span class='percentage' style='font-size: 2em;'>${percentageString}</span>\n      <span>${d.value} of ${totalSize}</span>\n    `);\n\n      // display summary and breadcrumbs if hidden\n      summary.style(\"visibility\", \"\");\n      breadcrumbs.style(\"visibility\", \"\");\n    }\n\n    // helper function click to handle mouseleave events/animations\n    function click() {\n      // Deactivate all segments then retransition each segment to full opacity.\n      sunburst.selectAll(\"path\").on(\"mouseover\", null);\n      sunburst\n        .selectAll(\"path\")\n        .transition()\n        .duration(1000)\n        .attr(\"opacity\", 1)\n        // @ts-expect-error ts-migrate(2554) FIXME: Expected 1 arguments, but got 2.\n        .each(\"end\", function endClick() {\n          // @ts-expect-error ts-migrate(2683) FIXME: 'this' implicitly has type 'any' because it does n... Remove this comment to see the full error message\n          d3.select(this).on(\"mouseover\", mouseover);\n        });\n\n      // hide summary and breadcrumbs if visible\n      breadcrumbs.style(\"visibility\", \"hidden\");\n      summary.style(\"visibility\", \"hidden\");\n    }\n\n    // Build only nodes of a threshold \"visible\" sizes to improve efficiency\n    // 0.005 radians = 0.29 degrees\n    const nodes = partition.nodes(data).filter((d: any) => d.dx > 0.005 && d.name !== exitNode);\n\n    // this section is required to update the colors.domain() every time the data updates\n    const uniqueNames = (function uniqueNames(a) {\n      const output: any = [];\n      a.forEach((d: any) => {\n        if (output.indexOf(d.name) === -1) output.push(d.name);\n      });\n      return output;\n    })(nodes);\n    colors.domain(uniqueNames); // update domain colors\n\n    // create path based on nodes\n    const path = sunburst\n      .data([data])\n      .selectAll(\"path\")\n      .data(nodes)\n      .enter()\n      .append(\"path\")\n      .classed(\"nodePath\", true)\n      // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'.\n      .attr(\"display\", d => (d.depth ? null : \"none\"))\n      .attr(\"d\", arc)\n      .attr(\"fill\", colorMap)\n      .attr(\"opacity\", 1)\n      .attr(\"stroke\", \"white\")\n      .on(\"mouseover\", mouseover);\n\n    // // trigger mouse click over sunburst to reset visualization summary\n    vis.on(\"click\", click);\n\n    // Update totalSize of the tree = value of root node from partition.\n    // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.\n    totalSize = path.node().__data__.value;\n  };\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/sunburst/renderer.less",
    "content": ".sunburst-visualization-container {\n  height: 400px;\n  display: flex;\n  flex-direction: column;\n\n  > div {\n    position: relative;\n\n    &:first-child {\n      flex-grow: 0;\n    }\n    &:last-child {\n      flex-grow: 1;\n    }\n  }\n\n  .sunburst-container,\n  .summary-container {\n    position: absolute;\n    left: 0;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    width: auto;\n    height: auto;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n  }\n\n  .summary-container {\n    font-size: 11px;\n    color: #666;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/Editor/ColumnEditor.tsx",
    "content": "import React from \"react\";\nimport SharedColumnEditor from \"../../shared/components/ColumnEditor\";\n\ntype OwnProps = {\n  column: {\n    name: string;\n    title?: string;\n    visible?: boolean;\n    alignContent?: \"left\" | \"center\" | \"right\";\n    displayAs?: any;\n    allowSearch?: boolean;\n    description?: string;\n  };\n  onChange?: (...args: any[]) => any;\n};\n\nconst columnEditorDefaultProps = {\n  onChange: () => {},\n};\n\ntype Props = OwnProps & typeof columnEditorDefaultProps;\n\nexport default function ColumnEditor({ column, onChange }: Props) {\n  return (\n    <SharedColumnEditor\n      column={column}\n      onChange={onChange}\n      variant=\"table\"\n      showSearch={true}\n    />\n  );\n}\n\nColumnEditor.defaultProps = columnEditorDefaultProps;\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/Editor/ColumnsSettings.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport getOptions from \"../getOptions\";\nimport ColumnsSettings from \"./ColumnsSettings\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(options: any, done: any) {\n  const data = {\n    columns: [{ name: \"a\", type: \"string\" }],\n    rows: [{ a: \"test\" }],\n  };\n  options = getOptions(options, data);\n  return enzyme.mount(\n    <ColumnsSettings\n      visualizationName=\"Test\"\n      data={data}\n      options={options}\n      onOptionsChange={(changedOptions: any) => {\n        expect(changedOptions).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Table -> Editor -> Columns Settings\", () => {\n  test(\"Toggles column visibility\", done => {\n    const el = mount({}, done);\n\n    findByTestID(el, \"Table.Column.a.Visibility\")\n      .last()\n      .simulate(\"click\");\n  });\n\n  test(\"Changes column title\", done => {\n    const el = mount({}, done);\n    findByTestID(el, \"Table.Column.a.Name\")\n      .last()\n      .simulate(\"click\"); // expand settings\n\n    findByTestID(el, \"Table.Column.a.Title\")\n      .last()\n      .simulate(\"change\", { target: { value: \"test\" } });\n  });\n\n  test(\"Changes column alignment\", done => {\n    const el = mount({}, done);\n    findByTestID(el, \"Table.Column.a.Name\")\n      .last()\n      .simulate(\"click\"); // expand settings\n\n    findByTestID(el, \"Table.Column.a.TextAlignment\")\n      .last()\n      .find('[data-test=\"TextAlignmentSelect.Right\"] input')\n      .simulate(\"change\", { target: { checked: true } });\n  });\n\n  test(\"Enables search by column data\", done => {\n    const el = mount({}, done);\n    findByTestID(el, \"Table.Column.a.Name\")\n      .last()\n      .simulate(\"click\"); // expand settings\n\n    findByTestID(el, \"Table.Column.a.UseForSearch\")\n      .last()\n      .find(\"input\")\n      .simulate(\"change\", { target: { checked: true } });\n  });\n\n  test(\"Changes column display type\", done => {\n    const el = mount({}, done);\n    findByTestID(el, \"Table.Column.a.Name\")\n      .last()\n      .simulate(\"click\"); // expand settings\n\n    findByTestID(el, \"Table.Column.a.DisplayAs\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Table.Column.a.DisplayAs.number\")\n      .last()\n      .simulate(\"click\");\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/Editor/ColumnsSettings.tsx",
    "content": "import React from \"react\";\nimport SharedColumnsSettings from \"../../shared/components/ColumnsSettings\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function ColumnsSettings({ options, onOptionsChange, data }: any) {\n  return (\n    <SharedColumnsSettings\n      options={options}\n      onOptionsChange={onOptionsChange}\n      variant=\"table\"\n    />\n  );\n}\n\nColumnsSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/Editor/GridSettings.test.tsx",
    "content": "import React from \"react\";\nimport enzyme from \"enzyme\";\n\nimport getOptions from \"../getOptions\";\nimport GridSettings from \"./GridSettings\";\n\nfunction findByTestID(wrapper: any, testId: any) {\n  return wrapper.find(`[data-test=\"${testId}\"]`);\n}\n\nfunction mount(options: any, done: any) {\n  const data = { columns: [], rows: [] };\n  options = getOptions(options, data);\n  return enzyme.mount(\n    <GridSettings\n      visualizationName=\"Test\"\n      data={data}\n      options={options}\n      onOptionsChange={(changedOptions: any) => {\n        expect(changedOptions).toMatchSnapshot();\n        done();\n      }}\n    />\n  );\n}\n\ndescribe(\"Visualizations -> Table -> Editor -> Grid Settings\", () => {\n  test(\"Changes items per page\", done => {\n    const el = mount(\n      {\n        itemsPerPage: 25,\n      },\n      done\n    );\n\n    findByTestID(el, \"Table.ItemsPerPage\")\n      .last()\n      .simulate(\"mouseDown\");\n    findByTestID(el, \"Table.ItemsPerPage.100\")\n      .last()\n      .simulate(\"click\");\n  });\n});\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/Editor/GridSettings.tsx",
    "content": "import { map } from \"lodash\";\nimport React from \"react\";\nimport { Section, Select } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nconst ALLOWED_ITEM_PER_PAGE = [5, 10, 15, 20, 25, 50, 100, 150, 200, 250, 500];\n\nexport default function GridSettings({ options, onOptionsChange }: any) {\n  return (\n    // @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message\n    <Section>\n      <Select\n        label=\"Items per page\"\n        data-test=\"Table.ItemsPerPage\"\n        defaultValue={options.itemsPerPage}\n        onChange={(itemsPerPage: any) => onOptionsChange({ itemsPerPage })}>\n        {map(ALLOWED_ITEM_PER_PAGE, value => (\n          // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n          <Select.Option key={`ipp${value}`} value={value} data-test={`Table.ItemsPerPage.${value}`}>\n            {value}\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          </Select.Option>\n        ))}\n      </Select>\n    </Section>\n  );\n}\n\nGridSettings.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/Editor/__snapshots__/ColumnsSettings.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Table -> Editor -> Columns Settings Changes column alignment 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"right\",\n      \"allowHTML\": false,\n      \"allowSearch\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"a\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100000,\n      \"title\": \"a\",\n      \"type\": \"string\",\n      \"visible\": true,\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Table -> Editor -> Columns Settings Changes column display type 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"allowSearch\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"number\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"a\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100000,\n      \"title\": \"a\",\n      \"type\": \"string\",\n      \"visible\": true,\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Table -> Editor -> Columns Settings Changes column title 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"allowSearch\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"a\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100000,\n      \"title\": \"test\",\n      \"type\": \"string\",\n      \"visible\": true,\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Table -> Editor -> Columns Settings Enables search by column data 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"allowSearch\": true,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"a\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100000,\n      \"title\": \"a\",\n      \"type\": \"string\",\n      \"visible\": true,\n    },\n  ],\n}\n`;\n\nexports[`Visualizations -> Table -> Editor -> Columns Settings Toggles column visibility 1`] = `\n{\n  \"columns\": [\n    {\n      \"alignContent\": \"left\",\n      \"allowHTML\": false,\n      \"allowSearch\": false,\n      \"booleanValues\": [\n        \"false\",\n        \"true\",\n      ],\n      \"dateTimeFormat\": undefined,\n      \"description\": \"\",\n      \"displayAs\": \"string\",\n      \"highlightLinks\": false,\n      \"imageHeight\": \"\",\n      \"imageTitleTemplate\": \"{{ @ }}\",\n      \"imageUrlTemplate\": \"{{ @ }}\",\n      \"imageWidth\": \"\",\n      \"linkOpenInNewTab\": true,\n      \"linkTextTemplate\": \"{{ @ }}\",\n      \"linkTitleTemplate\": \"{{ @ }}\",\n      \"linkUrlTemplate\": \"{{ @ }}\",\n      \"name\": \"a\",\n      \"nullValue\": \"null\",\n      \"numberFormat\": undefined,\n      \"order\": 100000,\n      \"title\": \"a\",\n      \"type\": \"string\",\n      \"visible\": false,\n    },\n  ],\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/Editor/__snapshots__/GridSettings.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Visualizations -> Table -> Editor -> Grid Settings Changes items per page 1`] = `\n{\n  \"itemsPerPage\": 100,\n}\n`;\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/Editor/editor.less",
    "content": ".table-visualization-editor-columns {\n  .ant-collapse {\n    background: transparent;\n  }\n\n  .ant-collapse-item {\n    background: #ffffff;\n\n    .drag-handle {\n      height: 20px;\n      margin-left: -16px;\n      padding: 0 16px;\n    }\n  }\n\n  .table-editor-columns-dragged-item {\n    z-index: 1;\n  }\n}\n\n.table-visualization-editor-column {\n  padding-left: 6px;\n\n  .image-dimension-selector {\n    display: flex;\n    align-items: center;\n\n    .image-dimension-selector-spacer {\n      padding-left: 5px;\n      padding-right: 5px;\n    }\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/Editor/index.tsx",
    "content": "import createTabbedEditor from \"@/components/visualizations/editor/createTabbedEditor\";\n\nimport ColumnsSettings from \"./ColumnsSettings\";\nimport GridSettings from \"./GridSettings\";\n\nimport \"./editor.less\";\n\nexport default createTabbedEditor([\n  { key: \"Columns\", title: \"Columns\", component: ColumnsSettings },\n  { key: \"Grid\", title: \"Grid\", component: GridSettings },\n]);\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/Renderer.tsx",
    "content": "import { filter, map, get, initial, last, reduce } from \"lodash\";\nimport React, { useMemo, useState, useEffect } from \"react\";\nimport Table from \"antd/lib/table\";\nimport Input from \"antd/lib/input\";\nimport InfoCircleFilledIcon from \"@ant-design/icons/InfoCircleFilled\";\nimport Popover from \"antd/lib/popover\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\n\nimport { prepareColumns, initRows, filterRows, sortRows } from \"./utils\";\n\nimport \"./renderer.less\";\n\nfunction joinColumns(array: any, separator = \", \") {\n  return reduce(\n    array,\n    (result, item, index) => {\n      // @ts-expect-error ts-migrate(2365) FIXME: Operator '>' cannot be applied to types 'string' a... Remove this comment to see the full error message\n      if (index > 0) {\n        // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string' is not assignable to par... Remove this comment to see the full error message\n        result.push(separator);\n      }\n      // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'any' is not assignable to parame... Remove this comment to see the full error message\n      result.push(item);\n      return result;\n    },\n    []\n  );\n}\n\nfunction getSearchColumns(columns: any, { limit = Infinity, renderColumn = (col: any) => col.title } = {}) {\n  const firstColumns = map(columns.slice(0, limit), col => renderColumn(col));\n  const restColumns = map(columns.slice(limit), col => col.title);\n  if (restColumns.length > 0) {\n    return [...joinColumns(firstColumns), ` and ${restColumns.length} others`];\n  }\n  if (firstColumns.length > 1) {\n    return [...joinColumns(initial(firstColumns)), ` and `, last(firstColumns)];\n  }\n  return firstColumns;\n}\n\nfunction SearchInputInfoIcon({ searchColumns }: any) {\n  return (\n    <Popover\n      arrowPointAtCenter\n      placement=\"topRight\"\n      content={\n        <div className=\"table-visualization-search-info-content\">\n          Search {getSearchColumns(searchColumns, { renderColumn: col => <code key={col.name}>{col.title}</code> })}\n        </div>\n      }>\n      <InfoCircleFilledIcon className=\"table-visualization-search-info-icon\" />\n    </Popover>\n  );\n}\n\ntype OwnSearchInputProps = {\n  onChange?: (...args: any[]) => any;\n};\n\nconst searchInputDefaultProps = {\n  onChange: () => {},\n};\n\ntype SearchInputProps = OwnSearchInputProps & typeof searchInputDefaultProps;\n\n// @ts-expect-error ts-migrate(2339) FIXME: Property 'searchColumns' does not exist on type 'S... Remove this comment to see the full error message\nfunction SearchInput({ searchColumns, ...props }: SearchInputProps) {\n  if (searchColumns.length <= 0) {\n    return null;\n  }\n\n  const searchColumnsLimit = 3;\n  return (\n    <Input.Search\n      {...props}\n      placeholder={`Search ${getSearchColumns(searchColumns, { limit: searchColumnsLimit }).join(\"\")}...`}\n      suffix={searchColumns.length > searchColumnsLimit ? <SearchInputInfoIcon searchColumns={searchColumns} /> : null}\n    />\n  );\n}\n\nSearchInput.defaultProps = searchInputDefaultProps;\n\nexport default function Renderer({ options, data }: any) {\n  const [searchTerm, setSearchTerm] = useState(\"\");\n  const [orderBy, setOrderBy] = useState([]);\n\n  const searchColumns = useMemo(() => filter(options.columns, \"allowSearch\"), [options.columns]);\n\n  const tableColumns = useMemo(() => {\n    const searchInput =\n      searchColumns.length > 0 ? (\n        // @ts-expect-error ts-migrate(2322) FIXME: Type '(event: any) => void' is not assignable to t... Remove this comment to see the full error message\n        <SearchInput searchColumns={searchColumns} onChange={(event: any) => setSearchTerm(event.target.value)} />\n      ) : null;\n    return prepareColumns(options.columns, searchInput, orderBy, (newOrderBy: any) => {\n      setOrderBy(newOrderBy);\n      // Remove text selection - may occur accidentally\n      // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.\n      document.getSelection().removeAllRanges();\n    });\n  }, [options.columns, searchColumns, orderBy]);\n\n  const preparedRows = useMemo(() => sortRows(filterRows(initRows(data.rows), searchTerm, searchColumns), orderBy), [\n    data.rows,\n    searchTerm,\n    searchColumns,\n    orderBy,\n  ]);\n\n  // If data or config columns change - reset sorting\n  useEffect(() => {\n    setOrderBy([]);\n  }, [options.columns, data.columns]);\n\n  if (data.rows.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"table-visualization-container\">\n      <Table\n        className=\"table-fixed-header\"\n        data-percy=\"show-scrollbars\"\n        data-test=\"TableVisualization\"\n        // @ts-expect-error ts-migrate(2322) FIXME: Type '{ key: any; dataIndex: string; align: any; s... Remove this comment to see the full error message\n        columns={tableColumns}\n        dataSource={preparedRows}\n        pagination={{\n          size: get(options, \"paginationSize\", \"\"),\n          // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'TablePagi... Remove this comment to see the full error message\n          position: \"bottom\",\n          pageSize: options.itemsPerPage,\n          hideOnSinglePage: true,\n          showSizeChanger: false,\n        }}\n        showSorterTooltip={false}\n      />\n    </div>\n  );\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/getOptions.ts",
    "content": "import _ from \"lodash\";\nimport {\n  getDefaultColumnsOptions,\n  getDefaultFormatOptions,\n  getColumnsOptions,\n} from \"@/visualizations/shared/columnUtils\";\n\nconst DEFAULT_OPTIONS = {\n  itemsPerPage: 25,\n  paginationSize: \"default\", // not editable through Editor\n};\n\n\nexport default function getOptions(options: any, { columns }: any) {\n  options = { ...DEFAULT_OPTIONS, ...options };\n  options.columns = _.map(getColumnsOptions(columns, options.columns, { allowSearch: false }), col => ({\n    ...getDefaultFormatOptions(col),\n    ...col,\n  }));\n  return options;\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/index.ts",
    "content": "import getOptions from \"./getOptions\";\nimport Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nexport default {\n  type: \"TABLE\",\n  name: \"Table\",\n  getOptions,\n  Renderer,\n  Editor,\n\n  autoHeight: true,\n  defaultRows: 14,\n  defaultColumns: 6,\n  minColumns: 2,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/renderer.less",
    "content": "@import \"../variables\";\n\n.table-visualization-container {\n  .ant-pagination.ant-table-pagination {\n    float: none;\n    display: block;\n    text-align: center;\n    margin-bottom: 0;\n  }\n\n  .ant-table {\n    overflow-x: auto;\n  }\n\n  .table-fixed-header {\n    table {\n      border-top: 0;\n\n      th:not(.table-visualization-search) {\n        position: sticky !important;\n        left: 0;\n        top: 0;\n        border-top: 0;\n        z-index: 1;\n        background: #fafafa !important;\n      }\n    }\n  }\n\n  table {\n    border-top: @border-width-base @border-style-base @border-color-split;\n    border-bottom: 0;\n\n    .display-as-number,\n    .display-as-boolean,\n    .display-as-datetime,\n    .display-as-image {\n      width: 1%;\n      white-space: nowrap;\n    }\n\n    .display-as-null {\n      font-style: italic;\n      color: @text-muted;\n    }\n\n    .table-visualization-spacer {\n      padding-left: 0;\n      padding-right: 0;\n\n      & > div:before {\n        content: none !important;\n      }\n    }\n\n    tbody tr:last-child td {\n      border-bottom: 0;\n    }\n\n    thead {\n      .ant-table-column-sorter-up,\n      .ant-table-column-sorter-down {\n        opacity: 0;\n        transition: opacity 0.3s;\n      }\n\n      &:hover,\n      .table-visualization-column-is-sorted {\n        .ant-table-column-sorter-up,\n        .ant-table-column-sorter-down {\n          opacity: 1;\n        }\n      }\n\n      th {\n        white-space: nowrap;\n\n        &.table-visualization-search {\n          padding-bottom: 0;\n\n          .ant-table-header-column {\n            display: block;\n          }\n        }\n\n        .ant-input-search {\n          font-weight: normal;\n\n          .ant-input-suffix .anticon {\n            cursor: auto;\n          }\n        }\n\n        // optimize room for th content\n        &.ant-table-column-has-actions.ant-table-column-has-sorters {\n          padding-right: 3px;\n        }\n\n        .table-visualization-heading {\n          display: inline-block;\n          max-width: 200px;\n          vertical-align: middle;\n          white-space: nowrap;\n          overflow: hidden;\n          text-overflow: ellipsis;\n\n          &[data-sort-column-index]:before {\n            @size: 12px;\n\n            content: attr(data-sort-column-index);\n            display: inline-block;\n            vertical-align: middle;\n            min-width: @size;\n            height: @size;\n            font-size: @size * 3/4;\n            border-radius: @size / 2;\n            background: #c0c0c0;\n            text-align: center;\n            line-height: @size;\n            color: #fff;\n            padding: 0 @size * 1/4;\n            margin: 0 5px 0 0;\n          }\n        }\n      }\n    }\n  }\n\n  /* START table x scroll */\n  .dashboard-widget-wrapper:not(.widget-auto-height-enabled) &,\n  .query-fixed-layout & {\n    position: absolute;\n    left: 0;\n    top: 0;\n    width: 100%;\n    height: 100%;\n\n    & div {\n      height: inherit;\n    }\n\n    .ant-spin-container {\n      display: flex;\n      flex-direction: column;\n\n      .ant-table {\n        flex-grow: 1;\n      }\n    }\n  }\n  /* END */\n}\n\n.table-visualization-search-info-content {\n  max-width: 400px;\n}\n\n.table-visualization-search-info-icon {\n  margin: 0 5px 0 0;\n\n  &:not(:hover):not(:active) {\n    color: @text-color-secondary;\n  }\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/table/utils.tsx",
    "content": "import { isNil, map, get, filter, each, sortBy, some, findIndex, toString } from \"lodash\";\nimport React from \"react\";\nimport cx from \"classnames\";\nimport Tooltip from \"antd/lib/tooltip\";\nimport ColumnTypes from \"../shared/columns\";\n\nfunction nextOrderByDirection(direction: any) {\n  switch (direction) {\n    case \"ascend\":\n      return \"descend\";\n    case \"descend\":\n      return null;\n    default:\n      return \"ascend\";\n  }\n}\n\nfunction toggleOrderBy(columnName: any, orderBy = [], multiColumnSort = false) {\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'name' does not exist on type 'never'.\n  const index = findIndex(orderBy, i => i.name === columnName);\n  const item = { name: columnName, direction: \"ascend\" };\n  if (index >= 0) {\n    // @ts-expect-error ts-migrate(2322) FIXME: Type 'string | null' is not assignable to type 'st... Remove this comment to see the full error message\n    item.direction = nextOrderByDirection(orderBy[index].direction);\n  }\n\n  if (multiColumnSort) {\n    if (!item.direction) {\n      // @ts-expect-error ts-migrate(2339) FIXME: Property 'name' does not exist on type 'never'.\n      return filter(orderBy, i => i.name !== columnName);\n    }\n    if (index >= 0) {\n      // @ts-expect-error ts-migrate(2322) FIXME: Type '{ name: any; direction: string; }' is not as... Remove this comment to see the full error message\n      orderBy[index] = item;\n    } else {\n      // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ name: any; direction: string; ... Remove this comment to see the full error message\n      orderBy.push(item);\n    }\n    return [...orderBy];\n  }\n  return item.direction ? [item] : [];\n}\n\nfunction getOrderByInfo(orderBy: any) {\n  const result = {};\n  each(orderBy, ({ name, direction }, index) => {\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    result[name] = { direction, index: index + 1 };\n  });\n  return result;\n}\n\nexport function prepareColumns(columns: any, searchInput: any, orderBy: any, onOrderByChange: any) {\n  columns = filter(columns, \"visible\");\n  columns = sortBy(columns, \"order\");\n\n  const isMultiColumnSort = orderBy.length > 1;\n  const orderByInfo = getOrderByInfo(orderBy);\n\n  let tableColumns = map(columns, column => {\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    const isAscend = orderByInfo[column.name] && orderByInfo[column.name].direction === \"ascend\";\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    const isDescend = orderByInfo[column.name] && orderByInfo[column.name].direction === \"descend\";\n\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    const sortColumnIndex = isMultiColumnSort && orderByInfo[column.name] ? orderByInfo[column.name].index : null;\n\n    const result = {\n      key: column.name,\n      dataIndex: `record[${JSON.stringify(column.name)}]`,\n      align: column.alignContent,\n      sorter: { multiple: 1 }, // using { multiple: 1 } to allow built-in multi-column sort arrows\n      sortOrder: get(orderByInfo, [column.name, \"direction\"], null),\n      title: (\n        <React.Fragment>\n          {column.description && (\n            <span style={{ paddingRight: 5 }}>\n              <Tooltip placement=\"top\" title={column.description}>\n                <div className=\"table-visualization-heading\">\n                  <i className=\"fa fa-info-circle\" aria-hidden=\"true\"></i>\n                </div>\n              </Tooltip>\n            </span>\n          )}\n          <Tooltip placement=\"top\" title={column.title}>\n            <div className=\"table-visualization-heading\" data-sort-column-index={sortColumnIndex}>\n              {column.title}\n            </div>\n          </Tooltip>\n        </React.Fragment>\n      ),\n      onHeaderCell: () => ({\n        className: cx({\n          \"table-visualization-column-is-sorted\": isAscend || isDescend,\n        }),\n        onClick: (event: any) => onOrderByChange(toggleOrderBy(column.name, orderBy, event.shiftKey)),\n      }),\n    };\n\n    // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n    const initColumn = ColumnTypes[column.displayAs];\n    const Component = initColumn(column);\n    // @ts-expect-error ts-migrate(2339) FIXME: Property 'render' does not exist on type '{ key: a... Remove this comment to see the full error message\n    result.render = (unused: any, row: any) => ({\n      children: <Component row={row.record} />,\n      props: { className: `display-as-${column.displayAs}` },\n    });\n\n    return result;\n  });\n\n  tableColumns.push({\n    key: \"###Redash::Visualizations::Table::Spacer###\",\n    // @ts-expect-error ts-migrate(2322) FIXME: Type 'null' is not assignable to type 'string'.\n    dataIndex: null,\n    // @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'Element'.\n    title: \"\",\n    className: \"table-visualization-spacer\",\n    render: () => \"\",\n    // @ts-expect-error ts-migrate(2741) FIXME: Property 'onClick' is missing in type '{ className... Remove this comment to see the full error message\n    onHeaderCell: () => ({ className: \"table-visualization-spacer\" }),\n  });\n\n  if (searchInput) {\n    // Add searchInput as the ColumnGroup for all table columns\n    tableColumns = [\n      {\n        key: \"table-search\",\n        title: searchInput,\n        // @ts-expect-error ts-migrate(2741) FIXME: Property 'onClick' is missing in type '{ className... Remove this comment to see the full error message\n        onHeaderCell: () => ({ className: \"table-visualization-search\" }),\n        children: tableColumns,\n      },\n    ];\n  }\n\n  return tableColumns;\n}\n\nexport function initRows(rows: any) {\n  return map(rows, (record, index) => ({ key: `record${index}`, record }));\n}\n\nexport function filterRows(rows: any, searchTerm: any, searchColumns: any) {\n  if (searchTerm !== \"\" && searchColumns.length > 0) {\n    searchTerm = searchTerm.toUpperCase();\n    const matchFields = map(searchColumns, column => {\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      const initColumn = ColumnTypes[column.displayAs];\n      const { prepareData } = initColumn(column);\n      return (row: any) => {\n        const { text } = prepareData(row);\n        return (\n          toString(text)\n            .toUpperCase()\n            .indexOf(searchTerm) >= 0\n        );\n      };\n    });\n\n    return filter(rows, row => some(matchFields, match => match(row.record)));\n  }\n  return rows;\n}\n\nexport function sortRows(rows: any, orderBy: any) {\n  if (orderBy.length === 0 || rows.length === 0) {\n    return rows;\n  }\n\n  const directions = { ascend: 1, descend: -1 };\n\n  // Create a copy of array before sorting, because .sort() will modify original array\n  return [...rows].sort((a, b) => {\n    let va;\n    let vb;\n    for (let i = 0; i < orderBy.length; i += 1) {\n      va = a.record[orderBy[i].name];\n      vb = b.record[orderBy[i].name];\n      if (isNil(va) || va < vb) {\n        // if a < b - we should return -1, but take in account direction\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        return -1 * directions[orderBy[i].direction];\n      }\n      if (va > vb || isNil(vb)) {\n        // if a > b - we should return 1, but take in account direction\n        // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n        return 1 * directions[orderBy[i].direction];\n      }\n    }\n    return 0;\n  });\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/variables.less",
    "content": "/* --------------------------------------------------------\n    Colors\n-----------------------------------------------------------*/\n@black: #000;\n@blue: #2196f3;\n@green: #4caf50;\n@orange: #ff9800;\n@visualizations-gray: rgba(102, 136, 153, 1);\n\n/* --------------------------------------------------------\n    Font\n-----------------------------------------------------------*/\n@font-family-monospace: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n@visualizations-font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell,\n  \"Helvetica Neue\", sans-serif;\n\n/* --------------------------------------------------------\n    Borders\n-----------------------------------------------------------*/\n@border-width-base: 1px;\n@border-style-base: solid;\n@border-color-split: hsv(0, 0, 91%);\n\n/* --------------------------------------------------------\n    Typography\n-----------------------------------------------------------*/\n@text-color: #595959;\n@text-muted: #828282;\n@text-color-secondary: fade(@black, 45%);\n\n/* --------------------------------------------------------\n    Table\n-----------------------------------------------------------*/\n@table-border-color: #f0f0f0;\n@table-header-color: #333;\n"
  },
  {
    "path": "viz-lib/src/visualizations/visualizationsSettings.tsx",
    "content": "import React from \"react\";\nimport { extend } from \"lodash\";\nimport Tooltip from \"antd/lib/tooltip\";\n\ntype HelpTriggerProps = {\n  title?: React.ReactNode;\n  href: string;\n  className?: string;\n  children?: React.ReactNode;\n};\n\nfunction HelpTrigger({ title, href, className, children }: HelpTriggerProps) {\n  return (\n    <Tooltip\n      title={\n        <React.Fragment>\n          {title}\n          <i className=\"fa fa-external-link\" style={{ marginLeft: 5 }} />\n        </React.Fragment>\n      }>\n      <a className={className} href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n        {children}\n      </a>\n    </Tooltip>\n  );\n}\n\nHelpTrigger.defaultValues = {\n  title: null,\n  className: null,\n  children: null,\n};\n\nfunction Link(props: any) {\n  return <a {...props} />;\n}\n\nexport const visualizationsSettings = {\n  HelpTriggerComponent: HelpTrigger,\n  LinkComponent: Link,\n  dateFormat: \"DD/MM/YYYY\",\n  dateTimeFormat: \"DD/MM/YYYY HH:mm\",\n  integerFormat: \"0,0\",\n  floatFormat: \"0,0.00\",\n  nullValue: \"null\",\n  booleanValues: [\"false\", \"true\"],\n  tableCellMaxJSONSize: 50000,\n  allowCustomJSVisualizations: false,\n  hidePlotlyModeBar: false,\n  choroplethAvailableMaps: {},\n};\n\nexport function updateVisualizationsSettings(options: any) {\n  extend(visualizationsSettings, options);\n}\n"
  },
  {
    "path": "viz-lib/src/visualizations/word-cloud/Editor.tsx",
    "content": "import { map, merge } from \"lodash\";\nimport React from \"react\";\nimport * as Grid from \"antd/lib/grid\";\nimport { Section, Select, InputNumber, ControlLabel } from \"@/components/visualizations/editor\";\nimport { EditorPropTypes } from \"@/visualizations/prop-types\";\n\nexport default function Editor({ options, data, onOptionsChange }: any) {\n  const optionsChanged = (newOptions: any) => {\n    onOptionsChange(merge({}, options, newOptions));\n  };\n\n  return (\n    <React.Fragment>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Words Column\"\n          data-test=\"WordCloud.WordsColumn\"\n          value={options.column}\n          onChange={(column: any) => optionsChanged({ column })}>\n          {map(data.columns, ({ name }) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={name} data-test={\"WordCloud.WordsColumn.\" + name}>\n              {name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        <Select\n          label=\"Frequencies Column\"\n          data-test=\"WordCloud.FrequenciesColumn\"\n          value={options.frequenciesColumn}\n          onChange={(frequenciesColumn: any) => optionsChanged({ frequenciesColumn })}>\n          {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          <Select.Option key=\"none\" value=\"\">\n            <i>(count word frequencies automatically)</i>\n            {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n          </Select.Option>\n          {map(data.columns, ({ name }) => (\n            // @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message\n            <Select.Option key={\"column-\" + name} value={name} data-test={\"WordCloud.FrequenciesColumn.\" + name}>\n              {name}\n              {/* @ts-expect-error ts-migrate(2339) FIXME: Property 'Option' does not exist on type '({ class... Remove this comment to see the full error message */}\n            </Select.Option>\n          ))}\n        </Select>\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'null | un... Remove this comment to see the full error message */}\n        <ControlLabel label=\"Words Length Limit\">\n          {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message */}\n          <Grid.Row gutter={15} type=\"flex\">\n            <Grid.Col span={12}>\n              <InputNumber\n                data-test=\"WordCloud.WordLengthLimit.Min\"\n                placeholder=\"Min\"\n                min={0}\n                value={options.wordLengthLimit.min}\n                onChange={(value: any) => optionsChanged({ wordLengthLimit: { min: value > 0 ? value : null } })}\n              />\n            </Grid.Col>\n            <Grid.Col span={12}>\n              <InputNumber\n                data-test=\"WordCloud.WordLengthLimit.Max\"\n                placeholder=\"Max\"\n                min={0}\n                value={options.wordLengthLimit.max}\n                onChange={(value: any) => optionsChanged({ wordLengthLimit: { max: value > 0 ? value : null } })}\n              />\n            </Grid.Col>\n          </Grid.Row>\n        </ControlLabel>\n      </Section>\n      {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */}\n      <Section>\n        {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'string' is not assignable to type 'null | un... Remove this comment to see the full error message */}\n        <ControlLabel label=\"Frequencies Limit\">\n          {/* @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message */}\n          <Grid.Row gutter={15} type=\"flex\">\n            <Grid.Col span={12}>\n              <InputNumber\n                data-test=\"WordCloud.WordCountLimit.Min\"\n                placeholder=\"Min\"\n                min={0}\n                value={options.wordCountLimit.min}\n                onChange={(value: any) => optionsChanged({ wordCountLimit: { min: value > 0 ? value : null } })}\n              />\n            </Grid.Col>\n            <Grid.Col span={12}>\n              <InputNumber\n                data-test=\"WordCloud.WordCountLimit.Max\"\n                placeholder=\"Max\"\n                min={0}\n                value={options.wordCountLimit.max}\n                onChange={(value: any) => optionsChanged({ wordCountLimit: { max: value > 0 ? value : null } })}\n              />\n            </Grid.Col>\n          </Grid.Row>\n        </ControlLabel>\n      </Section>\n    </React.Fragment>\n  );\n}\n\nEditor.propTypes = EditorPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/word-cloud/Renderer.tsx",
    "content": "import d3 from \"d3\";\nimport cloud from \"d3-cloud\";\nimport { each, filter, map, min, max, sortBy, toString } from \"lodash\";\nimport React, { useMemo, useState, useEffect } from \"react\";\nimport resizeObserver from \"@/services/resizeObserver\";\nimport { RendererPropTypes } from \"@/visualizations/prop-types\";\n\nimport \"./renderer.less\";\n\nfunction computeWordFrequencies(rows: any, column: any) {\n  const result = {};\n\n  each(rows, row => {\n    const wordsList = toString(row[column]).split(/\\s/g);\n    each(wordsList, d => {\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      result[d] = (result[d] || 0) + 1;\n    });\n  });\n\n  return result;\n}\n\nfunction getWordsWithFrequencies(rows: any, wordColumn: any, frequencyColumn: any) {\n  const result = {};\n\n  each(rows, row => {\n    const count = parseFloat(row[frequencyColumn]);\n    if (Number.isFinite(count) && count > 0) {\n      const word = toString(row[wordColumn]);\n      // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message\n      result[word] = count;\n    }\n  });\n\n  return result;\n}\n\nfunction applyLimitsToWords(words: any, { wordLength, wordCount }: any) {\n  wordLength.min = Number.isFinite(wordLength.min) ? wordLength.min : null;\n  wordLength.max = Number.isFinite(wordLength.max) ? wordLength.max : null;\n\n  wordCount.min = Number.isFinite(wordCount.min) ? wordCount.min : null;\n  wordCount.max = Number.isFinite(wordCount.max) ? wordCount.max : null;\n\n  return filter(words, ({ text, count }) => {\n    const wordLengthFits =\n      (!wordLength.min || text.length >= wordLength.min) && (!wordLength.max || text.length <= wordLength.max);\n    const wordCountFits = (!wordCount.min || count >= wordCount.min) && (!wordCount.max || count <= wordCount.max);\n    return wordLengthFits && wordCountFits;\n  });\n}\n\nfunction prepareWords(rows: any, options: any) {\n  let result: any = [];\n\n  if (options.column) {\n    if (options.frequenciesColumn) {\n      result = getWordsWithFrequencies(rows, options.column, options.frequenciesColumn);\n    } else {\n      result = computeWordFrequencies(rows, options.column);\n    }\n    result = sortBy(\n      map(result, (count, text) => ({ text, count })),\n      [({ count }: any) => -count, ({ text }: any) => -text.length] // \"count\" desc, length(\"text\") desc\n    );\n  }\n\n  // Add additional attributes\n  const counts = map(result, item => item.count);\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'scale' does not exist on type 'typeof im... Remove this comment to see the full error message\n  const wordSize = d3.scale\n    .linear()\n    .domain([min(counts), max(counts)])\n    .range([10, 100]); // min/max word size\n  // @ts-expect-error ts-migrate(2339) FIXME: Property 'scale' does not exist on type 'typeof im... Remove this comment to see the full error message\n  const color = d3.scale.category20();\n\n  each(result, (item, index) => {\n    item.size = wordSize(item.count);\n    item.color = color(index);\n    // @ts-expect-error ts-migrate(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message\n    item.angle = (index % 2) * 90; // make it stable between renderings\n  });\n\n  return applyLimitsToWords(result, {\n    wordLength: options.wordLengthLimit,\n    wordCount: options.wordCountLimit,\n  });\n}\n\nfunction scaleElement(node: any, container: any) {\n  node.style.transform = null;\n  const { width: nodeWidth, height: nodeHeight } = node.getBoundingClientRect();\n  const { width: containerWidth, height: containerHeight } = container.getBoundingClientRect();\n\n  const scaleX = containerWidth / nodeWidth;\n  const scaleY = containerHeight / nodeHeight;\n\n  node.style.transform = `scale(${Math.min(scaleX, scaleY)})`;\n}\n\nfunction createLayout() {\n  const fontFamily = window.getComputedStyle(document.body).fontFamily;\n\n  return (\n    cloud()\n      // make the area large enough to contain even very long words; word cloud will be placed in the center of the area\n      // TODO: dimensions probably should be larger, but `d3-cloud` has some performance issues related to these values\n      .size([5000, 5000])\n      .padding(3)\n      .font(fontFamily)\n      .rotate((d: any) => d.angle)\n      .fontSize((d: any) => d.size)\n      .random(() => 0.5)\n  ); // do not place words randomly - use compact layout\n}\n\nfunction render(container: any, words: any) {\n  container = d3.select(container);\n  container.selectAll(\"*\").remove();\n\n  const svg = container.append(\"svg\");\n  const g = svg.append(\"g\");\n  g.selectAll(\"text\")\n    .data(words)\n    .enter()\n    .append(\"text\")\n    .style(\"font-size\", (d: any) => `${d.size}px`)\n    .style(\"font-family\", (d: any) => d.font)\n    .style(\"fill\", (d: any) => d.color)\n    .attr(\"text-anchor\", \"middle\")\n    .attr(\"transform\", (d: any) => `translate(${[d.x, d.y]}) rotate(${d.rotate})`)\n    .text((d: any) => d.text);\n\n  const svgBounds = svg.node().getBoundingClientRect();\n  const gBounds = g.node().getBoundingClientRect();\n\n  svg.attr(\"width\", Math.ceil(gBounds.width)).attr(\"height\", Math.ceil(gBounds.height));\n  g.attr(\"transform\", `translate(${svgBounds.left - gBounds.left},${svgBounds.top - gBounds.top})`);\n\n  scaleElement(svg.node(), container.node());\n}\n\nexport default function Renderer({ data, options }: any) {\n  const [container, setContainer] = useState(null);\n  const [words, setWords] = useState([]);\n  const layout = useMemo(createLayout, []);\n\n  // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '() => () => layout.Cloud<cloud.W... Remove this comment to see the full error message\n  useEffect(() => {\n    layout\n      .words(prepareWords(data.rows, options))\n      // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'Word[]' is not assignable to par... Remove this comment to see the full error message\n      .on(\"end\", w => setWords(w))\n      .start();\n    // @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.\n    return () => layout.on(\"end\", null).stop();\n  }, [layout, data, options, setWords]);\n\n  useEffect(() => {\n    if (container) {\n      render(container, words);\n    }\n  }, [container, words]);\n\n  useEffect(() => {\n    if (container) {\n      const unwatch = resizeObserver(container, () => {\n        // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.\n        const svg = container.querySelector(\"svg\");\n        if (svg) {\n          scaleElement(svg, container);\n        }\n      });\n      return unwatch;\n    }\n  }, [container]);\n\n  // @ts-expect-error ts-migrate(2322) FIXME: Type 'Dispatch<SetStateAction<null>>' is not assig... Remove this comment to see the full error message\n  return <div className=\"word-cloud-visualization-container\" ref={setContainer} />;\n}\n\nRenderer.propTypes = RendererPropTypes;\n"
  },
  {
    "path": "viz-lib/src/visualizations/word-cloud/index.ts",
    "content": "import { merge } from \"lodash\";\n\nimport Renderer from \"./Renderer\";\nimport Editor from \"./Editor\";\n\nconst DEFAULT_OPTIONS = {\n  column: \"\",\n  frequenciesColumn: \"\",\n  wordLengthLimit: { min: null, max: null },\n  wordCountLimit: { min: null, max: null },\n};\n\nexport default {\n  type: \"WORD_CLOUD\",\n  name: \"Word Cloud\",\n  getOptions: (options: any) => merge({}, DEFAULT_OPTIONS, options),\n  Renderer,\n  Editor,\n\n  defaultRows: 8,\n};\n"
  },
  {
    "path": "viz-lib/src/visualizations/word-cloud/renderer.less",
    "content": ".word-cloud-visualization-container {\n  overflow: hidden;\n  height: 400px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  svg {\n    transform-origin: center center;\n    flex: 0 0 auto;\n  }\n}\n"
  },
  {
    "path": "viz-lib/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2019\",\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"jsx\": \"react\",\n    \"types\": [\"node\", \"jest\"],\n    \"outDir\": \"lib\",\n    \"declaration\": true,\n    \"strict\": true,\n    \"useUnknownInCatchVariables\": false,\n    \"noPropertyAccessFromIndexSignature\": false,\n    \"exactOptionalPropertyTypes\": false,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"baseUrl\": \"./\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src/**/*\"]\n}\n"
  },
  {
    "path": "viz-lib/webpack.config.js",
    "content": "const LessPluginAutoPrefix = require(\"less-plugin-autoprefix\");\nconst path = require(\"path\");\n\nconst isProduction = process.env.NODE_ENV === \"production\";\n\nmodule.exports = {\n  mode: isProduction ? \"production\" : \"development\",\n  entry: \"./src/index.ts\",\n  output: {\n    path: path.resolve(__dirname, \"dist\"),\n    filename: \"redash-visualizations.js\",\n    libraryTarget: \"umd\",\n    assetModuleFilename: 'images/[name][ext]'\n  },\n  resolve: {\n    symlinks: false,\n    extensions: [\".js\", \".jsx\", \".ts\", \".tsx\"],\n    fallback: {\n      fs: false,\n      path: false,\n      stream: false\n    }\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.[jt]sx?$/,\n        exclude: /node_modules/,\n        use: [\"babel-loader\"],\n      },\n      {\n        test: /\\.css$/,\n        use: [\"style-loader\", \"css-loader\"],\n      },\n      {\n        test: /\\.(png|jpe?g|gif|svg)(\\?.*)?$/,\n        type: 'asset/resource',\n      },\n      {\n        test: /\\.less$/,\n        use: [\n          \"style-loader\",\n          \"css-loader\",\n          {\n            loader: \"less-loader\",\n            options: {\n              lessOptions: {\n                plugins: [new LessPluginAutoPrefix({ browsers: [\"last 3 versions\"] })],\n                javascriptEnabled: true,\n\t      },\n            },\n          },\n        ],\n      },\n    ],\n  },\n  externals: [\n    {\n      lodash: \"lodash\",\n      react: \"react\",\n      \"react-dom\": \"react-dom\",\n    },\n    /^antd/i,\n  ],\n};\n"
  },
  {
    "path": "webpack.config.js",
    "content": "/* eslint-disable */\n\nconst webpack = require(\"webpack\");\nconst HtmlWebpackPlugin = require(\"html-webpack-plugin\");\nconst WebpackBuildNotifierPlugin = require(\"webpack-build-notifier\");\nconst { WebpackManifestPlugin } = require(\"webpack-manifest-plugin\");\nconst MiniCssExtractPlugin = require(\"mini-css-extract-plugin\");\nconst CopyWebpackPlugin = require(\"copy-webpack-plugin\");\nconst LessPluginAutoPrefix = require(\"less-plugin-autoprefix\");\nconst BundleAnalyzerPlugin = require(\"webpack-bundle-analyzer\")\n  .BundleAnalyzerPlugin;\nconst ReactRefreshWebpackPlugin = require(\"@pmmmwh/react-refresh-webpack-plugin\");\nconst ESLintPlugin = require(\"eslint-webpack-plugin\");\n\nconst path = require(\"path\");\n\nfunction optionalRequire(module, defaultReturn = undefined) {\n  try {\n    require.resolve(module);\n  } catch (e) {\n    if (e && e.code === \"MODULE_NOT_FOUND\") {\n      // Module was not found, return default value if any\n      return defaultReturn;\n    }\n    throw e;\n  }\n  return require(module);\n}\n\n// Load optionally configuration object (see scripts/README)\nconst CONFIG = optionalRequire(\"./scripts/config\", {});\n\nconst isProduction = process.env.NODE_ENV === \"production\";\nconst isDevelopment = !isProduction;\nconst isHotReloadingEnabled =\n  isDevelopment && process.env.HOT_RELOAD === \"true\";\n\nconst redashBackend = process.env.REDASH_BACKEND || \"http://localhost:5001\";\nconst baseHref = CONFIG.baseHref || \"/\";\nconst staticPath = CONFIG.staticPath || \"/static/\";\nconst htmlTitle = CONFIG.title || \"Redash\";\n\nconst basePath = path.join(__dirname, \"client\");\nconst appPath = path.join(__dirname, \"client\", \"app\");\n\nconst extensionsRelativePath =\n  process.env.EXTENSIONS_DIRECTORY || path.join(\"client\", \"app\", \"extensions\");\nconst extensionPath = path.join(__dirname, extensionsRelativePath);\n\n// Function to apply configuration overrides (see scripts/README)\nfunction maybeApplyOverrides(config) {\n  const overridesLocation =\n    process.env.REDASH_WEBPACK_OVERRIDES || \"./scripts/webpack/overrides\";\n  const applyOverrides = optionalRequire(overridesLocation);\n  if (!applyOverrides) {\n    return config;\n  }\n  console.info(\"Custom overrides found. Applying them...\");\n  const newConfig = applyOverrides(config);\n  console.info(\"Custom overrides applied successfully.\");\n  return newConfig;\n}\n\nconst config = {\n  mode: isProduction ? \"production\" : \"development\",\n  entry: {\n    app: [\n      \"./client/app/index.js\",\n      \"./client/app/assets/less/main.less\",\n      \"./client/app/assets/less/ant.less\"\n    ],\n    server: [\"./client/app/assets/less/server.less\"]\n  },\n  output: {\n    path: path.join(basePath, \"./dist\"),\n    filename: isProduction ? \"[name].[chunkhash].js\" : \"[name].js\",\n    publicPath: staticPath\n  },\n  node: {\n  },\n  resolve: {\n    symlinks: true,\n    extensions: [\".js\", \".jsx\", \".ts\", \".tsx\"],\n    alias: {\n      \"@\": appPath,\n      extensions: extensionPath\n    },\n    fallback: {\n      fs: false,\n      url: require.resolve(\"url/\"),\n      stream: require.resolve(\"stream-browserify\"),\n      assert: require.resolve(\"assert/\"),\n      util: require.resolve(\"util/\"),\n      process: require.resolve(\"process/browser\"),\n    }\n  },\n  plugins: [\n    new WebpackBuildNotifierPlugin({ title: \"Redash\" }),\n    // bundle only default `moment` locale (`en`)\n    new webpack.ContextReplacementPlugin(/moment[\\/\\\\]locale$/, /en/),\n    new HtmlWebpackPlugin({\n      template: \"./client/app/index.html\",\n      filename: \"index.html\",\n      excludeChunks: [\"server\"],\n      release: process.env.BUILD_VERSION || \"dev\",\n      staticPath,\n      baseHref,\n      title: htmlTitle\n    }),\n    new HtmlWebpackPlugin({\n      template: \"./client/app/multi_org.html\",\n      filename: \"multi_org.html\",\n      excludeChunks: [\"server\"]\n    }),\n    isProduction &&\n      new MiniCssExtractPlugin({\n        filename: \"[name].[chunkhash].css\"\n      }),\n    new WebpackManifestPlugin({\n      fileName: \"asset-manifest.json\",\n      publicPath: \"\"\n    }),\n    new CopyWebpackPlugin({\n      patterns: [\n        { from: \"client/app/assets/robots.txt\" },\n        { from: \"client/app/unsupported.html\" },\n        { from: \"client/app/unsupportedRedirect.js\" },\n        { from: \"client/app/assets/css/*.css\", to: \"styles/[name][ext]\" },\n        { from: \"client/app/assets/fonts\", to: \"fonts/\" }\n      ],\n    }),\n    isHotReloadingEnabled && new ReactRefreshWebpackPlugin({ overlay: false }),\n    !isProduction &&\n      new ESLintPlugin({\n        extensions: [\"js\", \"jsx\", \"ts\", \"tsx\"],\n        context: path.resolve(__dirname, \"client\"),\n        eslintPath: require.resolve(\"eslint\"),\n        failOnError: false,\n      }),\n    new webpack.ProvidePlugin({\n      // Make a global `process` variable that points to the `process` package,\n      // because the `util` package expects there to be a global variable named `process`.\n      // Thanks to https://stackoverflow.com/a/65018686/14239942\n      process: 'process/browser'\n    })\n  ].filter(Boolean),\n  optimization: {\n    splitChunks: {\n      chunks: chunk => {\n        return chunk.name != \"server\";\n      }\n    }\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.js$/,\n        enforce: \"pre\",\n        use: [\"source-map-loader\"],\n        resolve: {\n          fullySpecified: false\n        },\n        exclude: [\n          /node_modules\\/@plotly\\/mapbox-gl/,\n        ],\n      },\n      {\n        test: /\\.(t|j)sx?$/,\n        exclude: /node_modules/,\n        use: [\n          {\n            loader: require.resolve(\"babel-loader\"),\n            options: {\n              plugins: [\n                isHotReloadingEnabled && require.resolve(\"react-refresh/babel\")\n              ].filter(Boolean)\n            }\n          }\n        ]\n      },\n      {\n        test: /\\.html$/,\n        exclude: [/node_modules/, /index\\.html/, /multi_org\\.html/],\n        type: \"asset/source\"\n      },\n      {\n        test: /\\.css$/,\n        use: [\n          {\n            loader: isProduction ? MiniCssExtractPlugin.loader : \"style-loader\"\n          },\n          {\n            loader: \"css-loader\"\n          }\n        ]\n      },\n      {\n        test: /\\.less$/,\n        use: [\n          {\n            loader: isProduction ? MiniCssExtractPlugin.loader : \"style-loader\"\n          },\n          {\n            loader: \"css-loader\"\n          },\n          {\n            loader: \"less-loader\",\n            options: {\n              sourceMap: false,\n              lessOptions: {\n                plugins: [\n                  new LessPluginAutoPrefix({ browsers: [\"last 3 versions\"] })\n                ],\n                javascriptEnabled: true\n              }\n            }\n          }\n        ]\n      },\n      {\n        test: /\\.(png|jpe?g|gif|svg)(\\?.*)?$/,\n        type: \"asset/resource\",\n        generator: {\n          filename: (pathData) => {\n            const filePath = pathData.filename || \"\";\n            // Strip source prefix so assets/images/db-logos/foo.png → db-logos/foo.png\n            const m = filePath.match(/assets\\/images\\/(.*)/);\n            if (m) return `images/${m[1]}`;\n            // For images from node_modules or elsewhere, flatten to avoid deep paths\n            const parts = filePath.split(\"/\");\n            return `images/${parts[parts.length - 1]}`;\n          }\n        }\n      },\n      {\n        test: /\\.geo\\.json$/,\n        type: \"asset/resource\",\n        generator: {\n          filename: \"data/[hash:7].[name][ext]\"\n        }\n      },\n      {\n        test: /\\.(woff2?|eot|ttf|otf)(\\?.*)?$/,\n        type: \"asset\",\n        parser: {\n          dataUrlCondition: {\n            maxSize: 10000\n          }\n        },\n        generator: {\n          filename: \"fonts/[name].[hash:7][ext]\"\n        }\n      }\n    ]\n  },\n  devtool: isProduction ? \"source-map\" : \"eval-cheap-module-source-map\",\n  stats: {\n    children: false,\n    modules: false,\n    chunkModules: false\n  },\n  watchOptions: {\n    ignored: /\\.sw.$/\n  },\n  devServer: {\n    devMiddleware: {\n      index: \"/static/index.html\",\n      publicPath: staticPath,\n      stats: {\n        modules: false,\n        chunkModules: false\n      },\n    },\n    historyApiFallback: {\n      index: \"/static/index.html\",\n      rewrites: [{ from: /./, to: \"/static/index.html\" }]\n    },\n    proxy: [\n      {\n        context: [\n          \"/login\",\n          \"/logout\",\n          \"/invite\",\n          \"/setup\",\n          \"/status.json\",\n          \"/api\",\n          \"/oauth\"\n        ],\n        target: redashBackend + \"/\",\n        changeOrigin: false,\n        secure: false\n      },\n      {\n        context: path => {\n          // CSS/JS for server-rendered pages should be served from backend\n          return /^\\/static\\/[a-z]+\\.[0-9a-fA-F]+\\.(css|js)$/.test(path);\n        },\n        target: redashBackend + \"/\",\n        changeOrigin: true,\n        secure: false\n      }\n    ],\n    hot: isHotReloadingEnabled\n  },\n  performance: {\n    hints: false\n  }\n};\n\nif (process.env.DEV_SERVER_HOST) {\n  config.devServer.host = process.env.DEV_SERVER_HOST;\n}\n\nif (process.env.BUNDLE_ANALYZER) {\n  config.plugins.push(new BundleAnalyzerPlugin());\n}\n\nmodule.exports = maybeApplyOverrides(config);\n"
  },
  {
    "path": "worker.conf",
    "content": "[supervisord]\nlogfile=/dev/null\npidfile=/tmp/supervisord.pid\nnodaemon=true\n\n[unix_http_server]\nfile = /tmp/supervisor.sock\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[program:worker]\ncommand=./manage.py rq worker %(ENV_QUEUES)s\nprocess_name=%(program_name)s-%(process_num)s\nnumprocs=%(ENV_WORKERS_COUNT)s\ndirectory=/app\nstopsignal=TERM\nautostart=true\nautorestart=true\nstartsecs=300\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\n\n[eventlistener:worker_healthcheck]\nserverurl=AUTO\ncommand=./manage.py rq healthcheck\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\nevents=TICK_60"
  }
]