Merge branch 'feature/34548-multichannel-marketing-backend' into feature/34903-multichannel-marketing-frontend/main
This commit is contained in:
commit
4ae08a43b4
|
@ -19,6 +19,7 @@ jobs:
|
|||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
name: Run tests against PR in an environment with COT enabled
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
@ -13,7 +12,6 @@ permissions: {}
|
|||
jobs:
|
||||
cot-e2e-tests-run:
|
||||
name: Runs E2E tests with COT enabled.
|
||||
if: "${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'focus: custom order tables' }}"
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
|
@ -70,7 +68,6 @@ jobs:
|
|||
|
||||
cot-api-tests-run:
|
||||
name: Runs API tests with COT enabled.
|
||||
if: "${{ github.event_name == 'workflow_dispatch' || github.event.label.name == 'focus: custom order tables' }}"
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
|
@ -122,101 +119,101 @@ jobs:
|
|||
if-no-files-found: ignore
|
||||
retention-days: 5
|
||||
|
||||
test-summary:
|
||||
name: Post test results
|
||||
if: |
|
||||
always() &&
|
||||
! github.event.pull_request.head.repo.fork &&
|
||||
(
|
||||
contains( needs.*.result, 'success' ) ||
|
||||
contains( needs.*.result, 'failure' )
|
||||
)
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
needs: [cot-api-tests-run, cot-e2e-tests-run]
|
||||
steps:
|
||||
- name: Create dirs
|
||||
run: |
|
||||
mkdir -p repo
|
||||
mkdir -p artifacts/api
|
||||
mkdir -p artifacts/e2e
|
||||
mkdir -p output
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: repo
|
||||
|
||||
- name: Download API test report artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: api-test-report---pr-${{ github.event.number }}
|
||||
path: artifacts/api
|
||||
|
||||
- name: Download Playwright E2E test report artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: e2e-test-report---pr-${{ github.event.number }}
|
||||
path: artifacts/e2e
|
||||
|
||||
- name: Prepare test summary
|
||||
id: prepare-test-summary
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
API_SUMMARY_PATH: ${{ github.workspace }}/artifacts/api/allure-report/widgets/summary.json
|
||||
E2E_PW_SUMMARY_PATH: ${{ github.workspace }}/artifacts/e2e/allure-report/widgets/summary.json
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
SHA: ${{ github.event.pull_request.head.sha }}
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
const script = require( './repo/.github/workflows/scripts/prepare-test-summary.js' )
|
||||
return await script( { core } )
|
||||
|
||||
- name: Find PR comment by github-actions[bot]
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: Test Results Summary
|
||||
|
||||
- name: Create or update PR comment
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: ${{ steps.prepare-test-summary.outputs.result }}
|
||||
edit-mode: replace
|
||||
|
||||
publish-test-reports:
|
||||
name: Publish test reports
|
||||
if: |
|
||||
always() &&
|
||||
! github.event.pull_request.head.repo.fork &&
|
||||
(
|
||||
contains( needs.*.result, 'success' ) ||
|
||||
contains( needs.*.result, 'failure' )
|
||||
)
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [cot-api-tests-run, cot-e2e-tests-run]
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
steps:
|
||||
- name: Publish test reports
|
||||
env:
|
||||
API_ARTIFACT: api-test-report---pr-${{ github.event.number }}
|
||||
E2E_ARTIFACT: e2e-test-report---pr-${{ github.event.number }}
|
||||
run: |
|
||||
gh workflow run publish-test-reports-pr.yml \
|
||||
-f run_id=$RUN_ID \
|
||||
-f api_artifact=$API_ARTIFACT \
|
||||
-f e2e_artifact=$E2E_ARTIFACT \
|
||||
-f pr_number=$PR_NUMBER \
|
||||
-f commit_sha=$COMMIT_SHA \
|
||||
-f s3_root=public \
|
||||
--repo woocommerce/woocommerce-test-reports
|
||||
# test-summary:
|
||||
# name: Post test results
|
||||
# if: |
|
||||
# always() &&
|
||||
# ! github.event.pull_request.head.repo.fork &&
|
||||
# (
|
||||
# contains( needs.*.result, 'success' ) ||
|
||||
# contains( needs.*.result, 'failure' )
|
||||
# )
|
||||
# runs-on: ubuntu-20.04
|
||||
# permissions:
|
||||
# contents: read
|
||||
# needs: [cot-api-tests-run, cot-e2e-tests-run]
|
||||
# steps:
|
||||
# - name: Create dirs
|
||||
# run: |
|
||||
# mkdir -p repo
|
||||
# mkdir -p artifacts/api
|
||||
# mkdir -p artifacts/e2e
|
||||
# mkdir -p output
|
||||
#
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v3
|
||||
# with:
|
||||
# path: repo
|
||||
#
|
||||
# - name: Download API test report artifact
|
||||
# uses: actions/download-artifact@v3
|
||||
# with:
|
||||
# name: api-test-report---pr-${{ github.event.number }}
|
||||
# path: artifacts/api
|
||||
#
|
||||
# - name: Download Playwright E2E test report artifact
|
||||
# uses: actions/download-artifact@v3
|
||||
# with:
|
||||
# name: e2e-test-report---pr-${{ github.event.number }}
|
||||
# path: artifacts/e2e
|
||||
#
|
||||
# - name: Prepare test summary
|
||||
# id: prepare-test-summary
|
||||
# uses: actions/github-script@v6
|
||||
# env:
|
||||
# API_SUMMARY_PATH: ${{ github.workspace }}/artifacts/api/allure-report/widgets/summary.json
|
||||
# E2E_PW_SUMMARY_PATH: ${{ github.workspace }}/artifacts/e2e/allure-report/widgets/summary.json
|
||||
# PR_NUMBER: ${{ github.event.number }}
|
||||
# SHA: ${{ github.event.pull_request.head.sha }}
|
||||
# with:
|
||||
# result-encoding: string
|
||||
# script: |
|
||||
# const script = require( './repo/.github/workflows/scripts/prepare-test-summary.js' )
|
||||
# return await script( { core } )
|
||||
#
|
||||
# - name: Find PR comment by github-actions[bot]
|
||||
# uses: peter-evans/find-comment@v2
|
||||
# id: find-comment
|
||||
# with:
|
||||
# issue-number: ${{ github.event.pull_request.number }}
|
||||
# comment-author: 'github-actions[bot]'
|
||||
# body-includes: Test Results Summary
|
||||
#
|
||||
# - name: Create or update PR comment
|
||||
# uses: peter-evans/create-or-update-comment@v2
|
||||
# with:
|
||||
# comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
# issue-number: ${{ github.event.pull_request.number }}
|
||||
# body: ${{ steps.prepare-test-summary.outputs.result }}
|
||||
# edit-mode: replace
|
||||
#
|
||||
# publish-test-reports:
|
||||
# name: Publish test reports
|
||||
# if: |
|
||||
# always() &&
|
||||
# ! github.event.pull_request.head.repo.fork &&
|
||||
# (
|
||||
# contains( needs.*.result, 'success' ) ||
|
||||
# contains( needs.*.result, 'failure' )
|
||||
# )
|
||||
# runs-on: ubuntu-20.04
|
||||
# needs: [cot-api-tests-run, cot-e2e-tests-run]
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
|
||||
# PR_NUMBER: ${{ github.event.number }}
|
||||
# RUN_ID: ${{ github.run_id }}
|
||||
# COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
# steps:
|
||||
# - name: Publish test reports
|
||||
# env:
|
||||
# API_ARTIFACT: api-test-report---pr-${{ github.event.number }}
|
||||
# E2E_ARTIFACT: e2e-test-report---pr-${{ github.event.number }}
|
||||
# run: |
|
||||
# gh workflow run publish-test-reports-pr.yml \
|
||||
# -f run_id=$RUN_ID \
|
||||
# -f api_artifact=$API_ARTIFACT \
|
||||
# -f e2e_artifact=$E2E_ARTIFACT \
|
||||
# -f pr_number=$PR_NUMBER \
|
||||
# -f commit_sha=$COMMIT_SHA \
|
||||
# -f s3_root=public \
|
||||
# --repo woocommerce/woocommerce-test-reports
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Duplicate workflow that returns success for this check when there is no relevant file change. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
name: Run tests against PR
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '!**'
|
||||
- '**/changelog/**'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
|
@ -1,7 +1,9 @@
|
|||
name: Run tests against PR
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**/changelog/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
@ -14,7 +16,7 @@ jobs:
|
|||
name: Runs E2E tests.
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read
|
||||
env:
|
||||
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
|
||||
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
|
||||
|
@ -84,7 +86,7 @@ jobs:
|
|||
name: Runs API tests.
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read
|
||||
env:
|
||||
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results
|
||||
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report
|
||||
|
@ -136,7 +138,7 @@ jobs:
|
|||
name: Runs k6 Performance tests
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
@ -171,9 +173,9 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
needs: [api-tests-run, e2e-tests-run]
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
env:
|
||||
E2E_GRAND_TOTAL: ${{needs.e2e-tests-run.outputs.E2E_GRAND_TOTAL}}
|
||||
steps:
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Duplicate workflow that returns success for this check when there is no relevant file change. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
name: Build Live Branch
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '!**'
|
||||
- '**/changelog/**'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
|
@ -1,6 +1,8 @@
|
|||
name: Build Live Branch
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**/changelog/**'
|
||||
|
||||
concurrency:
|
||||
# Cancel concurrent jobs on pull_request but not push, by including the run_id in the concurrency group for the latter.
|
||||
|
@ -14,7 +16,7 @@ jobs:
|
|||
if: github.repository_owner == 'woocommerce'
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Duplicate workflow that returns success for this check when there is no relevant file change. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
name: Run code coverage on PR
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '!**'
|
||||
- '**/changelog/**'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
|
@ -1,59 +1,60 @@
|
|||
name: Run code coverage on PR
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**/changelog/**'
|
||||
workflow_dispatch:
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
run:
|
||||
shell: bash
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Code coverage (PHP 7.4, WP Latest)
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
services:
|
||||
database:
|
||||
image: mysql:5.6
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 100
|
||||
test:
|
||||
name: Code coverage (PHP 7.4, WP Latest)
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
services:
|
||||
database:
|
||||
image: mysql:5.6
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 100
|
||||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
|
||||
- name: Tool versions
|
||||
run: |
|
||||
php --version
|
||||
composer --version
|
||||
- name: Tool versions
|
||||
run: |
|
||||
php --version
|
||||
composer --version
|
||||
|
||||
- name: Build Admin feature config
|
||||
working-directory: plugins/woocommerce
|
||||
run:
|
||||
pnpm run build:feature-config
|
||||
- name: Build Admin feature config
|
||||
working-directory: plugins/woocommerce
|
||||
run: pnpm run build:feature-config
|
||||
|
||||
- name: Init DB and WP
|
||||
working-directory: plugins/woocommerce
|
||||
run: bash tests/bin/install.sh woo_test root root 127.0.0.1 latest
|
||||
- name: Init DB and WP
|
||||
working-directory: plugins/woocommerce
|
||||
run: bash tests/bin/install.sh woo_test root root 127.0.0.1 latest
|
||||
|
||||
- name: Run unit tests with code coverage. Allow to fail.
|
||||
working-directory: plugins/woocommerce
|
||||
run: |
|
||||
RUN_CODE_COVERAGE=1 bash tests/bin/phpunit.sh
|
||||
exit 0
|
||||
|
||||
- name: Send code coverage to Codecov.
|
||||
run: |
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- name: Run unit tests with code coverage. Allow to fail.
|
||||
working-directory: plugins/woocommerce
|
||||
run: |
|
||||
RUN_CODE_COVERAGE=1 bash tests/bin/phpunit.sh
|
||||
exit 0
|
||||
|
||||
- name: Send code coverage to Codecov.
|
||||
run: |
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Duplicate workflow that returns success for this check when there is no relevant file change. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
name: Run code coverage on PR
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '!**'
|
||||
- '**/changelog/**'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
|
@ -1,5 +1,8 @@
|
|||
name: Run code sniff on PR
|
||||
on: pull_request
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**/changelog/**'
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
@ -17,7 +20,7 @@ jobs:
|
|||
timeout-minutes: 15
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Duplicate workflow that returns success for this check when there is no relevant file change. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
name: Highlight templates changes
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '!**'
|
||||
- '**/changelog/**'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
|
@ -1,5 +1,8 @@
|
|||
name: Highlight templates changes
|
||||
on: pull_request
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**/changelog/**'
|
||||
|
||||
permissions: {}
|
||||
|
||||
|
@ -8,7 +11,7 @@ jobs:
|
|||
name: Check pull request changes to highlight
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read
|
||||
outputs:
|
||||
results: ${{ steps.results.outputs.results }}
|
||||
steps:
|
||||
|
@ -29,32 +32,32 @@ jobs:
|
|||
- name: Check results
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const template = '${{ steps.run.outputs.templates }}';
|
||||
script: |
|
||||
const template = '${{ steps.run.outputs.templates }}';
|
||||
|
||||
if ( template === '' ) {
|
||||
return;
|
||||
}
|
||||
if ( template === '' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const templateArr = template.split( '\n' );
|
||||
const modTemplateArr = [];
|
||||
let needsVersionBump = false;
|
||||
const templateArr = template.split( '\n' );
|
||||
const modTemplateArr = [];
|
||||
let needsVersionBump = false;
|
||||
|
||||
templateArr.forEach( ( el ) => {
|
||||
if ( el.match( /NOTICE/ ) ) {
|
||||
modTemplateArr.pop();
|
||||
return;
|
||||
}
|
||||
templateArr.forEach( ( el ) => {
|
||||
if ( el.match( /NOTICE/ ) ) {
|
||||
modTemplateArr.pop();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( el.match( /WARNING/ ) ) {
|
||||
needsVersionBump = true;
|
||||
}
|
||||
if ( el.match( /WARNING/ ) ) {
|
||||
needsVersionBump = true;
|
||||
}
|
||||
|
||||
modTemplateArr.push( el );
|
||||
} );
|
||||
modTemplateArr.push( el );
|
||||
} );
|
||||
|
||||
const templateResult = modTemplateArr.join( '\n' );
|
||||
const templateResult = modTemplateArr.join( '\n' );
|
||||
|
||||
if ( needsVersionBump ) {
|
||||
core.setFailed( `Templates have changed but template versions were not bumped:\n${ templateResult }` );
|
||||
}
|
||||
if ( needsVersionBump ) {
|
||||
core.setFailed( `Templates have changed but template versions were not bumped:\n${ templateResult }` );
|
||||
}
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
name: Run lint checks potentially affecting projects across the monorepo
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'trunk'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'trunk'
|
||||
concurrency:
|
||||
group: changelogger-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
group: changelogger-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
changelogger_used:
|
||||
name: Changelogger use
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
changelogger_used:
|
||||
name: Changelogger use
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
build: false
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
build: false
|
||||
|
||||
- name: Check change files are touched for touched projects
|
||||
env:
|
||||
BASE: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD: ${{ github.event.pull_request.head.sha }}
|
||||
run: php tools/monorepo/check-changelogger-use.php --debug "$BASE" "$HEAD"
|
||||
- name: Check change files are touched for touched projects
|
||||
env:
|
||||
BASE: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD: ${{ github.event.pull_request.head.sha }}
|
||||
run: php tools/monorepo/check-changelogger-use.php --debug "$BASE" "$HEAD"
|
||||
|
||||
- name: Run changelog validation
|
||||
run: pnpm run -r changelog validate
|
||||
- name: Run changelog validation
|
||||
run: pnpm run -r changelog validate
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
name: Lint and tests for JS packages and woocommerce-admin/client
|
||||
|
||||
on: pull_request
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**/changelog/**'
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
@ -12,15 +15,15 @@ jobs:
|
|||
name: Lint and Test JS
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run -r --filter='woocommerce/client/admin...' --filter='!@woocommerce/e2e*' --filter='!@woocommerce/api' --color lint
|
||||
|
||||
- name: Test
|
||||
run: pnpm run test --filter='woocommerce/client/admin...' --filter='!@woocommerce/e2e*' --filter='!@woocommerce/api' --color
|
||||
run: pnpm run test --filter='woocommerce/client/admin...' --filter='!@woocommerce/e2e*' --filter='!@woocommerce/api' --color
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Duplicate workflow that returns success for this check when there is no relevant file change. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
name: Run smoke tests against pull request.
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '!**'
|
||||
- '**/changelog/**'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
|
@ -1,23 +1,23 @@
|
|||
name: 'Label Pull Request Project'
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
label_project:
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
configuration-path: .github/project-pr-labeler.yml
|
||||
label_project:
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
configuration-path: .github/project-pr-labeler.yml
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Duplicate workflow that returns success for this check when there is no relevant file change. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
name: Lint and tests for JS packages and woocommerce-admin/client
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '!**'
|
||||
- '**/changelog/**'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
|
@ -1,13 +1,15 @@
|
|||
name: Run smoke tests against pull request.
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**/changelog/**'
|
||||
branches:
|
||||
- trunk
|
||||
types:
|
||||
- labeled
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
|
@ -17,8 +19,8 @@ jobs:
|
|||
if: "${{ contains(github.event.label.name, 'run: smoke tests') }}"
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Duplicate workflow that returns success for this check when there is no relevant file change. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
name: Run unit tests on PR
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '!**'
|
||||
- '**/changelog/**'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
|
@ -1,12 +1,14 @@
|
|||
name: Run unit tests on PR
|
||||
on:
|
||||
pull_request
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**/changelog/**'
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
run:
|
||||
shell: bash
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
|
@ -26,9 +28,9 @@ jobs:
|
|||
include:
|
||||
- wp: nightly
|
||||
php: '7.4'
|
||||
- wp: '5.9'
|
||||
- wp: '6.0'
|
||||
php: 7.4
|
||||
- wp: '5.8'
|
||||
- wp: '5.9'
|
||||
php: 7.4
|
||||
services:
|
||||
database:
|
||||
|
@ -44,25 +46,12 @@ jobs:
|
|||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
php-version: ${{ matrix.php }}
|
||||
|
||||
- name: Tool versions
|
||||
run: |
|
||||
php --version
|
||||
composer --version
|
||||
|
||||
- name: Add PHP8 Compatibility.
|
||||
run: |
|
||||
if [ "$(php -r "echo version_compare(PHP_VERSION,'8.0','>=');")" ]; then
|
||||
cd plugins/woocommerce
|
||||
curl -L https://github.com/woocommerce/phpunit/archive/add-compatibility-with-php8-to-phpunit-7.zip -o /tmp/phpunit-7.5-fork.zip
|
||||
unzip -d /tmp/phpunit-7.5-fork /tmp/phpunit-7.5-fork.zip
|
||||
composer bin phpunit config --unset platform
|
||||
composer bin phpunit config repositories.0 '{"type": "path", "url": "/tmp/phpunit-7.5-fork/phpunit-add-compatibility-with-php8-to-phpunit-7", "options": {"symlink": false}}'
|
||||
composer bin phpunit require --dev -W phpunit/phpunit:@dev --ignore-platform-reqs
|
||||
rm -rf ./vendor/phpunit/
|
||||
composer dump-autoload
|
||||
fi
|
||||
php --version
|
||||
composer --version
|
||||
|
||||
- name: Init DB and WP
|
||||
working-directory: plugins/woocommerce
|
||||
|
|
|
@ -49,7 +49,10 @@ This repository is not suitable for support. Please don't use our issue tracker
|
|||
* [The Official WooCommerce Facebook Group](https://www.facebook.com/groups/advanced.woocommerce).
|
||||
* For customizations, you may want to check our list of [WooExperts](https://woocommerce.com/experts/) or [Codeable](https://codeable.io/).
|
||||
|
||||
Support requests in issues on this repository will be closed on sight.
|
||||
NOTE: Unfortunately, we are unable to honor support requests in issues on this repository; as a result, any requests submitted in this manner will be closed.
|
||||
|
||||
## Community
|
||||
For peer to peer support, real-time announcements, and office hours, please [join our slack community](https://woocommerce.com/community-slack/)!
|
||||
|
||||
## Contributing to WooCommerce
|
||||
If you have a patch or have stumbled upon an issue with WooCommerce core, you can contribute this back to the code. Please read our [contributor guidelines](https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md) for more information on how you can do this.
|
||||
|
|
105
changelog.txt
105
changelog.txt
|
@ -1,5 +1,110 @@
|
|||
== Changelog ==
|
||||
|
||||
= 7.3.0 2023-01-10 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Fix - Remove redundant Pinterest plugin from marketing task [#36158](https://github.com/woocommerce/woocommerce/pull/36158)
|
||||
* Fix - Corrects a hard-coded reference to the WP post meta table within the HPOS Migration Helper, that would fail on some sites. [#36100](https://github.com/woocommerce/woocommerce/pull/36100)
|
||||
* Fix - Add a blank space between the emoji and the message within a notice popup [#35698](https://github.com/woocommerce/woocommerce/pull/35698)
|
||||
* Fix - Add a data migration for changed New Zealand and Ukraine state codes [#35960](https://github.com/woocommerce/woocommerce/pull/35960)
|
||||
* Fix - Add back missing scss files from assets. [#35624](https://github.com/woocommerce/woocommerce/pull/35624)
|
||||
* Fix - Address HPOS synchronization issues relating to the deletion of orders. [#35723](https://github.com/woocommerce/woocommerce/pull/35723)
|
||||
* Fix - Avoid a potential fatal error when forming edit-order URLs. [#35995](https://github.com/woocommerce/woocommerce/pull/35995)
|
||||
* Fix - Fix call of array_key_exists in SSR. [#35598](https://github.com/woocommerce/woocommerce/pull/35598)
|
||||
* Fix - Fix ellipsis dropdown menu is mostly hidden within the task list [#35949](https://github.com/woocommerce/woocommerce/pull/35949)
|
||||
* Fix - Fixes fatal error resulting from translating the WooCommerce main menu. [#35695](https://github.com/woocommerce/woocommerce/pull/35695)
|
||||
* Fix - Fix get orders REST API endpoint when using 'search' or 'parent' and HPOS is enabled [#35818](https://github.com/woocommerce/woocommerce/pull/35818)
|
||||
* Fix - Fix handling of non-ASCII product attributes when the attributes lookup table is in use [#34432](https://github.com/woocommerce/woocommerce/pull/34432)
|
||||
* Fix - Fix handling of statuses in orders list table (HPOS). [#35370](https://github.com/woocommerce/woocommerce/pull/35370)
|
||||
* Fix - Fix logo icon for Google Listings and Ads. [#35732](https://github.com/woocommerce/woocommerce/pull/35732)
|
||||
* Fix - Fix product tab to be shown on production build [#35976](https://github.com/woocommerce/woocommerce/pull/35976)
|
||||
* Fix - Fix regexp used for filtering country dropdown on the store details step #35941 [#35942](https://github.com/woocommerce/woocommerce/pull/35942)
|
||||
* Fix - Fix the gap in the featured product checkbox [#35710](https://github.com/woocommerce/woocommerce/pull/35710)
|
||||
* Fix - Fix tooltips not appearing in the orders list admin page. [#35638](https://github.com/woocommerce/woocommerce/pull/35638)
|
||||
* Fix - Fix unread note count on inbox panel [#35396](https://github.com/woocommerce/woocommerce/pull/35396)
|
||||
* Fix - Fix unsaved modal propmt to not be shown during form submission [#35657](https://github.com/woocommerce/woocommerce/pull/35657)
|
||||
* Fix - Fix version in template and function docblocks. [#35473](https://github.com/woocommerce/woocommerce/pull/35473)
|
||||
* Fix - Fix WooCommerce Admin client React build warnings and remove unnecessary scss imports. [#35930](https://github.com/woocommerce/woocommerce/pull/35930)
|
||||
* Fix - Fix wrong query param in onboarding product api call [#35926](https://github.com/woocommerce/woocommerce/pull/35926)
|
||||
* Fix - If order types have not been registered, do not attempt to count orders. [#35820](https://github.com/woocommerce/woocommerce/pull/35820)
|
||||
* Fix - Make the 'unprotected upload directory' notice dismissable. [#33544](https://github.com/woocommerce/woocommerce/pull/33544)
|
||||
* Fix - Update Playwright to 1.28.0 and explicitly set PHP version in GH action [#35679](https://github.com/woocommerce/woocommerce/pull/35679)
|
||||
* Fix - When importing product CSV, ensure line breaks within header columns do not break the import process. [#35880](https://github.com/woocommerce/woocommerce/pull/35880)
|
||||
* Add - Add CES exit prompt for setting pages, when tracking is enabled. [#35761](https://github.com/woocommerce/woocommerce/pull/35761)
|
||||
* Add - Add CES feedback functionality to the share feedback button within the Product MVP. [#35690](https://github.com/woocommerce/woocommerce/pull/35690)
|
||||
* Add - Add Denmark postcode validation. [#35653](https://github.com/woocommerce/woocommerce/pull/35653)
|
||||
* Add - Add exit prompt logic to get feedback if users leave product pages without saving when tracking is enabled. [#35728](https://github.com/woocommerce/woocommerce/pull/35728)
|
||||
* Add - Add FormFileUpload component [#35358](https://github.com/woocommerce/woocommerce/pull/35358)
|
||||
* Add - Add HPOS information to WC Tracker. [#35446](https://github.com/woocommerce/woocommerce/pull/35446)
|
||||
* Add - Add new option to create new attribute within add attribute modal. [#35100](https://github.com/woocommerce/woocommerce/pull/35100)
|
||||
* Add - Add new product management breadcrumbs to header [#35596](https://github.com/woocommerce/woocommerce/pull/35596)
|
||||
* Add - Add new Product MVP CES footer for gathering feedback on the new product management screen. [#35652](https://github.com/woocommerce/woocommerce/pull/35652)
|
||||
* Add - Add one-click installation to recommended extensions in Marketing page. [#35542](https://github.com/woocommerce/woocommerce/pull/35542)
|
||||
* Add - Add pagination to variations list [#35979](https://github.com/woocommerce/woocommerce/pull/35979)
|
||||
* Add - Add product settings menu in header [#35592](https://github.com/woocommerce/woocommerce/pull/35592)
|
||||
* Add - Add product tab headers and move sections to respective tabs [#35862](https://github.com/woocommerce/woocommerce/pull/35862)
|
||||
* Add - Add product variations list to new product management experience [#35889](https://github.com/woocommerce/woocommerce/pull/35889)
|
||||
* Add - Add support for custom order types in HPOS admin UI. [#35658](https://github.com/woocommerce/woocommerce/pull/35658)
|
||||
* Add - Add the woocommerce_order_applied_coupon hook [#35616](https://github.com/woocommerce/woocommerce/pull/35616)
|
||||
* Add - Add tracks events for 'product_view_product_click' and 'product_view_product_dismiss' [#35582](https://github.com/woocommerce/woocommerce/pull/35582)
|
||||
* Add - Introduces action `woocommerce_order_list_table_restrict_manage_orders` as an equivalent of the legacy `restrict_manage_posts` hook. [#36000](https://github.com/woocommerce/woocommerce/pull/36000)
|
||||
* Add - Open categories menu list when the user focus the category field [#35606](https://github.com/woocommerce/woocommerce/pull/35606)
|
||||
* Update - Match country name or ' - region' when filtering country select control #36120 [#36159](https://github.com/woocommerce/woocommerce/pull/36159)
|
||||
* Update - Update WooCommerce Blocks to 9.1.3 [#36125](https://github.com/woocommerce/woocommerce/pull/36125)
|
||||
* Update - Adapt the summary text in the product management form. [#35717](https://github.com/woocommerce/woocommerce/pull/35717)
|
||||
* Update - Add Codisto for WooCommerce to free extensions list [#36009](https://github.com/woocommerce/woocommerce/pull/36009)
|
||||
* Update - Add experimental open menu on focus option to the attribute and attribute term input fields. [#35758](https://github.com/woocommerce/woocommerce/pull/35758)
|
||||
* Update - Add missing tracks events [#35262](https://github.com/woocommerce/woocommerce/pull/35262)
|
||||
* Update - Add Pinterest for WooCommerce to free extensions list [#36003](https://github.com/woocommerce/woocommerce/pull/36003)
|
||||
* Update - Automatically create the custom order tables if the corresponding feature is enabled [#35357](https://github.com/woocommerce/woocommerce/pull/35357)
|
||||
* Update - Disable TikTok in the OBW [#35924](https://github.com/woocommerce/woocommerce/pull/35924)
|
||||
* Update - Include taxes migration in MigrationHelper::migrate_country_states [#35967](https://github.com/woocommerce/woocommerce/pull/35967)
|
||||
* Update - Increase consistency in relation to the way taxonomy term ordering is persisted. [#34645](https://github.com/woocommerce/woocommerce/pull/34645)
|
||||
* Update - Make product form header and actions responsive for smaller viewports [#35623](https://github.com/woocommerce/woocommerce/pull/35623)
|
||||
* Update - Remove welcome to woocommerce store note [#35342](https://github.com/woocommerce/woocommerce/pull/35342)
|
||||
* Update - Surface Amazon Pay as "Additional Payment Options" for UK/EU/JP [#35726](https://github.com/woocommerce/woocommerce/pull/35726)
|
||||
* Update - Update api-core-tests readme to reference correct directory for .env file [#35759](https://github.com/woocommerce/woocommerce/pull/35759)
|
||||
* Update - Update country data in api-core-tests to prevent numerous test data updates [#35557](https://github.com/woocommerce/woocommerce/pull/35557)
|
||||
* Update - update FAQ in readme consumed by .org [#35696](https://github.com/woocommerce/woocommerce/pull/35696)
|
||||
* Update - Update WooCommerce Blocks to 9.1.1 [#36004](https://github.com/woocommerce/woocommerce/pull/36004)
|
||||
* Update - Update wording for In-App Marketplace tour. [#35929](https://github.com/woocommerce/woocommerce/pull/35929)
|
||||
* Update - Updating all CES events to support two questions in modal. [#35680](https://github.com/woocommerce/woocommerce/pull/35680)
|
||||
* Dev - Allow the user to select multiple images in the Media Library [#35722](https://github.com/woocommerce/woocommerce/pull/35722)
|
||||
* Dev - Check if blocks have been added to rich text editors before updating value [#35626](https://github.com/woocommerce/woocommerce/pull/35626)
|
||||
* Dev - Make e2e tests compatible with nightly and release smoke test sites. [#35492](https://github.com/woocommerce/woocommerce/pull/35492)
|
||||
* Dev - Move file picker by clicking card into the MediaUploader component [#35738](https://github.com/woocommerce/woocommerce/pull/35738)
|
||||
* Dev - Update the "can manually add a variation" E2E test to prevent automatic creation of variations from all attributes. [#36008](https://github.com/woocommerce/woocommerce/pull/36008)
|
||||
* Tweak - Avoid deprecation notices under PHP 8.1 when calling wp_parse_url(). [#35648](https://github.com/woocommerce/woocommerce/pull/35648)
|
||||
* Tweak - Correct the usage of 'address' and 'addresses' within `wc_get_account_menu_items()`. [#32026](https://github.com/woocommerce/woocommerce/pull/32026)
|
||||
* Tweak - Create ProductForm component to merge similar structures between AddProductPage and EditProductPage [#35783](https://github.com/woocommerce/woocommerce/pull/35783)
|
||||
* Tweak - Improves efficiency of code responsible for determining plugin IDs (during feature compatibility checks). [#35727](https://github.com/woocommerce/woocommerce/pull/35727)
|
||||
* Tweak - Make the formatted shipping address available via the `woocommerce_cart_no_shipping_available_html` hook. [#30723](https://github.com/woocommerce/woocommerce/pull/30723)
|
||||
* Tweak - Make the OrdersTableDataStore init_order_record() and get_order_data_for_ids() functions protected rather than private [#35829](https://github.com/woocommerce/woocommerce/pull/35829)
|
||||
* Tweak - Move CSS about notice outside of .woocommerce class scope [#35912](https://github.com/woocommerce/woocommerce/pull/35912)
|
||||
* Tweak - Resolve an error in the product tracking code by testing to see if the `post_type` query var is set before checking its value. [#34501](https://github.com/woocommerce/woocommerce/pull/34501)
|
||||
* Tweak - Simplify wording within the customer emails for on-hold orders. [#31886](https://github.com/woocommerce/woocommerce/pull/31886)
|
||||
* Tweak - WooCommerce has now been tested up to WordPress 6.1.x. [#35985](https://github.com/woocommerce/woocommerce/pull/35985)
|
||||
* Performance - Split CALC_FOUND_ROW query into seperate count query for better performance. [#35468](https://github.com/woocommerce/woocommerce/pull/35468)
|
||||
* Enhancement - Add a bottom padding to the whole form [#35721](https://github.com/woocommerce/woocommerce/pull/35721)
|
||||
* Enhancement - Add a confirmation modal when the user tries to navigate away with unsaved changes [#35625](https://github.com/woocommerce/woocommerce/pull/35625)
|
||||
* Enhancement - Add a default placeholder title for newly added attributes and always show remove button for attributes [#35904](https://github.com/woocommerce/woocommerce/pull/35904)
|
||||
* Enhancement - Add help tip for Product Image and Product Gallery meta boxes [#35834](https://github.com/woocommerce/woocommerce/pull/35834)
|
||||
* Enhancement - Add support for product attribute taxonomy template [#35617](https://github.com/woocommerce/woocommerce/pull/35617)
|
||||
* Enhancement - Add warning banner that informs the user if they have conflicting tax display settings [#36010](https://github.com/woocommerce/woocommerce/pull/36010)
|
||||
* Enhancement - Change the width of pricing fields, SKU and Shipping Class to 50% in big screens (>960px) in new product management experience [#35545](https://github.com/woocommerce/woocommerce/pull/35545)
|
||||
* Enhancement - Fix price field currency symbol position [#35718](https://github.com/woocommerce/woocommerce/pull/35718)
|
||||
* Enhancement - Improve element stacking in modals on tablet and mobile [#35733](https://github.com/woocommerce/woocommerce/pull/35733)
|
||||
* Security - Customers REST API endpoint will now return user metadata only when requester has an administrator role [#36408](https://github.com/woocommerce/woocommerce/pull/36408).
|
||||
|
||||
= 7.2.3 2023-1-9
|
||||
|
||||
**WooCommerce Blocks 8.9.4**
|
||||
|
||||
* Fix - fatal error in WordPress 5.8 when creating a post or page. #7496
|
||||
* Fix - hangs in the block editor with WordPress 5.8. #8095
|
||||
* Fix - Filter by Attribute block crashing in the editor of WordPress 5.8. #8101
|
||||
|
||||
= 7.2.2 2022-12-21 =
|
||||
|
||||
** WooCommerce**
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Adding WooProductFieldItem slotfill.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Adding the WooProductSectionItem slotfill component.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add product field store and helper functions for rendering fields from config.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: tweak
|
||||
|
||||
Update spelling of Cancelled to Canceled for US English.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Migrate Link component to TS
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Migrate ProductImage component to TS
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Migrate Section component to TS
|
|
@ -21,7 +21,7 @@ export { default as FilterPicker } from './filter-picker';
|
|||
export { H, Section } from './section';
|
||||
export { ImageGallery, ImageGalleryItem } from './image-gallery';
|
||||
export { default as ImageUpload } from './image-upload';
|
||||
export { default as Link } from './link';
|
||||
export { Link } from './link';
|
||||
export { default as List } from './list';
|
||||
export { MediaUploader } from './media-uploader';
|
||||
export { default as MenuItem } from './ellipsis-menu/menu-item';
|
||||
|
@ -84,3 +84,6 @@ export { DynamicForm } from './dynamic-form';
|
|||
export { default as TourKit } from './tour-kit';
|
||||
export * as TourKitTypes from './tour-kit/types';
|
||||
export { CollapsibleContent } from './collapsible-content';
|
||||
export { createOrderedChildren, sortFillsByOrder } from './utils';
|
||||
export { WooProductFieldItem as __experimentalWooProductFieldItem } from './woo-product-field-item';
|
||||
export { WooProductSectionItem as __experimentalWooProductSectionItem } from './woo-product-section-item';
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import { partial } from 'lodash';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { getHistory } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Use `Link` to create a link to another resource. It accepts a type to automatically
|
||||
* create wp-admin links, wc-admin links, and external links.
|
||||
*/
|
||||
|
||||
function Link( { children, href, type, ...props } ) {
|
||||
// @todo Investigate further if we can use <Link /> directly.
|
||||
// With React Router 5+, <RouterLink /> cannot be used outside of the main <Router /> elements,
|
||||
// which seems to include components imported from @woocommerce/components. For now, we can use the history object directly.
|
||||
const wcAdminLinkHandler = ( onClick, event ) => {
|
||||
// If cmd, ctrl, alt, or shift are used, use default behavior to allow opening in a new tab.
|
||||
if (
|
||||
event.ctrlKey ||
|
||||
event.metaKey ||
|
||||
event.altKey ||
|
||||
event.shiftKey
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// If there is an onclick event, execute it.
|
||||
const onClickResult = onClick ? onClick( event ) : true;
|
||||
|
||||
// Mimic browser behavior and only continue if onClickResult is not explicitly false.
|
||||
if ( onClickResult === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
getHistory().push( event.target.closest( 'a' ).getAttribute( 'href' ) );
|
||||
};
|
||||
|
||||
const passProps = {
|
||||
...props,
|
||||
'data-link-type': type,
|
||||
};
|
||||
|
||||
if ( type === 'wc-admin' ) {
|
||||
passProps.onClick = partial( wcAdminLinkHandler, passProps.onClick );
|
||||
}
|
||||
|
||||
return (
|
||||
<a href={ href } { ...passProps }>
|
||||
{ children }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
Link.propTypes = {
|
||||
/**
|
||||
* The resource to link to.
|
||||
*/
|
||||
href: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Type of link. For wp-admin and wc-admin, the correct prefix is appended.
|
||||
*/
|
||||
type: PropTypes.oneOf( [ 'wp-admin', 'wc-admin', 'external' ] ).isRequired,
|
||||
};
|
||||
|
||||
Link.defaultProps = {
|
||||
type: 'wc-admin',
|
||||
};
|
||||
|
||||
Link.contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Link;
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import { partial } from 'lodash';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { getHistory } from '@woocommerce/navigation';
|
||||
import React from 'react';
|
||||
|
||||
interface LinkProps {
|
||||
/** Type of link. For wp-admin and wc-admin, the correct prefix is appended. */
|
||||
type?: 'wc-admin' | 'wp-admin' | 'external';
|
||||
/** The resource to link to. */
|
||||
href: string;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type LinkOnClickHandlerFunction = ( ...props: any ) => any; // we don't want to restrict this function at all
|
||||
type LinkOnclickHandler = (
|
||||
onClick: LinkOnClickHandlerFunction | undefined,
|
||||
event: React.MouseEvent< Element > | undefined
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Use `Link` to create a link to another resource. It accepts a type to automatically
|
||||
* create wp-admin links, wc-admin links, and external links.
|
||||
*/
|
||||
export const Link = ( {
|
||||
href,
|
||||
children,
|
||||
type = 'wc-admin',
|
||||
...props
|
||||
}: React.AnchorHTMLAttributes< HTMLAnchorElement > &
|
||||
LinkProps ): React.ReactElement => {
|
||||
// ( { children, href, type, ...props } ) => {
|
||||
// @todo Investigate further if we can use <Link /> directly.
|
||||
// With React Router 5+, <RouterLink /> cannot be used outside of the main <Router /> elements,
|
||||
// which seems to include components imported from @woocommerce/components. For now, we can use the history object directly.
|
||||
const wcAdminLinkHandler: LinkOnclickHandler = ( onClick, event ) => {
|
||||
// If cmd, ctrl, alt, or shift are used, use default behavior to allow opening in a new tab.
|
||||
if (
|
||||
event?.ctrlKey ||
|
||||
event?.metaKey ||
|
||||
event?.altKey ||
|
||||
event?.shiftKey
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
event?.preventDefault();
|
||||
|
||||
// If there is an onclick event, execute it.
|
||||
const onClickResult = onClick && event ? onClick( event ) : true;
|
||||
|
||||
// Mimic browser behavior and only continue if onClickResult is not explicitly false.
|
||||
if ( onClickResult === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( event?.target instanceof Element ) {
|
||||
const closestEventTarget = event.target
|
||||
.closest( 'a' )
|
||||
?.getAttribute( 'href' );
|
||||
if ( closestEventTarget ) {
|
||||
getHistory().push( closestEventTarget );
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
'@woocommerce/components/link is trying to push an undefined state into navigation stack'
|
||||
); // This shouldn't happen as we wrap with <a> below
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const passProps = {
|
||||
...props,
|
||||
'data-link-type': type,
|
||||
};
|
||||
|
||||
if ( type === 'wc-admin' ) {
|
||||
passProps.onClick = partial( wcAdminLinkHandler, passProps.onClick );
|
||||
}
|
||||
|
||||
return (
|
||||
<a href={ href } { ...passProps }>
|
||||
{ children }
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
Link.contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Link;
|
|
@ -3,11 +3,12 @@
|
|||
*/
|
||||
import { withConsole } from '@storybook/addon-console';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Link from '../';
|
||||
import Link from '..';
|
||||
|
||||
function logLinkClick( event ) {
|
||||
const a = event.currentTarget;
|
|
@ -1,13 +1,13 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Link from '../index';
|
||||
import { Link } from '..';
|
||||
|
||||
describe( 'Link', () => {
|
||||
it( 'should render `external` links', () => {
|
||||
|
@ -112,7 +112,7 @@ describe( 'Link', () => {
|
|||
return false;
|
||||
} );
|
||||
|
||||
const { container } = render(
|
||||
render(
|
||||
<Link
|
||||
href="https://woocommerce.com"
|
||||
type="external"
|
||||
|
@ -122,7 +122,9 @@ describe( 'Link', () => {
|
|||
</Link>
|
||||
);
|
||||
|
||||
fireEvent.click( container.firstChild );
|
||||
const testLink = screen.getByText( 'WooCommerce.com' );
|
||||
|
||||
fireEvent.click( testLink );
|
||||
|
||||
expect( clickHandler ).toHaveBeenCalled();
|
||||
} );
|
|
@ -0,0 +1,46 @@
|
|||
# Product Fields
|
||||
|
||||
Product Fields are used within the WooCommerce Admin product editor, for rendering new fields using PHP.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
// product-field.js
|
||||
( function ( element ) {
|
||||
const el = element.createElement;
|
||||
|
||||
registerProductField( 'number', {
|
||||
name: 'number',
|
||||
render: () => {
|
||||
return <InputControl type="number" />;
|
||||
},
|
||||
} );
|
||||
} )( window.wp.element );
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### registerProductField
|
||||
|
||||
Registers a new product field provided a unique name and an object defining its
|
||||
behavior.
|
||||
|
||||
_Usage_
|
||||
|
||||
```js
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { registerProductField } from '@woocommerce/components';
|
||||
|
||||
registerProductField( 'number', {
|
||||
name: 'number',
|
||||
render: () => {
|
||||
return <InputControl type="number" />;
|
||||
},
|
||||
} );
|
||||
```
|
||||
|
||||
_Parameters_
|
||||
|
||||
- _fieldName_ `string`: Field name.
|
||||
- _settings_ `Object`: Field settings.
|
||||
- _render_ `ComponentType`: React functional component to be rendered.
|
|
@ -0,0 +1,2 @@
|
|||
export * from './registration';
|
||||
export * from './render';
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { select, dispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { store as productFieldStore } from '../store';
|
||||
import { ProductFieldDefinition } from '../store/types';
|
||||
|
||||
/**
|
||||
* Registers a new product field provided a unique name and an object defining its
|
||||
* behavior. Once registered, the field is made available to use with the product form API.
|
||||
*
|
||||
* @param {string|Object} fieldName Field name.
|
||||
* @param {Object} settings Field settings.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* import { registerProductField } from '@woocommerce/components'
|
||||
*
|
||||
* registerProductFieldType( 'attributes-field', {
|
||||
* } );
|
||||
* ```
|
||||
*/
|
||||
export function registerProductField(
|
||||
fieldName: string,
|
||||
settings: ProductFieldDefinition
|
||||
) {
|
||||
if ( select( productFieldStore ).getProductField( fieldName ) ) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
'Product Field "' + fieldName + '" is already registered.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch( productFieldStore ).registerProductField( {
|
||||
attributes: {},
|
||||
...settings,
|
||||
} );
|
||||
|
||||
return select( productFieldStore ).getProductField( fieldName );
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { select } from '@wordpress/data';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { store as productFieldStore } from '../store';
|
||||
import { ProductFieldDefinition } from '../store/types';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function renderField( name: string, props: Record< string, any > ) {
|
||||
const fieldConfig: ProductFieldDefinition =
|
||||
select( productFieldStore ).getProductField( name );
|
||||
|
||||
if ( fieldConfig.render ) {
|
||||
return <fieldConfig.render { ...props } />;
|
||||
}
|
||||
if ( fieldConfig.type ) {
|
||||
return createElement( 'input', {
|
||||
type: fieldConfig.type,
|
||||
...props,
|
||||
} );
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export { store } from './store';
|
||||
export * from './api';
|
|
@ -0,0 +1,5 @@
|
|||
export enum TYPES {
|
||||
REGISTER_FIELD = 'REGISTER_FIELD',
|
||||
}
|
||||
|
||||
export default TYPES;
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TYPES from './action-types';
|
||||
import { ProductFieldDefinition } from './types';
|
||||
|
||||
export function registerProductField( field: ProductFieldDefinition ) {
|
||||
return {
|
||||
type: TYPES.REGISTER_FIELD as const,
|
||||
field,
|
||||
};
|
||||
}
|
||||
|
||||
export type Actions = ReturnType< typeof registerProductField >;
|
|
@ -0,0 +1 @@
|
|||
export const STORE_NAME = 'wc/admin/product/fields';
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createReduxStore, register } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import reducer from './reducer';
|
||||
import * as selectors from './selectors';
|
||||
import * as actions from './actions';
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
export const store = createReduxStore( STORE_NAME, {
|
||||
// @ts-expect-error reducer has correct format.
|
||||
reducer,
|
||||
selectors,
|
||||
actions,
|
||||
} );
|
||||
|
||||
register( store );
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TYPES from './action-types';
|
||||
import { Actions } from './actions';
|
||||
import { ProductFieldState } from './types';
|
||||
|
||||
const reducer = (
|
||||
state: ProductFieldState = {
|
||||
fields: {},
|
||||
},
|
||||
payload: Actions
|
||||
) => {
|
||||
if ( payload && 'type' in payload ) {
|
||||
switch ( payload.type ) {
|
||||
case TYPES.REGISTER_FIELD:
|
||||
return {
|
||||
...state,
|
||||
fields: {
|
||||
...state.fields,
|
||||
[ payload.field.name ]: payload.field,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export type State = ReturnType< typeof reducer >;
|
||||
export default reducer;
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import memoize from 'memoize-one';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductFieldState } from './types';
|
||||
|
||||
export function getProductField( state: ProductFieldState, name: string ) {
|
||||
return state.fields[ name ] || null;
|
||||
}
|
||||
|
||||
export const getRegisteredProductFields = memoize(
|
||||
( state: ProductFieldState ) => Object.keys( state.fields ),
|
||||
( [ newState ], [ oldState ] ) => {
|
||||
return newState.fields === oldState.fields;
|
||||
}
|
||||
);
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
export type ProductFieldDefinition = {
|
||||
name: string;
|
||||
type?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
render?: ComponentType;
|
||||
};
|
||||
|
||||
export type ProductFieldState = {
|
||||
fields: Record< string, ProductFieldDefinition >;
|
||||
};
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useState, createElement } from '@wordpress/element';
|
||||
import { createRegistry, RegistryProvider, select } from '@wordpress/data';
|
||||
import {
|
||||
// @ts-expect-error `__experimentalInputControl` does exist.
|
||||
__experimentalInputControl as InputControl,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { store } from '../store';
|
||||
import { registerProductField, renderField } from '../api';
|
||||
|
||||
const registry = createRegistry();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
registry.register( store );
|
||||
|
||||
registerProductField( 'text', {
|
||||
name: 'text',
|
||||
render: ( props ) => {
|
||||
return <InputControl type="text" { ...props } />;
|
||||
},
|
||||
} );
|
||||
|
||||
registerProductField( 'number', {
|
||||
name: 'number',
|
||||
render: () => {
|
||||
return <InputControl type="number" />;
|
||||
},
|
||||
} );
|
||||
|
||||
const RenderField = () => {
|
||||
const fields: string[] = select( store ).getRegisteredProductFields();
|
||||
const [ selectedField, setSelectedField ] = useState(
|
||||
fields ? fields[ 0 ] : undefined
|
||||
);
|
||||
|
||||
const handleChange = ( event ) => {
|
||||
setSelectedField( event.target.value );
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<select value={ selectedField } onChange={ handleChange }>
|
||||
{ fields.map( ( field ) => (
|
||||
<option key={ field } value={ field }>
|
||||
{ field }
|
||||
</option>
|
||||
) ) }
|
||||
</select>
|
||||
{ selectedField && renderField( selectedField, { name: 'test' } ) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic: React.FC = () => {
|
||||
return (
|
||||
<RegistryProvider value={ registry }>
|
||||
<RenderField />
|
||||
</RegistryProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Admin/experimental/product-fields',
|
||||
component: Basic,
|
||||
};
|
|
@ -2,7 +2,6 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { get } from 'lodash';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
|
@ -11,25 +10,47 @@ import { createElement } from '@wordpress/element';
|
|||
*/
|
||||
import { placeholderWhiteBackground as placeholder } from './placeholder';
|
||||
|
||||
type Image = {
|
||||
src?: string;
|
||||
};
|
||||
|
||||
type ProductImageProps = {
|
||||
/**
|
||||
* Product or variation object. The image to display will be pulled from
|
||||
* `product.images` or `variation.image`.
|
||||
* See https://woocommerce.github.io/woocommerce-rest-api-docs/#product-properties
|
||||
* and https://woocommerce.github.io/woocommerce-rest-api-docs/#product-variation-properties
|
||||
*/
|
||||
product?: {
|
||||
images?: Array< Image >;
|
||||
image?: Image;
|
||||
// ProductImage is only interested in product.images or varation.image
|
||||
// but product object can have other properties that we don't control.
|
||||
// allowing `any` here
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} & Record< string, any >;
|
||||
/** The width of image to display. */
|
||||
width?: number;
|
||||
/** The height of image to display. */
|
||||
height?: number;
|
||||
/** Additional CSS classes. */
|
||||
className?: string;
|
||||
/** Text to use as the image alt attribute. */
|
||||
alt?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Use `ProductImage` to display a product's or variation's featured image.
|
||||
* If no image can be found, a placeholder matching the front-end image
|
||||
* placeholder will be displayed.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Object} props.product
|
||||
* @param {string} props.alt
|
||||
* @param {number} props.width
|
||||
* @param {number} props.height
|
||||
* @param {string} props.className
|
||||
* @return {Object} -
|
||||
*/
|
||||
const ProductImage = ( {
|
||||
|
||||
const ProductImage: React.VFC< ProductImageProps > = ( {
|
||||
product,
|
||||
width = 33,
|
||||
height = 33,
|
||||
className = '',
|
||||
alt,
|
||||
width,
|
||||
height,
|
||||
className,
|
||||
...props
|
||||
} ) => {
|
||||
// The first returned image from the API is the featured/product image.
|
||||
|
@ -54,36 +75,4 @@ const ProductImage = ( {
|
|||
);
|
||||
};
|
||||
|
||||
ProductImage.propTypes = {
|
||||
/**
|
||||
* The width of image to display.
|
||||
*/
|
||||
width: PropTypes.number,
|
||||
/**
|
||||
* The height of image to display.
|
||||
*/
|
||||
height: PropTypes.number,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* Product or variation object. The image to display will be pulled from
|
||||
* `product.images` or `variation.image`.
|
||||
* See https://woocommerce.github.io/woocommerce-rest-api-docs/#product-properties
|
||||
* and https://woocommerce.github.io/woocommerce-rest-api-docs/#product-variation-properties
|
||||
*/
|
||||
product: PropTypes.object,
|
||||
/**
|
||||
* Text to use as the image alt attribute.
|
||||
*/
|
||||
alt: PropTypes.string,
|
||||
};
|
||||
|
||||
ProductImage.defaultProps = {
|
||||
width: 33,
|
||||
height: 33,
|
||||
className: '',
|
||||
};
|
||||
|
||||
export default ProductImage;
|
|
@ -2,6 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { ProductImage } from '@woocommerce/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
export const Basic = () => (
|
||||
<div>
|
|
@ -7,7 +7,7 @@ import { createElement } from '@wordpress/element';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ProductImage from '../';
|
||||
import ProductImage from '..';
|
||||
|
||||
describe( 'ProductImage', () => {
|
||||
test( 'should render the passed alt prop', () => {
|
|
@ -66,7 +66,6 @@ const completer: AutoCompleter = {
|
|||
const match = computeSuggestionMatch( product.name, query );
|
||||
return (
|
||||
<Fragment>
|
||||
{ /* @ts-expect-error TODO: migrate ProductImage component to TS. */ }
|
||||
<ProductImage
|
||||
key="thumbnail"
|
||||
className="woocommerce-search__result-thumbnail"
|
||||
|
|
|
@ -97,7 +97,6 @@ const completer: AutoCompleter = {
|
|||
);
|
||||
return (
|
||||
<Fragment>
|
||||
{ /* @ts-expect-error TODO: migrate ProductImage component to TS. */ }
|
||||
<ProductImage
|
||||
key="thumbnail"
|
||||
className="woocommerce-search__result-thumbnail"
|
||||
|
|
|
@ -15,7 +15,7 @@ import { Level } from './context';
|
|||
*
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
export function H( props ) {
|
||||
export function H( props: React.HTMLAttributes< HTMLHeadingElement > ) {
|
||||
const level = useContext( Level );
|
||||
|
||||
const Heading = 'h' + Math.min( level, 6 );
|
|
@ -1,56 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Level } from './context';
|
||||
|
||||
/**
|
||||
* The section wrapper, used to indicate a sub-section (and change the header level context).
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {import('react').ComponentType=} props.component
|
||||
* @param {import('react').ReactNode} props.children Children to render in the tip.
|
||||
* @param {string=} props.className
|
||||
* @return {JSX.Element} -
|
||||
*/
|
||||
export function Section( { component, children, ...props } ) {
|
||||
const Component = component || 'div';
|
||||
return (
|
||||
<Level.Consumer>
|
||||
{ ( level ) => (
|
||||
<Level.Provider value={ level + 1 }>
|
||||
{ component === false ? (
|
||||
children
|
||||
) : (
|
||||
<Component { ...props }>{ children }</Component>
|
||||
) }
|
||||
</Level.Provider>
|
||||
) }
|
||||
</Level.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
Section.propTypes = {
|
||||
/**
|
||||
* The wrapper component for this section. Optional, defaults to `div`. If passed false, no wrapper is used. Additional props
|
||||
* passed to Section are passed on to the component.
|
||||
*/
|
||||
component: PropTypes.oneOfType( [
|
||||
PropTypes.func,
|
||||
PropTypes.string,
|
||||
PropTypes.bool,
|
||||
] ),
|
||||
/**
|
||||
* The children inside this section, rendered in the `component`. This increases the context level for the next heading used.
|
||||
*/
|
||||
children: PropTypes.node,
|
||||
/**
|
||||
* Optional classname
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Level } from './context';
|
||||
|
||||
type SectionProps = {
|
||||
/** The wrapper component for this section. Optional, defaults to `div`. If passed false, no wrapper is used. Additional props passed to Section are passed on to the component. */
|
||||
component?: React.ComponentType | string | false;
|
||||
/** Optional classname */
|
||||
className?: string;
|
||||
/** The children inside this section, rendered in the `component`. This increases the context level for the next heading used. */
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* The section wrapper, used to indicate a sub-section (and change the header level context).
|
||||
*/
|
||||
export const Section: React.VFC< SectionProps > = ( {
|
||||
component,
|
||||
children,
|
||||
...props
|
||||
} ) => {
|
||||
const Component = component || 'div';
|
||||
return (
|
||||
<Level.Consumer>
|
||||
{ ( level ) => (
|
||||
<Level.Provider value={ level + 1 }>
|
||||
{ component === false ? (
|
||||
children
|
||||
) : (
|
||||
<Component { ...props }>{ children }</Component>
|
||||
) }
|
||||
</Level.Provider>
|
||||
) }
|
||||
</Level.Consumer>
|
||||
);
|
||||
};
|
|
@ -2,6 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { H, Section } from '@woocommerce/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
export const Basic = () => (
|
||||
<div>
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import React, { isValidElement, Fragment } from 'react';
|
||||
import { Slot, Fill } from '@wordpress/components';
|
||||
import { cloneElement, createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Ordered fill item.
|
||||
*
|
||||
* @param {Node} children - Node children.
|
||||
* @param {number} order - Node order.
|
||||
* @param {Array} props - Fill props.
|
||||
* @return {Node} Node.
|
||||
*/
|
||||
function createOrderedChildren< T = Fill.Props >(
|
||||
children: React.ReactNode,
|
||||
order: number,
|
||||
props: T
|
||||
) {
|
||||
if ( typeof children === 'function' ) {
|
||||
return cloneElement( children( props ), { order } );
|
||||
} else if ( isValidElement( children ) ) {
|
||||
return cloneElement( children, { ...props, order } );
|
||||
}
|
||||
throw Error( 'Invalid children type' );
|
||||
}
|
||||
export { createOrderedChildren };
|
||||
|
||||
/**
|
||||
* Sort fills by order for slot children.
|
||||
*
|
||||
* @param {Array} fills - slot's `Fill`s.
|
||||
* @return {Node} Node.
|
||||
*/
|
||||
export const sortFillsByOrder: Slot.Props[ 'children' ] = ( fills ) => {
|
||||
// Copy fills array here because its type is readonly array that doesn't have .sort method in Typescript definition.
|
||||
const sortedFills = [ ...fills ].sort( ( a, b ) => {
|
||||
return a[ 0 ].props.order - b[ 0 ].props.order;
|
||||
} );
|
||||
|
||||
return <Fragment>{ sortedFills }</Fragment>;
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
# WooProductFieldItem Slot & Fill
|
||||
|
||||
A Slotfill component that will allow you to add a new field to a specific section in the product editor.
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
<WooProductFieldItem id={ key } section="details" order={ 2 } pluginId="test-plugin" >
|
||||
{ () => {
|
||||
return (
|
||||
<TextControl
|
||||
label="Name"
|
||||
name={ `product-mvp-name` }
|
||||
placeholder="e.g. 12 oz Coffee Mug"
|
||||
value="Test Name"
|
||||
onChange={ () => console.debug( 'Changed!' ) }
|
||||
/>
|
||||
);
|
||||
} }
|
||||
</WooProductFieldItem>
|
||||
|
||||
<WooProductFieldItem.Slot section="details" />
|
||||
```
|
||||
|
||||
### WooProductFieldItem (fill)
|
||||
|
||||
This is the fill component. You must provide the `id` prop to identify your product field fill with a unique string. This component will accept a series of props:
|
||||
|
||||
| Prop | Type | Description |
|
||||
| -------------| -------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `id` | String | A unique string to identify your fill. Used for configuiration management. |
|
||||
| `section ` | String | The string used to identify the particular section where you want to render your field. |
|
||||
| `pluginId` | String | A unique plugin ID to identify the plugin/extension that this fill is associated with. |
|
||||
| `order` | Number | (optional) This number will dictate the order that the fields rendered by a Slot will be appear. |
|
||||
|
||||
### WooProductFieldItem.Slot (slot)
|
||||
|
||||
This is the slot component, and will not be used as frequently. It must also receive the required `location` prop that will be identical to the fill `location`.
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------- | ------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| `section` | String | Unique to the section that the Slot appears, and must be the same as the one provided to any fills. |
|
|
@ -0,0 +1 @@
|
|||
export * from './woo-product-field-item';
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Slot, Fill } from '@wordpress/components';
|
||||
import { createElement, Children } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { createOrderedChildren, sortFillsByOrder } from '../utils';
|
||||
|
||||
type WooProductFieldItemProps = {
|
||||
id: string;
|
||||
section: string;
|
||||
pluginId: string;
|
||||
order?: number;
|
||||
};
|
||||
|
||||
type WooProductFieldSlotProps = {
|
||||
section: string;
|
||||
};
|
||||
|
||||
export const WooProductFieldItem: React.FC< WooProductFieldItemProps > & {
|
||||
Slot: React.FC< Slot.Props & WooProductFieldSlotProps >;
|
||||
} = ( { children, order = 1, section } ) => (
|
||||
<Fill name={ `woocommerce_product_field_${ section }` }>
|
||||
{ ( fillProps: Fill.Props ) => {
|
||||
return createOrderedChildren< Fill.Props >(
|
||||
children,
|
||||
order,
|
||||
fillProps
|
||||
);
|
||||
} }
|
||||
</Fill>
|
||||
);
|
||||
|
||||
WooProductFieldItem.Slot = ( { fillProps, section } ) => (
|
||||
<Slot
|
||||
name={ `woocommerce_product_field_${ section }` }
|
||||
fillProps={ fillProps }
|
||||
>
|
||||
{ ( fills ) => {
|
||||
if ( ! sortFillsByOrder ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Children.map(
|
||||
sortFillsByOrder( fills )?.props.children,
|
||||
( child ) => (
|
||||
<div className="woocommerce-product-form__field">
|
||||
{ child }
|
||||
</div>
|
||||
)
|
||||
);
|
||||
} }
|
||||
</Slot>
|
||||
);
|
|
@ -0,0 +1,46 @@
|
|||
# WooProductSectionItem Slot & Fill
|
||||
|
||||
A Slotfill component that will allow you to add a new section to the product editor.
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
<WooProductSectionItem id={ key } location="tab/general" order={ 2 } pluginId="test-plugin" >
|
||||
{ () => {
|
||||
return (
|
||||
<ProductSectionLayout
|
||||
title={ __( 'Product test section', 'woocommerce' ) }
|
||||
description={ __(
|
||||
'In this area you can describe the section.',
|
||||
'woocommerce'
|
||||
) }
|
||||
>
|
||||
<Card>
|
||||
<CardBody>{ /* Section content */ }</CardBody>
|
||||
</Card>
|
||||
</ProductSectionLayout>
|
||||
);
|
||||
} }
|
||||
</WooProductSectionItem>
|
||||
|
||||
<WooProductSectionItem.Slot location="tab/general" />
|
||||
```
|
||||
|
||||
### WooProductSectionItem (fill)
|
||||
|
||||
This is the fill component. You must provide the `id` prop to identify your section fill with a unique string. This component will accept a series of props:
|
||||
|
||||
| Prop | Type | Description |
|
||||
| -------------| -------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `id` | String | A unique string to identify your fill. Used for configuiration management. |
|
||||
| `location` | String | The string used to identify the particular location that you want to render your section. |
|
||||
| `pluginId` | String | A unique plugin ID to identify the plugin/extension that this fill is associated with. |
|
||||
| `order` | Number | (optional) This number will dictate the order that the sections rendered by a Slot will be appear. |
|
||||
|
||||
### WooProductSectionItem.Slot (slot)
|
||||
|
||||
This is the slot component, and will not be used as frequently. It must also receive the required `location` prop that will be identical to the fill `location`.
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------- | ------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| `location` | String | Unique to the location that the Slot appears, and must be the same as the one provided to any fills. |
|
|
@ -0,0 +1 @@
|
|||
export * from './woo-product-section-item';
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Slot, Fill } from '@wordpress/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { createOrderedChildren, sortFillsByOrder } from '../utils';
|
||||
|
||||
type WooProductSectionItemProps = {
|
||||
id: string;
|
||||
location: string;
|
||||
pluginId: string;
|
||||
order?: number;
|
||||
};
|
||||
|
||||
type WooProductFieldSlotProps = {
|
||||
location: string;
|
||||
};
|
||||
|
||||
export const WooProductSectionItem: React.FC< WooProductSectionItemProps > & {
|
||||
Slot: React.FC< Slot.Props & WooProductFieldSlotProps >;
|
||||
} = ( { children, order = 1, location } ) => (
|
||||
<Fill name={ `woocommerce_product_section_${ location }` }>
|
||||
{ ( fillProps: Fill.Props ) => {
|
||||
return createOrderedChildren< Fill.Props >(
|
||||
children,
|
||||
order,
|
||||
fillProps
|
||||
);
|
||||
} }
|
||||
</Fill>
|
||||
);
|
||||
|
||||
WooProductSectionItem.Slot = ( { fillProps, location } ) => (
|
||||
<Slot
|
||||
name={ `woocommerce_product_section_${ location }` }
|
||||
fillProps={ fillProps }
|
||||
>
|
||||
{ ( fills ) => {
|
||||
if ( ! sortFillsByOrder ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sortFillsByOrder( fills );
|
||||
} }
|
||||
</Slot>
|
||||
);
|
|
@ -29,12 +29,14 @@ export function CustomerEffortScoreConsole( { label } ) {
|
|||
const onNoticeShown = () => console.log( 'onNoticeShown' );
|
||||
const onModalShown = () => console.log( 'onModalShown' );
|
||||
const onNoticeDismissed = () => console.log( 'onNoticeDismissed' );
|
||||
const recordScore = ( score, comments ) => console.log( { score, comments } );
|
||||
const recordScore = ( score, score2, comments ) => console.log( { score, score2, comments } );
|
||||
|
||||
return (
|
||||
<CustomerEffortScore
|
||||
recordScoreCallback={ recordScore }
|
||||
label={ label }
|
||||
title="My title"
|
||||
firstQuestion="My first question"
|
||||
secondQuestion="My optional second question"
|
||||
onNoticeShownCallback={ onNoticeShown }
|
||||
onNoticeDismissedCallback={ onNoticeDismissed }
|
||||
onModalShownCallback={ onModalShown }
|
||||
|
@ -62,7 +64,8 @@ const MyComponent = function() {
|
|||
setCeses(
|
||||
ceses.concat(
|
||||
<CustomerEffortScoreConsole
|
||||
label={ `survey ${ceses.length + 1}` }
|
||||
title={ `survey ${ceses.length + 1}` }
|
||||
firstQuestion="My first question"
|
||||
key={ ceses.length + 1 }
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: tweak
|
||||
|
||||
Set secondQuestion and title as optional
|
|
@ -19,11 +19,11 @@ type CustomerEffortScoreProps = {
|
|||
secondScore: number,
|
||||
comments: string
|
||||
) => void;
|
||||
title: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
noticeLabel?: string;
|
||||
firstQuestion: string;
|
||||
secondQuestion: string;
|
||||
secondQuestion?: string;
|
||||
onNoticeShownCallback?: () => void;
|
||||
onNoticeDismissedCallback?: () => void;
|
||||
onModalShownCallback?: () => void;
|
||||
|
@ -39,11 +39,11 @@ type CustomerEffortScoreProps = {
|
|||
*
|
||||
* @param {Object} props Component props.
|
||||
* @param {Function} props.recordScoreCallback Function to call when the score should be recorded.
|
||||
* @param {string} props.title The title displayed in the modal.
|
||||
* @param {string} [props.title] The title displayed in the modal.
|
||||
* @param {string} props.description The description displayed in the modal.
|
||||
* @param {string} props.noticeLabel The notice label displayed in the notice.
|
||||
* @param {string} props.firstQuestion The first survey question.
|
||||
* @param {string} props.secondQuestion The second survey question.
|
||||
* @param {string} [props.secondQuestion] The second survey question.
|
||||
* @param {Function} props.onNoticeShownCallback Function to call when the notice is shown.
|
||||
* @param {Function} props.onNoticeDismissedCallback Function to call when the notice is dismissed.
|
||||
* @param {Function} props.onModalShownCallback Function to call when the modal is shown.
|
||||
|
@ -120,7 +120,7 @@ CustomerEffortScore.propTypes = {
|
|||
/**
|
||||
* The title displayed in the modal.
|
||||
*/
|
||||
title: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
/**
|
||||
* The function to call when the notice is shown.
|
||||
*/
|
||||
|
@ -137,6 +137,14 @@ CustomerEffortScore.propTypes = {
|
|||
* Icon (React component) to be displayed.
|
||||
*/
|
||||
icon: PropTypes.element,
|
||||
/**
|
||||
* The first survey question.
|
||||
*/
|
||||
firstQuestion: PropTypes.string.isRequired,
|
||||
/**
|
||||
* The second survey question.
|
||||
*/
|
||||
secondQuestion: PropTypes.string,
|
||||
};
|
||||
|
||||
export { CustomerEffortScore };
|
||||
|
|
|
@ -28,14 +28,14 @@ import { __ } from '@wordpress/i18n';
|
|||
* @param {string} props.title Title displayed in the modal.
|
||||
* @param {string} props.description Description displayed in the modal.
|
||||
* @param {string} props.firstQuestion The first survey question.
|
||||
* @param {string} props.secondQuestion The second survey question.
|
||||
* @param {string} [props.secondQuestion] An optional second survey question.
|
||||
* @param {string} props.defaultScore Default score.
|
||||
* @param {Function} props.onCloseModal Callback for when user closes modal by clicking cancel.
|
||||
* @param {Function} props.customOptions List of custom score options, contains label and value.
|
||||
*/
|
||||
function CustomerFeedbackModal( {
|
||||
recordScoreCallback,
|
||||
title,
|
||||
title = __( 'Please share your feedback', 'woocommerce' ),
|
||||
description,
|
||||
firstQuestion,
|
||||
secondQuestion,
|
||||
|
@ -48,10 +48,10 @@ function CustomerFeedbackModal( {
|
|||
secondScore: number,
|
||||
comments: string
|
||||
) => void;
|
||||
title: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
firstQuestion: string;
|
||||
secondQuestion: string;
|
||||
secondQuestion?: string;
|
||||
defaultScore?: number;
|
||||
onCloseModal?: () => void;
|
||||
customOptions?: { label: string; value: string }[];
|
||||
|
@ -110,10 +110,8 @@ function CustomerFeedbackModal( {
|
|||
|
||||
const sendScore = () => {
|
||||
if (
|
||||
! (
|
||||
Number.isInteger( firstQuestionScore ) &&
|
||||
Number.isInteger( secondQuestionScore )
|
||||
)
|
||||
! Number.isInteger( firstQuestionScore ) ||
|
||||
( secondQuestion && ! Number.isInteger( secondQuestionScore ) )
|
||||
) {
|
||||
setShowNoScoreMessage( true );
|
||||
return;
|
||||
|
@ -175,28 +173,32 @@ function CustomerFeedbackModal( {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<Text
|
||||
variant="subtitle.small"
|
||||
as="p"
|
||||
weight="600"
|
||||
size="14"
|
||||
lineHeight="20px"
|
||||
>
|
||||
{ secondQuestion }
|
||||
</Text>
|
||||
{ secondQuestion && (
|
||||
<Text
|
||||
variant="subtitle.small"
|
||||
as="p"
|
||||
weight="600"
|
||||
size="14"
|
||||
lineHeight="20px"
|
||||
>
|
||||
{ secondQuestion }
|
||||
</Text>
|
||||
) }
|
||||
|
||||
<div className="woocommerce-customer-effort-score__selection">
|
||||
<RadioControl
|
||||
selected={ secondQuestionScore.toString( 10 ) }
|
||||
options={ options }
|
||||
onChange={ ( value ) =>
|
||||
onRadioControlChange(
|
||||
value as string,
|
||||
setSecondQuestionScore
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{ secondQuestion && (
|
||||
<div className="woocommerce-customer-effort-score__selection">
|
||||
<RadioControl
|
||||
selected={ secondQuestionScore.toString( 10 ) }
|
||||
options={ options }
|
||||
onChange={ ( value ) =>
|
||||
onRadioControlChange(
|
||||
value as string,
|
||||
setSecondQuestionScore
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) }
|
||||
|
||||
{ [ firstQuestionScore, secondQuestionScore ].some(
|
||||
( score ) => score === 1 || score === 2
|
||||
|
@ -250,9 +252,9 @@ function CustomerFeedbackModal( {
|
|||
|
||||
CustomerFeedbackModal.propTypes = {
|
||||
recordScoreCallback: PropTypes.func.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
firstQuestion: PropTypes.string.isRequired,
|
||||
secondQuestion: PropTypes.string.isRequired,
|
||||
secondQuestion: PropTypes.string,
|
||||
defaultScore: PropTypes.number,
|
||||
onCloseModal: PropTypes.func,
|
||||
};
|
||||
|
|
|
@ -107,4 +107,34 @@ describe( 'CustomerFeedbackModal', () => {
|
|||
} );
|
||||
}
|
||||
);
|
||||
|
||||
it( 'should render even if no second question is provided', async () => {
|
||||
render(
|
||||
<CustomerFeedbackModal
|
||||
recordScoreCallback={ mockRecordScoreCallback }
|
||||
title="Testing"
|
||||
firstQuestion="First question"
|
||||
/>
|
||||
);
|
||||
|
||||
// Wait for the modal to render.
|
||||
await screen.findByRole( 'dialog' );
|
||||
// Should be only one neutral emoji, since there is one question only
|
||||
expect( screen.getAllByLabelText( 'Neutral' ).length ).toBe( 1 );
|
||||
} );
|
||||
|
||||
it( 'should render default title if no title is provided', async () => {
|
||||
render(
|
||||
<CustomerFeedbackModal
|
||||
recordScoreCallback={ mockRecordScoreCallback }
|
||||
firstQuestion="First question"
|
||||
/>
|
||||
);
|
||||
|
||||
// Wait for the modal to render.
|
||||
await screen.findByRole( 'dialog' );
|
||||
expect(
|
||||
screen.queryByLabelText( 'Please share your feedback' )
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add manage_stock parent value to product variation type definition
|
|
@ -55,13 +55,21 @@ export interface ProductVariationImage {
|
|||
|
||||
export type ProductVariation = Omit<
|
||||
Product,
|
||||
'name' | 'slug' | 'attributes' | 'images'
|
||||
'name' | 'slug' | 'attributes' | 'images' | 'manage_stock'
|
||||
> & {
|
||||
attributes: ProductVariationAttribute[];
|
||||
/**
|
||||
* Variation image data.
|
||||
*/
|
||||
image?: ProductVariationImage;
|
||||
/**
|
||||
* Stock management at variation level. It can have a
|
||||
* 'parent' value if the parent product is managing
|
||||
* the stock at the time the variation was created.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
manage_stock: boolean | 'parent';
|
||||
};
|
||||
|
||||
type Query = Omit< ProductQuery, 'name' >;
|
||||
|
|
|
@ -16,7 +16,7 @@ const orderStatus = [
|
|||
[ 'Processing', 'wc-processing' ],
|
||||
[ 'On hold', 'wc-on-hold' ],
|
||||
[ 'Completed', 'wc-completed' ],
|
||||
[ 'Cancelled', 'wc-cancelled' ],
|
||||
[ 'Canceled', 'wc-cancelled' ],
|
||||
[ 'Refunded', 'wc-refunded' ],
|
||||
[ 'Failed', 'wc-failed' ],
|
||||
];
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { Component } from '@wordpress/element';
|
||||
import { first, last } from 'lodash';
|
||||
import { Spinner } from '@wordpress/components';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { getNewPath, getPersistedQuery } from '@woocommerce/navigation';
|
||||
|
||||
|
@ -49,7 +50,9 @@ export default class CategoryBreadcrumbs extends Component {
|
|||
|
||||
return category ? (
|
||||
<div className="woocommerce-table__breadcrumbs">
|
||||
{ this.getCategoryAncestors( category, categories ) }
|
||||
{ decodeEntities(
|
||||
this.getCategoryAncestors( category, categories )
|
||||
) }
|
||||
<Link
|
||||
href={ getNewPath(
|
||||
persistedQuery,
|
||||
|
@ -61,7 +64,7 @@ export default class CategoryBreadcrumbs extends Component {
|
|||
) }
|
||||
type="wc-admin"
|
||||
>
|
||||
{ category.name }
|
||||
{ decodeEntities( category.name ) }
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import { CheckboxControl, Icon } from '@wordpress/components';
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { chevronDown, chevronUp } from '@wordpress/icons';
|
||||
import { ProductCategory } from '@woocommerce/data';
|
||||
import { __experimentalSelectControlMenuItemProps as MenuItemProps } from '@woocommerce/components';
|
||||
|
@ -85,7 +86,7 @@ export const CategoryFieldItem: React.FC< CategoryFieldItemProps > = ( {
|
|||
<div className="woocommerce-category-field-dropdown__toggle-placeholder"></div>
|
||||
) }
|
||||
<CheckboxControl
|
||||
label={ item.data.name }
|
||||
label={ decodeEntities( item.data.name ) }
|
||||
checked={ selectedIds.includes( item.data.id ) }
|
||||
onChange={ () => item.data }
|
||||
/>
|
||||
|
|
|
@ -29,7 +29,11 @@ export default function useProductVariationNavigation( {
|
|||
const persistedQuery = getPersistedQuery();
|
||||
|
||||
return {
|
||||
actionHref: getNewPath( persistedQuery, `/product/${ product.id }` ),
|
||||
actionHref: getNewPath(
|
||||
persistedQuery,
|
||||
`/product/${ product.id }`,
|
||||
{}
|
||||
),
|
||||
prevHref: prevVariationId
|
||||
? getNewPath(
|
||||
persistedQuery,
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { WooProductFieldItem } from './woo-product-field-item';
|
||||
|
||||
type ProductFieldLayoutProps = {
|
||||
fieldName: string;
|
||||
categoryName: string;
|
||||
};
|
||||
|
||||
export const ProductFieldLayout: React.FC< ProductFieldLayoutProps > = ( {
|
||||
fieldName,
|
||||
categoryName,
|
||||
children,
|
||||
} ) => {
|
||||
return (
|
||||
<div className="product-field-layout">
|
||||
<WooProductFieldItem.Slot
|
||||
fieldName={ fieldName }
|
||||
categoryName={ categoryName }
|
||||
location="before"
|
||||
/>
|
||||
{ children }
|
||||
<WooProductFieldItem.Slot
|
||||
fieldName={ fieldName }
|
||||
categoryName={ categoryName }
|
||||
location="after"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -22,7 +22,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.woocommerce-product-form__field {
|
||||
.woocommerce-product-form__field:not(:first-child) {
|
||||
margin-top: $gap-large;
|
||||
|
||||
> .components-base-control {
|
||||
|
|
|
@ -8,7 +8,6 @@ import { FormSection } from '@woocommerce/components';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import './product-section-layout.scss';
|
||||
import { ProductFieldLayout } from './product-field-layout';
|
||||
|
||||
type ProductSectionLayoutProps = {
|
||||
title: string;
|
||||
|
@ -31,12 +30,7 @@ export const ProductSectionLayout: React.FC< ProductSectionLayoutProps > = ( {
|
|||
{ Children.map( children, ( child ) => {
|
||||
if ( isValidElement( child ) && child.props.onChange ) {
|
||||
return (
|
||||
<ProductFieldLayout
|
||||
fieldName={ child.props.name }
|
||||
categoryName={ title }
|
||||
>
|
||||
{ child }
|
||||
</ProductFieldLayout>
|
||||
<div className="product-field-layout">{ child }</div>
|
||||
);
|
||||
}
|
||||
return child;
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render } from '@testing-library/react';
|
||||
import { SlotFillProvider } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductFieldLayout } from '../product-field-layout';
|
||||
import { WooProductFieldItem } from '../woo-product-field-item';
|
||||
|
||||
describe( 'ProductFieldLayout', () => {
|
||||
beforeEach( () => {
|
||||
jest.clearAllMocks();
|
||||
} );
|
||||
|
||||
it( 'should allow adding extra fields before the field using slot fill', () => {
|
||||
const { queryByText } = render(
|
||||
<SlotFillProvider>
|
||||
<ProductFieldLayout
|
||||
fieldName="Name"
|
||||
categoryName="Product Details"
|
||||
>
|
||||
<div>Name field</div>
|
||||
</ProductFieldLayout>
|
||||
<div>
|
||||
<WooProductFieldItem
|
||||
fieldName="Name"
|
||||
categoryName="Product Details"
|
||||
location="before"
|
||||
>
|
||||
<div>New field</div>
|
||||
</WooProductFieldItem>
|
||||
</div>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
expect( queryByText( 'New field' ) ).toBeInTheDocument();
|
||||
expect( queryByText( 'New field' )?.nextSibling?.textContent ).toEqual(
|
||||
'Name field'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should allow adding extra fields after the field using slot fill', () => {
|
||||
const { queryByText } = render(
|
||||
<SlotFillProvider>
|
||||
<ProductFieldLayout
|
||||
fieldName="Name"
|
||||
categoryName="Product Details"
|
||||
>
|
||||
<div>Name field</div>
|
||||
</ProductFieldLayout>
|
||||
<div>
|
||||
<WooProductFieldItem
|
||||
fieldName="Name"
|
||||
categoryName="Product Details"
|
||||
location="after"
|
||||
>
|
||||
<div>New field</div>
|
||||
</WooProductFieldItem>
|
||||
</div>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
expect( queryByText( 'New field' ) ).toBeInTheDocument();
|
||||
expect( queryByText( 'Name field' )?.nextSibling?.textContent ).toEqual(
|
||||
'New field'
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should not render new slot fills when field name does not match', () => {
|
||||
const { queryByText } = render(
|
||||
<SlotFillProvider>
|
||||
<ProductFieldLayout
|
||||
fieldName="Name"
|
||||
categoryName="Product Details"
|
||||
>
|
||||
<div>Name field</div>
|
||||
</ProductFieldLayout>
|
||||
<div>
|
||||
<WooProductFieldItem
|
||||
fieldName="Description"
|
||||
categoryName="Product Details"
|
||||
location="after"
|
||||
>
|
||||
<div>New field</div>
|
||||
</WooProductFieldItem>
|
||||
</div>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
expect( queryByText( 'New field' ) ).not.toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should not render new slot fills when category name does not match', () => {
|
||||
const { queryByText } = render(
|
||||
<SlotFillProvider>
|
||||
<ProductFieldLayout
|
||||
fieldName="Name"
|
||||
categoryName="Product Details"
|
||||
>
|
||||
<div>Name field</div>
|
||||
</ProductFieldLayout>
|
||||
<div>
|
||||
<WooProductFieldItem
|
||||
fieldName="Name"
|
||||
categoryName="Images"
|
||||
location="after"
|
||||
>
|
||||
<div>New field</div>
|
||||
</WooProductFieldItem>
|
||||
</div>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
expect( queryByText( 'New field' ) ).not.toBeInTheDocument();
|
||||
} );
|
||||
} );
|
|
@ -8,28 +8,10 @@ import { render } from '@testing-library/react';
|
|||
*/
|
||||
import { ProductSectionLayout } from '../product-section-layout';
|
||||
|
||||
jest.mock( '../product-field-layout', () => {
|
||||
const productFieldLayoutMock: React.FC< {
|
||||
fieldName: string;
|
||||
categoryName: string;
|
||||
} > = ( { children, fieldName, categoryName } ) => {
|
||||
return (
|
||||
<div className="product-field-layout-mock">
|
||||
<span>fieldName: { fieldName }</span>
|
||||
<span>categoryName: { categoryName }</span>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return {
|
||||
ProductFieldLayout: productFieldLayoutMock,
|
||||
};
|
||||
} );
|
||||
|
||||
const SampleInputField: React.FC< { name: string; onChange: () => void } > = ( {
|
||||
name,
|
||||
} ) => {
|
||||
return <div>smaple-input-field-{ name }</div>;
|
||||
return <div>sample-input-field-{ name }</div>;
|
||||
};
|
||||
|
||||
describe( 'ProductSectionLayout', () => {
|
||||
|
@ -59,12 +41,9 @@ describe( 'ProductSectionLayout', () => {
|
|||
</ProductSectionLayout>
|
||||
);
|
||||
|
||||
expect( queryByText( 'fieldName: name' ) ).toBeInTheDocument();
|
||||
expect( queryAllByText( 'categoryName: Title' ).length ).toEqual( 2 );
|
||||
|
||||
expect( queryByText( 'smaple-input-field-name' ) ).toBeInTheDocument();
|
||||
expect( queryByText( 'sample-input-field-name' ) ).toBeInTheDocument();
|
||||
expect(
|
||||
queryByText( 'smaple-input-field-description' )
|
||||
queryByText( 'sample-input-field-description' )
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Slot, Fill } from '@wordpress/components';
|
||||
import { snakeCase } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { createOrderedChildren, sortFillsByOrder } from '~/utils';
|
||||
|
||||
// TODO: move this to a published JS package once ready.
|
||||
|
||||
/**
|
||||
* Create a Fill for extensions to add items to the Product edit page.
|
||||
*
|
||||
* @slotFill WooProductFieldItem
|
||||
* @scope woocommerce-admin
|
||||
* @example
|
||||
* const MyProductDetailsFieldItem = () => (
|
||||
* <WooProductFieldItem fieldName="name" categoryName="Product details" location="after">My header item</WooProductFieldItem>
|
||||
* );
|
||||
*
|
||||
* registerPlugin( 'my-extension', {
|
||||
* render: MyProductDetailsFieldItem,
|
||||
* scope: 'woocommerce-admin',
|
||||
* } );
|
||||
* @param {Object} param0
|
||||
* @param {Array} param0.children - Node children.
|
||||
* @param {string} param0.fieldName - Field name.
|
||||
* @param {string} param0.categoryName - Category name.
|
||||
* @param {number} param0.order - Order of Fill component.
|
||||
* @param {string} param0.location - Location before or after.
|
||||
*/
|
||||
export const WooProductFieldItem: React.FC< {
|
||||
fieldName: string;
|
||||
categoryName: string;
|
||||
order?: number;
|
||||
location: 'before' | 'after';
|
||||
} > & {
|
||||
Slot: React.FC<
|
||||
Slot.Props & {
|
||||
fieldName: string;
|
||||
categoryName: string;
|
||||
location: 'before' | 'after';
|
||||
}
|
||||
>;
|
||||
} = ( { children, fieldName, categoryName, location, order = 1 } ) => {
|
||||
const categoryKey = snakeCase( categoryName );
|
||||
const fieldKey = snakeCase( fieldName );
|
||||
return (
|
||||
<Fill
|
||||
name={ `woocommerce_product_${ categoryKey }_${ fieldKey }_${ location }` }
|
||||
>
|
||||
{ ( fillProps: Fill.Props ) => {
|
||||
return createOrderedChildren( children, order, fillProps );
|
||||
} }
|
||||
</Fill>
|
||||
);
|
||||
};
|
||||
|
||||
WooProductFieldItem.Slot = ( {
|
||||
fillProps,
|
||||
fieldName,
|
||||
categoryName,
|
||||
location,
|
||||
} ) => {
|
||||
const categoryKey = snakeCase( categoryName );
|
||||
const fieldKey = snakeCase( fieldName );
|
||||
return (
|
||||
<Slot
|
||||
name={ `woocommerce_product_${ categoryKey }_${ fieldKey }_${ location }` }
|
||||
fillProps={ fillProps }
|
||||
>
|
||||
{ sortFillsByOrder }
|
||||
</Slot>
|
||||
);
|
||||
};
|
|
@ -8,7 +8,7 @@ export const ProductFormTab: React.FC< {
|
|||
name: string;
|
||||
title: string;
|
||||
children: JSX.Element | JSX.Element[] | string;
|
||||
} > = ( { name, title, children } ) => {
|
||||
} > = ( { name, children } ) => {
|
||||
const classes = classnames(
|
||||
'woocommerce-product-form-tab',
|
||||
'woocommerce-product-form-tab__' + name
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Form, FormRef } from '@woocommerce/components';
|
||||
import {
|
||||
Form,
|
||||
FormRef,
|
||||
__experimentalWooProductSectionItem as WooProductSectionItem,
|
||||
} from '@woocommerce/components';
|
||||
import { PartialProduct, Product } from '@woocommerce/data';
|
||||
import { Ref } from 'react';
|
||||
|
||||
|
@ -47,6 +51,7 @@ export const ProductForm: React.FC< {
|
|||
<ProductDetailsSection />
|
||||
<ImagesSection />
|
||||
<AttributesSection />
|
||||
<WooProductSectionItem.Slot location="tab/general" />
|
||||
</ProductFormTab>
|
||||
<ProductFormTab
|
||||
name="pricing"
|
||||
|
|
|
@ -36,8 +36,14 @@ export const ProductVariationFormActions: React.FC = () => {
|
|||
const onSave = async () => {
|
||||
setIsSaving( true );
|
||||
updateProductVariation< Promise< ProductVariation > >(
|
||||
{ id: variationId, product_id: productId },
|
||||
values
|
||||
{ id: variationId, product_id: productId, context: 'edit' },
|
||||
{
|
||||
...values,
|
||||
manage_stock:
|
||||
values.manage_stock === 'parent'
|
||||
? undefined
|
||||
: values?.manage_stock,
|
||||
}
|
||||
)
|
||||
.then( () => {
|
||||
createNotice(
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
useFormContext,
|
||||
__experimentalRichTextEditor as RichTextEditor,
|
||||
__experimentalTooltip as Tooltip,
|
||||
__experimentalWooProductFieldItem as WooProductFieldItem,
|
||||
} from '@woocommerce/components';
|
||||
import interpolateComponents from '@automattic/interpolate-components';
|
||||
import {
|
||||
|
@ -241,6 +242,7 @@ export const ProductDetailsSection: React.FC = () => {
|
|||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
<WooProductFieldItem.Slot section="details" />
|
||||
</CardBody>
|
||||
</Card>
|
||||
</ProductSectionLayout>
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { Link, useFormContext } from '@woocommerce/components';
|
||||
import { Product, ProductAttribute } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -12,6 +13,28 @@ import { ProductSectionLayout } from '../layout/product-section-layout';
|
|||
import { Variations } from '../fields/variations';
|
||||
|
||||
export const ProductVariationsSection: React.FC = () => {
|
||||
const {
|
||||
getInputProps,
|
||||
values: { id: productId },
|
||||
} = useFormContext< Product >();
|
||||
|
||||
const { value: attributes }: { value: ProductAttribute[] } = getInputProps(
|
||||
'attributes',
|
||||
{
|
||||
productId,
|
||||
}
|
||||
);
|
||||
|
||||
const options = attributes
|
||||
? attributes.filter(
|
||||
( attribute: ProductAttribute ) => attribute.variation
|
||||
)
|
||||
: [];
|
||||
|
||||
if ( options.length === 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ProductSectionLayout
|
||||
title={ __( 'Variations', 'woocommerce' ) }
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
import { useDispatch } from '@wordpress/data';
|
||||
import { ITEMS_STORE_NAME } from '@woocommerce/data';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { getNewPath, navigateTo } from '@woocommerce/navigation';
|
||||
import { loadExperimentAssignment } from '@woocommerce/explat';
|
||||
import moment from 'moment';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
|
@ -25,6 +28,21 @@ export const useCreateProductByType = () => {
|
|||
}
|
||||
|
||||
setIsRequesting( true );
|
||||
|
||||
if ( type === 'physical' ) {
|
||||
const momentDate = moment().utc();
|
||||
const year = momentDate.format( 'YYYY' );
|
||||
const month = momentDate.format( 'MM' );
|
||||
const assignment = await loadExperimentAssignment(
|
||||
`woocommerce_product_creation_experience_${ year }${ month }_v1`
|
||||
);
|
||||
|
||||
if ( assignment.variationName === 'treatment' ) {
|
||||
navigateTo( { url: getNewPath( {}, '/add-product', {} ) } );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const data: {
|
||||
id?: number;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add a wp cli command for activating live branches.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Enable the live branches feature.
|
|
@ -6,7 +6,7 @@
|
|||
"license": "GPL-3.0-or-later",
|
||||
"prefer-stable": true,
|
||||
"minimum-stability": "dev",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.0",
|
||||
"require": {
|
||||
"composer/installers": "~1.7"
|
||||
},
|
||||
|
|
|
@ -153,6 +153,13 @@ Copy and paste the system status report from **WooCommerce > System Status** in
|
|||
* @return string
|
||||
*/
|
||||
protected function construct_ssr() {
|
||||
// This function depends on the WC core global being available. Sometimes, such as when we deactivate
|
||||
// WC to install a live branches version, WC will not be available and cause a crash if we don't exit early
|
||||
// here.
|
||||
if ( ! class_exists( 'WC' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( version_compare( WC()->version, '3.6', '<' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
@ -332,7 +339,7 @@ Copy and paste the system status report from **WooCommerce > System Status** in
|
|||
$items_to_remove = array( 'wc-beta-tester-settings', 'wc-beta-tester-version-picker', 'wc-beta-tester' );
|
||||
if ( isset( $submenu['plugins.php'] ) ) {
|
||||
foreach ( $submenu['plugins.php'] as $key => $menu ) {
|
||||
if ( in_array( $menu[2], $items_to_remove ) ) {
|
||||
if ( in_array( $menu[2], $items_to_remove, true ) ) {
|
||||
unset( $submenu['plugins.php'][ $key ] );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
/**
|
||||
* WooCommerce Beta Tester CLI controls
|
||||
*
|
||||
* @package WC_Beta_Tester
|
||||
*/
|
||||
|
||||
if ( ! class_exists( 'WP_CLI_Command' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control your local WooCommerce Beta Tester plugin.
|
||||
*/
|
||||
class WC_Beta_Tester_CLI extends WP_CLI_Command {
|
||||
|
||||
/**
|
||||
* Install a live branch of the WooCommerce plugin
|
||||
*
|
||||
* ## Options
|
||||
* <branch>
|
||||
* : The branch to install.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* wp wc-beta-tester install update/some-branch
|
||||
*
|
||||
* @param array $args Arguments passed to CLI.
|
||||
*/
|
||||
public function install( $args ) {
|
||||
$installer = new WC_Beta_Tester_Live_Branches_Installer();
|
||||
|
||||
$branch = $args[0];
|
||||
|
||||
$info = $installer->get_branch_info_from_manifest( $branch );
|
||||
|
||||
if ( ! $info ) {
|
||||
WP_CLI::error( "Could not find branch $branch in manifest" );
|
||||
} else {
|
||||
$install_result = $installer->install( $info->download_url, $info->branch, $info->version );
|
||||
|
||||
if ( is_wp_error( $install_result ) ) {
|
||||
WP_CLI::error( $install_result->get_error_message() );
|
||||
}
|
||||
|
||||
WP_CLI::success( "Installed $branch" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate WooCommerce.
|
||||
*
|
||||
* ## Examples
|
||||
* wp wc-beta-tester deactivate_woocommerce
|
||||
*/
|
||||
public function deactivate_woocommerce() {
|
||||
$installer = new WC_Beta_Tester_Live_Branches_Installer();
|
||||
$installer->deactivate_woocommerce();
|
||||
|
||||
WP_CLI::success( 'Deactivated WooCommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a live branch of the WooCommerce plugin.
|
||||
*
|
||||
* ## Options
|
||||
* <branch>
|
||||
* : The branch to activate.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* wp wc-beta-tester activate update/some-branch*
|
||||
*
|
||||
* @param array $args Arguments passed to CLI.
|
||||
*/
|
||||
public function activate( $args ) {
|
||||
$installer = new WC_Beta_Tester_Live_Branches_Installer();
|
||||
$branch = $args[0];
|
||||
$info = $installer->get_branch_info_from_manifest( $branch );
|
||||
|
||||
if ( ! $info ) {
|
||||
WP_CLI::error( "Could not find branch $branch in manifest" );
|
||||
} else {
|
||||
$installer->activate( $info->version );
|
||||
|
||||
WP_CLI::success( "Activated $branch" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,6 +46,26 @@ class WC_Beta_Tester_Live_Branches_Installer {
|
|||
return $wp_filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the download url of a WooCommerce plugin version from the manifest.
|
||||
*
|
||||
* @param string $branch The name of the branch.
|
||||
*/
|
||||
public function get_branch_info_from_manifest( $branch ) {
|
||||
$response = wp_remote_get( 'https://betadownload.jetpack.me/woocommerce-branches.json' );
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
|
||||
$obj = json_decode( $body );
|
||||
|
||||
foreach ( $obj->pr as $key => $value ) {
|
||||
if ( $value->branch === $branch ) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a WooCommerce plugin version by download url.
|
||||
*
|
||||
|
|
|
@ -18,9 +18,7 @@ class WC_Beta_Tester_Live_Branches {
|
|||
add_action( 'admin_enqueue_scripts', array( $this, 'register_scripts' ) );
|
||||
|
||||
// By the time this code runs it appears too late to hook into `admin_menu`.
|
||||
|
||||
// NOTE - We don't have feature flags, so add the following code to enable it
|
||||
// in development: `$this->register_page()`.
|
||||
$this->register_page();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"url": "git://github.com/woocommerce/woocommerce-beta-tester.git"
|
||||
},
|
||||
"title": "WooCommerce Beta Tester",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.0",
|
||||
"homepage": "http://github.com/woocommerce/woocommerce-beta-tester",
|
||||
"devDependencies": {
|
||||
"@types/react": "^17.0.2",
|
||||
|
|
|
@ -3,7 +3,7 @@ Contributors: automattic, bor0, claudiosanches, claudiulodro, kloon, mikejolley,
|
|||
Tags: woocommerce, woo commerce, beta, beta tester, bleeding edge, testing
|
||||
Requires at least: 4.7
|
||||
Tested up to: 6.0
|
||||
Stable tag: 2.1.0
|
||||
Stable tag: 2.2.0
|
||||
License: GPLv3
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ const BranchInfo = ( { branch }: { branch: Branch } ) => {
|
|||
|
||||
const WooCommerceVersionInfo = () => {
|
||||
// @ts-ignore
|
||||
const version = window?.wc?.WC_VERSION || 'unknown';
|
||||
const version = window?.wc?.wcSettings?.WC_VERSION || 'unknown';
|
||||
|
||||
return (
|
||||
<p>
|
||||
|
@ -136,6 +136,8 @@ export const BranchList = ( { branches }: { branches: Branch[] } ) => {
|
|||
uninstalledBranches[ 0 ]
|
||||
);
|
||||
|
||||
const installedBranchesExist = !! installedBranches.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card elevation={ 3 } css={ cardStyle }>
|
||||
|
@ -188,7 +190,7 @@ export const BranchList = ( { branches }: { branches: Branch[] } ) => {
|
|||
</CardBody>
|
||||
<CardFooter></CardFooter>
|
||||
</Card>
|
||||
{ installedBranches.length && (
|
||||
{ installedBranchesExist && (
|
||||
<Card elevation={ 3 } css={ cardStyle }>
|
||||
<CardHeader>
|
||||
<h2>Other Installed Branches</h2>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue