Merge branch 'trunk' into bump-required-php-to-7.3
This commit is contained in:
commit
74d0841eaf
|
@ -0,0 +1,37 @@
|
|||
name: Run API tests
|
||||
description: Runs the WooCommerce Core API tests and generates Allure report.
|
||||
permissions: {}
|
||||
|
||||
inputs:
|
||||
report-name:
|
||||
description: Name of Allure report to be generated.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Run API tests.
|
||||
id: run-api-tests
|
||||
working-directory: plugins/woocommerce
|
||||
shell: bash
|
||||
env:
|
||||
BASE_URL: http://localhost:8086
|
||||
USER_KEY: admin
|
||||
USER_SECRET: password
|
||||
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js
|
||||
|
||||
- name: Generate Test report.
|
||||
if: success() || ( failure() && steps.run-api-tests.conclusion == 'failure' )
|
||||
working-directory: plugins/woocommerce
|
||||
shell: bash
|
||||
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||
|
||||
- name: Archive test report
|
||||
if: success() || ( failure() && steps.run-api-tests.conclusion == 'failure' )
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ inputs.report-name }}
|
||||
path: |
|
||||
${{ env.ALLURE_RESULTS_DIR }}
|
||||
${{ env.ALLURE_REPORT_DIR }}
|
||||
retention-days: 20
|
|
@ -0,0 +1,41 @@
|
|||
name: Run E2E tests
|
||||
description: Runs the WooCommerce Core E2E tests and generates Allure report.
|
||||
permissions: {}
|
||||
|
||||
inputs:
|
||||
report-name:
|
||||
description: Name of Allure report to be generated.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Download and install Chromium browser.
|
||||
working-directory: plugins/woocommerce
|
||||
shell: bash
|
||||
run: pnpm exec playwright install chromium
|
||||
|
||||
- name: Run E2E tests.
|
||||
id: run-e2e-tests
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
USE_WP_ENV: 1
|
||||
working-directory: plugins/woocommerce
|
||||
shell: bash
|
||||
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js
|
||||
|
||||
- name: Generate Test report.
|
||||
if: success() || ( failure() && steps.run-e2e-tests.conclusion == 'failure' )
|
||||
working-directory: plugins/woocommerce
|
||||
shell: bash
|
||||
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
|
||||
|
||||
- name: Archive test report
|
||||
if: success() || ( failure() && steps.run-e2e-tests.conclusion == 'failure' )
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ inputs.report-name }}
|
||||
path: |
|
||||
${{ env.ALLURE_RESULTS_DIR }}
|
||||
${{ env.ALLURE_REPORT_DIR }}
|
||||
retention-days: 20
|
|
@ -0,0 +1,17 @@
|
|||
name: Run k6 performance tests
|
||||
description: Runs the WooCommerce Core k6 performance tests.
|
||||
permissions: {}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install k6
|
||||
shell: bash
|
||||
run: |
|
||||
curl https://github.com/grafana/k6/releases/download/v0.33.0/k6-v0.33.0-linux-amd64.tar.gz -L | tar xvz --strip-components 1
|
||||
|
||||
- name: Run k6 performance tests
|
||||
id: run-k6-tests
|
||||
shell: bash
|
||||
run: |
|
||||
./k6 run plugins/woocommerce/tests/performance/tests/gh-action-pr-requests.js
|
|
@ -0,0 +1,29 @@
|
|||
name: Setup local test environment
|
||||
description: Set up a wp-env testing environment
|
||||
permissions: {}
|
||||
|
||||
inputs:
|
||||
test-type:
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- e2e
|
||||
- api
|
||||
- k6
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Load docker images and start containers for E2E or API tests
|
||||
if: ( inputs.test-type == 'e2e' ) || ( inputs.test-type == 'api' )
|
||||
working-directory: plugins/woocommerce
|
||||
shell: bash
|
||||
run: pnpm run env:test
|
||||
|
||||
- name: Load docker images and start containers for k6 performance tests
|
||||
if: inputs.test-type == 'k6'
|
||||
working-directory: plugins/woocommerce
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm env:dev --filter=woocommerce
|
||||
pnpm env:performance-init --filter=woocommerce
|
|
@ -0,0 +1,41 @@
|
|||
name: Send Slack alert on PR merge test failure
|
||||
description: Send a Slack alert when automated tests failed on trunk after PR merge.
|
||||
permissions: {}
|
||||
|
||||
inputs:
|
||||
slack-bot-token:
|
||||
required: true
|
||||
channel-id:
|
||||
required: true
|
||||
test-type:
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- E2E
|
||||
- API
|
||||
- k6
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Compose Slack message
|
||||
id: compose-slack-message
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
SHA: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
TEST_TYPE: ${{ inputs.test-type }}
|
||||
with:
|
||||
script: |
|
||||
const script = require('./.github/actions/tests/slack-alert-on-pr-merge/scripts/compose-slack-message.js')
|
||||
const slackMessage = script()
|
||||
core.setOutput('slack-message', slackMessage)
|
||||
|
||||
- name: Send Slack alert
|
||||
uses: slackapi/slack-github-action@v1.23.0
|
||||
env:
|
||||
SLACK_BOT_TOKEN: ${{ inputs.slack-bot-token }}
|
||||
with:
|
||||
channel-id: ${{ inputs.channel-id }}
|
||||
payload: ${{ steps.compose-slack-message.outputs.slack-message }}
|
114
.github/actions/tests/slack-alert-on-pr-merge/scripts/compose-slack-message.js
vendored
Normal file
114
.github/actions/tests/slack-alert-on-pr-merge/scripts/compose-slack-message.js
vendored
Normal file
|
@ -0,0 +1,114 @@
|
|||
module.exports = () => {
|
||||
const {
|
||||
GITHUB_BASE_REF,
|
||||
GITHUB_RUN_ID,
|
||||
PR_NUMBER,
|
||||
PR_TITLE,
|
||||
SHA,
|
||||
TEST_TYPE,
|
||||
} = process.env;
|
||||
|
||||
// Slack message blocks
|
||||
const blocks = [];
|
||||
const dividerBlock = {
|
||||
type: 'divider',
|
||||
};
|
||||
const introBlock = {
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text: `${ TEST_TYPE } tests failed on \`${ GITHUB_BASE_REF }\` after merging PR <https://github.com/woocommerce/woocommerce/pull/${ PR_NUMBER }|#${ PR_NUMBER }>`,
|
||||
},
|
||||
};
|
||||
const prTitleBlock = {
|
||||
type: 'header',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: PR_TITLE,
|
||||
emoji: true,
|
||||
},
|
||||
};
|
||||
const prButtonBlock = {
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'View pull request :pr-merged:',
|
||||
emoji: true,
|
||||
},
|
||||
value: 'view_pr',
|
||||
url: `https://github.com/woocommerce/woocommerce/pull/${ PR_NUMBER }`,
|
||||
action_id: 'view-pr',
|
||||
},
|
||||
],
|
||||
};
|
||||
const mergeCommitBlock = {
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: `View merge commit ${ SHA.substring(
|
||||
0,
|
||||
7
|
||||
) } :alphabet-yellow-hash:`,
|
||||
emoji: true,
|
||||
},
|
||||
value: 'view_commit',
|
||||
url: `https://github.com/woocommerce/woocommerce/commit/${ SHA }`,
|
||||
action_id: 'view-commit',
|
||||
},
|
||||
],
|
||||
};
|
||||
const githubBlock = {
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'View GitHub run log :github:',
|
||||
emoji: true,
|
||||
},
|
||||
value: 'view_github',
|
||||
url: `https://github.com/woocommerce/woocommerce/actions/runs/${ GITHUB_RUN_ID }`,
|
||||
action_id: 'view-github',
|
||||
},
|
||||
],
|
||||
};
|
||||
const reportBlock = {
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'button',
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'View test report :colorful-bar-chart:',
|
||||
emoji: true,
|
||||
},
|
||||
value: 'view_report',
|
||||
url: `https://woocommerce.github.io/woocommerce-test-reports/pr-merge/${ PR_NUMBER }/${ TEST_TYPE.toLowerCase() }`,
|
||||
action_id: 'view-report',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Assemble blocks
|
||||
blocks.push( dividerBlock );
|
||||
blocks.push( introBlock );
|
||||
blocks.push( prTitleBlock );
|
||||
blocks.push( prButtonBlock );
|
||||
blocks.push( mergeCommitBlock );
|
||||
blocks.push( githubBlock );
|
||||
|
||||
if ( [ 'e2e', 'api' ].includes( TEST_TYPE.toLowerCase() ) ) {
|
||||
blocks.push( reportBlock );
|
||||
}
|
||||
|
||||
blocks.push( dividerBlock );
|
||||
|
||||
return { blocks };
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
name: Upload Allure files to bucket
|
||||
description: Upload Allure files to bucket.
|
||||
permissions: {}
|
||||
|
||||
inputs:
|
||||
destination-dir:
|
||||
description: Directory under the "artifacts" S3 folder to which the Allure files would be uploaded.
|
||||
required: true
|
||||
aws-region:
|
||||
required: true
|
||||
aws-access-key-id:
|
||||
required: true
|
||||
aws-secret-access-key:
|
||||
required: true
|
||||
s3-bucket:
|
||||
required: true
|
||||
include-allure-results:
|
||||
dafault: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1-node16
|
||||
with:
|
||||
aws-region: ${{ inputs.aws-region }}
|
||||
aws-access-key-id: ${{ inputs.aws-access-key-id }}
|
||||
aws-secret-access-key: ${{ inputs.aws-secret-access-key }}
|
||||
|
||||
- name: Upload 'allure-results' folder
|
||||
if: inputs.include-allure-results == true
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Uploading allure-results folder..."
|
||||
aws s3 sync ${{ env.ALLURE_RESULTS_DIR }} \
|
||||
${{ inputs.s3-bucket }}/artifacts/${{ github.run_id }}/${{ inputs.destination-dir }}/allure-results \
|
||||
--quiet
|
||||
echo "Done"
|
||||
|
||||
- name: Upload 'allure-report' folder
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Uploading allure-report folder..."
|
||||
aws s3 sync ${{ env.ALLURE_REPORT_DIR }} \
|
||||
${{ inputs.s3-bucket }}/artifacts/${{ github.run_id }}/${{ inputs.destination-dir }}/allure-report \
|
||||
--quiet
|
||||
echo "Done"
|
|
@ -173,7 +173,7 @@ jobs:
|
|||
return await script( { core } )
|
||||
|
||||
- name: Find PR comment by github-actions[bot]
|
||||
uses: peter-evans/find-comment@v2
|
||||
uses: peter-evans/find-comment@034abe94d3191f9c89d870519735beae326f2bdb
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
@ -181,7 +181,7 @@ jobs:
|
|||
body-includes: Test Results Summary
|
||||
|
||||
- name: Create or update PR comment
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
|
|
@ -219,7 +219,7 @@ jobs:
|
|||
return await script( { core } )
|
||||
|
||||
- name: Find PR comment by github-actions[bot]
|
||||
uses: peter-evans/find-comment@v2
|
||||
uses: peter-evans/find-comment@034abe94d3191f9c89d870519735beae326f2bdb
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
@ -227,7 +227,7 @@ jobs:
|
|||
body-includes: Test Results Summary
|
||||
|
||||
- name: Create or update PR comment
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
name: Remind reviewers to also review the testing instructions.
|
||||
on:
|
||||
pull_request:
|
||||
types: [review_requested]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
add-testing-instructions-review-comment:
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Get the username of requested reviewers
|
||||
id: get_reviewer_username
|
||||
run: |
|
||||
# Retrieves the username of all reviewers and stores them in a comma-separated list
|
||||
reviewers=$(echo '${{ toJson(github.event.pull_request.requested_reviewers[*].login) }}' | jq -r 'map("@\(.)") | join(", ")')
|
||||
echo "REVIEWERS=$reviewers" >> $GITHUB_ENV
|
||||
|
||||
- name: Get the name of requested teams
|
||||
id: get_team_name
|
||||
run: |
|
||||
# Retrieves the name of all teams asked for review and stores them in a comma-separated list
|
||||
teams=$(echo '${{ toJson(github.event.pull_request.requested_teams[*].slug) }}' | jq -r 'map("@woocommerce/\(.)") | join(", ")')
|
||||
echo "TEAMS=$teams" >> $GITHUB_ENV
|
||||
|
||||
- name: Find the comment by github-actions[bot] asking for reviewing the testing instructions
|
||||
uses: peter-evans/find-comment@034abe94d3191f9c89d870519735beae326f2bdb
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: please make sure to review the testing instructions
|
||||
|
||||
- name: Create or update PR comment asking for reviewers to review the testing instructions
|
||||
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d
|
||||
with:
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
Hi ${{ env.REVIEWERS }}, ${{ env.TEAMS }}
|
||||
|
||||
Apart from reviewing the code changes, please make sure to review the testing instructions as well.
|
||||
|
||||
You can follow this guide to find out what good testing instructions should look like:
|
||||
https://github.com/woocommerce/woocommerce/wiki/Writing-high-quality-testing-instructions
|
||||
edit-mode: replace
|
|
@ -0,0 +1,168 @@
|
|||
name: Run tests against trunk after PR merge
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
api:
|
||||
name: Run API tests
|
||||
runs-on: ubuntu-20.04
|
||||
if: (github.event.pull_request.merged == true) && (github.event.pull_request.base.ref == 'trunk')
|
||||
permissions:
|
||||
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
|
||||
ARTIFACT_NAME: api-pr-merge-${{ github.event.pull_request.number }}-run-${{ github.run_number }}
|
||||
steps:
|
||||
- name: Checkout merge commit on trunk
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
build-filters: woocommerce
|
||||
|
||||
- name: Setup local test environment
|
||||
uses: ./.github/actions/tests/setup-local-test-environment
|
||||
with:
|
||||
test-type: api
|
||||
|
||||
- name: Run API tests
|
||||
id: run-api-composite-action
|
||||
uses: ./.github/actions/tests/run-api-tests
|
||||
with:
|
||||
report-name: ${{ env.ARTIFACT_NAME }}
|
||||
|
||||
- name: Upload Allure files to bucket
|
||||
if: success() || ( failure() && steps.run-api-composite-action.conclusion == 'failure' )
|
||||
uses: ./.github/actions/tests/upload-allure-files-to-bucket
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
|
||||
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
|
||||
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
|
||||
destination-dir: ${{ env.ARTIFACT_NAME }}
|
||||
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
|
||||
|
||||
- name: Publish Allure report
|
||||
if: success() || ( failure() && steps.run-api-composite-action.conclusion == 'failure' )
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
|
||||
run: |
|
||||
gh workflow run publish-test-reports-trunk-merge.yml \
|
||||
-f run_id=${{ github.run_id }} \
|
||||
-f artifact=${{ env.ARTIFACT_NAME }} \
|
||||
-f pr_number=${{ github.event.pull_request.number }} \
|
||||
-f test_type="api" \
|
||||
--repo woocommerce/woocommerce-test-reports
|
||||
|
||||
- name: Send Slack alert on test failure
|
||||
if: failure() && steps.run-api-composite-action.conclusion == 'failure'
|
||||
uses: ./.github/actions/tests/slack-alert-on-pr-merge
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.E2E_SLACK_TOKEN }}
|
||||
channel-id: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }}
|
||||
test-type: API
|
||||
|
||||
e2e:
|
||||
name: Run E2E tests
|
||||
needs: [api]
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
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
|
||||
ARTIFACT_NAME: e2e-pr-merge-${{ github.event.pull_request.number }}-run-${{ github.run_number }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
build-filters: woocommerce
|
||||
|
||||
- name: Setup local test environment
|
||||
uses: ./.github/actions/tests/setup-local-test-environment
|
||||
with:
|
||||
test-type: e2e
|
||||
|
||||
- name: Run E2E tests
|
||||
id: run-e2e-composite-action
|
||||
timeout-minutes: 60
|
||||
uses: ./.github/actions/tests/run-e2e-tests
|
||||
env:
|
||||
E2E_MAX_FAILURES: 15
|
||||
with:
|
||||
report-name: ${{ env.ARTIFACT_NAME }}
|
||||
|
||||
- name: Upload Allure files to bucket
|
||||
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
|
||||
uses: ./.github/actions/tests/upload-allure-files-to-bucket
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
|
||||
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
|
||||
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
|
||||
destination-dir: ${{ env.ARTIFACT_NAME }}
|
||||
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
|
||||
include-allure-results: false
|
||||
|
||||
- name: Publish Allure report
|
||||
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
|
||||
run: |
|
||||
gh workflow run publish-test-reports-trunk-merge.yml \
|
||||
-f run_id=${{ github.run_id }} \
|
||||
-f artifact=${{ env.ARTIFACT_NAME }} \
|
||||
-f pr_number=${{ github.event.pull_request.number }} \
|
||||
-f test_type="e2e" \
|
||||
--repo woocommerce/woocommerce-test-reports
|
||||
|
||||
- name: Send Slack alert on test failure
|
||||
if: failure() && steps.run-e2e-composite-action.conclusion == 'failure'
|
||||
uses: ./.github/actions/tests/slack-alert-on-pr-merge
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.E2E_SLACK_TOKEN }}
|
||||
channel-id: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }}
|
||||
test-type: E2E
|
||||
|
||||
k6:
|
||||
name: Run k6 Performance tests
|
||||
needs: [api]
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
|
||||
- name: Setup WooCommerce Monorepo
|
||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
|
||||
- name: Setup local test environment
|
||||
uses: ./.github/actions/tests/setup-local-test-environment
|
||||
with:
|
||||
test-type: k6
|
||||
|
||||
- name: Run k6 performance tests
|
||||
id: run-k6-composite-action
|
||||
uses: './.github/actions/tests/run-k6-tests'
|
||||
|
||||
- name: Send Slack alert on test failure
|
||||
if: failure() && steps.run-k6-composite-action.conclusion == 'failure'
|
||||
uses: ./.github/actions/tests/slack-alert-on-pr-merge
|
||||
with:
|
||||
slack-bot-token: ${{ secrets.E2E_SLACK_TOKEN }}
|
||||
channel-id: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }}
|
||||
test-type: k6
|
|
@ -1,5 +1,13 @@
|
|||
== Changelog ==
|
||||
|
||||
= 7.5.1 2023-03-21 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Fix - Fix no enforcing of min/max limits in quantity selector of variable products. [#36871](https://github.com/woocommerce/woocommerce/pull/36871)
|
||||
* Dev - Update column definitions with synonymous types to prevent dbDelta from trying to ALTER them on each install. [#37277](https://github.com/woocommerce/woocommerce/pull/37277)
|
||||
* Update - Update WooCommerce Blocks to 9.6.6. [#37298](https://github.com/woocommerce/woocommerce/pull/37298)
|
||||
|
||||
= 7.5.0 2023-03-14 =
|
||||
|
||||
**WooCommerce**
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Adding support for modifying fill name to WooHeaderItem.
|
|
@ -11,6 +11,20 @@ import {
|
|||
|
||||
export const WC_HEADER_SLOT_NAME = 'woocommerce_header_item';
|
||||
|
||||
/**
|
||||
* Get the slot fill name for the generic header slot or a specific header if provided.
|
||||
*
|
||||
* @param name Name of the specific header.
|
||||
* @return string
|
||||
*/
|
||||
const getSlotFillName = ( name?: string ) => {
|
||||
if ( ! name || ! name.length ) {
|
||||
return WC_HEADER_SLOT_NAME;
|
||||
}
|
||||
|
||||
return `${ WC_HEADER_SLOT_NAME }/${ name }`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a Fill for extensions to add items to the WooCommerce Admin header.
|
||||
*
|
||||
|
@ -26,17 +40,19 @@ export const WC_HEADER_SLOT_NAME = 'woocommerce_header_item';
|
|||
* scope: 'woocommerce-admin',
|
||||
* } );
|
||||
* @param {Object} param0
|
||||
* @param {Array} param0.name - Header name.
|
||||
* @param {Array} param0.children - Node children.
|
||||
* @param {Array} param0.order - Node order.
|
||||
*/
|
||||
export const WooHeaderItem: React.FC< {
|
||||
name?: string;
|
||||
children?: React.ReactNode;
|
||||
order?: number;
|
||||
} > & {
|
||||
Slot: React.FC< Slot.Props >;
|
||||
} = ( { children, order = 1 } ) => {
|
||||
Slot: React.FC< Slot.Props & { name?: string } >;
|
||||
} = ( { children, order = 1, name = '' } ) => {
|
||||
return (
|
||||
<Fill name={ WC_HEADER_SLOT_NAME }>
|
||||
<Fill name={ getSlotFillName( name ) }>
|
||||
{ ( fillProps: Fill.Props ) => {
|
||||
return createOrderedChildren( children, order, fillProps );
|
||||
} }
|
||||
|
@ -44,8 +60,8 @@ export const WooHeaderItem: React.FC< {
|
|||
);
|
||||
};
|
||||
|
||||
WooHeaderItem.Slot = ( { fillProps } ) => (
|
||||
<Slot name={ WC_HEADER_SLOT_NAME } fillProps={ fillProps }>
|
||||
WooHeaderItem.Slot = ( { fillProps, name = '' } ) => (
|
||||
<Slot name={ getSlotFillName( name ) } fillProps={ fillProps }>
|
||||
{ sortFillsByOrder }
|
||||
</Slot>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Move additional components to @woocommerce/customer-effort-score.
|
|
@ -51,6 +51,7 @@
|
|||
"@woocommerce/eslint-plugin": "workspace:*",
|
||||
"@woocommerce/internal-style-build": "workspace:*",
|
||||
"@woocommerce/navigation": "workspace:*",
|
||||
"@woocommerce/tracks": "workspace:*",
|
||||
"@wordpress/browserslist-config": "wp-6.0",
|
||||
"concurrently": "^7.0.0",
|
||||
"css-loader": "^3.6.0",
|
||||
|
|
|
@ -3,18 +3,17 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import {
|
||||
CustomerFeedbackModal,
|
||||
STORE_KEY,
|
||||
} from '@woocommerce/customer-effort-score';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getStoreAgeInWeeks } from './utils';
|
||||
import { ADMIN_INSTALL_TIMESTAMP_OPTION_NAME } from './constants';
|
||||
import { CustomerFeedbackModal } from '../';
|
||||
import { getStoreAgeInWeeks } from '../../utils';
|
||||
import { STORE_KEY } from '../../store';
|
||||
import { ADMIN_INSTALL_TIMESTAMP_OPTION_NAME } from '../../constants';
|
||||
|
||||
export const PRODUCT_MVP_CES_ACTION_OPTION_NAME =
|
||||
'woocommerce_ces_product_mvp_ces_action';
|
|
@ -4,17 +4,15 @@
|
|||
import { useEffect } from 'react';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withDispatch, withSelect } from '@wordpress/data';
|
||||
import {
|
||||
QUEUE_OPTION_NAME,
|
||||
STORE_KEY,
|
||||
} from '@woocommerce/customer-effort-score';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import CustomerEffortScoreTracks from './customer-effort-score-tracks';
|
||||
import { CustomerEffortScoreTracks } from '../';
|
||||
import { QUEUE_OPTION_NAME, STORE_KEY } from '../../store';
|
||||
|
||||
/**
|
||||
* Maps the queue of CES tracks surveys to CustomerEffortScoreTracks
|
||||
|
@ -27,7 +25,7 @@ import CustomerEffortScoreTracks from './customer-effort-score-tracks';
|
|||
* @param {boolean} props.resolving Whether the queue is resolving.
|
||||
* @param {Function} props.clearQueue Sets up clearing of the queue on the next page load.
|
||||
*/
|
||||
function CustomerEffortScoreTracksContainer( {
|
||||
function _CustomerEffortScoreTracksContainer( {
|
||||
queue,
|
||||
resolving,
|
||||
clearQueue,
|
||||
|
@ -67,7 +65,7 @@ function CustomerEffortScoreTracksContainer( {
|
|||
);
|
||||
}
|
||||
|
||||
CustomerEffortScoreTracksContainer.propTypes = {
|
||||
_CustomerEffortScoreTracksContainer.propTypes = {
|
||||
/**
|
||||
* The queue of CES tracks surveys to display.
|
||||
*/
|
||||
|
@ -82,7 +80,7 @@ CustomerEffortScoreTracksContainer.propTypes = {
|
|||
clearQueue: PropTypes.func,
|
||||
};
|
||||
|
||||
export default compose(
|
||||
export const CustomerEffortScoreTracksContainer = compose(
|
||||
withSelect( ( select ) => {
|
||||
const { getCesSurveyQueue, isResolving } = select( STORE_KEY );
|
||||
const queue = getCesSurveyQueue();
|
||||
|
@ -109,4 +107,4 @@ export default compose(
|
|||
},
|
||||
};
|
||||
} )
|
||||
)( CustomerEffortScoreTracksContainer );
|
||||
)( _CustomerEffortScoreTracksContainer );
|
|
@ -1,26 +1,24 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useState } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import {
|
||||
ALLOW_TRACKING_OPTION_NAME,
|
||||
CustomerEffortScore,
|
||||
} from '@woocommerce/customer-effort-score';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { withSelect, withDispatch } from '@wordpress/data';
|
||||
import { createElement, useState } from '@wordpress/element';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { CustomerEffortScore } from '../';
|
||||
import {
|
||||
SHOWN_FOR_ACTIONS_OPTION_NAME,
|
||||
ADMIN_INSTALL_TIMESTAMP_OPTION_NAME,
|
||||
} from './constants';
|
||||
import { getStoreAgeInWeeks } from './utils';
|
||||
ALLOW_TRACKING_OPTION_NAME,
|
||||
SHOWN_FOR_ACTIONS_OPTION_NAME,
|
||||
} from '../../constants';
|
||||
import { getStoreAgeInWeeks } from '../../utils';
|
||||
|
||||
/**
|
||||
* A CustomerEffortScore wrapper that uses tracks to track the selected
|
||||
|
@ -43,7 +41,7 @@ import { getStoreAgeInWeeks } from './utils';
|
|||
* @param {Function} props.updateOptions Function to update options.
|
||||
* @param {Function} props.createNotice Function to create a snackbar.
|
||||
*/
|
||||
function CustomerEffortScoreTracks( {
|
||||
function _CustomerEffortScoreTracks( {
|
||||
action,
|
||||
trackProps,
|
||||
title,
|
||||
|
@ -176,7 +174,7 @@ function CustomerEffortScoreTracks( {
|
|||
);
|
||||
}
|
||||
|
||||
CustomerEffortScoreTracks.propTypes = {
|
||||
_CustomerEffortScoreTracks.propTypes = {
|
||||
/**
|
||||
* The action name sent to Tracks.
|
||||
*/
|
||||
|
@ -219,7 +217,7 @@ CustomerEffortScoreTracks.propTypes = {
|
|||
createNotice: PropTypes.func,
|
||||
};
|
||||
|
||||
export default compose(
|
||||
export const CustomerEffortScoreTracks = compose(
|
||||
withSelect( ( select ) => {
|
||||
const { getOption, hasFinishedResolution } =
|
||||
select( OPTIONS_STORE_NAME );
|
||||
|
@ -262,4 +260,4 @@ export default compose(
|
|||
createNotice,
|
||||
};
|
||||
} )
|
||||
)( CustomerEffortScoreTracks );
|
||||
)( _CustomerEffortScoreTracks );
|
|
@ -0,0 +1,8 @@
|
|||
export * from './customer-effort-score';
|
||||
export * from './customer-effort-score-modal-container';
|
||||
export * from './customer-effort-score-tracks';
|
||||
export * from './customer-effort-score-tracks-container';
|
||||
export * from './customer-feedback-simple';
|
||||
export * from './customer-feedback-modal';
|
||||
export * from './product-mvp-feedback-modal';
|
||||
export * from './feedback-modal';
|
|
@ -1 +1,7 @@
|
|||
export const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
|
||||
'woocommerce_admin_install_timestamp';
|
||||
|
||||
export const ALLOW_TRACKING_OPTION_NAME = 'woocommerce_allow_tracking';
|
||||
|
||||
export const SHOWN_FOR_ACTIONS_OPTION_NAME =
|
||||
'woocommerce_ces_shown_for_actions';
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './use-customer-effort-score-exit-page-tracker';
|
|
@ -1,9 +1,5 @@
|
|||
export * from './components/customer-effort-score';
|
||||
export * from './components/customer-feedback-simple';
|
||||
export * from './components/customer-feedback-modal';
|
||||
export * from './components/product-mvp-feedback-modal';
|
||||
export * from './components/feedback-modal';
|
||||
export * from './hooks/use-customer-effort-score-exit-page-tracker';
|
||||
export * from './store';
|
||||
export * from './utils/customer-effort-score-exit-page';
|
||||
export * from './components';
|
||||
export * from './constants';
|
||||
export * from './hooks';
|
||||
export * from './store';
|
||||
export * from './utils';
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export * from './customer-effort-score-exit-page';
|
||||
export * from './get-store-age-in-weeks';
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Adding header slot fill and more menu to header with slot fill.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Fix issue were template was not re-synced when switching between products.
|
|
@ -31,6 +31,7 @@
|
|||
"@automattic/interpolate-components": "^1.2.0",
|
||||
"@types/lodash": "^4.14.179",
|
||||
"@types/wordpress__blocks": "^11.0.7",
|
||||
"@woocommerce/admin-layout": "workspace:*",
|
||||
"@woocommerce/components": "workspace:*",
|
||||
"@woocommerce/currency": "workspace:*",
|
||||
"@woocommerce/data": "workspace:^4.1.0",
|
||||
|
@ -52,6 +53,7 @@
|
|||
"@wordpress/interface": "wp-6.0",
|
||||
"@wordpress/keyboard-shortcuts": "wp-6.0",
|
||||
"@wordpress/media-utils": "wp-6.0",
|
||||
"@wordpress/plugins": "wp-6.0",
|
||||
"@wordpress/url": "wp-6.0",
|
||||
"classnames": "^2.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -75,6 +77,7 @@
|
|||
"@types/wordpress__data": "^6.0.2",
|
||||
"@types/wordpress__editor": "^13.0.0",
|
||||
"@types/wordpress__media-utils": "^3.0.0",
|
||||
"@types/wordpress__plugins": "^3.0.0",
|
||||
"@woocommerce/eslint-plugin": "workspace:*",
|
||||
"@woocommerce/internal-style-build": "workspace:*",
|
||||
"@wordpress/block-editor": "^9.8.0",
|
||||
|
|
|
@ -94,7 +94,7 @@ export function BlockEditor( {
|
|||
synchronizeBlocksWithTemplate( [], _settings?.template ),
|
||||
{}
|
||||
);
|
||||
}, [] );
|
||||
}, [ product.id ] );
|
||||
|
||||
if ( ! blocks ) {
|
||||
return null;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement, StrictMode } from '@wordpress/element';
|
||||
import { createElement, StrictMode, Fragment } from '@wordpress/element';
|
||||
import { PluginArea } from '@wordpress/plugins';
|
||||
import {
|
||||
EditorSettings,
|
||||
EditorBlockListSettings,
|
||||
|
@ -54,10 +55,14 @@ export function Editor( { product, settings }: EditorProps ) {
|
|||
/>
|
||||
}
|
||||
content={
|
||||
<BlockEditor
|
||||
settings={ settings }
|
||||
product={ product }
|
||||
/>
|
||||
<>
|
||||
<BlockEditor
|
||||
settings={ settings }
|
||||
product={ product }
|
||||
/>
|
||||
{ /* @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated. */ }
|
||||
<PluginArea scope="woocommerce-product-block-editor" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
|
|
|
@ -11,3 +11,8 @@
|
|||
.interface-interface-skeleton__sidebar {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.interface-interface-skeleton__header{
|
||||
// Higher than the sidebar which has a z-index of 90.
|
||||
z-index: 100;
|
||||
}
|
||||
|
|
|
@ -7,12 +7,18 @@ import { useDispatch, useSelect } from '@wordpress/data';
|
|||
import { createElement } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { navigateTo, getNewPath } from '@woocommerce/navigation';
|
||||
import { WooHeaderItem } from '@woocommerce/admin-layout';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { AUTO_DRAFT_NAME, getHeaderTitle } from '../../utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { MoreMenu } from './more-menu';
|
||||
|
||||
export type HeaderProps = {
|
||||
productId: number;
|
||||
productName: string;
|
||||
|
@ -85,6 +91,8 @@ export function Header( { productId, productName }: HeaderProps ) {
|
|||
? __( 'Add', 'woocommerce' )
|
||||
: __( 'Save', 'woocommerce' ) }
|
||||
</Button>
|
||||
<WooHeaderItem.Slot name="product" />
|
||||
<MoreMenu />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './header';
|
||||
export * from './woo-more-menu-item';
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './more-menu';
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import { MoreMenuDropdown } from '@wordpress/interface';
|
||||
//import { displayShortcut } from '@wordpress/keycodes';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { WooProductMoreMenuItem } from '../woo-more-menu-item';
|
||||
|
||||
export const MoreMenu = () => {
|
||||
return (
|
||||
<>
|
||||
<MoreMenuDropdown>
|
||||
{ ( { onClose }: { onClose: () => void } ) => (
|
||||
<>
|
||||
<WooProductMoreMenuItem.Slot
|
||||
fillProps={ { onClose } }
|
||||
/>
|
||||
</>
|
||||
) }
|
||||
</MoreMenuDropdown>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -7,4 +7,25 @@
|
|||
&__actions {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.components-popover__content {
|
||||
min-width: auto;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.woocommerce-product-header__actions {
|
||||
display: flex;
|
||||
|
||||
> * + * {
|
||||
margin-left: $gap;
|
||||
}
|
||||
}
|
||||
|
||||
.components-dropdown-menu__toggle {
|
||||
&.is-opened {
|
||||
|
||||
background-color: #1E1E1E;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './woo-more-menu-item';
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
createSlotFill,
|
||||
Slot as BaseSlot,
|
||||
Fill as BaseFill,
|
||||
} from '@wordpress/components';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
|
||||
type WooProductMoreMenuSlot = React.FC< BaseSlot.Props >;
|
||||
|
||||
type WooProductMoreMenuFill = React.FC< BaseFill.Props > & {
|
||||
Slot?: WooProductMoreMenuSlot;
|
||||
};
|
||||
|
||||
type CreateSlotFillReturn = {
|
||||
Fill: WooProductMoreMenuFill;
|
||||
Slot: WooProductMoreMenuSlot;
|
||||
};
|
||||
|
||||
const { Fill, Slot }: CreateSlotFillReturn = createSlotFill(
|
||||
'WooProductMoreMenuItem'
|
||||
);
|
||||
|
||||
Fill.Slot = ( { fillProps } ) => (
|
||||
<Slot fillProps={ fillProps }>
|
||||
{ ( fills ) => {
|
||||
return isEmpty( fills ) ? null : <>{ fills }</>;
|
||||
} }
|
||||
</Slot>
|
||||
);
|
||||
|
||||
export const WooProductMoreMenuItem = Fill as WooProductMoreMenuFill & {
|
||||
Slot: WooProductMoreMenuSlot;
|
||||
};
|
|
@ -10,6 +10,7 @@ export { DetailsFeatureField as __experimentalDetailsFeatureField } from './deta
|
|||
export { DetailsCategoriesField as __experimentalDetailsCategoriesField } from './details-categories-field';
|
||||
export { DetailsSummaryField as __experimentalDetailsSummaryField } from './details-summary-field';
|
||||
export { DetailsDescriptionField as __experimentalDetailsDescriptionField } from './details-description-field';
|
||||
export { WooProductMoreMenuItem as __experimentalWooProductMoreMenuItem } from './header';
|
||||
export {
|
||||
Editor as __experimentalEditor,
|
||||
ProductEditorSettings,
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
export const SHOWN_FOR_ACTIONS_OPTION_NAME =
|
||||
'woocommerce_ces_shown_for_actions';
|
||||
export const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
|
||||
'woocommerce_admin_install_timestamp';
|
|
@ -1,3 +0,0 @@
|
|||
export { default as CustomerEffortScoreTracks } from './customer-effort-score-tracks';
|
||||
export { default as CustomerEffortScoreTracksContainer } from './customer-effort-score-tracks-container';
|
||||
export * from './customer-effort-score-modal-container.tsx';
|
|
@ -9,6 +9,7 @@ import { WooFooterItem } from '@woocommerce/admin-layout';
|
|||
import { Pill } from '@woocommerce/components';
|
||||
import {
|
||||
ALLOW_TRACKING_OPTION_NAME,
|
||||
SHOWN_FOR_ACTIONS_OPTION_NAME,
|
||||
STORE_KEY,
|
||||
} from '@woocommerce/customer-effort-score';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
|
@ -17,7 +18,6 @@ import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import './product-mvp-ces-footer.scss';
|
||||
import { SHOWN_FOR_ACTIONS_OPTION_NAME } from './constants';
|
||||
|
||||
export const PRODUCT_MVP_CES_ACTION_OPTION_NAME =
|
||||
'woocommerce_ces_product_mvp_ces_action';
|
||||
|
|
|
@ -11,7 +11,9 @@ import { getAdminLink } from '@woocommerce/settings';
|
|||
import { useFormContext } from '@woocommerce/components';
|
||||
import { Product } from '@woocommerce/data';
|
||||
|
||||
export const ProductMVPFeedbackModalContainer: React.FC = () => {
|
||||
export const ProductMVPFeedbackModalContainer: React.FC< {
|
||||
productId?: number;
|
||||
} > = ( { productId: _productId } ) => {
|
||||
const { values } = useFormContext< Product >();
|
||||
const { hideProductMVPFeedbackModal } = useDispatch( STORE_KEY );
|
||||
const { isProductMVPModalVisible } = useSelect( ( select ) => {
|
||||
|
@ -21,8 +23,10 @@ export const ProductMVPFeedbackModalContainer: React.FC = () => {
|
|||
};
|
||||
} );
|
||||
|
||||
const classicEditorUrl = values.id
|
||||
? getAdminLink( `post.php?post=${ values.id }&action=edit` )
|
||||
const productId = _productId ?? values.id;
|
||||
|
||||
const classicEditorUrl = productId
|
||||
? getAdminLink( `post.php?post=${ productId }&action=edit` )
|
||||
: getAdminLink( 'post-new.php?post_type=product' );
|
||||
|
||||
const recordScore = ( checked: string[], comments: string ) => {
|
||||
|
|
|
@ -9,9 +9,3 @@ export const WELCOME_MODAL_DISMISSED_OPTION_NAME =
|
|||
*/
|
||||
export const WELCOME_FROM_CALYPSO_MODAL_DISMISSED_OPTION_NAME =
|
||||
'woocommerce_welcome_from_calypso_modal_dismissed';
|
||||
|
||||
/**
|
||||
* WooCommerce Admin installation timestamp option name.
|
||||
*/
|
||||
export const WOOCOMMERCE_ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
|
||||
'woocommerce_admin_install_timestamp';
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import '@wordpress/notices';
|
||||
import { render } from '@wordpress/element';
|
||||
import { CustomerEffortScoreTracksContainer } from '@woocommerce/customer-effort-score';
|
||||
import {
|
||||
withCurrentUserHydration,
|
||||
withSettingsHydration,
|
||||
|
@ -14,7 +15,6 @@ import {
|
|||
import './stylesheets/_index.scss';
|
||||
import { getAdminSetting } from '~/utils/admin-settings';
|
||||
import { PageLayout, EmbedLayout, PrimaryLayout as NoticeArea } from './layout';
|
||||
import { CustomerEffortScoreTracksContainer } from './customer-effort-score-tracks';
|
||||
import { EmbeddedBodyLayout } from './embedded-body-layout';
|
||||
import { WcAdminPaymentsGatewaysBannerSlot } from './payments/payments-settings-banner-slotfill';
|
||||
import { WcAdminConflictErrorSlot } from './settings/conflict-error-slotfill.js';
|
||||
|
|
|
@ -17,7 +17,10 @@ import { Children, cloneElement } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { get, isFunction, identity, memoize } from 'lodash';
|
||||
import { parse } from 'qs';
|
||||
import { triggerExitPageCesSurvey } from '@woocommerce/customer-effort-score';
|
||||
import {
|
||||
CustomerEffortScoreModalContainer,
|
||||
triggerExitPageCesSurvey,
|
||||
} from '@woocommerce/customer-effort-score';
|
||||
import { getHistory, getQuery } from '@woocommerce/navigation';
|
||||
import {
|
||||
PLUGINS_STORE_NAME,
|
||||
|
@ -38,7 +41,6 @@ import { Header } from '../header';
|
|||
import { Footer } from './footer';
|
||||
import Notices from './notices';
|
||||
import TransientNotices from './transient-notices';
|
||||
import { CustomerEffortScoreModalContainer } from '../customer-effort-score-tracks';
|
||||
import { getAdminSetting } from '~/utils/admin-settings';
|
||||
import '~/activity-panel';
|
||||
import '~/mobile-banner';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export { useIntroductionBanner } from './useIntroductionBanner';
|
||||
export { useInstalledPlugins } from './useInstalledPlugins';
|
||||
export { useInstalledPluginsWithoutChannels } from './useInstalledPluginsWithoutChannels';
|
||||
export { useRegisteredChannels } from './useRegisteredChannels';
|
||||
export { useRecommendedChannels } from './useRecommendedChannels';
|
||||
export { useCampaignTypes } from './useCampaignTypes';
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY } from '~/marketing/data/constants';
|
||||
import { InstalledPlugin } from '~/marketing/types';
|
||||
|
||||
export type UseInstalledPlugins = {
|
||||
installedPlugins: InstalledPlugin[];
|
||||
activatingPlugins: string[];
|
||||
activateInstalledPlugin: ( slug: string ) => void;
|
||||
loadInstalledPluginsAfterActivation: ( slug: string ) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to return plugins and methods for "Installed extensions" card.
|
||||
*/
|
||||
export const useInstalledPlugins = (): UseInstalledPlugins => {
|
||||
const { installedPlugins, activatingPlugins } = useSelect( ( select ) => {
|
||||
const { getInstalledPlugins, getActivatingPlugins } =
|
||||
select( STORE_KEY );
|
||||
|
||||
return {
|
||||
installedPlugins: getInstalledPlugins(),
|
||||
activatingPlugins: getActivatingPlugins(),
|
||||
};
|
||||
}, [] );
|
||||
const { activateInstalledPlugin, loadInstalledPluginsAfterActivation } =
|
||||
useDispatch( STORE_KEY );
|
||||
|
||||
return {
|
||||
installedPlugins,
|
||||
activatingPlugins,
|
||||
activateInstalledPlugin,
|
||||
loadInstalledPluginsAfterActivation,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { chain } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY } from '~/marketing/data/constants';
|
||||
import { InstalledPlugin } from '~/marketing/types';
|
||||
import { useRecommendedChannels } from './useRecommendedChannels';
|
||||
import { useRegisteredChannels } from './useRegisteredChannels';
|
||||
|
||||
export type UseInstalledPluginsWithoutChannels = {
|
||||
data: InstalledPlugin[];
|
||||
activatingPlugins: string[];
|
||||
activateInstalledPlugin: ( slug: string ) => void;
|
||||
loadInstalledPluginsAfterActivation: ( slug: string ) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to return plugins and methods for "Installed extensions" card.
|
||||
* The list of installed plugins does not include registered and recommended marketing channels.
|
||||
*/
|
||||
export const useInstalledPluginsWithoutChannels =
|
||||
(): UseInstalledPluginsWithoutChannels => {
|
||||
const { installedPlugins, activatingPlugins } = useSelect(
|
||||
( select ) => {
|
||||
const { getInstalledPlugins, getActivatingPlugins } =
|
||||
select( STORE_KEY );
|
||||
|
||||
return {
|
||||
installedPlugins:
|
||||
getInstalledPlugins< InstalledPlugin[] >(),
|
||||
activatingPlugins: getActivatingPlugins(),
|
||||
};
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const {
|
||||
loading: loadingRegisteredChannels,
|
||||
data: dataRegisteredChannels,
|
||||
} = useRegisteredChannels();
|
||||
const {
|
||||
loading: loadingRecommendedChannels,
|
||||
data: dataRecommendedChannels,
|
||||
} = useRecommendedChannels();
|
||||
|
||||
const { activateInstalledPlugin, loadInstalledPluginsAfterActivation } =
|
||||
useDispatch( STORE_KEY );
|
||||
|
||||
const loading = loadingRegisteredChannels || loadingRecommendedChannels;
|
||||
const installedPluginsWithoutChannels = chain( installedPlugins )
|
||||
.differenceWith(
|
||||
dataRegisteredChannels || [],
|
||||
( a, b ) => a.slug === b.slug
|
||||
)
|
||||
.differenceWith(
|
||||
dataRecommendedChannels || [],
|
||||
( a, b ) => a.slug === b.product
|
||||
)
|
||||
.value();
|
||||
|
||||
return {
|
||||
data: loading ? [] : installedPluginsWithoutChannels,
|
||||
activatingPlugins,
|
||||
activateInstalledPlugin,
|
||||
loadInstalledPluginsAfterActivation,
|
||||
};
|
||||
};
|
|
@ -6,8 +6,7 @@ import { render, screen } from '@testing-library/react';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useInstalledPlugins } from '../../hooks';
|
||||
import { useRecommendedPlugins } from './useRecommendedPlugins';
|
||||
import { useRecommendedPluginsWithoutChannels } from './useRecommendedPluginsWithoutChannels';
|
||||
import { DiscoverTools } from './DiscoverTools';
|
||||
|
||||
jest.mock( '@woocommerce/components', () => {
|
||||
|
@ -20,23 +19,20 @@ jest.mock( '@woocommerce/components', () => {
|
|||
};
|
||||
} );
|
||||
|
||||
jest.mock( './useRecommendedPlugins', () => ( {
|
||||
useRecommendedPlugins: jest.fn(),
|
||||
jest.mock( './useRecommendedPluginsWithoutChannels', () => ( {
|
||||
useRecommendedPluginsWithoutChannels: jest.fn(),
|
||||
} ) );
|
||||
|
||||
jest.mock( '../../hooks', () => ( {
|
||||
useInstalledPlugins: jest.fn(),
|
||||
jest.mock( '~/marketing/hooks', () => ( {
|
||||
useInstalledPluginsWithoutChannels: jest.fn( () => ( {} ) ),
|
||||
} ) );
|
||||
|
||||
describe( 'DiscoverTools component', () => {
|
||||
it( 'should render a Spinner when loading is in progress', () => {
|
||||
( useRecommendedPlugins as jest.Mock ).mockReturnValue( {
|
||||
( useRecommendedPluginsWithoutChannels as jest.Mock ).mockReturnValue( {
|
||||
isInitializing: true,
|
||||
isLoading: true,
|
||||
plugins: [],
|
||||
} );
|
||||
( useInstalledPlugins as jest.Mock ).mockReturnValue( {
|
||||
loadInstalledPluginsAfterActivation: jest.fn(),
|
||||
data: [],
|
||||
} );
|
||||
render( <DiscoverTools /> );
|
||||
|
||||
|
@ -44,13 +40,10 @@ describe( 'DiscoverTools component', () => {
|
|||
} );
|
||||
|
||||
it( 'should render message and link when loading is finish and there are no plugins', () => {
|
||||
( useRecommendedPlugins as jest.Mock ).mockReturnValue( {
|
||||
( useRecommendedPluginsWithoutChannels as jest.Mock ).mockReturnValue( {
|
||||
isInitializing: false,
|
||||
isLoading: false,
|
||||
plugins: [],
|
||||
} );
|
||||
( useInstalledPlugins as jest.Mock ).mockReturnValue( {
|
||||
loadInstalledPluginsAfterActivation: jest.fn(),
|
||||
data: [],
|
||||
} );
|
||||
render( <DiscoverTools /> );
|
||||
|
||||
|
@ -66,10 +59,12 @@ describe( 'DiscoverTools component', () => {
|
|||
|
||||
describe( 'With plugins loaded', () => {
|
||||
it( 'should render `direct_install: true` plugins with "Install plugin" button', () => {
|
||||
( useRecommendedPlugins as jest.Mock ).mockReturnValue( {
|
||||
(
|
||||
useRecommendedPluginsWithoutChannels as jest.Mock
|
||||
).mockReturnValue( {
|
||||
isInitializing: false,
|
||||
isLoading: false,
|
||||
plugins: [
|
||||
data: [
|
||||
{
|
||||
title: 'Google Listings and Ads',
|
||||
description:
|
||||
|
@ -95,9 +90,6 @@ describe( 'DiscoverTools component', () => {
|
|||
},
|
||||
],
|
||||
} );
|
||||
( useInstalledPlugins as jest.Mock ).mockReturnValue( {
|
||||
loadInstalledPluginsAfterActivation: jest.fn(),
|
||||
} );
|
||||
render( <DiscoverTools /> );
|
||||
|
||||
// Assert that we have the "Sales channels" tab, the plugin name, the "Built by WooCommerce" pill, and the "Install plugin" button.
|
||||
|
@ -112,10 +104,12 @@ describe( 'DiscoverTools component', () => {
|
|||
} );
|
||||
|
||||
it( 'should render `direct_install: false` plugins with "View details" button', () => {
|
||||
( useRecommendedPlugins as jest.Mock ).mockReturnValue( {
|
||||
(
|
||||
useRecommendedPluginsWithoutChannels as jest.Mock
|
||||
).mockReturnValue( {
|
||||
isInitializing: false,
|
||||
isLoading: false,
|
||||
plugins: [
|
||||
data: [
|
||||
{
|
||||
title: 'WooCommerce Zapier',
|
||||
description:
|
||||
|
@ -136,9 +130,6 @@ describe( 'DiscoverTools component', () => {
|
|||
},
|
||||
],
|
||||
} );
|
||||
( useInstalledPlugins as jest.Mock ).mockReturnValue( {
|
||||
loadInstalledPluginsAfterActivation: jest.fn(),
|
||||
} );
|
||||
render( <DiscoverTools /> );
|
||||
|
||||
// Assert that we have the CRM tab, plugin name, and "View details" button.
|
||||
|
|
|
@ -14,13 +14,13 @@ import {
|
|||
CardBody,
|
||||
CenteredSpinner,
|
||||
} from '~/marketing/components';
|
||||
import { useRecommendedPlugins } from './useRecommendedPlugins';
|
||||
import { useRecommendedPluginsWithoutChannels } from './useRecommendedPluginsWithoutChannels';
|
||||
import { PluginsTabPanel } from './PluginsTabPanel';
|
||||
import './DiscoverTools.scss';
|
||||
|
||||
export const DiscoverTools = () => {
|
||||
const { isInitializing, isLoading, plugins, installAndActivate } =
|
||||
useRecommendedPlugins();
|
||||
const { isInitializing, isLoading, data, installAndActivate } =
|
||||
useRecommendedPluginsWithoutChannels();
|
||||
|
||||
/**
|
||||
* Renders card body.
|
||||
|
@ -38,7 +38,7 @@ export const DiscoverTools = () => {
|
|||
);
|
||||
}
|
||||
|
||||
if ( plugins.length === 0 ) {
|
||||
if ( data.length === 0 ) {
|
||||
return (
|
||||
<CardBody className="woocommerce-marketing-discover-tools-card-body-empty-content">
|
||||
<Icon icon={ trendingUp } size={ 32 } />
|
||||
|
@ -66,7 +66,7 @@ export const DiscoverTools = () => {
|
|||
|
||||
return (
|
||||
<PluginsTabPanel
|
||||
plugins={ plugins }
|
||||
plugins={ data }
|
||||
isLoading={ isLoading }
|
||||
onInstallAndActivate={ installAndActivate }
|
||||
/>
|
||||
|
|
|
@ -14,7 +14,7 @@ import { flatMapDeep, uniqBy } from 'lodash';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { CardDivider, PluginCardBody } from '~/marketing/components';
|
||||
import { useInstalledPlugins } from '~/marketing/hooks';
|
||||
import { useInstalledPluginsWithoutChannels } from '~/marketing/hooks';
|
||||
import { RecommendedPlugin } from '~/marketing/types';
|
||||
import { getInAppPurchaseUrl } from '~/lib/in-app-purchase';
|
||||
import { createNoticesFromResponse } from '~/lib/notices';
|
||||
|
@ -60,7 +60,8 @@ export const PluginsTabPanel = ( {
|
|||
null
|
||||
);
|
||||
const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME );
|
||||
const { loadInstalledPluginsAfterActivation } = useInstalledPlugins();
|
||||
const { loadInstalledPluginsAfterActivation } =
|
||||
useInstalledPluginsWithoutChannels();
|
||||
|
||||
/**
|
||||
* Install and activate a plugin.
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY } from '~/marketing/data/constants';
|
||||
import { RecommendedPlugin } from '~/marketing/types';
|
||||
|
||||
const selector = 'getRecommendedPlugins';
|
||||
const category = 'marketing';
|
||||
|
||||
export const useRecommendedPlugins = () => {
|
||||
const { invalidateResolution, installAndActivateRecommendedPlugin } =
|
||||
useDispatch( STORE_KEY );
|
||||
|
||||
const installAndActivate = ( plugin: string ) => {
|
||||
installAndActivateRecommendedPlugin( plugin, category );
|
||||
invalidateResolution( selector, [ category ] );
|
||||
};
|
||||
|
||||
return useSelect( ( select ) => {
|
||||
const { getRecommendedPlugins, hasFinishedResolution } =
|
||||
select( STORE_KEY );
|
||||
const plugins =
|
||||
getRecommendedPlugins< RecommendedPlugin[] >( category );
|
||||
const isLoading = ! hasFinishedResolution( selector, [ category ] );
|
||||
|
||||
return {
|
||||
isInitializing: ! plugins.length && isLoading,
|
||||
isLoading,
|
||||
plugins,
|
||||
installAndActivate,
|
||||
};
|
||||
}, [] );
|
||||
};
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { differenceWith } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY } from '~/marketing/data/constants';
|
||||
import { useRecommendedChannels } from '~/marketing/hooks';
|
||||
import { RecommendedPlugin } from '~/marketing/types';
|
||||
|
||||
type UseRecommendedPluginsWithoutChannels = {
|
||||
/**
|
||||
* Boolean indicating whether it is initializing.
|
||||
*/
|
||||
isInitializing: boolean;
|
||||
|
||||
/**
|
||||
* Boolean indicating whether it is loading.
|
||||
*
|
||||
* This will be true when data is being refetched
|
||||
* after `invalidateResolution` is called in the `installAndActivate` method.
|
||||
*/
|
||||
isLoading: boolean;
|
||||
|
||||
/**
|
||||
* An array of recommended marketing plugins without marketing channels.
|
||||
*/
|
||||
data: RecommendedPlugin[];
|
||||
|
||||
/**
|
||||
* Install and activate a plugin.
|
||||
*/
|
||||
installAndActivate: ( slug: string ) => void;
|
||||
};
|
||||
|
||||
const selector = 'getRecommendedPlugins';
|
||||
const category = 'marketing';
|
||||
|
||||
/**
|
||||
* A hook to return a list of recommended plugins without marketing channels,
|
||||
* and related methods, to be used with the `DiscoverTools` component.
|
||||
*/
|
||||
export const useRecommendedPluginsWithoutChannels =
|
||||
(): UseRecommendedPluginsWithoutChannels => {
|
||||
const {
|
||||
loading: loadingRecommendedPlugins,
|
||||
data: dataRecommendedPlugins,
|
||||
} = useSelect( ( select ) => {
|
||||
const { getRecommendedPlugins, hasFinishedResolution } =
|
||||
select( STORE_KEY );
|
||||
|
||||
return {
|
||||
loading: ! hasFinishedResolution( selector, [ category ] ),
|
||||
data: getRecommendedPlugins< RecommendedPlugin[] >( category ),
|
||||
};
|
||||
}, [] );
|
||||
|
||||
const {
|
||||
loading: loadingRecommendedChannels,
|
||||
data: dataRecommendedChannels,
|
||||
} = useRecommendedChannels();
|
||||
|
||||
const { invalidateResolution, installAndActivateRecommendedPlugin } =
|
||||
useDispatch( STORE_KEY );
|
||||
|
||||
const isInitializing =
|
||||
( loadingRecommendedPlugins && ! dataRecommendedPlugins.length ) ||
|
||||
( loadingRecommendedChannels && ! dataRecommendedChannels );
|
||||
|
||||
const loading = loadingRecommendedPlugins || loadingRecommendedChannels;
|
||||
|
||||
const recommendedPluginsWithoutChannels = differenceWith(
|
||||
dataRecommendedPlugins,
|
||||
dataRecommendedChannels || [],
|
||||
( a, b ) => a.product === b.product
|
||||
);
|
||||
|
||||
const installAndActivate = ( slug: string ) => {
|
||||
installAndActivateRecommendedPlugin( slug, category );
|
||||
invalidateResolution( selector, [ category ] );
|
||||
};
|
||||
|
||||
return {
|
||||
isInitializing,
|
||||
isLoading: loading,
|
||||
data: isInitializing ? [] : recommendedPluginsWithoutChannels,
|
||||
installAndActivate,
|
||||
};
|
||||
};
|
|
@ -16,13 +16,13 @@ import {
|
|||
PluginCardBody,
|
||||
} from '~/marketing/components';
|
||||
import { InstalledPlugin } from '~/marketing/types';
|
||||
import { useInstalledPlugins } from '~/marketing/hooks';
|
||||
import { useInstalledPluginsWithoutChannels } from '~/marketing/hooks';
|
||||
|
||||
export const InstalledExtensions = () => {
|
||||
const { installedPlugins, activatingPlugins, activateInstalledPlugin } =
|
||||
useInstalledPlugins();
|
||||
const { data, activatingPlugins, activateInstalledPlugin } =
|
||||
useInstalledPluginsWithoutChannels();
|
||||
|
||||
if ( installedPlugins.length === 0 ) {
|
||||
if ( data.length === 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ export const InstalledExtensions = () => {
|
|||
|
||||
return (
|
||||
<CollapsibleCard header={ __( 'Installed extensions', 'woocommerce' ) }>
|
||||
{ installedPlugins.map( ( el, idx ) => {
|
||||
{ data.map( ( el, idx ) => {
|
||||
return (
|
||||
<Fragment key={ el.slug }>
|
||||
<PluginCardBody
|
||||
|
@ -90,9 +90,7 @@ export const InstalledExtensions = () => {
|
|||
description={ el.description }
|
||||
button={ getButton( el ) }
|
||||
/>
|
||||
{ idx !== installedPlugins.length - 1 && (
|
||||
<CardDivider />
|
||||
) }
|
||||
{ idx !== data.length - 1 && <CardDivider /> }
|
||||
</Fragment>
|
||||
);
|
||||
} ) }
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { MenuItem } from '@wordpress/components';
|
||||
import {
|
||||
ALLOW_TRACKING_OPTION_NAME,
|
||||
STORE_KEY as CES_STORE_KEY,
|
||||
} from '@woocommerce/customer-effort-score';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ClassicEditorIcon } from '../../images/classic-editor-icon';
|
||||
import { NEW_PRODUCT_MANAGEMENT } from '~/customer-effort-score-tracks/product-mvp-ces-footer';
|
||||
|
||||
export const ClassicEditorMenuItem = ( {
|
||||
onClose,
|
||||
productId,
|
||||
}: {
|
||||
productId: number;
|
||||
onClose: () => void;
|
||||
} ) => {
|
||||
const { showProductMVPFeedbackModal } = useDispatch( CES_STORE_KEY );
|
||||
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
|
||||
|
||||
const { allowTracking, resolving: isLoading } = useSelect( ( select ) => {
|
||||
const { getOption, hasFinishedResolution } =
|
||||
select( OPTIONS_STORE_NAME );
|
||||
|
||||
const allowTrackingOption =
|
||||
getOption( ALLOW_TRACKING_OPTION_NAME ) || 'no';
|
||||
|
||||
const resolving = ! hasFinishedResolution( 'getOption', [
|
||||
ALLOW_TRACKING_OPTION_NAME,
|
||||
] );
|
||||
|
||||
return {
|
||||
allowTracking: allowTrackingOption === 'yes',
|
||||
resolving,
|
||||
};
|
||||
} );
|
||||
|
||||
const classicEditorUrl = productId
|
||||
? getAdminLink( `post.php?post=${ productId }&action=edit` )
|
||||
: getAdminLink( 'post-new.php?post_type=product' );
|
||||
|
||||
if ( isLoading ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
if ( allowTracking ) {
|
||||
updateOptions( {
|
||||
[ NEW_PRODUCT_MANAGEMENT ]: 'no',
|
||||
} );
|
||||
showProductMVPFeedbackModal();
|
||||
onClose();
|
||||
} else {
|
||||
window.location.href = classicEditorUrl;
|
||||
onClose();
|
||||
}
|
||||
} }
|
||||
icon={ <ClassicEditorIcon /> }
|
||||
iconPosition="right"
|
||||
>
|
||||
{ __( 'Use the classic editor', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { MenuItem } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { STORE_KEY as CES_STORE_KEY } from '@woocommerce/customer-effort-score';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { FeedbackIcon } from '../../images/feedback-icon';
|
||||
|
||||
export const FeedbackMenuItem = ( { onClose }: { onClose: () => void } ) => {
|
||||
const { showCesModal } = useDispatch( CES_STORE_KEY );
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
showCesModal(
|
||||
{
|
||||
action: 'new_product',
|
||||
title: __(
|
||||
"How's your experience with the product editor?",
|
||||
'woocommerce'
|
||||
),
|
||||
firstQuestion: __(
|
||||
'The product editing screen is easy to use',
|
||||
'woocommerce'
|
||||
),
|
||||
secondQuestion: __(
|
||||
"The product editing screen's functionality meets my needs",
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
{ shouldShowComments: () => true },
|
||||
{
|
||||
type: 'snackbar',
|
||||
icon: <span>🌟</span>,
|
||||
}
|
||||
);
|
||||
onClose();
|
||||
} }
|
||||
icon={ <FeedbackIcon /> }
|
||||
iconPosition="right"
|
||||
>
|
||||
{ __( 'Share feedback', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export * from './feedback-menu-item';
|
||||
export * from './classic-editor-menu-item';
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __experimentalWooProductMoreMenuItem as WooProductMoreMenuItem } from '@woocommerce/product-editor';
|
||||
import { registerPlugin } from '@wordpress/plugins';
|
||||
import { WooHeaderItem } from '@woocommerce/admin-layout';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductMVPFeedbackModalContainer } from '~/customer-effort-score-tracks/product-mvp-feedback-modal-container';
|
||||
import {
|
||||
FeedbackMenuItem,
|
||||
ClassicEditorMenuItem,
|
||||
} from '../fills/more-menu-items';
|
||||
|
||||
const MoreMenuFill = ( { onClose }: { onClose: () => void } ) => {
|
||||
const [ id ] = useEntityProp( 'postType', 'product', 'id' );
|
||||
|
||||
return (
|
||||
<>
|
||||
<FeedbackMenuItem onClose={ onClose } />
|
||||
<ClassicEditorMenuItem productId={ id } onClose={ onClose } />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ProductHeaderFill = () => {
|
||||
const [ id ] = useEntityProp( 'postType', 'product', 'id' );
|
||||
|
||||
return <ProductMVPFeedbackModalContainer productId={ id } />;
|
||||
};
|
||||
|
||||
registerPlugin( 'wc-admin-more-menu', {
|
||||
// @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated.
|
||||
scope: 'woocommerce-product-block-editor',
|
||||
render: () => (
|
||||
<>
|
||||
<WooProductMoreMenuItem>
|
||||
{ ( { onClose }: { onClose: () => void } ) => (
|
||||
<MoreMenuFill onClose={ onClose } />
|
||||
) }
|
||||
</WooProductMoreMenuItem>
|
||||
<WooHeaderItem name="product">
|
||||
<ProductHeaderFill />
|
||||
</WooHeaderItem>
|
||||
</>
|
||||
),
|
||||
} );
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { AUTO_DRAFT_NAME } from '@woocommerce/product-editor';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import { useDispatch, resolveSelect } from '@wordpress/data';
|
||||
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
|
||||
export function useProductEntityRecord(
|
||||
productId: string | undefined
|
||||
): Product | undefined {
|
||||
const { saveEntityRecord } = useDispatch( 'core' );
|
||||
const [ product, setProduct ] = useState< Product | undefined >(
|
||||
undefined
|
||||
);
|
||||
|
||||
useEffect( () => {
|
||||
const getRecordPromise: Promise< Product > = productId
|
||||
? resolveSelect( 'core' ).getEntityRecord< Product >(
|
||||
'postType',
|
||||
'product',
|
||||
Number.parseInt( productId, 10 )
|
||||
)
|
||||
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Incorrect types.
|
||||
( saveEntityRecord( 'postType', 'product', {
|
||||
title: AUTO_DRAFT_NAME,
|
||||
status: 'auto-draft',
|
||||
} ) as Promise< Product > );
|
||||
getRecordPromise
|
||||
.then( ( autoDraftProduct: Product ) => {
|
||||
setProduct( autoDraftProduct );
|
||||
} )
|
||||
.catch( ( e ) => {
|
||||
setProduct( undefined );
|
||||
throw e;
|
||||
} );
|
||||
}, [ productId ] );
|
||||
|
||||
return product;
|
||||
}
|
|
@ -2,53 +2,39 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { DropdownMenu, MenuItem } from '@wordpress/components';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { DropdownMenu } from '@wordpress/components';
|
||||
import { useFormContext } from '@woocommerce/components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { WooHeaderItem } from '@woocommerce/admin-layout';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { moreVertical } from '@wordpress/icons';
|
||||
import { OPTIONS_STORE_NAME, Product } from '@woocommerce/data';
|
||||
import { useFormContext } from '@woocommerce/components';
|
||||
import {
|
||||
ALLOW_TRACKING_OPTION_NAME,
|
||||
STORE_KEY as CES_STORE_KEY,
|
||||
} from '@woocommerce/customer-effort-score';
|
||||
import { ALLOW_TRACKING_OPTION_NAME } from '@woocommerce/customer-effort-score';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ClassicEditorIcon } from './images/classic-editor-icon';
|
||||
import { FeedbackIcon } from './images/feedback-icon';
|
||||
import { NEW_PRODUCT_MANAGEMENT } from '~/customer-effort-score-tracks/product-mvp-ces-footer';
|
||||
|
||||
import {
|
||||
FeedbackMenuItem,
|
||||
ClassicEditorMenuItem,
|
||||
} from './fills/more-menu-items';
|
||||
|
||||
import './product-more-menu.scss';
|
||||
|
||||
export const ProductMoreMenu = () => {
|
||||
const { values } = useFormContext< Product >();
|
||||
const { showCesModal, showProductMVPFeedbackModal } =
|
||||
useDispatch( CES_STORE_KEY );
|
||||
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
|
||||
|
||||
const { allowTracking, resolving: isLoading } = useSelect( ( select ) => {
|
||||
const { getOption, hasFinishedResolution } =
|
||||
select( OPTIONS_STORE_NAME );
|
||||
|
||||
const allowTrackingOption =
|
||||
getOption( ALLOW_TRACKING_OPTION_NAME ) || 'no';
|
||||
const { resolving: isLoading } = useSelect( ( select ) => {
|
||||
const { hasFinishedResolution } = select( OPTIONS_STORE_NAME );
|
||||
|
||||
const resolving = ! hasFinishedResolution( 'getOption', [
|
||||
ALLOW_TRACKING_OPTION_NAME,
|
||||
] );
|
||||
|
||||
return {
|
||||
allowTracking: allowTrackingOption === 'yes',
|
||||
resolving,
|
||||
};
|
||||
} );
|
||||
|
||||
const classEditorUrl = values.id
|
||||
? getAdminLink( `post.php?post=${ values.id }&action=edit` )
|
||||
: getAdminLink( 'post-new.php?post_type=product' );
|
||||
|
||||
if ( isLoading ) {
|
||||
return null;
|
||||
}
|
||||
|
@ -63,55 +49,11 @@ export const ProductMoreMenu = () => {
|
|||
>
|
||||
{ ( { onClose } ) => (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
showCesModal(
|
||||
{
|
||||
action: 'new_product',
|
||||
title: __(
|
||||
"How's your experience with the product editor?",
|
||||
'woocommerce'
|
||||
),
|
||||
firstQuestion: __(
|
||||
'The product editing screen is easy to use',
|
||||
'woocommerce'
|
||||
),
|
||||
secondQuestion: __(
|
||||
"The product editing screen's functionality meets my needs",
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
{ shouldShowComments: () => true },
|
||||
{
|
||||
type: 'snackbar',
|
||||
icon: <span>🌟</span>,
|
||||
}
|
||||
);
|
||||
onClose();
|
||||
} }
|
||||
icon={ <FeedbackIcon /> }
|
||||
iconPosition="right"
|
||||
>
|
||||
{ __( 'Share feedback', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
if ( allowTracking ) {
|
||||
updateOptions( {
|
||||
[ NEW_PRODUCT_MANAGEMENT ]: 'no',
|
||||
} );
|
||||
showProductMVPFeedbackModal();
|
||||
onClose();
|
||||
} else {
|
||||
window.location.href = classEditorUrl;
|
||||
onClose();
|
||||
}
|
||||
} }
|
||||
icon={ <ClassicEditorIcon /> }
|
||||
iconPosition="right"
|
||||
>
|
||||
{ __( 'Use the classic editor', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
<FeedbackMenuItem onClose={ onClose } />
|
||||
<ClassicEditorMenuItem
|
||||
productId={ values.id }
|
||||
onClose={ onClose }
|
||||
/>
|
||||
</>
|
||||
) }
|
||||
</DropdownMenu>
|
||||
|
|
|
@ -3,26 +3,28 @@
|
|||
*/
|
||||
import {
|
||||
__experimentalEditor as Editor,
|
||||
AUTO_DRAFT_NAME,
|
||||
ProductEditorSettings,
|
||||
} from '@woocommerce/product-editor';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import { useDispatch, useSelect, select as WPSelect } from '@wordpress/data';
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
|
||||
import { Spinner } from '@wordpress/components';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useProductEntityRecord } from './hooks/use-product-entity-record';
|
||||
|
||||
import './product-page.scss';
|
||||
import './product-block-page.scss';
|
||||
import './fills/product-block-editor-fills';
|
||||
|
||||
declare const productBlockEditorSettings: ProductEditorSettings;
|
||||
|
||||
const ProductEditor: React.FC< { product: Product | undefined } > = ( {
|
||||
product,
|
||||
} ) => {
|
||||
export default function ProductPage() {
|
||||
const { productId } = useParams();
|
||||
|
||||
const product = useProductEntityRecord( productId );
|
||||
|
||||
if ( ! product?.id ) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
@ -33,57 +35,4 @@ const ProductEditor: React.FC< { product: Product | undefined } > = ( {
|
|||
settings={ productBlockEditorSettings || {} }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const EditProductEditor: React.FC< { productId: number } > = ( {
|
||||
productId,
|
||||
} ) => {
|
||||
const { product } = useSelect(
|
||||
( select: typeof WPSelect ) => {
|
||||
const { getEntityRecord } = select( 'core' );
|
||||
|
||||
return {
|
||||
product: getEntityRecord(
|
||||
'postType',
|
||||
'product',
|
||||
productId
|
||||
) as Product,
|
||||
};
|
||||
},
|
||||
[ productId ]
|
||||
);
|
||||
|
||||
return <ProductEditor product={ product } />;
|
||||
};
|
||||
|
||||
const AddProductEditor = () => {
|
||||
const { saveEntityRecord } = useDispatch( 'core' );
|
||||
const [ product, setProduct ] = useState< Product | undefined >(
|
||||
undefined
|
||||
);
|
||||
|
||||
useEffect( () => {
|
||||
saveEntityRecord( 'postType', 'product', {
|
||||
title: AUTO_DRAFT_NAME,
|
||||
status: 'auto-draft',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore Incorrect types.
|
||||
} ).then( ( autoDraftProduct: Product ) => {
|
||||
setProduct( autoDraftProduct );
|
||||
} );
|
||||
}, [] );
|
||||
|
||||
return <ProductEditor product={ product } />;
|
||||
};
|
||||
|
||||
export default function ProductPage() {
|
||||
const { productId } = useParams();
|
||||
|
||||
if ( productId ) {
|
||||
return (
|
||||
<EditProductEditor productId={ Number.parseInt( productId, 10 ) } />
|
||||
);
|
||||
}
|
||||
|
||||
return <AddProductEditor />;
|
||||
}
|
||||
|
|
|
@ -10,8 +10,11 @@ import { OPTIONS_STORE_NAME, WCDataSelector, WEEK } from '@woocommerce/data';
|
|||
import { Button, Card, CardHeader } from '@wordpress/components';
|
||||
import { Text } from '@woocommerce/experimental';
|
||||
import {
|
||||
ADMIN_INSTALL_TIMESTAMP_OPTION_NAME,
|
||||
ALLOW_TRACKING_OPTION_NAME,
|
||||
CustomerFeedbackModal,
|
||||
CustomerFeedbackSimple,
|
||||
SHOWN_FOR_ACTIONS_OPTION_NAME,
|
||||
} from '@woocommerce/customer-effort-score';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
|
@ -27,11 +30,7 @@ type TaskListCompletedHeaderProps = {
|
|||
customerEffortScore: boolean;
|
||||
};
|
||||
|
||||
const ADMIN_INSTALL_TIMESTAMP_OPTION_NAME =
|
||||
'woocommerce_admin_install_timestamp';
|
||||
const SHOWN_FOR_ACTIONS_OPTION_NAME = 'woocommerce_ces_shown_for_actions';
|
||||
const CUSTOMER_EFFORT_SCORE_ACTION = 'store_setup';
|
||||
const ALLOW_TRACKING_OPTION_NAME = 'woocommerce_allow_tracking';
|
||||
|
||||
function getStoreAgeInWeeks( adminInstallTimestamp: number ) {
|
||||
if ( adminInstallTimestamp === 0 ) {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Refactoring product editor more menu items, and using in block editor slot fills.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Set quantity value when stock tracking is enabled
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Run E2E tests on PR merge to trunk.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Filter out marketing channels in "Installed extensions" and "Discover more marketing tools" cards.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Overwrite clone method to prevent duplicate datq when saving a clone.
|
|
@ -0,0 +1,5 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
Comment: Revert #36768
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
|
||||
Remove timeouts in e2e tests for variable products and analytics.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Move additional CES-related components to @woocommerce/customer-effort-score.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Replacing multiple components on the block product page with a single hook.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: This PR updates stable tag, no changelog entry is required.
|
||||
|
|
@ -131,6 +131,17 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method overwrites the base class's clone method to make it a no-op. In base class WC_Data, we are unsetting the meta_id to clone.
|
||||
* It seems like this was done to avoid conflicting the metadata when duplicating products. However, doing that does not seems necessary for orders.
|
||||
* In-fact, when we do that for orders, we lose the capability to clone orders with custom meta data by caching plugins. This is because, when we clone an order object for caching, it will clone the metadata without the ID. Unfortunately, when this cached object with nulled meta ID is retreived, WC_Data will consider it as a new meta and will insert it as a new meta-data causing duplicates.
|
||||
*
|
||||
* Eventually, we should move away from overwriting the __clone method in base class itself, since it's easily possible to still duplicate the product without having to hook into the __clone method.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*/
|
||||
public function __clone() {}
|
||||
|
||||
/**
|
||||
* Get internal type.
|
||||
*
|
||||
|
|
|
@ -50,8 +50,8 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
woocommerce_wp_text_input(
|
||||
array(
|
||||
'id' => '_stock',
|
||||
'value' => wc_stock_amount( $product_object->get_stock_quantity( 'edit' ) ),
|
||||
'label' => __( 'Stock quantity', 'woocommerce' ),
|
||||
'value' => wc_stock_amount( $product_object->get_stock_quantity( 'edit' ) ?? 1 ),
|
||||
'label' => __( 'Quantity', 'woocommerce' ),
|
||||
'desc_tip' => true,
|
||||
'description' => __( 'Stock quantity. If this is a variable product this value will be used to control stock for all variations, unless you define stock at variation level.', 'woocommerce' ),
|
||||
'type' => 'number',
|
||||
|
|
|
@ -4,7 +4,7 @@ Tags: online store, ecommerce, shop, shopping cart, sell online, storefront, che
|
|||
Requires at least: 5.9
|
||||
Tested up to: 6.1
|
||||
Requires PHP: 7.2
|
||||
Stable tag: 7.5.0
|
||||
Stable tag: 7.5.1
|
||||
License: GPLv3
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
|
|
@ -40,11 +40,23 @@ class RemoteInboxNotificationsEngine {
|
|||
|
||||
// Hook into WCA updated. This is hooked up here rather than in
|
||||
// on_admin_init because that runs too late to hook into the action.
|
||||
WC()->queue()->schedule_single(
|
||||
time(),
|
||||
add_action(
|
||||
'woocommerce_updated',
|
||||
array( __CLASS__, 'run_on_woocommerce_admin_updated' ),
|
||||
'woocommerce-remote-inbox-engine'
|
||||
function() {
|
||||
$next_hook = WC()->queue()->get_next(
|
||||
'woocommerce_run_on_woocommerce_admin_updated',
|
||||
array( __CLASS__, 'run_on_woocommerce_admin_updated' ),
|
||||
'woocommerce-remote-inbox-engine'
|
||||
);
|
||||
if ( null === $next_hook ) {
|
||||
WC()->queue()->schedule_single(
|
||||
time(),
|
||||
'woocommerce_run_on_woocommerce_admin_updated',
|
||||
array( __CLASS__, 'run_on_woocommerce_admin_updated' ),
|
||||
'woocommerce-remote-inbox-engine'
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_filter( 'woocommerce_get_note_from_db', array( __CLASS__, 'get_note_from_db' ), 10, 1 );
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Automattic\WooCommerce\Caches;
|
||||
|
||||
use Automattic\WooCommerce\Caching\CacheException;
|
||||
use Automattic\WooCommerce\Caching\ObjectCache;
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,7 +36,13 @@ test.describe( 'Analytics pages', () => {
|
|||
'//button[@title="Choose which analytics to display and the section name"]'
|
||||
);
|
||||
await page.click( 'text=Move up' );
|
||||
await page.waitForTimeout( 1000 );
|
||||
|
||||
// wait for the changes to be saved
|
||||
await page.waitForResponse(
|
||||
( response ) =>
|
||||
response.url().includes( '/users/' ) &&
|
||||
response.status() === 200
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
||||
|
|
|
@ -55,19 +55,28 @@ test.describe.serial( 'Add New Variable Product Page', () => {
|
|||
if ( i > 0 ) {
|
||||
await page.click( 'button.add_attribute' );
|
||||
}
|
||||
const input = `input[name="attribute_names[${ i }]"]`;
|
||||
|
||||
await page.waitForSelector( input, { timeout: 1000 } ); // Wait for up to 1 second
|
||||
await page.fill( input, `attr #${ i + 1 }` );
|
||||
await page.fill(
|
||||
`textarea[name="attribute_values[${ i }]"]`,
|
||||
'val1 | val2'
|
||||
);
|
||||
await page
|
||||
.locator(
|
||||
`.woocommerce_attribute_data input[name="attribute_names[${ i }]"]`
|
||||
)
|
||||
.fill( `attr #${ i + 1 }` );
|
||||
await page
|
||||
.locator(
|
||||
`.woocommerce_attribute_data textarea[name="attribute_values[${ i }]"]`
|
||||
)
|
||||
.fill( 'val1 | val2' );
|
||||
}
|
||||
await page.keyboard.press( 'ArrowUp' );
|
||||
await page.click( 'text=Save attributes' );
|
||||
|
||||
await page.waitForTimeout( 1000 ); // Wait for 1 second
|
||||
// wait for the attributes to be saved
|
||||
await page.waitForResponse(
|
||||
( response ) =>
|
||||
response.url().includes( '/post.php?post=' ) &&
|
||||
response.status() === 200
|
||||
);
|
||||
|
||||
// Save before going to the Variations tab to prevent variations from all attributes to be automatically created
|
||||
await page.locator( '#save-post' ).click();
|
||||
await expect(
|
||||
|
@ -206,24 +215,28 @@ test.describe.serial( 'Add New Variable Product Page', () => {
|
|||
if ( i > 0 ) {
|
||||
await page.click( 'button.add_attribute' );
|
||||
}
|
||||
const input = `input[name="attribute_names[${ i }]"]`;
|
||||
|
||||
await page.waitForSelector( input, { timeout: 1000 } ); // Wait for up to 1 seconds
|
||||
await page.fill( input, `attr #${ i + 1 }` );
|
||||
await page.fill(
|
||||
`textarea[name="attribute_values[${ i }]"]`,
|
||||
'val1 | val2'
|
||||
);
|
||||
await page
|
||||
.locator(
|
||||
`.woocommerce_attribute_data input[name="attribute_names[${ i }]"]`
|
||||
)
|
||||
.fill( `attr #${ i + 1 }` );
|
||||
await page
|
||||
.locator(
|
||||
`.woocommerce_attribute_data textarea[name="attribute_values[${ i }]"]`
|
||||
)
|
||||
.fill( 'val1 | val2' );
|
||||
await page.keyboard.press( 'ArrowUp' );
|
||||
await page.click( 'text=Save attributes' );
|
||||
await expect(
|
||||
page
|
||||
.locator( '.woocommerce_attribute.closed' )
|
||||
.filter( { hasText: `attr #${ i + 1 }` } )
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
await page.waitForTimeout( 1000 ); // Wait for 1 second
|
||||
// wait for the attributes to be saved
|
||||
await page.waitForResponse(
|
||||
( response ) =>
|
||||
response.url().includes( '/post.php?post=' ) &&
|
||||
response.status() === 200
|
||||
);
|
||||
|
||||
// Save before going to the Variations tab to prevent variations from all attributes to be automatically created
|
||||
await page.locator( '#save-post' ).click();
|
||||
await expect(
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
use Automattic\WooCommerce\Caches\OrderCache;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
/**
|
||||
* Class OrderCacheTest.
|
||||
*/
|
||||
class OrderCacheTest extends \WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* System under test.
|
||||
*
|
||||
* @var OrderCache
|
||||
*/
|
||||
private $sut;
|
||||
|
||||
/**
|
||||
* Setup test.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->sut = new OrderCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the order cache does not cause duplicate data storage.
|
||||
*/
|
||||
public function test_meta_is_not_duplicated_when_cached() {
|
||||
global $wpdb;
|
||||
if ( ! OrderUtil::orders_cache_usage_is_enabled() ) {
|
||||
// tip: add HPOS=1 env variable to run this test.
|
||||
$this->markTestSkipped( 'HPOS based caching is not enabled.' );
|
||||
}
|
||||
$order = WC_Helper_Order::create_order();
|
||||
$order->add_meta_data( 'test', 'test' );
|
||||
$order->save_meta_data();
|
||||
|
||||
$order = wc_get_order( $order->get_id() );
|
||||
$this->assertTrue( $this->sut->is_cached( $order->get_id() ), 'Order was not cached, but it was expected to be cached. Are you sure that HPOS based caching is enabled.' );
|
||||
|
||||
$order2 = wc_get_order( $order->get_id() );
|
||||
$order2->save_meta_data();
|
||||
|
||||
$orders_meta_table = OrdersTableDataStore::get_meta_table_name();
|
||||
$query = $wpdb->prepare( "SELECT id FROM $orders_meta_table WHERE order_id = %d AND meta_key = %s", $order->get_id(), 'test' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$this->assertEquals( 1, count( $wpdb->get_col( $query ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared query.
|
||||
}
|
||||
|
||||
}
|
1278
pnpm-lock.yaml
1278
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue