Merge branch 'trunk' into e2e/release-plugins-0

This commit is contained in:
rodelgc 2023-03-22 18:04:42 +08:00
commit 06da097968
12 changed files with 372 additions and 121 deletions

View File

@ -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

View File

@ -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';

View File

@ -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,
};
};

View File

@ -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,
};
};

View File

@ -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.

View File

@ -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 }
/>

View File

@ -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.

View File

@ -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,
};
}, [] );
};

View File

@ -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,
};
};

View File

@ -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>
);
} ) }

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Run E2E tests on PR merge to trunk.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Filter out marketing channels in "Installed extensions" and "Discover more marketing tools" cards.