Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion includes/Experiments/Review_Notes/Review_Notes.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,22 @@ public function maybe_set_ai_author( $prepared_comment, \WP_REST_Request $reques
*/
public function enqueue_assets(): void {
Asset_Loader::enqueue_script( 'review_notes', 'experiments/review-notes' );

/**
* Filters the minimum content length required to enable review notes.
*
* @since x.x.x
*
* @param int $min_content_length The minimum number of characters required. Default 100.
*/
$min_content_length = (int) apply_filters( 'wpai_review_notes_min_content_length', 100 );

Asset_Loader::localize_script(
'review_notes',
'ReviewNotesData',
array(
'enabled' => $this->is_enabled(),
'enabled' => $this->is_enabled(),
'minContentLength' => $min_content_length,
)
);
}
Expand Down
40 changes: 31 additions & 9 deletions src/experiments/review-notes/components/ReviewNotesPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,20 @@ import { useReviewBlock, useReviewNotes } from '../hooks/useReviewNotes';
* reviewable blocks.
*/
export default function ReviewNotesPlugin() {
const { isReviewing, progress, total, lastRunCount, runReview } =
useReviewNotes();
const { isReviewing: isReviewingBlock, reviewBlock } = useReviewBlock();
const {
isReviewing,
progress,
total,
lastRunCount,
runReview,
isContentTooShort,
minContentLength,
} = useReviewNotes();
const {
isReviewing: isReviewingBlock,
reviewBlock,
isContentTooShort: isBlockReviewDisabled,
} = useReviewBlock();
const { openGeneralSidebar } = useDispatch( editPostStore );
const openNotesPanel = () =>
openGeneralSidebar?.( 'edit-post/collab-sidebar' );
Expand All @@ -54,10 +65,19 @@ export default function ReviewNotesPlugin() {
const buttonLabel = isReviewing
? reviewingLabel
: __( 'Generate Review Notes', 'ai' );
const buttonDescription = __(
'This will review the content of this post block-by-block, and create Notes attached to each block with suggestions.',
'ai'
);
const buttonDescription = isContentTooShort
? sprintf(
/* translators: %d: minimum number of characters required. */
__(
'Review Notes will be available when the post content has at least %d characters.',
'ai'
),
minContentLength
)
: __(
'This will review the content of this post block-by-block, and create Notes attached to each block with suggestions.',
'ai'
);

return (
<>
Expand All @@ -69,7 +89,7 @@ export default function ReviewNotesPlugin() {
icon={ commentContent }
onClick={ runReview }
isBusy={ isReviewing }
disabled={ isReviewing }
disabled={ isReviewing || isContentTooShort }
style={ {
justifyContent: 'center',
width: '100%',
Expand Down Expand Up @@ -136,7 +156,9 @@ export default function ReviewNotesPlugin() {
icon={
isReviewingBlock ? <Spinner /> : commentContent
}
disabled={ isReviewingBlock }
disabled={
isReviewingBlock || isBlockReviewDisabled
}
onClick={ () => {
if ( clientId ) {
reviewBlock( clientId );
Expand Down
61 changes: 52 additions & 9 deletions src/experiments/review-notes/hooks/useReviewNotes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
/**
* WordPress dependencies
*/
import { dispatch, select } from '@wordpress/data';
import { dispatch, select, useSelect } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { store as coreStore } from '@wordpress/core-data';
import { store as editPostStore } from '@wordpress/edit-post';
import { store as editorStore } from '@wordpress/editor';
import { useState } from '@wordpress/element';
import { __, _n, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { count } from '@wordpress/wordcount';

/**
* Internal dependencies
Expand Down Expand Up @@ -70,6 +71,32 @@ interface NoteRecord {
[ key: string ]: unknown;
}

/**
* Hook for determining whether Review Notes should be available for the
* current post content.
*
* @return Availability state for the Review Notes feature.
*/
function useReviewNotesAvailability(): {
content: string;
minContentLength: number;
isContentTooShort: boolean;
} {
const content =
useSelect( ( selectStore ) => {
return ( selectStore( editorStore ) as any ).getEditedPostContent();
}, [] ) ?? '';
const minContentLength: number =
( window as any ).aiReviewNotesData?.minContentLength ?? 100;

return {
content,
minContentLength,
isContentTooShort:
count( content, 'characters_including_spaces' ) < minContentLength,
};
}

/**
* Reviews a single block and creates/updates a Note if suggestions are found.
*
Expand Down Expand Up @@ -165,18 +192,26 @@ export function useReviewNotes(): {
progress: number;
total: number;
lastRunCount: number | null;
isContentTooShort: boolean;
minContentLength: number;
runReview: () => Promise< void >;
} {
const [ isReviewing, setIsReviewing ] = useState< boolean >( false );
const [ progress, setProgress ] = useState< number >( 0 );
const [ total, setTotal ] = useState< number >( 0 );
const [ lastRunCount, setLastRunCount ] = useState< number | null >( null );
const { content, isContentTooShort, minContentLength } =
useReviewNotesAvailability();

const runReview = async () => {
if ( ! ensureProvider( NOTICE_ID ) ) {
return;
}

if ( isContentTooShort ) {
return;
}

setIsReviewing( true );
setProgress( 0 );
setTotal( 0 );
Expand All @@ -188,9 +223,6 @@ export function useReviewNotes(): {
const postId = (
select( editorStore ) as any
).getCurrentPostId() as number;
const content = (
select( editorStore ) as any
).getEditedPostContent() as string;

// Get all blocks and flatten the tree.
const allBlocks = (
Expand Down Expand Up @@ -279,7 +311,15 @@ export function useReviewNotes(): {
}
};

return { isReviewing, progress, total, lastRunCount, runReview };
return {
isReviewing,
progress,
total,
lastRunCount,
isContentTooShort,
minContentLength,
runReview,
};
}

/**
Expand All @@ -289,11 +329,17 @@ export function useReviewNotes(): {
*/
export function useReviewBlock(): {
isReviewing: boolean;
isContentTooShort: boolean;
reviewBlock: ( clientId: string ) => Promise< void >;
} {
const [ isReviewing, setIsReviewing ] = useState< boolean >( false );
const { content, isContentTooShort } = useReviewNotesAvailability();

const reviewBlock = async ( clientId: string ) => {
if ( isContentTooShort ) {
return;
}

setIsReviewing( true );

( dispatch( noticesStore ) as any ).removeNotice(
Expand All @@ -312,9 +358,6 @@ export function useReviewBlock(): {
const postId = (
select( editorStore ) as any
).getCurrentPostId() as number;
const content = (
select( editorStore ) as any
).getEditedPostContent() as string;

// Fetch fresh note state for this invocation.
const [ pendingNotes, approvedNotes ] = await Promise.all( [
Expand Down Expand Up @@ -379,7 +422,7 @@ export function useReviewBlock(): {
}
};

return { isReviewing, reviewBlock };
return { isReviewing, isContentTooShort, reviewBlock };
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,46 @@ public function test_ai_note_comment_meta_is_registered() {
$this->assertTrue( $registered['ai_note']['show_in_rest'], 'ai_note meta should have show_in_rest enabled' );
}

/**
* Tests that enqueue_assets() localizes the default minimum content length.
*
* @since x.x.x
*/
public function test_enqueue_assets_localizes_default_min_content_length() {
$GLOBALS['wp_scripts'] = new \WP_Scripts();

$this->experiment->enqueue_assets();

$this->assertTrue( wp_script_is( 'ai_review_notes', 'enqueued' ) );
$this->assertStringContainsString(
'"minContentLength":"100"',
(string) wp_scripts()->get_data( 'ai_review_notes', 'data' )
);
}

/**
* Tests that enqueue_assets() localizes the filtered minimum content length.
*
* @since x.x.x
*/
public function test_enqueue_assets_localizes_filtered_min_content_length() {
$filter = static function () {
return 250;
};

add_filter( 'wpai_review_notes_min_content_length', $filter );
$GLOBALS['wp_scripts'] = new \WP_Scripts();

$this->experiment->enqueue_assets();

remove_filter( 'wpai_review_notes_min_content_length', $filter );

$this->assertStringContainsString(
'"minContentLength":"250"',
(string) wp_scripts()->get_data( 'ai_review_notes', 'data' )
);
}

// -------------------------------------------------------------------------
// maybe_set_ai_author()
// -------------------------------------------------------------------------
Expand Down
Loading
Loading