Introduce live branches UI page to WooCommerce Beta Tester (#35643)

* Add basic classes to register the Live Branches page
* Add API endpoint to load live branch manifest
* TS/JS tooling, prettier tooling
This commit is contained in:
Sam Seay 2022-11-30 14:17:27 +13:00 committed by GitHub
parent a734772c69
commit 978604d0f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 786 additions and 759 deletions

View File

@ -0,0 +1,3 @@
// Import the default config file and expose it in the project root.
// Useful for editor integrations.
module.exports = require("@wordpress/prettier-config");

View File

@ -41,16 +41,17 @@ function register_woocommerce_admin_test_helper_rest_route( $route, $callback, $
);
}
require( 'admin-notes/delete-all-notes.php' );
require( 'admin-notes/add-note.php' );
require( 'tools/trigger-wca-install.php' );
require( 'tools/trigger-cron-job.php' );
require( 'tools/run-wc-admin-daily.php' );
require( 'options/rest-api.php' );
require( 'tools/delete-all-products.php' );
require( 'tools/disable-wc-email.php' );
require( 'tools/trigger-update-callbacks.php' );
require( 'tracks/class-tracks-debug-log.php' );
require( 'features/features.php' );
require( 'rest-api-filters/rest-api-filters.php' );
require( 'rest-api-filters/hook.php' );
require 'admin-notes/delete-all-notes.php';
require 'admin-notes/add-note.php';
require 'tools/trigger-wca-install.php';
require 'tools/trigger-cron-job.php';
require 'tools/run-wc-admin-daily.php';
require 'options/rest-api.php';
require 'tools/delete-all-products.php';
require 'tools/disable-wc-email.php';
require 'tools/trigger-update-callbacks.php';
require 'tracks/class-tracks-debug-log.php';
require 'features/features.php';
require 'rest-api-filters/rest-api-filters.php';
require 'rest-api-filters/hook.php';
require 'live-branches/manifest.php';

View File

@ -0,0 +1,24 @@
<?php
/**
* Register REST endpoint for fetching live branches manifest.
*
* @package WC_Beta_Tester
*/
register_woocommerce_admin_test_helper_rest_route(
'/live-branches/manifest/v1',
'fetch_live_branches_manifest',
array(
'methods' => 'GET',
)
);
/**
* API endpoint to fetch the manifest of live branches.
*/
function fetch_live_branches_manifest() {
$response = wp_remote_get( 'https://betadownload.jetpack.me/woocommerce-branches.json' );
$body = wp_remote_retrieve_body( $response );
return new WP_REST_Response( json_decode( $body ), 200 );
}

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add a basic UI screen for installing from live branches (disabled in prod)

View File

@ -0,0 +1,78 @@
<?php
/**
* Beta Tester Plugin Live Branches feature class.
*
* @package WC_Beta_Tester
*/
defined( 'ABSPATH' ) || exit;
/**
* WC_Beta_Tester Live Branches Feature Class.
*/
class WC_Beta_Tester_Live_Branches {
/**
* Constructor.
*/
public function __construct() {
add_action( 'admin_enqueue_scripts', array( $this, 'register_scripts' ) );
// By the time this code runs it appears too late to hook into `admin_menu`.
// NOTE - We don't have feature flags, so add the following code to enable it
// in development: `$this->register_page()`.
}
/**
* Register live branches scripts.
*/
public function register_scripts() {
if ( ! method_exists( 'Automattic\WooCommerce\Admin\PageController', 'is_admin_or_embed_page' ) ||
! \Automattic\WooCommerce\Admin\PageController::is_admin_or_embed_page()
) {
return;
}
$script_path = '/build/live-branches.js';
$script_asset_path = dirname( __FILE__ ) . '/../build/live-branches.asset.php';
$script_asset = file_exists( $script_asset_path )
? require $script_asset_path
: array(
'dependencies' => array(),
'version' => filemtime( $script_path ),
);
$script_url = WC_Beta_Tester::instance()->plugin_url() . $script_path;
wp_register_script(
'woocommerce-beta-tester-live-branches',
$script_url,
$script_asset['dependencies'],
$script_asset['version'],
true
);
wp_enqueue_script( 'woocommerce-beta-tester-live-branches' );
}
/**
* Register live branches page.
*/
public function register_page() {
if ( ! function_exists( 'wc_admin_register_page' ) ) {
return;
}
wc_admin_register_page(
array(
'id' => 'woocommerce-beta-tester-live-branches',
// phpcs:disable
'title' => __( 'Live Branches', 'woocommerce-beta-tester' ),
'path' => '/live-branches',
'parent' => 'woocommerce',
'capability' => 'read',
)
);
}
}
return new WC_Beta_Tester_Live_Branches();

