@@ -7,7 +7,6 @@ import Knock from "../../knock";
77
88import {
99 DEFAULT_GROUP_KEY ,
10- SelectionResult ,
1110 byKey ,
1211 checkStateIfThrottled ,
1312 findDefaultGroup ,
@@ -46,6 +45,8 @@ import {
4645 SelectFilterParams ,
4746 SelectGuideOpts ,
4847 SelectGuidesOpts ,
48+ SelectQueryLimit ,
49+ SelectionResult ,
4950 StepMessageState ,
5051 StoreState ,
5152 TargetParams ,
@@ -150,7 +151,16 @@ const safeJsonParseDebugParams = (value: string): DebugState => {
150151 }
151152} ;
152153
153- const select = ( state : StoreState , filters : SelectFilterParams = { } ) => {
154+ type SelectQueryMetadata = {
155+ limit : SelectQueryLimit ;
156+ opts : SelectGuideOpts ;
157+ } ;
158+
159+ const select = (
160+ state : StoreState ,
161+ filters : SelectFilterParams ,
162+ metadata : SelectQueryMetadata ,
163+ ) => {
154164 // A map of selected guides as values, with its order index as keys.
155165 const result = new SelectionResult ( ) ;
156166
@@ -175,7 +185,8 @@ const select = (state: StoreState, filters: SelectFilterParams = {}) => {
175185 result . set ( index , guide ) ;
176186 }
177187
178- result . metadata = { guideGroup : defaultGroup } ;
188+ result . metadata = { guideGroup : defaultGroup , filters, ...metadata } ;
189+
179190 return result ;
180191} ;
181192
@@ -617,14 +628,35 @@ export class KnockGuideClient {
617628 `[Guide] .selectGuides (filters: ${ formatFilters ( filters ) } ; state: ${ formatState ( state ) } )` ,
618629 ) ;
619630
620- const selectedGuide = this . selectGuide ( state , filters , opts ) ;
621- if ( ! selectedGuide ) {
631+ // 1. First, call selectGuide() using the same filters to ensure we have a
632+ // group stage open and respect throttling. This isn't the real query, but
633+ // rather it's a shortcut ahead of handling the actual query result below.
634+ const selectedGuide = this . selectGuide ( state , filters , {
635+ ...opts ,
636+ // Don't record this result, not the actual query result we need.
637+ recordSelectQuery : false ,
638+ } ) ;
639+
640+ // 2. Now make the actual select query with the provided filters and opts,
641+ // and record the result (as needed). By default, we only record the result
642+ // while in debugging.
643+ const { recordSelectQuery = ! ! state . debug ?. debugging } = opts ;
644+ const metadata : SelectQueryMetadata = {
645+ limit : "all" ,
646+ opts : { ...opts , recordSelectQuery } ,
647+ } ;
648+ const result = select ( state , filters , metadata ) ;
649+ this . maybeRecordSelectResult ( result ) ;
650+
651+ // 3. Stop if there is not at least one guide to return.
652+ if ( ! selectedGuide && ! opts . includeThrottled ) {
622653 return [ ] ;
623654 }
624655
625656 // There should be at least one guide to return here now.
626- const guides = [ ...select ( state , filters ) . values ( ) ] ;
657+ const guides = [ ...result . values ( ) ] ;
627658
659+ // 4. If throttled, filter out any throttled guides.
628660 if ( ! opts . includeThrottled && checkStateIfThrottled ( state ) ) {
629661 const unthrottledGuides = guides . filter (
630662 ( g ) => g . bypass_global_group_limit ,
@@ -657,32 +689,6 @@ export class KnockGuideClient {
657689 return undefined ;
658690 }
659691
660- const result = select ( state , filters ) ;
661-
662- if ( result . size === 0 ) {
663- this . knock . log ( "[Guide] Selection found zero result" ) ;
664- return undefined ;
665- }
666-
667- const [ index , guide ] = [ ...result ] [ 0 ] ! ;
668- this . knock . log (
669- `[Guide] Selection found: \`${ guide . key } \` (total: ${ result . size } )` ,
670- ) ;
671-
672- // If a guide ignores the group limit, then return immediately to render
673- // always.
674- if ( guide . bypass_global_group_limit ) {
675- this . knock . log ( `[Guide] Returning the unthrottled guide: ${ guide . key } ` ) ;
676- return guide ;
677- }
678-
679- // Check if inside the throttle window (i.e. throttled) and if so stop and
680- // return undefined unless explicitly given the option to include throttled.
681- if ( ! opts . includeThrottled && checkStateIfThrottled ( state ) ) {
682- this . knock . log ( `[Guide] Throttling the selected guide: ${ guide . key } ` ) ;
683- return undefined ;
684- }
685-
686692 // Starting here to the end of this method represents the core logic of how
687693 // "group stage" works. It provides a mechanism for 1) figuring out which
688694 // guide components are about to render on a page, 2) determining which
@@ -716,6 +722,35 @@ export class KnockGuideClient {
716722 this . stage = this . openGroupStage ( ) ; // Assign here to make tsc happy
717723 }
718724
725+ // Must come AFTER we ensure a group stage exists above, so we can record
726+ // select queries. By default, we only record the result while in debugging.
727+ const { recordSelectQuery = ! ! state . debug ?. debugging } = opts ;
728+ const metadata : SelectQueryMetadata = {
729+ limit : "one" ,
730+ opts : { ...opts , recordSelectQuery } ,
731+ } ;
732+ const result = select ( state , filters , metadata ) ;
733+ this . maybeRecordSelectResult ( result ) ;
734+
735+ if ( result . size === 0 ) {
736+ this . knock . log ( "[Guide] Selection found zero result" ) ;
737+ return undefined ;
738+ }
739+
740+ const [ index , guide ] = [ ...result ] [ 0 ] ! ;
741+ this . knock . log (
742+ `[Guide] Selection found: \`${ guide . key } \` (total: ${ result . size } )` ,
743+ ) ;
744+
745+ // If a guide ignores the group limit, then return immediately to render
746+ // always.
747+ if ( guide . bypass_global_group_limit ) {
748+ this . knock . log ( `[Guide] Returning the unthrottled guide: ${ guide . key } ` ) ;
749+ return guide ;
750+ }
751+
752+ const throttled = ! opts . includeThrottled && checkStateIfThrottled ( state ) ;
753+
719754 switch ( this . stage . status ) {
720755 case "open" : {
721756 this . knock . log ( `[Guide] Adding to the group stage: ${ guide . key } ` ) ;
@@ -725,8 +760,16 @@ export class KnockGuideClient {
725760
726761 case "patch" : {
727762 this . knock . log ( `[Guide] Patching the group stage: ${ guide . key } ` ) ;
763+ // Refresh the ordered queue in the group stage while continuing to
764+ // render the currently resolved guide while in patch window, so that
765+ // we can re-resolve when the group stage closes.
728766 this . stage . ordered [ index ] = guide . key ;
729767
768+ if ( throttled ) {
769+ this . knock . log ( `[Guide] Throttling the selected guide: ${ guide . key } ` ) ;
770+ return undefined ;
771+ }
772+
730773 const ret = this . stage . resolved === guide . key ? guide : undefined ;
731774 this . knock . log (
732775 `[Guide] Returning \`${ ret ?. key } \` (stage: ${ formatGroupStage ( this . stage ) } )` ,
@@ -735,6 +778,11 @@ export class KnockGuideClient {
735778 }
736779
737780 case "closed" : {
781+ if ( throttled ) {
782+ this . knock . log ( `[Guide] Throttling the selected guide: ${ guide . key } ` ) ;
783+ return undefined ;
784+ }
785+
738786 const ret = this . stage . resolved === guide . key ? guide : undefined ;
739787 this . knock . log (
740788 `[Guide] Returning \`${ ret ?. key } \` (stage: ${ formatGroupStage ( this . stage ) } )` ,
@@ -744,6 +792,42 @@ export class KnockGuideClient {
744792 }
745793 }
746794
795+ // Record select query results by accumulating them by 1) key or type first,
796+ // and then 2) "one" or "all".
797+ private maybeRecordSelectResult ( result : SelectionResult ) {
798+ if ( ! result . metadata ) return ;
799+
800+ const { opts, filters, limit } = result . metadata ;
801+ if ( ! opts . recordSelectQuery ) return ;
802+ if ( ! filters . key && ! filters . type ) return ;
803+ if ( ! this . stage || this . stage . status === "closed" ) return ;
804+
805+ // Deep merge to accumulate the results.
806+ const queriedByKey = this . stage . results . key || { } ;
807+ if ( filters . key ) {
808+ queriedByKey [ filters . key ] = {
809+ ...( queriedByKey [ filters . key ] || { } ) ,
810+ ...{ [ limit ] : result } ,
811+ } ;
812+ }
813+ const queriedByType = this . stage . results . type || { } ;
814+ if ( filters . type ) {
815+ queriedByType [ filters . type ] = {
816+ ...( queriedByType [ filters . type ] || { } ) ,
817+ ...{ [ limit ] : result } ,
818+ } ;
819+ }
820+
821+ this . stage = {
822+ ...this . stage ,
823+ results : { key : queriedByKey , type : queriedByType } ,
824+ } ;
825+ }
826+
827+ getStage ( ) {
828+ return this . stage ;
829+ }
830+
747831 private openGroupStage ( ) {
748832 this . knock . log ( "[Guide] Opening a new group stage" ) ;
749833
@@ -759,6 +843,7 @@ export class KnockGuideClient {
759843 this . stage = {
760844 status : "open" ,
761845 ordered : [ ] ,
846+ results : { } ,
762847 timeoutId,
763848 } ;
764849
0 commit comments