mirror of
https://github.com/apache/superset.git
synced 2026-07-19 05:05:39 +00:00
Compare commits
5 Commits
remove-mor
...
supersetbo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e676ec311 | ||
|
|
91301bcd5b | ||
|
|
67ad7da5cc | ||
|
|
e0deb704f9 | ||
|
|
abf3790ea6 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,2 +1,3 @@
|
|||||||
docker/**/*.sh text eol=lf
|
docker/**/*.sh text eol=lf
|
||||||
*.svg binary
|
*.svg binary
|
||||||
|
*.ipynb binary
|
||||||
|
|||||||
2
.github/actions/chart-releaser-action
vendored
2
.github/actions/chart-releaser-action
vendored
Submodule .github/actions/chart-releaser-action updated: a917fd15b2...120944e663
134
.github/workflows/ephemeral-env.yml
vendored
134
.github/workflows/ephemeral-env.yml
vendored
@@ -1,30 +1,25 @@
|
|||||||
name: Ephemeral env workflow
|
name: Ephemeral env workflow
|
||||||
|
|
||||||
|
# Example manual trigger: gh workflow run ephemeral-env.yml --ref fix_ephemerals --field comment_body="/testenv up" --field issue_number=666
|
||||||
|
|
||||||
on:
|
on:
|
||||||
issue_comment:
|
issue_comment:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
comment_body:
|
||||||
|
description: 'Comment body to simulate /testenv command'
|
||||||
|
required: true
|
||||||
|
default: '/testenv up'
|
||||||
|
issue_number:
|
||||||
|
description: 'Issue or PR number'
|
||||||
|
required: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
config:
|
|
||||||
runs-on: "ubuntu-22.04"
|
|
||||||
if: github.event.issue.pull_request
|
|
||||||
outputs:
|
|
||||||
has-secrets: ${{ steps.check.outputs.has-secrets }}
|
|
||||||
steps:
|
|
||||||
- name: "Check for secrets"
|
|
||||||
id: check
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [ -n "${{ (secrets.AWS_ACCESS_KEY_ID != '' && secrets.AWS_SECRET_ACCESS_KEY != '') || '' }}" ]; then
|
|
||||||
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ephemeral-env-comment:
|
ephemeral-env-comment:
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}-comment
|
group: ${{ github.workflow }}-${{ github.event.inputs.issue_number || github.event.issue.number || github.run_id }}-comment
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
needs: config
|
|
||||||
if: needs.config.outputs.has-secrets
|
|
||||||
name: Evaluate ephemeral env comment trigger (/testenv)
|
name: Evaluate ephemeral env comment trigger (/testenv)
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
permissions:
|
permissions:
|
||||||
@@ -44,18 +39,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
result-encoding: string
|
result-encoding: string
|
||||||
script: |
|
script: |
|
||||||
const pattern = /^\/testenv (up|down)/
|
const pattern = /^\/testenv (up|down)/;
|
||||||
const result = pattern.exec(context.payload.comment.body)
|
const result = pattern.exec('${{ github.event.inputs.comment_body || github.event.comment.body }}');
|
||||||
return result === null ? 'noop' : result[1]
|
return result === null ? 'noop' : result[1];
|
||||||
|
|
||||||
- name: Eval comment body for feature flags
|
- name: Looking for feature flags
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
id: eval-feature-flags
|
id: eval-feature-flags
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const pattern = /FEATURE_(\w+)=(\w+)/g;
|
const pattern = /FEATURE_(\w+)=(\w+)/g;
|
||||||
let results = [];
|
let results = [];
|
||||||
[...context.payload.comment.body.matchAll(pattern)].forEach(match => {
|
[...'${{ github.event.inputs.comment_body || github.event.comment.body }}'.matchAll(pattern)].forEach(match => {
|
||||||
const config = {
|
const config = {
|
||||||
name: `SUPERSET_FEATURE_${match[1]}`,
|
name: `SUPERSET_FEATURE_${match[1]}`,
|
||||||
value: match[2],
|
value: match[2],
|
||||||
@@ -67,24 +62,47 @@ jobs:
|
|||||||
- name: Limit to committers
|
- name: Limit to committers
|
||||||
if: >
|
if: >
|
||||||
steps.eval-body.outputs.result != 'noop' &&
|
steps.eval-body.outputs.result != 'noop' &&
|
||||||
|
github.event_name == 'issue_comment' &&
|
||||||
github.event.comment.author_association != 'MEMBER' &&
|
github.event.comment.author_association != 'MEMBER' &&
|
||||||
github.event.comment.author_association != 'OWNER'
|
github.event.comment.author_association != 'OWNER'
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{github.token}}
|
github-token: ${{ github.token }}
|
||||||
script: |
|
script: |
|
||||||
const errMsg = '@${{ github.event.comment.user.login }} Ephemeral environment creation is currently limited to committers.'
|
const errMsg = '@${{ github.event.comment.user.login }} Ephemeral environment creation is currently limited to committers.';
|
||||||
github.rest.issues.createComment({
|
github.rest.issues.createComment({
|
||||||
issue_number: ${{ github.event.issue.number }},
|
issue_number: ${{ github.event.issue.number }},
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
body: errMsg
|
body: errMsg
|
||||||
})
|
});
|
||||||
core.setFailed(errMsg)
|
core.setFailed(errMsg);
|
||||||
|
|
||||||
|
- name: Reply with confirmation comment
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const issueNumber = ${{ github.event.inputs.issue_number || github.event.issue.number }};
|
||||||
|
const user = '${{ github.event.comment.user.login || github.actor }}';
|
||||||
|
const action = '${{ steps.eval-body.outputs.result }}';
|
||||||
|
const runId = context.runId;
|
||||||
|
const workflowUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
|
||||||
|
const body = action === 'noop'
|
||||||
|
? `@${user} No ephemeral environment action detected. Please use '/testenv up' or '/testenv down'. [View workflow run](${workflowUrl}).`
|
||||||
|
: `@${user} Processing your ephemeral environment request [here](${workflowUrl}).`;
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
ephemeral-docker-build:
|
ephemeral-docker-build:
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}-build
|
group: ${{ github.workflow }}-${{ github.event.inputs.issue_number || github.event.issue.number || github.run_id }}-build
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
needs: ephemeral-env-comment
|
needs: ephemeral-env-comment
|
||||||
name: ephemeral-docker-build
|
name: ephemeral-docker-build
|
||||||
@@ -98,9 +116,9 @@ jobs:
|
|||||||
const request = {
|
const request = {
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
pull_number: ${{ github.event.issue.number }},
|
pull_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
|
||||||
}
|
};
|
||||||
core.info(`Getting PR #${request.pull_number} from ${request.owner}/${request.repo}`)
|
core.info(`Getting PR #${request.pull_number} from ${request.owner}/${request.repo}`);
|
||||||
const pr = await github.rest.pulls.get(request);
|
const pr = await github.rest.pulls.get(request);
|
||||||
return pr.data;
|
return pr.data;
|
||||||
|
|
||||||
@@ -121,12 +139,17 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Setup supersetbot
|
||||||
|
uses: ./.github/actions/setup-supersetbot/
|
||||||
|
|
||||||
- name: Build ephemeral env image
|
- name: Build ephemeral env image
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
./scripts/build_docker.py \
|
supersetbot docker \
|
||||||
"ci" \
|
--preset ci \
|
||||||
"pull_request" \
|
--platform linux/amd64 \
|
||||||
--build_context_ref ${{ github.event.issue.number }}
|
--context-ref "$RELEASE"
|
||||||
|
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v4
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
@@ -146,7 +169,7 @@ jobs:
|
|||||||
ECR_REPOSITORY: superset-ci
|
ECR_REPOSITORY: superset-ci
|
||||||
IMAGE_TAG: apache/superset:${{ steps.get-sha.outputs.sha }}-ci
|
IMAGE_TAG: apache/superset:${{ steps.get-sha.outputs.sha }}-ci
|
||||||
run: |
|
run: |
|
||||||
docker tag $IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:pr-${{ github.event.issue.number }}-ci
|
docker tag $IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci
|
||||||
docker push -a $ECR_REGISTRY/$ECR_REPOSITORY
|
docker push -a $ECR_REGISTRY/$ECR_REPOSITORY
|
||||||
|
|
||||||
ephemeral-env-up:
|
ephemeral-env-up:
|
||||||
@@ -181,22 +204,22 @@ jobs:
|
|||||||
aws ecr describe-images \
|
aws ecr describe-images \
|
||||||
--registry-id $(echo "${{ steps.login-ecr.outputs.registry }}" | grep -Eo "^[0-9]+") \
|
--registry-id $(echo "${{ steps.login-ecr.outputs.registry }}" | grep -Eo "^[0-9]+") \
|
||||||
--repository-name superset-ci \
|
--repository-name superset-ci \
|
||||||
--image-ids imageTag=pr-${{ github.event.issue.number }}-ci
|
--image-ids imageTag=pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci
|
||||||
|
|
||||||
- name: Fail on missing container image
|
- name: Fail on missing container image
|
||||||
if: steps.check-image.outcome == 'failure'
|
if: steps.check-image.outcome == 'failure'
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{github.token}}
|
github-token: ${{ github.token }}
|
||||||
script: |
|
script: |
|
||||||
const errMsg = '@${{ github.event.comment.user.login }} Container image not yet published for this PR. Please try again when build is complete.'
|
const errMsg = '@${{ github.event.comment.user.login }} Container image not yet published for this PR. Please try again when build is complete.';
|
||||||
github.rest.issues.createComment({
|
github.rest.issues.createComment({
|
||||||
issue_number: ${{ github.event.issue.number }},
|
issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
body: errMsg
|
body: errMsg
|
||||||
})
|
});
|
||||||
core.setFailed(errMsg)
|
core.setFailed(errMsg);
|
||||||
|
|
||||||
- name: Fill in the new image ID in the Amazon ECS task definition
|
- name: Fill in the new image ID in the Amazon ECS task definition
|
||||||
id: task-def
|
id: task-def
|
||||||
@@ -204,7 +227,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
task-definition: .github/workflows/ecs-task-definition.json
|
task-definition: .github/workflows/ecs-task-definition.json
|
||||||
container-name: superset-ci
|
container-name: superset-ci
|
||||||
image: ${{ steps.login-ecr.outputs.registry }}/superset-ci:pr-${{ github.event.issue.number }}-ci
|
image: ${{ steps.login-ecr.outputs.registry }}/superset-ci:pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-ci
|
||||||
|
|
||||||
- name: Update env vars in the Amazon ECS task definition
|
- name: Update env vars in the Amazon ECS task definition
|
||||||
run: |
|
run: |
|
||||||
@@ -213,30 +236,29 @@ jobs:
|
|||||||
- name: Describe ECS service
|
- name: Describe ECS service
|
||||||
id: describe-services
|
id: describe-services
|
||||||
run: |
|
run: |
|
||||||
echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.issue.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT
|
echo "active=$(aws ecs describe-services --cluster superset-ci --services pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service | jq '.services[] | select(.status == "ACTIVE") | any')" >> $GITHUB_OUTPUT
|
||||||
- name: Create ECS service
|
- name: Create ECS service
|
||||||
if: steps.describe-services.outputs.active != 'true'
|
|
||||||
id: create-service
|
id: create-service
|
||||||
|
if: steps.describe-services.outputs.active != 'true'
|
||||||
env:
|
env:
|
||||||
ECR_SUBNETS: subnet-0e15a5034b4121710,subnet-0e8efef4a72224974
|
ECR_SUBNETS: subnet-0e15a5034b4121710,subnet-0e8efef4a72224974
|
||||||
ECR_SECURITY_GROUP: sg-092ff3a6ae0574d91
|
ECR_SECURITY_GROUP: sg-092ff3a6ae0574d91
|
||||||
run: |
|
run: |
|
||||||
aws ecs create-service \
|
aws ecs create-service \
|
||||||
--cluster superset-ci \
|
--cluster superset-ci \
|
||||||
--service-name pr-${{ github.event.issue.number }}-service \
|
--service-name pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service \
|
||||||
--task-definition superset-ci \
|
--task-definition superset-ci \
|
||||||
--launch-type FARGATE \
|
--launch-type FARGATE \
|
||||||
--desired-count 1 \
|
--desired-count 1 \
|
||||||
--platform-version LATEST \
|
--platform-version LATEST \
|
||||||
--network-configuration "awsvpcConfiguration={subnets=[$ECR_SUBNETS],securityGroups=[$ECR_SECURITY_GROUP],assignPublicIp=ENABLED}" \
|
--network-configuration "awsvpcConfiguration={subnets=[$ECR_SUBNETS],securityGroups=[$ECR_SECURITY_GROUP],assignPublicIp=ENABLED}" \
|
||||||
--tags key=pr,value=${{ github.event.issue.number }} key=github_user,value=${{ github.actor }}
|
--tags key=pr,value=${{ github.event.inputs.issue_number || github.event.issue.number }} key=github_user,value=${{ github.actor }}
|
||||||
|
|
||||||
- name: Deploy Amazon ECS task definition
|
- name: Deploy Amazon ECS task definition
|
||||||
id: deploy-task
|
id: deploy-task
|
||||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
|
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
|
||||||
with:
|
with:
|
||||||
task-definition: ${{ steps.task-def.outputs.task-definition }}
|
task-definition: ${{ steps.task-def.outputs.task-definition }}
|
||||||
service: pr-${{ github.event.issue.number }}-service
|
service: pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service
|
||||||
cluster: superset-ci
|
cluster: superset-ci
|
||||||
wait-for-service-stability: true
|
wait-for-service-stability: true
|
||||||
wait-for-minutes: 10
|
wait-for-minutes: 10
|
||||||
@@ -244,18 +266,15 @@ jobs:
|
|||||||
- name: List tasks
|
- name: List tasks
|
||||||
id: list-tasks
|
id: list-tasks
|
||||||
run: |
|
run: |
|
||||||
echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.issue.number }}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT
|
echo "task=$(aws ecs list-tasks --cluster superset-ci --service-name pr-${{ github.event.inputs.issue_number || github.event.issue.number }}-service | jq '.taskArns | first')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Get network interface
|
- name: Get network interface
|
||||||
id: get-eni
|
id: get-eni
|
||||||
run: |
|
run: |
|
||||||
echo "eni=$(aws ecs describe-tasks --cluster superset-ci --tasks ${{ steps.list-tasks.outputs.task }} | jq '.tasks | .[0] | .attachments | .[0] | .details | map(select(.name=="networkInterfaceId")) | .[0] | .value')" >> $GITHUB_OUTPUT
|
echo "eni=$(aws ecs describe-tasks --cluster superset-ci --tasks ${{ steps.list-tasks.outputs.task }} | jq '.tasks | .[0] | .attachments | .[0] | .details | map(select(.name==\"networkInterfaceId\")) | .[0] | .value')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Get public IP
|
- name: Get public IP
|
||||||
id: get-ip
|
id: get-ip
|
||||||
run: |
|
run: |
|
||||||
echo "ip=$(aws ec2 describe-network-interfaces --network-interface-ids ${{ steps.get-eni.outputs.eni }} | jq -r '.NetworkInterfaces | first | .Association.PublicIp')" >> $GITHUB_OUTPUT
|
echo "ip=$(aws ec2 describe-network-interfaces --network-interface-ids ${{ steps.get-eni.outputs.eni }} | jq -r '.NetworkInterfaces | first | .Association.PublicIp')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Comment (success)
|
- name: Comment (success)
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
@@ -263,12 +282,11 @@ jobs:
|
|||||||
github-token: ${{github.token}}
|
github-token: ${{github.token}}
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.createComment({
|
github.rest.issues.createComment({
|
||||||
issue_number: ${{ github.event.issue.number }},
|
issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
body: '@${{ github.event.comment.user.login }} Ephemeral environment spinning up at http://${{ steps.get-ip.outputs.ip }}:8080. Credentials are `admin`/`admin`. Please allow several minutes for bootstrapping and startup.'
|
body: '@${{ github.event.inputs.user_login || github.event.comment.user.login }} Ephemeral environment spinning up at http://${{ steps.get-ip.outputs.ip }}:8080. Credentials are `admin`/`admin`. Please allow several minutes for bootstrapping and startup.'
|
||||||
})
|
})
|
||||||
|
|
||||||
- name: Comment (failure)
|
- name: Comment (failure)
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
@@ -276,8 +294,8 @@ jobs:
|
|||||||
github-token: ${{github.token}}
|
github-token: ${{github.token}}
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.createComment({
|
github.rest.issues.createComment({
|
||||||
issue_number: ${{ github.event.issue.number }},
|
issue_number: ${{ github.event.inputs.issue_number || github.event.issue.number }},
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
body: '@${{ github.event.comment.user.login }} Ephemeral environment creation failed. Please check the Actions logs for details.'
|
body: '@${{ github.event.inputs.user_login || github.event.comment.user.login }} Ephemeral environment creation failed. Please check the Actions logs for details.'
|
||||||
})
|
})
|
||||||
|
|||||||
12
Dockerfile
12
Dockerfile
@@ -115,7 +115,7 @@ RUN mkdir -p ${PYTHONPATH} superset/static requirements superset-frontend apache
|
|||||||
libldap2-dev \
|
libldap2-dev \
|
||||||
&& touch superset/static/version_info.json \
|
&& touch superset/static/version_info.json \
|
||||||
&& chown -R superset:superset ./* \
|
&& chown -R superset:superset ./* \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
COPY --chown=superset:superset pyproject.toml setup.py MANIFEST.in README.md ./
|
COPY --chown=superset:superset pyproject.toml setup.py MANIFEST.in README.md ./
|
||||||
# setup.py uses the version information in package.json
|
# setup.py uses the version information in package.json
|
||||||
@@ -128,7 +128,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
|||||||
&& pip install --no-cache-dir --upgrade setuptools pip \
|
&& pip install --no-cache-dir --upgrade setuptools pip \
|
||||||
&& pip install --no-cache-dir -r requirements/base.txt \
|
&& pip install --no-cache-dir -r requirements/base.txt \
|
||||||
&& apt-get autoremove -yqq --purge build-essential \
|
&& apt-get autoremove -yqq --purge build-essential \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
# Copy the compiled frontend assets
|
# Copy the compiled frontend assets
|
||||||
COPY --chown=superset:superset --from=superset-node /app/superset/static/assets superset/static/assets
|
COPY --chown=superset:superset --from=superset-node /app/superset/static/assets superset/static/assets
|
||||||
@@ -177,7 +177,7 @@ RUN apt-get update -qq \
|
|||||||
libxtst6 \
|
libxtst6 \
|
||||||
git \
|
git \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
pip install --no-cache-dir playwright
|
pip install --no-cache-dir playwright
|
||||||
@@ -199,13 +199,13 @@ RUN if [ "$INCLUDE_FIREFOX" = "true" ]; then \
|
|||||||
&& wget -q https://github.com/mozilla/geckodriver/releases/download/${GECKODRIVER_VERSION}/geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz -O - | tar xfz - -C /usr/local/bin \
|
&& wget -q https://github.com/mozilla/geckodriver/releases/download/${GECKODRIVER_VERSION}/geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz -O - | tar xfz - -C /usr/local/bin \
|
||||||
&& wget -q https://download-installer.cdn.mozilla.net/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/firefox-${FIREFOX_VERSION}.tar.bz2 -O - | tar xfj - -C /opt \
|
&& wget -q https://download-installer.cdn.mozilla.net/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/firefox-${FIREFOX_VERSION}.tar.bz2 -O - | tar xfj - -C /opt \
|
||||||
&& ln -s /opt/firefox/firefox /usr/local/bin/firefox \
|
&& ln -s /opt/firefox/firefox /usr/local/bin/firefox \
|
||||||
&& apt-get autoremove -yqq --purge wget bzip2 && rm -rf /var/[log,tmp]/* /tmp/* /var/lib/apt/lists/*; \
|
&& apt-get autoremove -yqq --purge wget bzip2 && rm -rf /var/[log,tmp]/* /tmp/* /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Installing mysql client os-level dependencies in dev image only because GPL
|
# Installing mysql client os-level dependencies in dev image only because GPL
|
||||||
RUN apt-get install -yqq --no-install-recommends \
|
RUN apt-get install -yqq --no-install-recommends \
|
||||||
default-libmysqlclient-dev \
|
default-libmysqlclient-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
COPY --chown=superset:superset requirements/development.txt requirements/
|
COPY --chown=superset:superset requirements/development.txt requirements/
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
@@ -213,7 +213,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
|||||||
build-essential \
|
build-essential \
|
||||||
&& pip install --no-cache-dir -r requirements/development.txt \
|
&& pip install --no-cache-dir -r requirements/development.txt \
|
||||||
&& apt-get autoremove -yqq --purge build-essential \
|
&& apt-get autoremove -yqq --purge build-essential \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
USER superset
|
USER superset
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ x-superset-user: &superset-user root
|
|||||||
x-superset-depends-on: &superset-depends-on
|
x-superset-depends-on: &superset-depends-on
|
||||||
- db
|
- db
|
||||||
- redis
|
- redis
|
||||||
- superset-checks
|
|
||||||
x-superset-volumes: &superset-volumes
|
x-superset-volumes: &superset-volumes
|
||||||
# /app/pythonpath_docker will be appended to the PYTHONPATH in the final container
|
# /app/pythonpath_docker will be appended to the PYTHONPATH in the final container
|
||||||
- ./docker:/app/docker
|
- ./docker:/app/docker
|
||||||
@@ -131,23 +130,6 @@ services:
|
|||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- REDIS_SSL=false
|
- REDIS_SSL=false
|
||||||
|
|
||||||
superset-checks:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
target: python-base
|
|
||||||
cache_from:
|
|
||||||
- apache/superset-cache:3.10-slim-bookworm
|
|
||||||
container_name: superset_checks
|
|
||||||
command: ["/app/scripts/check-env.py"]
|
|
||||||
env_file:
|
|
||||||
- path: docker/.env # default
|
|
||||||
required: true
|
|
||||||
- path: docker/.env-local # optional override
|
|
||||||
required: false
|
|
||||||
user: *superset-user
|
|
||||||
healthcheck:
|
|
||||||
disable: true
|
|
||||||
|
|
||||||
superset-init:
|
superset-init:
|
||||||
build:
|
build:
|
||||||
<<: *common-build
|
<<: *common-build
|
||||||
@@ -179,6 +161,7 @@ services:
|
|||||||
# set this to false if you have perf issues running the npm i; npm run dev in-docker
|
# set this to false if you have perf issues running the npm i; npm run dev in-docker
|
||||||
# if you do so, you have to run this manually on the host, which should perform better!
|
# if you do so, you have to run this manually on the host, which should perform better!
|
||||||
BUILD_SUPERSET_FRONTEND_IN_DOCKER: true
|
BUILD_SUPERSET_FRONTEND_IN_DOCKER: true
|
||||||
|
NPM_RUN_PRUNE: false
|
||||||
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
|
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
|
||||||
container_name: superset_node
|
container_name: superset_node
|
||||||
command: ["/app/docker/docker-frontend.sh"]
|
command: ["/app/docker/docker-frontend.sh"]
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ if [ "$BUILD_SUPERSET_FRONTEND_IN_DOCKER" = "true" ]; then
|
|||||||
echo "Building Superset frontend in dev mode inside docker container"
|
echo "Building Superset frontend in dev mode inside docker container"
|
||||||
cd /app/superset-frontend
|
cd /app/superset-frontend
|
||||||
|
|
||||||
|
if [ "$NPM_RUN_PRUNE" = "true" ]; then
|
||||||
|
echo "Running `npm run prune`"
|
||||||
|
npm run prune
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Running `npm install`"
|
echo "Running `npm install`"
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ We have a set of build "presets" that each represent a combination of
|
|||||||
parameters for the build, mostly pointing to either different target layer
|
parameters for the build, mostly pointing to either different target layer
|
||||||
for the build, and/or base image.
|
for the build, and/or base image.
|
||||||
|
|
||||||
Here are the build presets that are exposed through the `build_docker.py` script:
|
Here are the build presets that are exposed through the `supersetbot docker` utility:
|
||||||
|
|
||||||
- `lean`: The default Docker image, including both frontend and backend. Tags
|
- `lean`: The default Docker image, including both frontend and backend. Tags
|
||||||
without a build_preset are lean builds (ie: `latest`, `4.0.0`, `3.0.0`, ...). `lean`
|
without a build_preset are lean builds (ie: `latest`, `4.0.0`, `3.0.0`, ...). `lean`
|
||||||
@@ -62,8 +62,8 @@ Here are the build presets that are exposed through the `build_docker.py` script
|
|||||||
|
|
||||||
|
|
||||||
For insights or modifications to the build matrix and tagging conventions,
|
For insights or modifications to the build matrix and tagging conventions,
|
||||||
check the [build_docker.py](https://github.com/apache/superset/blob/master/scripts/build_docker.py)
|
check the [supersetbot docker](https://github.com/apache-superset/supersetbot)
|
||||||
script and the [docker.yml](https://github.com/apache/superset/blob/master/.github/workflows/docker.yml)
|
subcommand and the [docker.yml](https://github.com/apache/superset/blob/master/.github/workflows/docker.yml)
|
||||||
GitHub action.
|
GitHub action.
|
||||||
|
|
||||||
## Key ARGs in Dockerfile
|
## Key ARGs in Dockerfile
|
||||||
|
|||||||
@@ -95,6 +95,14 @@ perform those operations. In this case, we recommend you set the env var
|
|||||||
Simply trigger `npm i && npm run dev`, this should be MUCH faster.
|
Simply trigger `npm i && npm run dev`, this should be MUCH faster.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Sometimes, your npm-related state can get out-of-wack, running `npm run prune` from
|
||||||
|
the `superset-frontend/` folder will nuke the various' packages `node_module/` folders
|
||||||
|
and help you start fresh. In the context of `docker compose` setting
|
||||||
|
`export NPM_RUN_PRUNE=true` prior to running `docker compose up` will trigger that
|
||||||
|
from within docker. This will slow down the startup, but will fix various npm-related issues.
|
||||||
|
:::
|
||||||
|
|
||||||
### Option #2 - build a set of immutable images from the local branch
|
### Option #2 - build a set of immutable images from the local branch
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -77,10 +77,6 @@ versions officially supported by Superset. We'd recommend using a Python version
|
|||||||
like [pyenv](https://github.com/pyenv/pyenv)
|
like [pyenv](https://github.com/pyenv/pyenv)
|
||||||
(and also [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv)).
|
(and also [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv)).
|
||||||
|
|
||||||
:::tip
|
|
||||||
To identify the Python version used by the official docker image, see the [Dockerfile](https://github.com/apache/superset/blob/master/Dockerfile). Additional docker images published for newer versions of Python can be found in [this file](https://github.com/apache/superset/blob/master/scripts/build_docker.py).
|
|
||||||
:::
|
|
||||||
|
|
||||||
Let's also make sure we have the latest version of `pip` and `setuptools`:
|
Let's also make sure we have the latest version of `pip` and `setuptools`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ billiard==4.2.0
|
|||||||
# via celery
|
# via celery
|
||||||
blinker==1.9.0
|
blinker==1.9.0
|
||||||
# via flask
|
# via flask
|
||||||
bottleneck==1.3.8
|
bottleneck==1.4.2
|
||||||
# via pandas
|
# via pandas
|
||||||
brotli==1.1.0
|
brotli==1.1.0
|
||||||
# via flask-compress
|
# via flask-compress
|
||||||
@@ -148,9 +148,7 @@ geopy==2.4.1
|
|||||||
google-auth==2.29.0
|
google-auth==2.29.0
|
||||||
# via shillelagh
|
# via shillelagh
|
||||||
greenlet==3.0.3
|
greenlet==3.0.3
|
||||||
# via
|
# via shillelagh
|
||||||
# shillelagh
|
|
||||||
# sqlalchemy
|
|
||||||
gunicorn==22.0.0
|
gunicorn==22.0.0
|
||||||
# via apache-superset
|
# via apache-superset
|
||||||
hashids==1.3.1
|
hashids==1.3.1
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
-r base.txt
|
-r base.txt
|
||||||
-e file:.
|
-e file:.
|
||||||
# via
|
# via
|
||||||
# -r requirements/base.in
|
# -r /home/runner/work/superset/superset/requirements/base.in
|
||||||
# -r requirements/development.in
|
# -r requirements/development.in
|
||||||
astroid==3.1.0
|
astroid==3.1.0
|
||||||
# via pylint
|
# via pylint
|
||||||
@@ -20,7 +20,7 @@ botocore==1.34.112
|
|||||||
# via
|
# via
|
||||||
# boto3
|
# boto3
|
||||||
# s3transfer
|
# s3transfer
|
||||||
build==1.2.1
|
build==1.2.2.post1
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
cached-property==1.5.2
|
cached-property==1.5.2
|
||||||
# via tableschema
|
# via tableschema
|
||||||
@@ -149,7 +149,7 @@ pillow==10.3.0
|
|||||||
# via
|
# via
|
||||||
# apache-superset
|
# apache-superset
|
||||||
# matplotlib
|
# matplotlib
|
||||||
pip-compile-multi==2.6.3
|
pip-compile-multi==2.6.4
|
||||||
# via apache-superset
|
# via apache-superset
|
||||||
pip-tools==7.4.1
|
pip-tools==7.4.1
|
||||||
# via pip-compile-multi
|
# via pip-compile-multi
|
||||||
@@ -188,7 +188,7 @@ pyee==11.0.1
|
|||||||
# via playwright
|
# via playwright
|
||||||
pyfakefs==5.3.5
|
pyfakefs==5.3.5
|
||||||
# via apache-superset
|
# via apache-superset
|
||||||
pyhive[presto]==0.7.0
|
pyhive[hive]==0.7.0
|
||||||
# via apache-superset
|
# via apache-superset
|
||||||
pyinstrument==4.4.0
|
pyinstrument==4.4.0
|
||||||
# via apache-superset
|
# via apache-superset
|
||||||
@@ -196,7 +196,7 @@ pylint==3.1.0
|
|||||||
# via apache-superset
|
# via apache-superset
|
||||||
pyproject-api==1.6.1
|
pyproject-api==1.6.1
|
||||||
# via tox
|
# via tox
|
||||||
pyproject-hooks==1.0.0
|
pyproject-hooks==1.2.0
|
||||||
# via
|
# via
|
||||||
# build
|
# build
|
||||||
# pip-tools
|
# pip-tools
|
||||||
@@ -221,6 +221,8 @@ ruff==0.4.5
|
|||||||
# via apache-superset
|
# via apache-superset
|
||||||
s3transfer==0.10.1
|
s3transfer==0.10.1
|
||||||
# via boto3
|
# via boto3
|
||||||
|
sasl==0.3.1
|
||||||
|
# via pyhive
|
||||||
sqlalchemy-bigquery==1.11.0
|
sqlalchemy-bigquery==1.11.0
|
||||||
# via apache-superset
|
# via apache-superset
|
||||||
sqloxide==0.1.43
|
sqloxide==0.1.43
|
||||||
@@ -232,17 +234,19 @@ tableschema==1.20.10
|
|||||||
thrift==0.16.0
|
thrift==0.16.0
|
||||||
# via
|
# via
|
||||||
# apache-superset
|
# apache-superset
|
||||||
|
# pyhive
|
||||||
# thrift-sasl
|
# thrift-sasl
|
||||||
thrift-sasl==0.4.3
|
thrift-sasl==0.4.3
|
||||||
# via apache-superset
|
# via
|
||||||
tomli==2.0.1
|
# apache-superset
|
||||||
|
# pyhive
|
||||||
|
tomli==2.1.0
|
||||||
# via
|
# via
|
||||||
# build
|
# build
|
||||||
# coverage
|
# coverage
|
||||||
# pip-tools
|
# pip-tools
|
||||||
# pylint
|
# pylint
|
||||||
# pyproject-api
|
# pyproject-api
|
||||||
# pyproject-hooks
|
|
||||||
# pytest
|
# pytest
|
||||||
# tox
|
# tox
|
||||||
tomlkit==0.12.5
|
tomlkit==0.12.5
|
||||||
@@ -267,7 +271,7 @@ virtualenv==20.23.1
|
|||||||
# via
|
# via
|
||||||
# pre-commit
|
# pre-commit
|
||||||
# tox
|
# tox
|
||||||
wheel==0.43.0
|
wheel==0.45.1
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
xlrd==2.0.1
|
xlrd==2.0.1
|
||||||
# via dataflows-tabulator
|
# via dataflows-tabulator
|
||||||
|
|||||||
@@ -1,294 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
from textwrap import dedent
|
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
REPO = "apache/superset"
|
|
||||||
CACHE_REPO = f"{REPO}-cache"
|
|
||||||
BASE_PY_IMAGE = "3.10-slim-bookworm"
|
|
||||||
|
|
||||||
|
|
||||||
def run_cmd(command: str, raise_on_failure: bool = True) -> str:
|
|
||||||
process = subprocess.Popen(
|
|
||||||
command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
output = ""
|
|
||||||
if process.stdout is not None:
|
|
||||||
for line in iter(process.stdout.readline, ""):
|
|
||||||
print(line.strip()) # Print the line to stdout in real-time
|
|
||||||
output += line
|
|
||||||
|
|
||||||
process.wait() # Wait for the subprocess to finish
|
|
||||||
|
|
||||||
if process.returncode != 0 and raise_on_failure:
|
|
||||||
raise subprocess.CalledProcessError(process.returncode, command, output)
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
def get_git_sha() -> str:
|
|
||||||
return run_cmd("git rev-parse HEAD").strip()
|
|
||||||
|
|
||||||
|
|
||||||
def get_build_context_ref(build_context: str) -> str:
|
|
||||||
"""
|
|
||||||
Given a context, return a ref:
|
|
||||||
- if context is pull_request, return the PR's id
|
|
||||||
- if context is push, return the branch
|
|
||||||
- if context is release, return the release ref
|
|
||||||
"""
|
|
||||||
|
|
||||||
event = os.getenv("GITHUB_EVENT_NAME")
|
|
||||||
github_ref = os.getenv("GITHUB_REF", "")
|
|
||||||
|
|
||||||
if event == "pull_request":
|
|
||||||
github_head_ref = os.getenv("GITHUB_HEAD_REF", "")
|
|
||||||
return re.sub("[^a-zA-Z0-9]", "-", github_head_ref)[:40]
|
|
||||||
elif event == "release":
|
|
||||||
return re.sub("refs/tags/", "", github_ref)[:40]
|
|
||||||
elif event == "push":
|
|
||||||
return re.sub("[^a-zA-Z0-9]", "-", re.sub("refs/heads/", "", github_ref))[:40]
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def is_latest_release(release: str) -> bool:
|
|
||||||
output = (
|
|
||||||
run_cmd(
|
|
||||||
f"./scripts/tag_latest_release.sh {release} --dry-run",
|
|
||||||
raise_on_failure=False,
|
|
||||||
)
|
|
||||||
or ""
|
|
||||||
)
|
|
||||||
return "SKIP_TAG::false" in output
|
|
||||||
|
|
||||||
|
|
||||||
def make_docker_tag(l: list[str]) -> str: # noqa: E741
|
|
||||||
return f"{REPO}:" + "-".join([o for o in l if o])
|
|
||||||
|
|
||||||
|
|
||||||
def get_docker_tags(
|
|
||||||
build_preset: str,
|
|
||||||
build_platforms: list[str],
|
|
||||||
sha: str,
|
|
||||||
build_context: str,
|
|
||||||
build_context_ref: str,
|
|
||||||
force_latest: bool = False,
|
|
||||||
) -> set[str]:
|
|
||||||
"""
|
|
||||||
Return a set of tags given a given build context
|
|
||||||
"""
|
|
||||||
tags: set[str] = set()
|
|
||||||
tag_chunks: list[str] = []
|
|
||||||
|
|
||||||
is_latest = is_latest_release(build_context_ref)
|
|
||||||
|
|
||||||
if build_preset != "lean":
|
|
||||||
# Always add the preset_build name if different from default (lean)
|
|
||||||
tag_chunks += [build_preset]
|
|
||||||
|
|
||||||
if len(build_platforms) == 1:
|
|
||||||
build_platform = build_platforms[0]
|
|
||||||
short_build_platform = build_platform.replace("linux/", "").replace("64", "")
|
|
||||||
if short_build_platform != "amd":
|
|
||||||
# Always a platform indicator if different from default (amd)
|
|
||||||
tag_chunks += [short_build_platform]
|
|
||||||
|
|
||||||
# Always craft a tag for the SHA
|
|
||||||
tags.add(make_docker_tag([sha] + tag_chunks))
|
|
||||||
# also a short SHA, cause it's nice
|
|
||||||
tags.add(make_docker_tag([sha[:7]] + tag_chunks))
|
|
||||||
|
|
||||||
if build_context == "release":
|
|
||||||
# add a release tag
|
|
||||||
tags.add(make_docker_tag([build_context_ref] + tag_chunks))
|
|
||||||
if is_latest or force_latest:
|
|
||||||
# add a latest tag
|
|
||||||
tags.add(make_docker_tag(["latest"] + tag_chunks))
|
|
||||||
elif build_context == "push" and build_context_ref == "master":
|
|
||||||
tags.add(make_docker_tag(["master"] + tag_chunks))
|
|
||||||
elif build_context == "pull_request":
|
|
||||||
tags.add(make_docker_tag([f"pr-{build_context_ref}"] + tag_chunks))
|
|
||||||
return tags
|
|
||||||
|
|
||||||
|
|
||||||
def get_docker_command(
|
|
||||||
build_preset: str,
|
|
||||||
build_platforms: list[str],
|
|
||||||
is_authenticated: bool,
|
|
||||||
sha: str,
|
|
||||||
build_context: str,
|
|
||||||
build_context_ref: str,
|
|
||||||
force_latest: bool = False,
|
|
||||||
) -> str:
|
|
||||||
tag = "" # noqa: F841
|
|
||||||
build_target = ""
|
|
||||||
py_ver = BASE_PY_IMAGE
|
|
||||||
docker_context = "."
|
|
||||||
|
|
||||||
if build_preset == "dev":
|
|
||||||
build_target = "dev"
|
|
||||||
elif build_preset == "lean":
|
|
||||||
build_target = "lean"
|
|
||||||
elif build_preset == "py311":
|
|
||||||
build_target = "lean"
|
|
||||||
py_ver = "3.11-slim-bookworm"
|
|
||||||
elif build_preset == "websocket":
|
|
||||||
build_target = ""
|
|
||||||
docker_context = "superset-websocket"
|
|
||||||
elif build_preset == "ci":
|
|
||||||
build_target = "ci"
|
|
||||||
elif build_preset == "dockerize":
|
|
||||||
build_target = ""
|
|
||||||
docker_context = "-f dockerize.Dockerfile ."
|
|
||||||
else:
|
|
||||||
print(f"Invalid build preset: {build_preset}")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# Try to get context reference if missing
|
|
||||||
if not build_context_ref:
|
|
||||||
build_context_ref = get_build_context_ref(build_context)
|
|
||||||
|
|
||||||
tags = get_docker_tags(
|
|
||||||
build_preset,
|
|
||||||
build_platforms,
|
|
||||||
sha,
|
|
||||||
build_context,
|
|
||||||
build_context_ref,
|
|
||||||
force_latest,
|
|
||||||
)
|
|
||||||
docker_tags = ("\\\n" + 8 * " ").join([f"-t {s} " for s in tags])
|
|
||||||
|
|
||||||
docker_args = "--load" if not is_authenticated else "--push"
|
|
||||||
target_argument = f"--target {build_target}" if build_target else ""
|
|
||||||
|
|
||||||
cache_ref = f"{CACHE_REPO}:{py_ver}"
|
|
||||||
if len(build_platforms) == 1:
|
|
||||||
build_platform = build_platforms[0]
|
|
||||||
short_build_platform = build_platform.replace("linux/", "").replace("64", "")
|
|
||||||
cache_ref = f"{CACHE_REPO}:{py_ver}-{short_build_platform}"
|
|
||||||
platform_arg = "--platform " + ",".join(build_platforms)
|
|
||||||
|
|
||||||
cache_from_arg = f"--cache-from=type=registry,ref={cache_ref}"
|
|
||||||
cache_to_arg = (
|
|
||||||
f"--cache-to=type=registry,mode=max,ref={cache_ref}" if is_authenticated else ""
|
|
||||||
)
|
|
||||||
build_arg = f"--build-arg PY_VER={py_ver}" if py_ver else ""
|
|
||||||
actor = os.getenv("GITHUB_ACTOR")
|
|
||||||
|
|
||||||
return dedent(
|
|
||||||
f"""\
|
|
||||||
docker buildx build \\
|
|
||||||
{docker_args} \\
|
|
||||||
{docker_tags} \\
|
|
||||||
{cache_from_arg} \\
|
|
||||||
{cache_to_arg} \\
|
|
||||||
{build_arg} \\
|
|
||||||
{platform_arg} \\
|
|
||||||
{target_argument} \\
|
|
||||||
--label sha={sha} \\
|
|
||||||
--label target={build_target} \\
|
|
||||||
--label build_trigger={build_context} \\
|
|
||||||
--label base={py_ver} \\
|
|
||||||
--label build_actor={actor} \\
|
|
||||||
{docker_context}"""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.argument(
|
|
||||||
"build_preset",
|
|
||||||
type=click.Choice(["lean", "dev", "dockerize", "websocket", "py311", "ci"]),
|
|
||||||
)
|
|
||||||
@click.argument("build_context", type=click.Choice(["push", "pull_request", "release"]))
|
|
||||||
@click.option(
|
|
||||||
"--platform",
|
|
||||||
type=click.Choice(["linux/arm64", "linux/amd64"]),
|
|
||||||
default=["linux/amd64"],
|
|
||||||
multiple=True,
|
|
||||||
)
|
|
||||||
@click.option("--build_context_ref", help="a reference to the pr, release or branch")
|
|
||||||
@click.option("--dry-run", is_flag=True, help="Run the command in dry-run mode.")
|
|
||||||
@click.option("--verbose", is_flag=True, help="Print more info")
|
|
||||||
@click.option(
|
|
||||||
"--force-latest", is_flag=True, help="Force the 'latest' tag on the release"
|
|
||||||
)
|
|
||||||
def main(
|
|
||||||
build_preset: str,
|
|
||||||
build_context: str,
|
|
||||||
build_context_ref: str,
|
|
||||||
platform: list[str],
|
|
||||||
dry_run: bool,
|
|
||||||
force_latest: bool,
|
|
||||||
verbose: bool,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
This script executes docker build and push commands based on given arguments.
|
|
||||||
"""
|
|
||||||
|
|
||||||
is_authenticated = (
|
|
||||||
True if os.getenv("DOCKERHUB_TOKEN") and os.getenv("DOCKERHUB_USER") else False
|
|
||||||
)
|
|
||||||
|
|
||||||
if force_latest and build_context != "release":
|
|
||||||
print(
|
|
||||||
"--force-latest can only be applied if the build context is set to 'release'"
|
|
||||||
)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if build_context == "release" and not build_context_ref.strip():
|
|
||||||
print("Release number has to be provided")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
docker_build_command = get_docker_command(
|
|
||||||
build_preset,
|
|
||||||
platform,
|
|
||||||
is_authenticated,
|
|
||||||
get_git_sha(),
|
|
||||||
build_context,
|
|
||||||
build_context_ref,
|
|
||||||
force_latest,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not dry_run:
|
|
||||||
print("Executing Docker Build Command:")
|
|
||||||
print(docker_build_command)
|
|
||||||
script = ""
|
|
||||||
if os.getenv("DOCKERHUB_USER"):
|
|
||||||
script = dedent(
|
|
||||||
f"""\
|
|
||||||
docker logout
|
|
||||||
docker login --username "{os.getenv("DOCKERHUB_USER")}" --password "{os.getenv("DOCKERHUB_TOKEN")}"
|
|
||||||
DOCKER_ARGS="--push"
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
script = script + docker_build_command
|
|
||||||
if verbose:
|
|
||||||
run_cmd("cat Dockerfile")
|
|
||||||
stdout = run_cmd(script) # noqa: F841
|
|
||||||
else:
|
|
||||||
print("Dry Run - Docker Build Command:")
|
|
||||||
print(docker_build_command)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@superset-ui/switchboard",
|
|
||||||
"version": "0.18.26-0",
|
|
||||||
"lockfileVersion": 2,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "@superset-ui/switchboard",
|
|
||||||
"version": "0.18.26-0",
|
|
||||||
"license": "Apache-2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -51,7 +51,7 @@ export type LastModified = {
|
|||||||
export type Owner = {
|
export type Owner = {
|
||||||
type: MetadataType.Owner;
|
type: MetadataType.Owner;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
owners?: string[];
|
owners?: string[] | string;
|
||||||
createdOn: string;
|
createdOn: string;
|
||||||
onClick?: (type: string) => void;
|
onClick?: (type: string) => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,27 +23,18 @@ import { styled } from '@superset-ui/core';
|
|||||||
import { Tooltip, TooltipPlacement } from 'src/components/Tooltip';
|
import { Tooltip, TooltipPlacement } from 'src/components/Tooltip';
|
||||||
import { ContentType } from './ContentType';
|
import { ContentType } from './ContentType';
|
||||||
import { config } from './ContentConfig';
|
import { config } from './ContentConfig';
|
||||||
|
import {
|
||||||
export const MIN_NUMBER_ITEMS = 2;
|
HORIZONTAL_PADDING,
|
||||||
export const MAX_NUMBER_ITEMS = 6;
|
ICON_PADDING,
|
||||||
|
ICON_WIDTH,
|
||||||
const HORIZONTAL_PADDING = 12;
|
VERTICAL_PADDING,
|
||||||
const VERTICAL_PADDING = 8;
|
TEXT_MIN_WIDTH,
|
||||||
const ICON_PADDING = 8;
|
TEXT_MAX_WIDTH,
|
||||||
const SPACE_BETWEEN_ITEMS = 16;
|
SPACE_BETWEEN_ITEMS,
|
||||||
const ICON_WIDTH = 16;
|
ORDER,
|
||||||
const TEXT_MIN_WIDTH = 70;
|
MIN_NUMBER_ITEMS,
|
||||||
const TEXT_MAX_WIDTH = 150;
|
MAX_NUMBER_ITEMS,
|
||||||
const ORDER = {
|
} from './constants';
|
||||||
dashboards: 0,
|
|
||||||
table: 1,
|
|
||||||
sql: 2,
|
|
||||||
rows: 3,
|
|
||||||
tags: 4,
|
|
||||||
description: 5,
|
|
||||||
owner: 6,
|
|
||||||
lastModified: 7,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Bar = styled.div<{ count: number }>`
|
const Bar = styled.div<{ count: number }>`
|
||||||
${({ theme, count }) => `
|
${({ theme, count }) => `
|
||||||
|
|||||||
39
superset-frontend/src/components/MetadataBar/constants.ts
Normal file
39
superset-frontend/src/components/MetadataBar/constants.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const MIN_NUMBER_ITEMS = 2;
|
||||||
|
export const MAX_NUMBER_ITEMS = 6;
|
||||||
|
|
||||||
|
export const HORIZONTAL_PADDING = 12;
|
||||||
|
export const VERTICAL_PADDING = 8;
|
||||||
|
export const ICON_PADDING = 8;
|
||||||
|
export const SPACE_BETWEEN_ITEMS = 16;
|
||||||
|
export const ICON_WIDTH = 16;
|
||||||
|
export const TEXT_MIN_WIDTH = 70;
|
||||||
|
export const TEXT_MAX_WIDTH = 150;
|
||||||
|
export const ORDER = {
|
||||||
|
dashboards: 0,
|
||||||
|
table: 1,
|
||||||
|
sql: 2,
|
||||||
|
rows: 3,
|
||||||
|
tags: 4,
|
||||||
|
description: 5,
|
||||||
|
owner: 6,
|
||||||
|
lastModified: 7,
|
||||||
|
};
|
||||||
@@ -16,7 +16,8 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import MetadataBar, { MIN_NUMBER_ITEMS, MAX_NUMBER_ITEMS } from './MetadataBar';
|
import MetadataBar from './MetadataBar';
|
||||||
|
import { MIN_NUMBER_ITEMS, MAX_NUMBER_ITEMS } from './constants';
|
||||||
|
|
||||||
export type { MetadataBarProps } from './MetadataBar';
|
export type { MetadataBarProps } from './MetadataBar';
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
t,
|
t,
|
||||||
getClientErrorObject,
|
getClientErrorObject,
|
||||||
getCategoricalSchemeRegistry,
|
getCategoricalSchemeRegistry,
|
||||||
|
promiseTimeout,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
addChart,
|
addChart,
|
||||||
@@ -737,9 +738,9 @@ export const persistDashboardLabelsColor = () => async (dispatch, getState) => {
|
|||||||
} = getState();
|
} = getState();
|
||||||
|
|
||||||
if (labelsColorMapMustSync || sharedLabelsColorsMustSync) {
|
if (labelsColorMapMustSync || sharedLabelsColorsMustSync) {
|
||||||
storeDashboardMetadata(id, metadata);
|
|
||||||
dispatch(setDashboardLabelsColorMapSynced());
|
dispatch(setDashboardLabelsColorMapSynced());
|
||||||
dispatch(setDashboardSharedLabelsColorsSynced());
|
dispatch(setDashboardSharedLabelsColorsSynced());
|
||||||
|
storeDashboardMetadata(id, metadata);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -755,16 +756,13 @@ export const applyDashboardLabelsColorOnLoad = metadata => async dispatch => {
|
|||||||
try {
|
try {
|
||||||
const updatedMetadata = { ...metadata };
|
const updatedMetadata = { ...metadata };
|
||||||
const customLabelsColor = metadata.label_colors || {};
|
const customLabelsColor = metadata.label_colors || {};
|
||||||
const sharedLabelsColor = metadata.shared_label_colors || [];
|
|
||||||
let hasChanged = false;
|
let hasChanged = false;
|
||||||
|
|
||||||
// backward compatibility of shared_label_colors
|
// backward compatibility of shared_label_colors
|
||||||
const sharedLabels = metadata.shared_label_colors || [];
|
const sharedLabels = metadata.shared_label_colors || [];
|
||||||
if (!Array.isArray(sharedLabels) && Object.keys(sharedLabels).length > 0) {
|
if (!Array.isArray(sharedLabels) && Object.keys(sharedLabels).length > 0) {
|
||||||
hasChanged = true;
|
hasChanged = true;
|
||||||
updatedMetadata.shared_label_colors = getFreshSharedLabels(
|
updatedMetadata.shared_label_colors = [];
|
||||||
Object.keys(sharedLabelsColor),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// backward compatibility of map_label_colors
|
// backward compatibility of map_label_colors
|
||||||
const hasMapLabelColors =
|
const hasMapLabelColors =
|
||||||
@@ -828,27 +826,28 @@ export const applyDashboardLabelsColorOnLoad = metadata => async dispatch => {
|
|||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
export const ensureSyncedLabelsColorMap = metadata => (dispatch, getState) => {
|
export const ensureSyncedLabelsColorMap = metadata => (dispatch, getState) => {
|
||||||
const {
|
const syncLabelsColorMap = () => {
|
||||||
dashboardState: { labelsColorMapMustSync },
|
const {
|
||||||
} = getState();
|
dashboardState: { labelsColorMapMustSync },
|
||||||
const updatedMetadata = { ...metadata };
|
} = getState();
|
||||||
const customLabelsColor = metadata.label_colors || {};
|
const updatedMetadata = { ...metadata };
|
||||||
const isMapSynced = isLabelsColorMapSynced(metadata);
|
const customLabelsColor = metadata.label_colors || {};
|
||||||
const mustSync = !isMapSynced;
|
const isMapSynced = isLabelsColorMapSynced(metadata);
|
||||||
|
const mustSync = !isMapSynced;
|
||||||
|
|
||||||
if (mustSync) {
|
if (mustSync) {
|
||||||
const freshestColorMapEntries = getLabelsColorMapEntries(customLabelsColor);
|
const freshestColorMapEntries =
|
||||||
updatedMetadata.map_label_colors = freshestColorMapEntries;
|
getLabelsColorMapEntries(customLabelsColor);
|
||||||
dispatch(setDashboardMetadata(updatedMetadata));
|
updatedMetadata.map_label_colors = freshestColorMapEntries;
|
||||||
}
|
dispatch(setDashboardMetadata(updatedMetadata));
|
||||||
|
}
|
||||||
|
|
||||||
if (mustSync && !labelsColorMapMustSync) {
|
if (mustSync && !labelsColorMapMustSync) {
|
||||||
// prepare to persist the just applied labels color map
|
// prepare to persist the just applied labels color map
|
||||||
dispatch(setDashboardLabelsColorMapSync());
|
dispatch(setDashboardLabelsColorMapSync());
|
||||||
}
|
}
|
||||||
if (!mustSync && labelsColorMapMustSync) {
|
};
|
||||||
dispatch(setDashboardLabelsColorMapSynced());
|
promiseTimeout(syncLabelsColorMap, 500);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -856,18 +855,21 @@ export const ensureSyncedLabelsColorMap = metadata => (dispatch, getState) => {
|
|||||||
* Ensure that the stored shared labels colors match current.
|
* Ensure that the stored shared labels colors match current.
|
||||||
*
|
*
|
||||||
* @param {*} metadata - the dashboard metadata
|
* @param {*} metadata - the dashboard metadata
|
||||||
|
* @param {*} forceFresh - when true it will use the fresh shared labels ignoring stored ones
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
export const ensureSyncedSharedLabelsColors =
|
export const ensureSyncedSharedLabelsColors =
|
||||||
metadata => (dispatch, getState) => {
|
(metadata, forceFresh = false) =>
|
||||||
// using a timeout to let the rendered charts finish processing labels
|
(dispatch, getState) => {
|
||||||
setTimeout(() => {
|
const syncSharedLabelsColors = () => {
|
||||||
const {
|
const {
|
||||||
dashboardState: { sharedLabelsColorsMustSync },
|
dashboardState: { sharedLabelsColorsMustSync },
|
||||||
} = getState();
|
} = getState();
|
||||||
const updatedMetadata = { ...metadata };
|
const updatedMetadata = { ...metadata };
|
||||||
const sharedLabelsColors = metadata.shared_label_colors || [];
|
const sharedLabelsColors = metadata.shared_label_colors || [];
|
||||||
const freshLabelsColors = getFreshSharedLabels(sharedLabelsColors);
|
const freshLabelsColors = getFreshSharedLabels(
|
||||||
|
forceFresh ? [] : sharedLabelsColors,
|
||||||
|
);
|
||||||
const isSharedLabelsColorsSynced = isEqual(
|
const isSharedLabelsColorsSynced = isEqual(
|
||||||
sharedLabelsColors,
|
sharedLabelsColors,
|
||||||
freshLabelsColors,
|
freshLabelsColors,
|
||||||
@@ -884,10 +886,8 @@ export const ensureSyncedSharedLabelsColors =
|
|||||||
// prepare to persist the shared labels colors
|
// prepare to persist the shared labels colors
|
||||||
dispatch(setDashboardSharedLabelsColorsSync());
|
dispatch(setDashboardSharedLabelsColorsSync());
|
||||||
}
|
}
|
||||||
if (!mustSync && sharedLabelsColorsMustSync) {
|
};
|
||||||
dispatch(setDashboardSharedLabelsColorsSynced());
|
promiseTimeout(syncSharedLabelsColors, 500);
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -909,7 +909,6 @@ export const updateDashboardLabelsColor =
|
|||||||
const fullLabelsColors = metadata.map_label_colors || {};
|
const fullLabelsColors = metadata.map_label_colors || {};
|
||||||
const sharedLabelsColors = metadata.shared_label_colors || [];
|
const sharedLabelsColors = metadata.shared_label_colors || [];
|
||||||
const customLabelsColors = metadata.label_colors || {};
|
const customLabelsColors = metadata.label_colors || {};
|
||||||
const updatedMetadata = { ...metadata };
|
|
||||||
|
|
||||||
// for dashboards with no color scheme, the charts should always use their individual schemes
|
// for dashboards with no color scheme, the charts should always use their individual schemes
|
||||||
// this logic looks for unique labels (not shared across multiple charts) of each rendered chart
|
// this logic looks for unique labels (not shared across multiple charts) of each rendered chart
|
||||||
@@ -965,11 +964,7 @@ export const updateDashboardLabelsColor =
|
|||||||
const shouldGoFresh = shouldReset.length > 0 ? shouldReset : false;
|
const shouldGoFresh = shouldReset.length > 0 ? shouldReset : false;
|
||||||
const shouldMerge = !shouldGoFresh;
|
const shouldMerge = !shouldGoFresh;
|
||||||
// re-apply the color map first to get fresh maps accordingly
|
// re-apply the color map first to get fresh maps accordingly
|
||||||
applyColors(updatedMetadata, shouldGoFresh, shouldMerge);
|
applyColors(metadata, shouldGoFresh, shouldMerge);
|
||||||
// new data may have appeared in the map (data changes)
|
|
||||||
// or new slices may have appeared while changing tabs
|
|
||||||
dispatch(ensureSyncedLabelsColorMap(updatedMetadata));
|
|
||||||
dispatch(ensureSyncedSharedLabelsColors(updatedMetadata));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to update colors for new charts and labels:', e);
|
console.error('Failed to update colors for new charts and labels:', e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import {
|
|||||||
applyDashboardLabelsColorOnLoad,
|
applyDashboardLabelsColorOnLoad,
|
||||||
updateDashboardLabelsColor,
|
updateDashboardLabelsColor,
|
||||||
persistDashboardLabelsColor,
|
persistDashboardLabelsColor,
|
||||||
|
ensureSyncedSharedLabelsColors,
|
||||||
|
ensureSyncedLabelsColorMap,
|
||||||
} from 'src/dashboard/actions/dashboardState';
|
} from 'src/dashboard/actions/dashboardState';
|
||||||
import { getColorNamespace, resetColors } from 'src/utils/colorScheme';
|
import { getColorNamespace, resetColors } from 'src/utils/colorScheme';
|
||||||
import { NATIVE_FILTER_DIVIDER_PREFIX } from '../nativeFilters/FiltersConfigModal/utils';
|
import { NATIVE_FILTER_DIVIDER_PREFIX } from '../nativeFilters/FiltersConfigModal/utils';
|
||||||
@@ -96,7 +98,6 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
|||||||
const [dashboardLabelsColorInitiated, setDashboardLabelsColorInitiated] =
|
const [dashboardLabelsColorInitiated, setDashboardLabelsColorInitiated] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const prevRenderedChartIds = useRef<number[]>([]);
|
const prevRenderedChartIds = useRef<number[]>([]);
|
||||||
|
|
||||||
const prevTabIndexRef = useRef();
|
const prevTabIndexRef = useRef();
|
||||||
const tabIndex = useMemo(() => {
|
const tabIndex = useMemo(() => {
|
||||||
const nextTabIndex = findTabIndexByComponentId({
|
const nextTabIndex = findTabIndexByComponentId({
|
||||||
@@ -110,6 +111,18 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
|||||||
prevTabIndexRef.current = nextTabIndex;
|
prevTabIndexRef.current = nextTabIndex;
|
||||||
return nextTabIndex;
|
return nextTabIndex;
|
||||||
}, [dashboardLayout, directPathToChild]);
|
}, [dashboardLayout, directPathToChild]);
|
||||||
|
// when all charts have rendered, enforce fresh shared labels
|
||||||
|
const shouldForceFreshSharedLabelsColors =
|
||||||
|
dashboardLabelsColorInitiated &&
|
||||||
|
renderedChartIds.length > 0 &&
|
||||||
|
chartIds.length === renderedChartIds.length &&
|
||||||
|
prevRenderedChartIds.current.length < renderedChartIds.length;
|
||||||
|
|
||||||
|
const onBeforeUnload = useCallback(() => {
|
||||||
|
dispatch(persistDashboardLabelsColor());
|
||||||
|
resetColors(getColorNamespace(dashboardInfo?.metadata?.color_namespace));
|
||||||
|
prevRenderedChartIds.current = [];
|
||||||
|
}, [dashboardInfo?.metadata?.color_namespace, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nativeFilterScopes.length === 0) {
|
if (nativeFilterScopes.length === 0) {
|
||||||
@@ -148,11 +161,12 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
|||||||
const activeKey = min === 0 ? DASHBOARD_GRID_ID : min.toString();
|
const activeKey = min === 0 ? DASHBOARD_GRID_ID : min.toString();
|
||||||
const TOP_OF_PAGE_RANGE = 220;
|
const TOP_OF_PAGE_RANGE = 220;
|
||||||
|
|
||||||
const onBeforeUnload = useCallback(() => {
|
useEffect(() => {
|
||||||
dispatch(persistDashboardLabelsColor());
|
if (shouldForceFreshSharedLabelsColors) {
|
||||||
resetColors(getColorNamespace(dashboardInfo?.metadata?.color_namespace));
|
// all available charts have rendered, enforce freshest shared label colors
|
||||||
prevRenderedChartIds.current = [];
|
dispatch(ensureSyncedSharedLabelsColors(dashboardInfo.metadata, true));
|
||||||
}, [dashboardInfo?.metadata?.color_namespace, dispatch]);
|
}
|
||||||
|
}, [dashboardInfo.metadata, dispatch, shouldForceFreshSharedLabelsColors]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// verify freshness of color map
|
// verify freshness of color map
|
||||||
@@ -161,7 +175,6 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
dashboardLabelsColorInitiated &&
|
dashboardLabelsColorInitiated &&
|
||||||
dashboardInfo?.id &&
|
|
||||||
numRenderedCharts > 0 &&
|
numRenderedCharts > 0 &&
|
||||||
prevRenderedChartIds.current.length < numRenderedCharts
|
prevRenderedChartIds.current.length < numRenderedCharts
|
||||||
) {
|
) {
|
||||||
@@ -170,12 +183,20 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
|||||||
);
|
);
|
||||||
prevRenderedChartIds.current = renderedChartIds;
|
prevRenderedChartIds.current = renderedChartIds;
|
||||||
dispatch(updateDashboardLabelsColor(newRenderedChartIds));
|
dispatch(updateDashboardLabelsColor(newRenderedChartIds));
|
||||||
|
// new data may have appeared in the map (data changes)
|
||||||
|
// or new slices may have appeared while changing tabs
|
||||||
|
dispatch(ensureSyncedLabelsColorMap(dashboardInfo.metadata));
|
||||||
|
|
||||||
|
if (!shouldForceFreshSharedLabelsColors) {
|
||||||
|
dispatch(ensureSyncedSharedLabelsColors(dashboardInfo.metadata));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
dashboardInfo?.id,
|
|
||||||
renderedChartIds,
|
renderedChartIds,
|
||||||
dispatch,
|
dispatch,
|
||||||
dashboardLabelsColorInitiated,
|
dashboardLabelsColorInitiated,
|
||||||
|
dashboardInfo.metadata,
|
||||||
|
shouldForceFreshSharedLabelsColors,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -183,9 +204,9 @@ const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
|
|||||||
labelsColorMap.source = LabelsColorMapSource.Dashboard;
|
labelsColorMap.source = LabelsColorMapSource.Dashboard;
|
||||||
|
|
||||||
if (dashboardInfo?.id && !dashboardLabelsColorInitiated) {
|
if (dashboardInfo?.id && !dashboardLabelsColorInitiated) {
|
||||||
|
dispatch(applyDashboardLabelsColorOnLoad(dashboardInfo.metadata));
|
||||||
// apply labels color as dictated by stored metadata (if any)
|
// apply labels color as dictated by stored metadata (if any)
|
||||||
setDashboardLabelsColorInitiated(true);
|
setDashboardLabelsColorInitiated(true);
|
||||||
dispatch(applyDashboardLabelsColorOnLoad(dashboardInfo.metadata));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import ConnectedHeaderActionsDropdown from 'src/dashboard/components/Header/Head
|
|||||||
import PublishedStatus from 'src/dashboard/components/PublishedStatus';
|
import PublishedStatus from 'src/dashboard/components/PublishedStatus';
|
||||||
import UndoRedoKeyListeners from 'src/dashboard/components/UndoRedoKeyListeners';
|
import UndoRedoKeyListeners from 'src/dashboard/components/UndoRedoKeyListeners';
|
||||||
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
|
import PropertiesModal from 'src/dashboard/components/PropertiesModal';
|
||||||
import getOwnerName from 'src/utils/getOwnerName';
|
|
||||||
import {
|
import {
|
||||||
UNDO_LIMIT,
|
UNDO_LIMIT,
|
||||||
SAVE_TYPE_OVERWRITE,
|
SAVE_TYPE_OVERWRITE,
|
||||||
@@ -55,7 +54,6 @@ import setPeriodicRunner, {
|
|||||||
stopPeriodicRender,
|
stopPeriodicRender,
|
||||||
} from 'src/dashboard/util/setPeriodicRunner';
|
} from 'src/dashboard/util/setPeriodicRunner';
|
||||||
import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
|
import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
|
||||||
import MetadataBar, { MetadataType } from 'src/components/MetadataBar';
|
|
||||||
import DashboardEmbedModal from '../EmbeddedModal';
|
import DashboardEmbedModal from '../EmbeddedModal';
|
||||||
import OverwriteConfirm from '../OverwriteConfirm';
|
import OverwriteConfirm from '../OverwriteConfirm';
|
||||||
import {
|
import {
|
||||||
@@ -88,6 +86,7 @@ import { logEvent } from '../../../logger/actions';
|
|||||||
import { dashboardInfoChanged } from '../../actions/dashboardInfo';
|
import { dashboardInfoChanged } from '../../actions/dashboardInfo';
|
||||||
import isDashboardLoading from '../../util/isDashboardLoading';
|
import isDashboardLoading from '../../util/isDashboardLoading';
|
||||||
import { useChartIds } from '../../util/charts/useChartIds';
|
import { useChartIds } from '../../util/charts/useChartIds';
|
||||||
|
import { useDashboardMetadataBar } from './useDashboardMetadataBar';
|
||||||
|
|
||||||
const extensionsRegistry = getExtensionsRegistry();
|
const extensionsRegistry = getExtensionsRegistry();
|
||||||
|
|
||||||
@@ -472,32 +471,7 @@ const Header = () => {
|
|||||||
setShowingEmbedModal(false);
|
setShowingEmbedModal(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getMetadataItems = useCallback(
|
const metadataBar = useDashboardMetadataBar(dashboardInfo);
|
||||||
() => [
|
|
||||||
{
|
|
||||||
type: MetadataType.LastModified,
|
|
||||||
value: dashboardInfo.changed_on_delta_humanized,
|
|
||||||
modifiedBy:
|
|
||||||
getOwnerName(dashboardInfo.changed_by) || t('Not available'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: MetadataType.Owner,
|
|
||||||
createdBy: getOwnerName(dashboardInfo.created_by) || t('Not available'),
|
|
||||||
owners:
|
|
||||||
dashboardInfo.owners.length > 0
|
|
||||||
? dashboardInfo.owners.map(getOwnerName)
|
|
||||||
: t('None'),
|
|
||||||
createdOn: dashboardInfo.created_on_delta_humanized,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
dashboardInfo.changed_by,
|
|
||||||
dashboardInfo.changed_on_delta_humanized,
|
|
||||||
dashboardInfo.created_by,
|
|
||||||
dashboardInfo.created_on_delta_humanized,
|
|
||||||
dashboardInfo.owners,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const userCanEdit =
|
const userCanEdit =
|
||||||
dashboardInfo.dash_edit_perm && !dashboardInfo.is_managed_externally;
|
dashboardInfo.dash_edit_perm && !dashboardInfo.is_managed_externally;
|
||||||
@@ -579,15 +553,13 @@ const Header = () => {
|
|||||||
visible={!editMode}
|
visible={!editMode}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
!editMode && !isEmbedded && (
|
!editMode && !isEmbedded && metadataBar,
|
||||||
<MetadataBar items={getMetadataItems()} tooltipPlacement="bottom" />
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
boundActionCreators.savePublished,
|
boundActionCreators.savePublished,
|
||||||
dashboardInfo.id,
|
dashboardInfo.id,
|
||||||
editMode,
|
editMode,
|
||||||
getMetadataItems,
|
metadataBar,
|
||||||
isEmbedded,
|
isEmbedded,
|
||||||
isPublished,
|
isPublished,
|
||||||
userCanEdit,
|
userCanEdit,
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { t } from '@superset-ui/core';
|
||||||
|
import { DashboardInfo } from 'src/dashboard/types';
|
||||||
|
import MetadataBar, { MetadataType } from 'src/components/MetadataBar';
|
||||||
|
import getOwnerName from 'src/utils/getOwnerName';
|
||||||
|
|
||||||
|
export const useDashboardMetadataBar = (dashboardInfo: DashboardInfo) => {
|
||||||
|
const items = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
type: MetadataType.LastModified as const,
|
||||||
|
value: dashboardInfo.changed_on_delta_humanized,
|
||||||
|
modifiedBy:
|
||||||
|
getOwnerName(dashboardInfo.changed_by) || t('Not available'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: MetadataType.Owner as const,
|
||||||
|
createdBy: getOwnerName(dashboardInfo.created_by) || t('Not available'),
|
||||||
|
owners:
|
||||||
|
dashboardInfo.owners.length > 0
|
||||||
|
? dashboardInfo.owners.map(getOwnerName)
|
||||||
|
: t('None'),
|
||||||
|
createdOn: dashboardInfo.created_on_delta_humanized,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
dashboardInfo.changed_by,
|
||||||
|
dashboardInfo.changed_on_delta_humanized,
|
||||||
|
dashboardInfo.created_by,
|
||||||
|
dashboardInfo.created_on_delta_humanized,
|
||||||
|
dashboardInfo.owners,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return <MetadataBar items={items} tooltipPlacement="bottom" />;
|
||||||
|
};
|
||||||
@@ -52,6 +52,9 @@ const initialState: { dashboardInfo: DashboardInfo } = {
|
|||||||
conf: {},
|
conf: {},
|
||||||
},
|
},
|
||||||
crossFiltersEnabled: true,
|
crossFiltersEnabled: true,
|
||||||
|
created_on_delta_humanized: '',
|
||||||
|
changed_on_delta_humanized: '',
|
||||||
|
owners: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import Database from 'src/types/Database';
|
|||||||
import { UrlParamEntries } from 'src/utils/urlUtils';
|
import { UrlParamEntries } from 'src/utils/urlUtils';
|
||||||
|
|
||||||
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
||||||
|
import Owner from 'src/types/Owner';
|
||||||
import { ChartState } from '../explore/types';
|
import { ChartState } from '../explore/types';
|
||||||
|
|
||||||
export type { Dashboard } from 'src/types/Dashboard';
|
export type { Dashboard } from 'src/types/Dashboard';
|
||||||
@@ -139,6 +140,11 @@ export type DashboardInfo = {
|
|||||||
};
|
};
|
||||||
crossFiltersEnabled: boolean;
|
crossFiltersEnabled: boolean;
|
||||||
filterBarOrientation: FilterBarOrientation;
|
filterBarOrientation: FilterBarOrientation;
|
||||||
|
created_on_delta_humanized: string;
|
||||||
|
changed_on_delta_humanized: string;
|
||||||
|
changed_by?: Owner;
|
||||||
|
created_by?: Owner;
|
||||||
|
owners: Owner[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChartsState = { [key: string]: Chart };
|
export type ChartsState = { [key: string]: Chart };
|
||||||
|
|||||||
@@ -16,12 +16,12 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Tooltip } from 'src/components/Tooltip';
|
import { Tooltip } from 'src/components/Tooltip';
|
||||||
import { css, logging, SupersetClient, t, tn } from '@superset-ui/core';
|
import { css, logging, SupersetClient, t } from '@superset-ui/core';
|
||||||
import { chartPropShape } from 'src/dashboard/util/propShapes';
|
import { chartPropShape } from 'src/dashboard/util/propShapes';
|
||||||
import AlteredSliceTag from 'src/components/AlteredSliceTag';
|
import AlteredSliceTag from 'src/components/AlteredSliceTag';
|
||||||
import Button from 'src/components/Button';
|
import Button from 'src/components/Button';
|
||||||
@@ -29,10 +29,10 @@ import Icons from 'src/components/Icons';
|
|||||||
import PropertiesModal from 'src/explore/components/PropertiesModal';
|
import PropertiesModal from 'src/explore/components/PropertiesModal';
|
||||||
import { sliceUpdated } from 'src/explore/actions/exploreActions';
|
import { sliceUpdated } from 'src/explore/actions/exploreActions';
|
||||||
import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
|
import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
|
||||||
import MetadataBar, { MetadataType } from 'src/components/MetadataBar';
|
|
||||||
import { setSaveChartModalVisibility } from 'src/explore/actions/saveModalActions';
|
import { setSaveChartModalVisibility } from 'src/explore/actions/saveModalActions';
|
||||||
import { applyColors, resetColors } from 'src/utils/colorScheme';
|
import { applyColors, resetColors } from 'src/utils/colorScheme';
|
||||||
import { useExploreAdditionalActionsMenu } from '../useExploreAdditionalActionsMenu';
|
import { useExploreAdditionalActionsMenu } from '../useExploreAdditionalActionsMenu';
|
||||||
|
import { useExploreMetadataBar } from './useExploreMetadataBar';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
actions: PropTypes.object.isRequired,
|
actions: PropTypes.object.isRequired,
|
||||||
@@ -160,48 +160,7 @@ export const ExploreChartHeader = ({
|
|||||||
metadata?.dashboards,
|
metadata?.dashboards,
|
||||||
);
|
);
|
||||||
|
|
||||||
const metadataBar = useMemo(() => {
|
const metadataBar = useExploreMetadataBar(metadata, slice);
|
||||||
if (!metadata) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const items = [];
|
|
||||||
items.push({
|
|
||||||
type: MetadataType.Dashboards,
|
|
||||||
title:
|
|
||||||
metadata.dashboards.length > 0
|
|
||||||
? tn(
|
|
||||||
'Added to 1 dashboard',
|
|
||||||
'Added to %s dashboards',
|
|
||||||
metadata.dashboards.length,
|
|
||||||
metadata.dashboards.length,
|
|
||||||
)
|
|
||||||
: t('Not added to any dashboard'),
|
|
||||||
description:
|
|
||||||
metadata.dashboards.length > 0
|
|
||||||
? t(
|
|
||||||
'You can preview the list of dashboards in the chart settings dropdown.',
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
});
|
|
||||||
items.push({
|
|
||||||
type: MetadataType.LastModified,
|
|
||||||
value: metadata.changed_on_humanized,
|
|
||||||
modifiedBy: metadata.changed_by || t('Not available'),
|
|
||||||
});
|
|
||||||
items.push({
|
|
||||||
type: MetadataType.Owner,
|
|
||||||
createdBy: metadata.created_by || t('Not available'),
|
|
||||||
owners: metadata.owners.length > 0 ? metadata.owners : t('None'),
|
|
||||||
createdOn: metadata.created_on_humanized,
|
|
||||||
});
|
|
||||||
if (slice?.description) {
|
|
||||||
items.push({
|
|
||||||
type: MetadataType.Description,
|
|
||||||
value: slice?.description,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return <MetadataBar items={items} tooltipPlacement="bottom" />;
|
|
||||||
}, [metadata, slice?.description]);
|
|
||||||
|
|
||||||
const oldSliceName = slice?.slice_name;
|
const oldSliceName = slice?.slice_name;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { t, tn } from '@superset-ui/core';
|
||||||
|
import MetadataBar, { MetadataType } from 'src/components/MetadataBar';
|
||||||
|
import { ExplorePageInitialData } from 'src/explore/types';
|
||||||
|
|
||||||
|
export const useExploreMetadataBar = (
|
||||||
|
metadata: ExplorePageInitialData['metadata'],
|
||||||
|
slice: ExplorePageInitialData['slice'],
|
||||||
|
) =>
|
||||||
|
useMemo(() => {
|
||||||
|
if (!metadata) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const items = [];
|
||||||
|
if (metadata.dashboards) {
|
||||||
|
items.push({
|
||||||
|
type: MetadataType.Dashboards as const,
|
||||||
|
title:
|
||||||
|
metadata.dashboards.length > 0
|
||||||
|
? tn(
|
||||||
|
'Added to 1 dashboard',
|
||||||
|
'Added to %s dashboards',
|
||||||
|
metadata.dashboards.length,
|
||||||
|
metadata.dashboards.length,
|
||||||
|
)
|
||||||
|
: t('Not added to any dashboard'),
|
||||||
|
description:
|
||||||
|
metadata.dashboards.length > 0
|
||||||
|
? t(
|
||||||
|
'You can preview the list of dashboards in the chart settings dropdown.',
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
items.push({
|
||||||
|
type: MetadataType.LastModified as const,
|
||||||
|
value: metadata.changed_on_humanized,
|
||||||
|
modifiedBy: metadata.changed_by || t('Not available'),
|
||||||
|
});
|
||||||
|
items.push({
|
||||||
|
type: MetadataType.Owner as const,
|
||||||
|
createdBy: metadata.created_by || t('Not available'),
|
||||||
|
owners: metadata.owners.length > 0 ? metadata.owners : t('None'),
|
||||||
|
createdOn: metadata.created_on_humanized,
|
||||||
|
});
|
||||||
|
if (slice?.description) {
|
||||||
|
items.push({
|
||||||
|
type: MetadataType.Description as const,
|
||||||
|
value: slice?.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return <MetadataBar items={items} tooltipPlacement="bottom" />;
|
||||||
|
}, [metadata, slice?.description]);
|
||||||
@@ -82,6 +82,10 @@ export interface ExplorePageInitialData {
|
|||||||
owners: string[];
|
owners: string[];
|
||||||
created_by?: string;
|
created_by?: string;
|
||||||
changed_by?: string;
|
changed_by?: string;
|
||||||
|
dashboards?: {
|
||||||
|
id: number;
|
||||||
|
dashboard_title: string;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
saveAction?: SaveActionType | null;
|
saveAction?: SaveActionType | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const getColorNamespace = (namespace?: string) => namespace || undefined;
|
|||||||
* Get labels shared across all charts in a dashboard.
|
* Get labels shared across all charts in a dashboard.
|
||||||
* Merges a fresh instance of shared label colors with a stored one.
|
* Merges a fresh instance of shared label colors with a stored one.
|
||||||
*
|
*
|
||||||
|
* @param currentSharedLabels - existing shared labels to merge with fresh
|
||||||
* @returns Record<string, string>
|
* @returns Record<string, string>
|
||||||
*/
|
*/
|
||||||
export const getFreshSharedLabels = (
|
export const getFreshSharedLabels = (
|
||||||
@@ -74,7 +75,7 @@ export const getSharedLabelsColorMapEntries = (
|
|||||||
* @returns all color entries except custom label colors
|
* @returns all color entries except custom label colors
|
||||||
*/
|
*/
|
||||||
export const getLabelsColorMapEntries = (
|
export const getLabelsColorMapEntries = (
|
||||||
customLabelsColor: Record<string, string>,
|
customLabelsColor: Record<string, string> = {},
|
||||||
): Record<string, string> => {
|
): Record<string, string> => {
|
||||||
const labelsColorMapInstance = getLabelsColorMap();
|
const labelsColorMapInstance = getLabelsColorMap();
|
||||||
const allEntries = Object.fromEntries(labelsColorMapInstance.getColorMap());
|
const allEntries = Object.fromEntries(labelsColorMapInstance.getColorMap());
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ from sqlalchemy import text
|
|||||||
from sqlalchemy.engine.reflection import Inspector
|
from sqlalchemy.engine.reflection import Inspector
|
||||||
from sqlalchemy.engine.url import URL
|
from sqlalchemy.engine.url import URL
|
||||||
from sqlalchemy.exc import NoSuchTableError
|
from sqlalchemy.exc import NoSuchTableError
|
||||||
from trino.exceptions import HttpError
|
|
||||||
|
|
||||||
from superset import db
|
from superset import db
|
||||||
from superset.constants import QUERY_CANCEL_KEY, QUERY_EARLY_CANCEL_KEY, USER_AGENT
|
from superset.constants import QUERY_CANCEL_KEY, QUERY_EARLY_CANCEL_KEY, USER_AGENT
|
||||||
@@ -61,6 +60,12 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# since trino is an optional dependency, we need to handle the ImportError
|
||||||
|
from trino.exceptions import HttpError
|
||||||
|
except ImportError:
|
||||||
|
HttpError = Exception
|
||||||
|
|
||||||
|
|
||||||
class CustomTrinoAuthErrorMeta(type):
|
class CustomTrinoAuthErrorMeta(type):
|
||||||
def __instancecheck__(cls, instance: object) -> bool:
|
def __instancecheck__(cls, instance: object) -> bool:
|
||||||
|
|||||||
@@ -1,290 +0,0 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
SHA = "22e7c602b9aa321ec7e0df4bb0033048664dcdf0"
|
|
||||||
PR_ID = "666"
|
|
||||||
OLD_REL = "2.1.0"
|
|
||||||
NEW_REL = "2.1.1"
|
|
||||||
REPO = "apache/superset"
|
|
||||||
|
|
||||||
# Add the 'scripts' directory to sys.path
|
|
||||||
scripts_dir = os.path.abspath(
|
|
||||||
os.path.join(os.path.dirname(__file__), "../../../scripts")
|
|
||||||
)
|
|
||||||
sys.path.append(scripts_dir)
|
|
||||||
|
|
||||||
import build_docker as docker_utils # Replace with the actual function name # noqa: E402
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def set_env_var():
|
|
||||||
os.environ["TEST_ENV"] = "true"
|
|
||||||
yield
|
|
||||||
del os.environ["TEST_ENV"]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"release, expected_bool",
|
|
||||||
[
|
|
||||||
("2.1.0", False),
|
|
||||||
("2.1.1", True),
|
|
||||||
("1.0.0", False),
|
|
||||||
("3.0.0", True),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_is_latest_release(release, expected_bool):
|
|
||||||
assert docker_utils.is_latest_release(release) == expected_bool
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"build_preset, build_platforms, sha, build_context, build_context_ref, expected_tags",
|
|
||||||
[
|
|
||||||
# PRs
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
["linux/arm64"],
|
|
||||||
SHA,
|
|
||||||
"pull_request",
|
|
||||||
PR_ID,
|
|
||||||
[f"{REPO}:22e7c60-arm", f"{REPO}:{SHA}-arm", f"{REPO}:pr-{PR_ID}-arm"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"ci",
|
|
||||||
["linux/amd64"],
|
|
||||||
SHA,
|
|
||||||
"pull_request",
|
|
||||||
PR_ID,
|
|
||||||
[f"{REPO}:22e7c60-ci", f"{REPO}:{SHA}-ci", f"{REPO}:pr-{PR_ID}-ci"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
["linux/amd64"],
|
|
||||||
SHA,
|
|
||||||
"pull_request",
|
|
||||||
PR_ID,
|
|
||||||
[f"{REPO}:22e7c60", f"{REPO}:{SHA}", f"{REPO}:pr-{PR_ID}"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
["linux/arm64"],
|
|
||||||
SHA,
|
|
||||||
"pull_request",
|
|
||||||
PR_ID,
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-dev-arm",
|
|
||||||
f"{REPO}:{SHA}-dev-arm",
|
|
||||||
f"{REPO}:pr-{PR_ID}-dev-arm",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
["linux/amd64"],
|
|
||||||
SHA,
|
|
||||||
"pull_request",
|
|
||||||
PR_ID,
|
|
||||||
[f"{REPO}:22e7c60-dev", f"{REPO}:{SHA}-dev", f"{REPO}:pr-{PR_ID}-dev"],
|
|
||||||
),
|
|
||||||
# old releases
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
["linux/arm64"],
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
OLD_REL,
|
|
||||||
[f"{REPO}:22e7c60-arm", f"{REPO}:{SHA}-arm", f"{REPO}:{OLD_REL}-arm"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
["linux/amd64"],
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
OLD_REL,
|
|
||||||
[f"{REPO}:22e7c60", f"{REPO}:{SHA}", f"{REPO}:{OLD_REL}"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
["linux/arm64"],
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
OLD_REL,
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-dev-arm",
|
|
||||||
f"{REPO}:{SHA}-dev-arm",
|
|
||||||
f"{REPO}:{OLD_REL}-dev-arm",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
["linux/amd64"],
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
OLD_REL,
|
|
||||||
[f"{REPO}:22e7c60-dev", f"{REPO}:{SHA}-dev", f"{REPO}:{OLD_REL}-dev"],
|
|
||||||
),
|
|
||||||
# new releases
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
["linux/arm64"],
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
NEW_REL,
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-arm",
|
|
||||||
f"{REPO}:{SHA}-arm",
|
|
||||||
f"{REPO}:{NEW_REL}-arm",
|
|
||||||
f"{REPO}:latest-arm",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
["linux/amd64"],
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
NEW_REL,
|
|
||||||
[f"{REPO}:22e7c60", f"{REPO}:{SHA}", f"{REPO}:{NEW_REL}", f"{REPO}:latest"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
["linux/arm64"],
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
NEW_REL,
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-dev-arm",
|
|
||||||
f"{REPO}:{SHA}-dev-arm",
|
|
||||||
f"{REPO}:{NEW_REL}-dev-arm",
|
|
||||||
f"{REPO}:latest-dev-arm",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
["linux/amd64"],
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
NEW_REL,
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-dev",
|
|
||||||
f"{REPO}:{SHA}-dev",
|
|
||||||
f"{REPO}:{NEW_REL}-dev",
|
|
||||||
f"{REPO}:latest-dev",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
# merge on master
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
["linux/arm64"],
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
[f"{REPO}:22e7c60-arm", f"{REPO}:{SHA}-arm", f"{REPO}:master-arm"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
["linux/amd64"],
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
[f"{REPO}:22e7c60", f"{REPO}:{SHA}", f"{REPO}:master"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
["linux/arm64"],
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-dev-arm",
|
|
||||||
f"{REPO}:{SHA}-dev-arm",
|
|
||||||
f"{REPO}:master-dev-arm",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
["linux/amd64"],
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
[f"{REPO}:22e7c60-dev", f"{REPO}:{SHA}-dev", f"{REPO}:master-dev"],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_get_docker_tags(
|
|
||||||
build_preset, build_platforms, sha, build_context, build_context_ref, expected_tags
|
|
||||||
):
|
|
||||||
tags = docker_utils.get_docker_tags(
|
|
||||||
build_preset, build_platforms, sha, build_context, build_context_ref
|
|
||||||
)
|
|
||||||
for tag in expected_tags:
|
|
||||||
assert tag in tags
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"build_preset, build_platforms, is_authenticated, sha, build_context, build_context_ref, contains",
|
|
||||||
[
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
["linux/amd64"],
|
|
||||||
True,
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
["--push", f"-t {REPO}:master "],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
["linux/amd64"],
|
|
||||||
False,
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
["--load", f"-t {REPO}:master-dev ", "--target dev"],
|
|
||||||
),
|
|
||||||
# multi-platform
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
["linux/arm64", "linux/amd64"],
|
|
||||||
True,
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
["--platform linux/arm64,linux/amd64"],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_get_docker_command(
|
|
||||||
build_preset,
|
|
||||||
build_platforms,
|
|
||||||
is_authenticated,
|
|
||||||
sha,
|
|
||||||
build_context,
|
|
||||||
build_context_ref,
|
|
||||||
contains,
|
|
||||||
):
|
|
||||||
cmd = docker_utils.get_docker_command(
|
|
||||||
build_preset,
|
|
||||||
build_platforms,
|
|
||||||
is_authenticated,
|
|
||||||
sha,
|
|
||||||
build_context,
|
|
||||||
build_context_ref,
|
|
||||||
)
|
|
||||||
for s in contains:
|
|
||||||
assert s in cmd
|
|
||||||
@@ -1,268 +0,0 @@
|
|||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
SHA = "22e7c602b9aa321ec7e0df4bb0033048664dcdf0"
|
|
||||||
PR_ID = "666"
|
|
||||||
OLD_REL = "2.1.0"
|
|
||||||
NEW_REL = "2.1.1"
|
|
||||||
REPO = "apache/superset"
|
|
||||||
|
|
||||||
# Add the 'scripts' directory to sys.path
|
|
||||||
scripts_dir = os.path.abspath(
|
|
||||||
os.path.join(os.path.dirname(__file__), "../../../scripts")
|
|
||||||
)
|
|
||||||
sys.path.append(scripts_dir)
|
|
||||||
|
|
||||||
import build_docker as docker_utils # Replace with the actual function name # noqa: E402
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def set_env_var():
|
|
||||||
os.environ["TEST_ENV"] = "true"
|
|
||||||
yield
|
|
||||||
del os.environ["TEST_ENV"]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"release, expected_bool",
|
|
||||||
[
|
|
||||||
("2.1.0", False),
|
|
||||||
("2.1.1", True),
|
|
||||||
("1.0.0", False),
|
|
||||||
("3.0.0", True),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_is_latest_release(release, expected_bool):
|
|
||||||
assert docker_utils.is_latest_release(release) == expected_bool
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"build_preset, build_platform, sha, build_context, build_context_ref, expected_tags",
|
|
||||||
[
|
|
||||||
# PRs
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
"linux/arm64",
|
|
||||||
SHA,
|
|
||||||
"pull_request",
|
|
||||||
PR_ID,
|
|
||||||
[f"{REPO}:22e7c60-arm", f"{REPO}:{SHA}-arm"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
"linux/amd64",
|
|
||||||
SHA,
|
|
||||||
"pull_request",
|
|
||||||
PR_ID,
|
|
||||||
[f"{REPO}:22e7c60", f"{REPO}:{SHA}"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
"linux/arm64",
|
|
||||||
SHA,
|
|
||||||
"pull_request",
|
|
||||||
PR_ID,
|
|
||||||
[f"{REPO}:22e7c60-dev-arm", f"{REPO}:{SHA}-dev-arm"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
"linux/amd64",
|
|
||||||
SHA,
|
|
||||||
"pull_request",
|
|
||||||
PR_ID,
|
|
||||||
[f"{REPO}:22e7c60-dev", f"{REPO}:{SHA}-dev"],
|
|
||||||
),
|
|
||||||
# old releases
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
"linux/arm64",
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
OLD_REL,
|
|
||||||
[f"{REPO}:22e7c60-arm", f"{REPO}:{SHA}-arm", f"{REPO}:{OLD_REL}-arm"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
"linux/amd64",
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
OLD_REL,
|
|
||||||
[f"{REPO}:22e7c60", f"{REPO}:{SHA}", f"{REPO}:{OLD_REL}"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
"linux/arm64",
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
OLD_REL,
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-dev-arm",
|
|
||||||
f"{REPO}:{SHA}-dev-arm",
|
|
||||||
f"{REPO}:{OLD_REL}-dev-arm",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
"linux/amd64",
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
OLD_REL,
|
|
||||||
[f"{REPO}:22e7c60-dev", f"{REPO}:{SHA}-dev", f"{REPO}:{OLD_REL}-dev"],
|
|
||||||
),
|
|
||||||
# new releases
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
"linux/arm64",
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
NEW_REL,
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-arm",
|
|
||||||
f"{REPO}:{SHA}-arm",
|
|
||||||
f"{REPO}:{NEW_REL}-arm",
|
|
||||||
f"{REPO}:latest-arm",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
"linux/amd64",
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
NEW_REL,
|
|
||||||
[f"{REPO}:22e7c60", f"{REPO}:{SHA}", f"{REPO}:{NEW_REL}", f"{REPO}:latest"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
"linux/arm64",
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
NEW_REL,
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-dev-arm",
|
|
||||||
f"{REPO}:{SHA}-dev-arm",
|
|
||||||
f"{REPO}:{NEW_REL}-dev-arm",
|
|
||||||
f"{REPO}:latest-dev-arm",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
"linux/amd64",
|
|
||||||
SHA,
|
|
||||||
"release",
|
|
||||||
NEW_REL,
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-dev",
|
|
||||||
f"{REPO}:{SHA}-dev",
|
|
||||||
f"{REPO}:{NEW_REL}-dev",
|
|
||||||
f"{REPO}:latest-dev",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
# merge on master
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
"linux/arm64",
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
[f"{REPO}:22e7c60-arm", f"{REPO}:{SHA}-arm", f"{REPO}:master-arm"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
"linux/amd64",
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
[f"{REPO}:22e7c60", f"{REPO}:{SHA}", f"{REPO}:master"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
"linux/arm64",
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
[
|
|
||||||
f"{REPO}:22e7c60-dev-arm",
|
|
||||||
f"{REPO}:{SHA}-dev-arm",
|
|
||||||
f"{REPO}:master-dev-arm",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
"linux/amd64",
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
[f"{REPO}:22e7c60-dev", f"{REPO}:{SHA}-dev", f"{REPO}:master-dev"],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_get_docker_tags(
|
|
||||||
build_preset, build_platform, sha, build_context, build_context_ref, expected_tags
|
|
||||||
):
|
|
||||||
tags = docker_utils.get_docker_tags(
|
|
||||||
build_preset, build_platform, sha, build_context, build_context_ref
|
|
||||||
)
|
|
||||||
for tag in expected_tags:
|
|
||||||
assert tag in tags
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"build_preset, build_platform, is_authenticated, sha, build_context, build_context_ref, contains",
|
|
||||||
[
|
|
||||||
(
|
|
||||||
"lean",
|
|
||||||
"linux/amd64",
|
|
||||||
True,
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
["--push", f"-t {REPO}:master "],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"dev",
|
|
||||||
"linux/amd64",
|
|
||||||
False,
|
|
||||||
SHA,
|
|
||||||
"push",
|
|
||||||
"master",
|
|
||||||
["--load", f"-t {REPO}:master-dev "],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_get_docker_command(
|
|
||||||
build_preset,
|
|
||||||
build_platform,
|
|
||||||
is_authenticated,
|
|
||||||
sha,
|
|
||||||
build_context,
|
|
||||||
build_context_ref,
|
|
||||||
contains,
|
|
||||||
):
|
|
||||||
cmd = docker_utils.get_docker_command(
|
|
||||||
build_preset,
|
|
||||||
build_platform,
|
|
||||||
is_authenticated,
|
|
||||||
sha,
|
|
||||||
build_context,
|
|
||||||
build_context_ref,
|
|
||||||
)
|
|
||||||
for s in contains:
|
|
||||||
assert s in cmd
|
|
||||||
Reference in New Issue
Block a user