View File

@ -101,6 +101,7 @@ class WC_Beta_Tester {
* Include any classes we need within admin.
*/
public function includes() {
include_once dirname( __FILE__ ) . '/class-wc-beta-tester-live-branches.php';
include_once dirname( __FILE__ ) . '/class-wc-beta-tester-admin-menus.php';
include_once dirname( __FILE__ ) . '/class-wc-beta-tester-admin-assets.php';
}

View File

@ -10,25 +10,38 @@
"version": "2.1.0",
"homepage": "http://github.com/woocommerce/woocommerce-beta-tester",
"devDependencies": {
"@types/react": "^17.0.2",
"@types/react-dom": "^18.0.6",
"@types/wordpress__components": "^19.10.1",
"@woocommerce/dependency-extraction-webpack-plugin": "workspace:*",
"@woocommerce/eslint-plugin": "workspace:*",
"@wordpress/env": "^4.8.0",
"@wordpress/prettier-config": "^2.5.0",
"@wordpress/scripts": "^19.2.4",
"eslint": "5.16.0",
"prettier": "npm:wp-prettier@^2.6.2",
"ts-loader": "^9.4.1",
"typescript": "^4.8.3",
"uglify-js": "^3.5.3"
},
"dependencies": {
"@emotion/react": "^11.10.4",
"@types/prop-types": "^15.7.4",
"@woocommerce/components": "workspace:*",
"@woocommerce/data": "workspace:*",
"@wordpress/api-fetch": "^3.21.5",
"@wordpress/components": "^12.0.7",
"@wordpress/api-fetch": "^3.23.1",
"@wordpress/components": "^12.0.9",
"@wordpress/compose": "^3.24.4",
"@wordpress/data": "^4.26.7",
"@wordpress/data-controls": "^1.20.7",
"@wordpress/element": "^2.19.1",
"@wordpress/element": "^4.18.0",
"@wordpress/hooks": "^2.11.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"assets": {
"js": {
"min": "assets/js/*.min.js",

View File

@ -0,0 +1,48 @@
/**
* External dependencies
*/
import {
Card,
CardBody,
CardFooter,
CardHeader,
// @ts-ignore
__experimentalHeading as Heading,
} from '@wordpress/components';
import { Spinner } from '@woocommerce/components';
import { css } from '@emotion/react';
/**
* Internal dependencies
*/
import { useLiveBranchesData } from './hooks/live-branches';
import { BranchList } from './components/BranchList';
const cardStyle = css( {
marginTop: '32px',
} );
export const App = () => {
const { branches, isLoading } = useLiveBranchesData();
return (
<>
<Heading level={ 1 }>
Live Branches - Install and test WooCommerce PRs
</Heading>
<Card elevation={ 3 } css={ cardStyle }>
<CardHeader>
<h2>Active PRs</h2>
</CardHeader>
<CardBody>
{ isLoading ? (
<Spinner />
) : (
<BranchList branches={ branches } />
) }
</CardBody>
<CardFooter></CardFooter>
</Card>
</>
);
};

View File

@ -0,0 +1,51 @@
/**
* External dependencies
*/
import {
// @ts-ignore
__experimentalItemGroup as ItemGroup,
// @ts-ignore
__experimentalItem as Item,
Button,
} from '@wordpress/components';
/**
* Internal dependencies
*/
import { Branch } from '../hooks/live-branches';
const BranchListItem = ( { branch }: { branch: Branch } ) => {
return (
<Item>
<p>
Download URL:{ ' ' }
<a href={ branch.download_url }>{ branch.download_url }</a>
</p>
<p>
Pull Request:{ ' ' }
<a
href={ `https://github.com/woocommerce/woocommerce/pull/${ branch.pr }` }
>
{ branch.branch }
</a>
</p>
<Button
variant="primary"
onClick={ () => console.log( 'Do install stuffs' ) }
>
Install
</Button>
</Item>
);
};
export const BranchList = ( { branches }: { branches: Branch[] } ) => {
return (
<ItemGroup isSeparated>
{ /* @ts-ignore */ }
{ branches.map( ( branch ) => (
<BranchListItem key={ branch.commit } branch={ branch } />
) ) }
</ItemGroup>
);
};

View File

@ -0,0 +1,41 @@
// @ts-ignore
import apiFetch from '@wordpress/api-fetch';
import { useEffect, useState } from 'react';
// @ts-ignore
import { API_NAMESPACE } from '../../features/data/constants';
export type Branch = {
branch: string;
commit: string;
download_url: string;
update_date: string;
version: string;
pr: number;
};
export const useLiveBranchesData = () => {
const [ branches, setBranches ] = useState< Branch[] >( [] );
const [ loading, setLoading ] = useState< boolean >( true );
useEffect( () => {
const getBranches = async () => {
const res = await apiFetch( {
path: `${ API_NAMESPACE }/live-branches/manifest/v1`,
method: 'GET',
} );
setBranches(
// @ts-ignore
Object.entries( res.pr ).map( ( [ , value ] ) => {
return value;
} ) as Branch[]
);
setLoading( false );
};
getBranches();
}, [] );
return { branches, isLoading: loading };
};

View File

@ -0,0 +1,22 @@
/**
* External dependencies
*/
import { addFilter } from '@wordpress/hooks';
/**
* Internal dependencies
*/
import { App } from './App';
addFilter( 'woocommerce_admin_pages_list', 'live-branches', ( pages ) => {
pages.push( {
container: App,
path: '/live-branches',
wpOpenMenu: 'toplevel_page_woocommerce',
capability: 'read',
breadcrumbs: [ 'Live Branches' ],
navArgs: { id: 'live-branches' },
} );
return pages;
} );

View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
// Target latest version of ECMAScript.
"target": "esnext",
// Search under node_modules for non-relative imports.
"moduleResolution": "node",
"jsx": "react-jsx",
// Temporary for resolving the test data json, remove when moving to using the API
"resolveJsonModule": true,
"jsxImportSource": "@emotion/react",
// Enable strictest settings like strictNullChecks & noImplicitAny.
"strict": true,
// Import non-ES modules as default imports.
"esModuleInterop": true,
// Skip type checking of declaration files because some libraries define two copies of the same type in an inconsistent way
"skipLibCheck": true,
"module": "esnext",
},
}

View File

@ -3,6 +3,25 @@ const WooCommerceDependencyExtractionWebpackPlugin = require( '@woocommerce/depe
module.exports = {
...defaultConfig,
entry: {
...defaultConfig.entry,
// Separate entry point for the live-branches page.
'live-branches': './src/live-branches/index.tsx',
},
module: {
...defaultConfig.module,
rules: [
...defaultConfig.module.rules,
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [ '.js', '.jsx', '.tsx', '.ts' ],
},
plugins: [
...defaultConfig.plugins.filter(
( plugin ) =>

File diff suppressed because it is too large Load Diff