Repository: vitessio/vitess
Pull request #10306 (2022-05-14 18:42:12)

Hints to git cli and github diff to hide diffs in proto generated files by default
## Description

Adds a couple of hints to hide the generated protobuf files:

* Adds .gitattributes to the generated proto directories. This tells `git diff` to only inform that the files differ but not show the actual diffs which can be hundreds of lines for some `pb.go` files. The files continue to show in the `git status` output.
* Follow-up of #10303. Adds golang protobuf generated files using `make proto` to the root .gitattributes file to optimize GitHub's diff view.

## Related Issue(s)
#10303 

## Checklist

-   [ ] "Backport me!" label has been added if this change should be backported
-   [X] Tests were added or are not required
-   [X] Documentation was added or is not required


rohit-nayak-ps: Closing this since this approach doesn't seem to be working for go lang proto files. Even without it the files were already hidden by github. I had expected the statistics that show changed lines to be different, but that is not happening: https://github.com/vitessio/vitess/pull/10306/files

Patch:
diff --git a/.gitattributes b/.gitattributes
index c2fb548f38..900d4f0253 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,1 +1,6 @@
 web/vtadmin/src/proto/** linguist-generated=true
+web/vtadmin/src/proto/*.js -diff
+web/vtadmin/src/proto/*.ts -diff
+go/vt/proto/** linguist-generated=true
+go/vt/proto/*/*.pb.go -diff
+go/vt/sqlparser/sql.go linguist-generated=true -diff
diff --git a/go/vt/schemadiff/mysql.go b/go/vt/schemadiff/mysql.go
index 06e16b8e1b..768714af7a 100644
--- a/go/vt/schemadiff/mysql.go
+++ b/go/vt/schemadiff/mysql.go
@@ -21,6 +21,14 @@ var engineCasing = map[string]string{
 	"MYISAM": "MyISAM",
 }
 
+var integralTypes = map[string]bool{
+	"tinyint":   true,
+	"smallint":  true,
+	"mediumint": true,
+	"int":       true,
+	"bigint":    true,
+}
+
 var charsetTypes = map[string]bool{
 	"char":       true,
 	"varchar":    true,
diff --git a/go/vt/schemadiff/table.go b/go/vt/schemadiff/table.go
index c698eea1e6..18994cfff9 100644
--- a/go/vt/schemadiff/table.go
+++ b/go/vt/schemadiff/table.go
@@ -351,6 +351,16 @@ func (c *CreateTableEntity) normalizeColumnOptions() {
 			col.Type.Charset.Name = charset
 		}
 
+		// Remove any lengths for integral types since it is deprecated there and
+		// doesn't mean anything anymore.
+		if _, ok := integralTypes[col.Type.Type]; ok {
+			// We can remove the length except when we have a boolean, which is
+			// stored as a tinyint(1) and treated special.
+			if !isBool(col.Type) {
+				col.Type.Length = nil
+			}
+		}
+
 		if _, ok := charsetTypes[col.Type.Type]; ok {
 			// If the charset is explicitly configured and it mismatches, we don't normalize
 			// anything for charsets or collations and move on.
@@ -391,6 +401,10 @@ func (c *CreateTableEntity) normalizeColumnOptions() {
 	}
 }
 
+func isBool(colType sqlparser.ColumnType) bool {
+	return colType.Type == "tinyint" && colType.Length != nil && sqlparser.CanonicalString(colType.Length) == "1"
+}
+
 func (c *CreateTableEntity) normalizePartitionOptions() {
 	if c.CreateTable.TableSpec.PartitionOption == nil {
 		return
diff --git a/go/vt/sqlparser/ast.go b/go/vt/sqlparser/ast.go
index acd91f5d53..01d61ceaa3 100644
--- a/go/vt/sqlparser/ast.go
+++ b/go/vt/sqlparser/ast.go
@@ -2353,8 +2353,12 @@ type (
 		JSONVal Expr
 	}
 
-	// Offset is another AST type that is used during planning and never produced by the parser
-	Offset int
+	// Offset is an AST type that is used during planning and never produced by the parser
+	// it is the column offset from the incoming result stream
+	Offset struct {
+		V        int
+		Original string
+	}
 
 	// JSONArrayExpr represents JSON_ARRAY()
 	// More information on https://dev.mysql.com/doc/refman/8.0/en/json-creation-functions.html#function_json-array
@@ -2597,7 +2601,7 @@ func (*ExtractedSubquery) iExpr()                  {}
 func (*TrimFuncExpr) iExpr()                       {}
 func (*JSONSchemaValidFuncExpr) iExpr()            {}
 func (*JSONSchemaValidationReportFuncExpr) iExpr() {}
-func (Offset) iExpr()                              {}
+func (*Offset) iExpr()                             {}
 func (*JSONPrettyExpr) iExpr()                     {}
 func (*JSONStorageFreeExpr) iExpr()                {}
 func (*JSONStorageSizeExpr) iExpr()                {}
diff --git a/go/vt/sqlparser/ast_clone.go b/go/vt/sqlparser/ast_clone.go
index c771ab85ee..6b1c85e7c2 100644
--- a/go/vt/sqlparser/ast_clone.go
+++ b/go/vt/sqlparser/ast_clone.go
@@ -255,8 +255,8 @@ func CloneSQLNode(in SQLNode) SQLNode {
 		return CloneRefOfNotExpr(in)
 	case *NullVal:
 		return CloneRefOfNullVal(in)
-	case Offset:
-		return in
+	case *Offset:
+		return CloneRefOfOffset(in)
 	case OnDup:
 		return CloneOnDup(in)
 	case *OptLike:
@@ -1612,6 +1612,15 @@ func CloneRefOfNullVal(n *NullVal) *NullVal {
 	return &out
 }
 
+// CloneRefOfOffset creates a deep clone of the input.
+func CloneRefOfOffset(n *Offset) *Offset {
+	if n == nil {
+		return nil
+	}
+	out := *n
+	return &out
+}
+
 // CloneOnDup creates a deep clone of the input.
 func CloneOnDup(n OnDup) OnDup {
 	if n == nil {
@@ -2815,8 +2824,8 @@ func CloneExpr(in Expr) Expr {
 		return CloneRefOfNotExpr(in)
 	case *NullVal:
 		return CloneRefOfNullVal(in)
-	case Offset:
-		return in
+	case *Offset:
+		return CloneRefOfOffset(in)
 	case *OrExpr:
 		return CloneRefOfOrExpr(in)
 	case *Subquery:
@@ -2961,8 +2970,8 @@ func CloneJSONPathParam(in JSONPathParam) JSONPathParam {
 		return CloneRefOfNotExpr(in)
 	case *NullVal:
 		return CloneRefOfNullVal(in)
-	case Offset:
-		return in
+	case *Offset:
+		return CloneRefOfOffset(in)
 	case *OrExpr:
 		return CloneRefOfOrExpr(in)
 	case *Subquery:
diff --git a/go/vt/sqlparser/ast_equals.go b/go/vt/sqlparser/ast_equals.go
index 41ce6a314d..0d311eb350 100644
--- a/go/vt/sqlparser/ast_equals.go
+++ b/go/vt/sqlparser/ast_equals.go
@@ -722,12 +722,12 @@ func EqualsSQLNode(inA, inB SQLNode) bool {
 			return false
 		}
 		return EqualsRefOfNullVal(a, b)
-	case Offset:
-		b, ok := inB.(Offset)
+	case *Offset:
+		b, ok := inB.(*Offset)
 		if !ok {
 			return false
 		}
-		return a == b
+		return EqualsRefOfOffset(a, b)
 	case OnDup:
 		b, ok := inB.(OnDup)
 		if !ok {
@@ -2607,6 +2607,18 @@ func EqualsRefOfNullVal(a, b *NullVal) bool {
 	return true
 }
 
+// EqualsRefOfOffset does deep equals between the two objects.
+func EqualsRefOfOffset(a, b *Offset) bool {
+	if a == b {
+		return true
+	}
+	if a == nil || b == nil {
+		return false
+	}
+	return a.V == b.V &&
+		a.Original == b.Original
+}
+
 // EqualsOnDup does deep equals between the two objects.
 func EqualsOnDup(a, b OnDup) bool {
 	if len(a) != len(b) {
@@ -4454,12 +4466,12 @@ func EqualsExpr(inA, inB Expr) bool {
 			return false
 		}
 		return EqualsRefOfNullVal(a, b)
-	case Offset:
-		b, ok := inB.(Offset)
+	case *Offset:
+		b, ok := inB.(*Offset)
 		if !ok {
 			return false
 		}
-		return a == b
+		return EqualsRefOfOffset(a, b)
 	case *OrExpr:
 		b, ok := inB.(*OrExpr)
 		if !ok {
@@ -4850,12 +4862,12 @@ func EqualsJSONPathParam(inA, inB JSONPathParam) bool {
 			return false
 		}
 		return EqualsRefOfNullVal(a, b)
-	case Offset:
-		b, ok := inB.(Offset)
+	case *Offset:
+		b, ok := inB.(*Offset)
 		if !ok {
 			return false
 		}
-		return a == b
+		return EqualsRefOfOffset(a, b)
 	case *OrExpr:
 		b, ok := inB.(*OrExpr)
 		if !ok {
diff --git a/go/vt/sqlparser/ast_format.go b/go/vt/sqlparser/ast_format.go
index 7be21d9633..5f8eadf912 100644
--- a/go/vt/sqlparser/ast_format.go
+++ b/go/vt/sqlparser/ast_format.go
@@ -2144,8 +2144,13 @@ func (node *JtOnResponse) Format(buf *TrackedBuffer) {
 }
 
 // Format formats the node.
-func (node Offset) Format(buf *TrackedBuffer) {
-	buf.astPrintf(node, "[%d]", int(node))
+// Using capital letter for this function as an indicator that it's not a normal function call ¯\_(ツ)_/¯
+func (node *Offset) Format(buf *TrackedBuffer) {
+	if node.Original == "" {
+		buf.astPrintf(node, "OFFSET(%d)", node.V)
+	} else {
+		buf.astPrintf(node, "OFFSET(%d, '%s')", node.V, node.Original)
+	}
 }
 
 // Format formats the node.
@@ -2160,7 +2165,7 @@ func (node *JSONSchemaValidationReportFuncExpr) Format(buf *TrackedBuffer) {
 
 // Format formats the node.
 func (node *JSONArrayExpr) Format(buf *TrackedBuffer) {
-	//buf.astPrintf(node,"%s(,"node.Name.Lowered())
+	// buf.astPrintf(node,"%s(,"node.Name.Lowered())
 	buf.literal("json_array(")
 	if len(node.Params) > 0 {
 		var prefix string
@@ -2174,7 +2179,7 @@ func (node *JSONArrayExpr) Format(buf *TrackedBuffer) {
 
 // Format formats the node.
 func (node *JSONObjectExpr) Format(buf *TrackedBuffer) {
-	//buf.astPrintf(node,"%s(,"node.Name.Lowered())
+	// buf.astPrintf(node,"%s(,"node.Name.Lowered())
 	buf.literal("json_object(")
 	if len(node.Params) > 0 {
 		for i, p := range node.Params {
diff --git a/go/vt/sqlparser/ast_format_fast.go b/go/vt/sqlparser/ast_format_fast.go
index d48e056e73..069cfc5bc4 100644
--- a/go/vt/sqlparser/ast_format_fast.go
+++ b/go/vt/sqlparser/ast_format_fast.go
@@ -2793,10 +2793,19 @@ func (node *JtOnResponse) formatFast(buf *TrackedBuffer) {
 }
 
 // formatFast formats the node.
-func (node Offset) formatFast(buf *TrackedBuffer) {
-	buf.WriteByte('[')
-	buf.WriteString(fmt.Sprintf("%d", int(node)))
-	buf.WriteByte(']')
+// Using capital letter for this function as an indicator that it's not a normal function call ¯\_(ツ)_/¯
+func (node *Offset) formatFast(buf *TrackedBuffer) {
+	if node.Original == "" {
+		buf.WriteString("OFFSET(")
+		buf.WriteString(fmt.Sprintf("%d", node.V))
+		buf.WriteByte(')')
+	} else {
+		buf.WriteString("OFFSET(")
+		buf.WriteString(fmt.Sprintf("%d", node.V))
+		buf.WriteString(", '")
+		buf.WriteString(node.Original)
+		buf.WriteString("')")
+	}
 }
 
 // formatFast formats the node.
@@ -2819,7 +2828,7 @@ func (node *JSONSchemaValidationReportFuncExpr) formatFast(buf *TrackedBuffer) {
 
 // formatFast formats the node.
 func (node *JSONArrayExpr) formatFast(buf *TrackedBuffer) {
-	//buf.astPrintf(node,"%s(,"node.Name.Lowered())
+	// buf.astPrintf(node,"%s(,"node.Name.Lowered())
 	buf.WriteString("json_array(")
 	if len(node.Params) > 0 {
 		var prefix string
@@ -2834,7 +2843,7 @@ func (node *JSONArrayExpr) formatFast(buf *TrackedBuffer) {
 
 // formatFast formats the node.
 func (node *JSONObjectExpr) formatFast(buf *TrackedBuffer) {
-	//buf.astPrintf(node,"%s(,"node.Name.Lowered())
+	// buf.astPrintf(node,"%s(,"node.Name.Lowered())
 	buf.WriteString("json_object(")
 	if len(node.Params) > 0 {
 		for i, p := range node.Params {
diff --git a/go/vt/sqlparser/ast_funcs.go b/go/vt/sqlparser/ast_funcs.go
index 966893730f..2a07f97109 100644
--- a/go/vt/sqlparser/ast_funcs.go
+++ b/go/vt/sqlparser/ast_funcs.go
@@ -670,6 +670,10 @@ func NewColIdentWithAt(str string, at AtCount) ColIdent {
 	}
 }
 
+func NewOffset(v int, original Expr) *Offset {
+	return &Offset{V: v, Original: String(original)}
+}
+
 // IsEmpty returns true if the name is empty.
 func (node ColIdent) IsEmpty() bool {
 	return node.val == ""
diff --git a/go/vt/sqlparser/ast_rewrite.go b/go/vt/sqlparser/ast_rewrite.go
index 2efcebfd0d..0d6f2f4264 100644
--- a/go/vt/sqlparser/ast_rewrite.go
+++ b/go/vt/sqlparser/ast_rewrite.go
@@ -254,8 +254,8 @@ func (a *application) rewriteSQLNode(parent SQLNode, node SQLNode, replacer repl
 		return a.rewriteRefOfNotExpr(parent, node, replacer)
 	case *NullVal:
 		return a.rewriteRefOfNullVal(parent, node, replacer)
-	case Offset:
-		return a.rewriteOffset(parent, node, replacer)
+	case *Offset:
+		return a.rewriteRefOfOffset(parent, node, replacer)
 	case OnDup:
 		return a.rewriteOnDup(parent, node, replacer)
 	case *OptLike:
@@ -3938,6 +3938,30 @@ func (a *application) rewriteRefOfNullVal(parent SQLNode, node *NullVal, replace
 	}
 	return true
 }
+func (a *application) rewriteRefOfOffset(parent SQLNode, node *Offset, replacer replacerFunc) bool {
+	if node == nil {
+		return true
+	}
+	if a.pre != nil {
+		a.cur.replacer = replacer
+		a.cur.parent = parent
+		a.cur.node = node
+		if !a.pre(&a.cur) {
+			return true
+		}
+	}
+	if a.post != nil {
+		if a.pre == nil {
+			a.cur.replacer = replacer
+			a.cur.parent = parent
+			a.cur.node = node
+		}
+		if !a.post(&a.cur) {
+			return false
+		}
+	}
+	return true
+}
 func (a *application) rewriteOnDup(parent SQLNode, node OnDup, replacer replacerFunc) bool {
 	if node == nil {
 		return true
@@ -6856,8 +6880,8 @@ func (a *application) rewriteExpr(parent SQLNode, node Expr, replacer replacerFu
 		return a.rewriteRefOfNotExpr(parent, node, replacer)
 	case *NullVal:
 		return a.rewriteRefOfNullVal(parent, node, replacer)
-	case Offset:
-		return a.rewriteOffset(parent, node, replacer)
+	case *Offset:
+		return a.rewriteRefOfOffset(parent, node, replacer)
 	case *OrExpr:
 		return a.rewriteRefOfOrExpr(parent, node, replacer)
 	case *Subquery:
@@ -6998,8 +7022,8 @@ func (a *application) rewriteJSONPathParam(parent SQLNode, node JSONPathParam, r
 		return a.rewriteRefOfNotExpr(parent, node, replacer)
 	case *NullVal:
 		return a.rewriteRefOfNullVal(parent, node, replacer)
-	case Offset:
-		return a.rewriteOffset(parent, node, replacer)
+	case *Offset:
+		return a.rewriteRefOfOffset(parent, node, replacer)
 	case *OrExpr:
 		return a.rewriteRefOfOrExpr(parent, node, replacer)
 	case *Subquery:
@@ -7350,27 +7374,6 @@ func (a *application) rewriteMatchAction(parent SQLNode, node MatchAction, repla
 	}
 	return true
 }
-func (a *application) rewriteOffset(parent SQLNode, node Offset, replacer replacerFunc) bool {
-	if a.pre != nil {
-		a.cur.replacer = replacer
-		a.cur.parent = parent
-		a.cur.node = node
-		if !a.pre(&a.cur) {
-			return true
-		}
-	}
-	if a.post != nil {
-		if a.pre == nil {
-			a.cur.replacer = replacer
-			a.cur.parent = parent
-			a.cur.node = node
-		}
-		if !a.post(&a.cur) {
-			return false
-		}
-	}
-	return true
-}
 func (a *application) rewriteReferenceAction(parent SQLNode, node ReferenceAction, replacer replacerFunc) bool {
 	if a.pre != nil {
 		a.cur.replacer = replacer
diff --git a/go/vt/sqlparser/ast_visit.go b/go/vt/sqlparser/ast_visit.go
index 8faf0f6fe7..6da9388788 100644
--- a/go/vt/sqlparser/ast_visit.go
+++ b/go/vt/sqlparser/ast_visit.go
@@ -254,8 +254,8 @@ func VisitSQLNode(in SQLNode, f Visit) error {
 		return VisitRefOfNotExpr(in, f)
 	case *NullVal:
 		return VisitRefOfNullVal(in, f)
-	case Offset:
-		return VisitOffset(in, f)
+	case *Offset:
+		return VisitRefOfOffset(in, f)
 	case OnDup:
 		return VisitOnDup(in, f)
 	case *OptLike:
@@ -2033,6 +2033,15 @@ func VisitRefOfNullVal(in *NullVal, f Visit) error {
 	}
 	return nil
 }
+func VisitRefOfOffset(in *Offset, f Visit) error {
+	if in == nil {
+		return nil
+	}
+	if cont, err := f(in); err != nil || !cont {
+		return err
+	}
+	return nil
+}
 func VisitOnDup(in OnDup, f Visit) error {
 	if in == nil {
 		return nil
@@ -3505,8 +3514,8 @@ func VisitExpr(in Expr, f Visit) error {
 		return VisitRefOfNotExpr(in, f)
 	case *NullVal:
 		return VisitRefOfNullVal(in, f)
-	case Offset:
-		return VisitOffset(in, f)
+	case *Offset:
+		return VisitRefOfOffset(in, f)
 	case *OrExpr:
 		return VisitRefOfOrExpr(in, f)
 	case *Subquery:
@@ -3647,8 +3656,8 @@ func VisitJSONPathParam(in JSONPathParam, f Visit) error {
 		return VisitRefOfNotExpr(in, f)
 	case *NullVal:
 		return VisitRefOfNullVal(in, f)
-	case Offset:
-		return VisitOffset(in, f)
+	case *Offset:
+		return VisitRefOfOffset(in, f)
 	case *OrExpr:
 		return VisitRefOfOrExpr(in, f)
 	case *Subquery:
@@ -3880,10 +3889,6 @@ func VisitMatchAction(in MatchAction, f Visit) error {
 	_, err := f(in)
 	return err
 }
-func VisitOffset(in Offset, f Visit) error {
-	_, err := f(in)
-	return err
-}
 func VisitReferenceAction(in ReferenceAction, f Visit) error {
 	_, err := f(in)
 	return err
diff --git a/go/vt/sqlparser/cached_size.go b/go/vt/sqlparser/cached_size.go
index 767efd39a7..b93fbe4f35 100644
--- a/go/vt/sqlparser/cached_size.go
+++ b/go/vt/sqlparser/cached_size.go
@@ -2104,6 +2104,18 @@ func (cached *NotExpr) CachedSize(alloc bool) int64 {
 	}
 	return size
 }
+func (cached *Offset) CachedSize(alloc bool) int64 {
+	if cached == nil {
+		return int64(0)
+	}
+	size := int64(0)
+	if alloc {
+		size += int64(24)
+	}
+	// field Original string
+	size += hack.RuntimeAllocSize(int64(len(cached.Original)))
+	return size
+}
 func (cached *OptLike) CachedSize(alloc bool) int64 {
 	if cached == nil {
 		return int64(0)
diff --git a/go/vt/vtadmin/api.go b/go/vt/vtadmin/api.go
index b70119ceb3..8fe1ce9f48 100644
--- a/go/vt/vtadmin/api.go
+++ b/go/vt/vtadmin/api.go
@@ -460,12 +460,7 @@ func (api *API) DeleteTablet(ctx context.Context, req *vtadminpb.DeleteTabletReq
 	span, ctx := trace.NewSpan(ctx, "API.DeleteTablet")
 	defer span.Finish()
 
-	tablet, err := api.getTabletForAction(ctx, span, rbac.DeleteAction, req.Alias, req.ClusterIds)
-	if err != nil {
-		return nil, err
-	}
-
-	c, err := api.getClusterForRequest(tablet.Cluster.Id)
+	tablet, c, err := api.getTabletForAction(ctx, span, rbac.DeleteAction, req.Alias, req.ClusterIds)
 	if err != nil {
 		return nil, err
 	}
@@ -1040,7 +1035,8 @@ func (api *API) GetTablet(ctx context.Context, req *vtadminpb.GetTabletRequest)
 	span, ctx := trace.NewSpan(ctx, "API.GetTablet")
 	defer span.Finish()
 
-	return api.getTabletForAction(ctx, span, rbac.GetAction, req.Alias, req.ClusterIds)
+	t, _, err := api.getTabletForAction(ctx, span, rbac.GetAction, req.Alias, req.ClusterIds)
+	return t, err
 }
 
 // GetTablets is part of the vtadminpb.VTAdminServer interface.
@@ -1332,12 +1328,7 @@ func (api *API) PingTablet(ctx context.Context, req *vtadminpb.PingTabletRequest
 	span, ctx := trace.NewSpan(ctx, "API.PingTablet")
 	defer span.Finish()
 
-	tablet, err := api.getTabletForAction(ctx, span, rbac.PingAction, req.Alias, req.ClusterIds)
-	if err != nil {
-		return nil, err
-	}
-
-	c, err := api.getClusterForRequest(tablet.Cluster.Id)
+	tablet, c, err := api.getTabletForAction(ctx, span, rbac.PingAction, req.Alias, req.ClusterIds)
 	if err != nil {
 		return nil, err
 	}
@@ -1363,12 +1354,7 @@ func (api *API) RefreshState(ctx context.Context, req *vtadminpb.RefreshStateReq
 	span, ctx := trace.NewSpan(ctx, "API.RefreshState")
 	defer span.Finish()
 
-	tablet, err := api.getTabletForAction(ctx, span, rbac.PutAction, req.Alias, req.ClusterIds)
-	if err != nil {
-		return nil, err
-	}
-
-	c, err := api.getClusterForRequest(tablet.Cluster.Id)
+	tablet, c, err := api.getTabletForAction(ctx, span, rbac.PutAction, req.Alias, req.ClusterIds)
 	if err != nil {
 		return nil, err
 	}
@@ -1434,12 +1420,7 @@ func (api *API) ReparentTablet(ctx context.Context, req *vtadminpb.ReparentTable
 	span, ctx := trace.NewSpan(ctx, "API.ReparentTablet")
 	defer span.Finish()
 
-	tablet, err := api.getTabletForAction(ctx, span, rbac.ReparentTabletAction, req.Alias, req.ClusterIds)
-	if err != nil {
-		return nil, err
-	}
-
-	c, err := api.getClusterForRequest(tablet.Cluster.Id)
+	tablet, c, err := api.getTabletForAction(ctx, span, rbac.ReparentTabletAction, req.Alias, req.ClusterIds)
 	if err != nil {
 		return nil, err
 	}
@@ -1452,12 +1433,7 @@ func (api *API) RunHealthCheck(ctx context.Context, req *vtadminpb.RunHealthChec
 	span, ctx := trace.NewSpan(ctx, "API.RunHealthCheck")
 	defer span.Finish()
 
-	tablet, err := api.getTabletForAction(ctx, span, rbac.GetAction, req.Alias, req.ClusterIds)
-	if err != nil {
-		return nil, err
-	}
-
-	c, err := api.getClusterForRequest(tablet.Cluster.Id)
+	tablet, c, err := api.getTabletForAction(ctx, span, rbac.GetAction, req.Alias, req.ClusterIds)
 	if err != nil {
 		return nil, err
 	}
@@ -1483,12 +1459,7 @@ func (api *API) SetReadOnly(ctx context.Context, req *vtadminpb.SetReadOnlyReque
 	span, ctx := trace.NewSpan(ctx, "API.SetReadOnly")
 	defer span.Finish()
 
-	tablet, err := api.getTabletForAction(ctx, span, rbac.ManageTabletWritabilityAction, req.Alias, req.ClusterIds)
-	if err != nil {
-		return nil, err
-	}
-
-	c, err := api.getClusterForRequest(tablet.Cluster.Id)
+	tablet, c, err := api.getTabletForAction(ctx, span, rbac.ManageTabletWritabilityAction, req.Alias, req.ClusterIds)
 	if err != nil {
 		return nil, err
 	}
@@ -1509,12 +1480,7 @@ func (api *API) SetReadWrite(ctx context.Context, req *vtadminpb.SetReadWriteReq
 	span, ctx := trace.NewSpan(ctx, "API.SetReadWrite")
 	defer span.Finish()
 
-	tablet, err := api.getTabletForAction(ctx, span, rbac.ManageTabletWritabilityAction, req.Alias, req.ClusterIds)
-	if err != nil {
-		return nil, err
-	}
-
-	c, err := api.getClusterForRequest(tablet.Cluster.Id)
+	tablet, c, err := api.getTabletForAction(ctx, span, rbac.ManageTabletWritabilityAction, req.Alias, req.ClusterIds)
 	if err != nil {
 		return nil, err
 	}
@@ -1535,12 +1501,7 @@ func (api *API) StartReplication(ctx context.Context, req *vtadminpb.StartReplic
 	span, ctx := trace.NewSpan(ctx, "API.StartReplication")
 	defer span.Finish()
 
-	tablet, err := api.getTabletForAction(ctx, span, rbac.ManageTabletReplicationAction, req.Alias, req.ClusterIds)
-	if err != nil {
-		return nil, err
-	}
-
-	c, err := api.getClusterForRequest(tablet.Cluster.Id)
+	tablet, c, err := api.getTabletForAction(ctx, span, rbac.ManageTabletReplicationAction, req.Alias, req.ClusterIds)
 	if err != nil {
 		return nil, err
 	}
@@ -1561,12 +1522,7 @@ func (api *API) StopReplication(ctx context.Context, req *vtadminpb.StopReplicat
 	span, ctx := trace.NewSpan(ctx, "API.StopReplication")
 	defer span.Finish()
 
-	tablet, err := api.getTabletForAction(ctx, span, rbac.ManageTabletReplicationAction, req.Alias, req.ClusterIds)
-	if err != nil {
-		return nil, err
-	}
-
-	c, err := api.getClusterForRequest(tablet.Cluster.Id)
+	tablet, c, err := api.getTabletForAction(ctx, span, rbac.ManageTabletReplicationAction, req.Alias, req.ClusterIds)
 	if err != nil {
 		return nil, err
 	}
@@ -1863,22 +1819,38 @@ func (api *API) getClustersForRequest(ids []string) ([]*cluster.Cluster, []strin
 	return clusters, ids
 }
 
-func (api *API) getTabletForAction(ctx context.Context, span trace.Span, action rbac.Action, alias *topodatapb.TabletAlias, clusterIds []string) (*vtadminpb.Tablet, error) {
+func (api *API) getTabletForAction(ctx context.Context, span trace.Span, action rbac.Action, alias *topodatapb.TabletAlias, clusterIDs []string) (*vtadminpb.Tablet, *cluster.Cluster, error) {
+	return api.getTabletForResourceAndAction(ctx, span, rbac.TabletResource, action, alias, clusterIDs)
+}
+
+func (api *API) getTabletForShardAction(ctx context.Context, span trace.Span, action rbac.Action, alias *topodatapb.TabletAlias, clusterIDs []string) (*vtadminpb.Tablet, *cluster.Cluster, error) {
+	return api.getTabletForResourceAndAction(ctx, span, rbac.ShardResource, action, alias, clusterIDs)
+}
+
+func (api *API) getTabletForResourceAndAction(
+	ctx context.Context,
+	span trace.Span,
+	resource rbac.Resource,
+	action rbac.Action,
+	alias *topodatapb.TabletAlias,
+	clusterIDs []string,
+) (*vtadminpb.Tablet, *cluster.Cluster, error) {
 	span.Annotate("tablet_alias", topoproto.TabletAliasString(alias))
 	span.Annotate("tablet_cell", alias.Cell)
 	span.Annotate("tablet_uid", alias.Uid)
 
-	clusters, ids := api.getClustersForRequest(clusterIds)
+	clusters, ids := api.getClustersForRequest(clusterIDs)
 
 	var (
+		m   sync.Mutex
+		wg  sync.WaitGroup
+		rec concurrency.AllErrorRecorder
+
 		tablets []*vtadminpb.Tablet
-		wg      sync.WaitGroup
-		er      concurrency.AllErrorRecorder
-		m       sync.Mutex
 	)
 
 	for _, c := range clusters {
-		if !api.authz.IsAuthorized(ctx, c.ID, rbac.TabletResource, action) {
+		if !api.authz.IsAuthorized(ctx, c.ID, resource, action) {
 			continue
 		}
 
@@ -1891,7 +1863,7 @@ func (api *API) getTabletForAction(ctx context.Context, span trace.Span, action
 				return topoproto.TabletAliasEqual(t.Tablet.Alias, alias)
 			}, -1)
 			if err != nil {
-				er.RecordError(fmt.Errorf("FindTablets(cluster = %s): %w", c.ID, err))
+				rec.RecordError(fmt.Errorf("FindTablets(cluster = %s): %w", c.ID, err))
 				return
 			}
 
@@ -1903,16 +1875,23 @@ func (api *API) getTabletForAction(ctx context.Context, span trace.Span, action
 
 	wg.Wait()
 
-	if er.HasErrors() {
-		return nil, er.Error()
+	if rec.HasErrors() {
+		return nil, nil, rec.Error()
 	}
 
 	switch len(tablets) {
 	case 0:
-		return nil, vterrors.Errorf(vtrpcpb.Code_NOT_FOUND, "%s: %s, searched clusters = %v", errors.ErrNoTablet, alias, ids)
+		return nil, nil, vterrors.Errorf(vtrpcpb.Code_NOT_FOUND, "%s: %s, searched clusters = %v", errors.ErrNoTablet, alias, ids)
 	case 1:
-		return tablets[0], nil
+		t := tablets[0]
+		for _, c := range clusters {
+			if c.ID == t.Cluster.Id {
+				return t, c, nil
+			}
+		}
+
+		return nil, nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "impossible: found tablet from cluster %s but cannot find cluster with that id", t.Cluster.Id)
 	}
 
-	return nil, vterrors.Errorf(vtrpcpb.Code_NOT_FOUND, "%s: %s, searched clusters = %v", errors.ErrAmbiguousTablet, alias, ids)
+	return nil, nil, vterrors.Errorf(vtrpcpb.Code_NOT_FOUND, "%s: %s, searched clusters = %v", errors.ErrAmbiguousTablet, alias, ids)
 }
diff --git a/go/vt/vtgate/evalengine/translate.go b/go/vt/vtgate/evalengine/translate.go
index 6a5d9ba507..4239d0df06 100644
--- a/go/vt/vtgate/evalengine/translate.go
+++ b/go/vt/vtgate/evalengine/translate.go
@@ -572,8 +572,8 @@ func translateExpr(e sqlparser.Expr, lookup TranslationLookup) (Expr, error) {
 		return NewLiteralInt(0), nil
 	case *sqlparser.ColName:
 		return translateColName(node, lookup)
-	case sqlparser.Offset:
-		return NewColumn(int(node), getCollation(node, lookup)), nil
+	case *sqlparser.Offset:
+		return NewColumn(node.V, getCollation(node, lookup)), nil
 	case *sqlparser.ComparisonExpr:
 		return translateComparisonExpr(node.Operator, node.Left, node.Right, lookup)
 	case sqlparser.Argument:
diff --git a/go/vt/vtgate/planbuilder/abstract/queryprojection.go b/go/vt/vtgate/planbuilder/abstract/queryprojection.go
index f91c3ae576..7ba9fde31a 100644
--- a/go/vt/vtgate/planbuilder/abstract/queryprojection.go
+++ b/go/vt/vtgate/planbuilder/abstract/queryprojection.go
@@ -45,6 +45,9 @@ type (
 		OrderExprs         []OrderBy
 		CanPushDownSorting bool
 		HasStar            bool
+
+		// AddedColumn keeps a counter for expressions added to solve HAVING expressions the user is not selecting
+		AddedColumn int
 	}
 
 	// OrderBy contains the expression to used in order by and also if ordering is needed at VTGate level then what the weight_string function expression to be sent down for evaluation.
@@ -65,15 +68,20 @@ type (
 		aliasedExpr *sqlparser.AliasedExpr
 	}
 
+	// Aggr encodes all information needed for aggregation functions
 	Aggr struct {
 		Original *sqlparser.AliasedExpr
-		Func     *sqlparser.FuncExpr
 		OpCode   engine.AggregateOpcode
 		Alias    string
 		// The index at which the user expects to see this aggregated function. Set to nil, if the user does not ask for it
 		Index    *int
 		Distinct bool
 	}
+
+	AggrRewriter struct {
+		qp  *QueryProjection
+		Err error
+	}
 )
 
 func (b GroupBy) AsOrderBy() OrderBy {
@@ -172,6 +180,47 @@ func CreateQPFromSelect(sel *sqlparser.Select) (*QueryProjection, error) {
 	return qp, nil
 }
 
+// Rewrite will go through an expression, add aggregations to the QP, and rewrite them to use column offset
+func (ar *AggrRewriter) Rewrite() func(*sqlparser.Cursor) bool {
+	return func(cursor *sqlparser.Cursor) bool {
+		if ar.Err != nil {
+			return false
+		}
+		sqlNode := cursor.Node()
+		if sqlparser.IsAggregation(sqlNode) {
+			fExp := sqlNode.(*sqlparser.FuncExpr)
+			for offset, expr := range ar.qp.SelectExprs {
+				ae, err := expr.GetAliasedExpr()
+				if err != nil {
+					ar.Err = err
+					return false
+				}
+				if sqlparser.EqualsExpr(ae.Expr, fExp) {
+					cursor.Replace(sqlparser.NewOffset(offset, fExp))
+					return false // no need to visit aggregation children
+				}
+			}
+
+			col := SelectExpr{
+				Aggr: true,
+				Col:  &sqlparser.AliasedExpr{Expr: fExp},
+			}
+			ar.qp.HasAggr = true
+
+			cursor.Replace(sqlparser.NewOffset(len(ar.qp.SelectExprs), fExp))
+			ar.qp.SelectExprs = append(ar.qp.SelectExprs, col)
+			ar.qp.AddedColumn++
+		}
+
+		return true
+	}
+}
+
+// AggrRewriter extracts
+func (qp *QueryProjection) AggrRewriter() *AggrRewriter {
+	return &AggrRewriter{qp: qp}
+}
+
 func (qp *QueryProjection) addSelectExpressions(sel *sqlparser.Select) error {
 	for _, selExp := range sel.SelectExprs {
 		switch selExp := selExp.(type) {
@@ -445,7 +494,6 @@ func (qp *QueryProjection) AggregationExpressions() (out []Aggr, err error) {
 
 		out = append(out, Aggr{
 			Original: aliasedExpr,
-			Func:     fExpr,
 			OpCode:   opcode,
 			Alias:    aliasedExpr.ColumnName(),
 			Index:    &idxCopy,
@@ -513,7 +561,7 @@ func (qp *QueryProjection) AlignGroupByAndOrderBy() {
 		// The query didn't ask for any particular order, so we are free to add arbitrary ordering.
 		// We'll align the grouping and ordering by the output columns
 		newGrouping = qp.GetGrouping()
-		sort.Sort(GroupBys(newGrouping))
+		SortGrouping(newGrouping)
 		for _, groupBy := range newGrouping {
 			qp.OrderExprs = append(qp.OrderExprs, groupBy.AsOrderBy())
 		}
@@ -549,6 +597,10 @@ func (qp *QueryProjection) AddGroupBy(by GroupBy) {
 	qp.groupByExprs = append(qp.groupByExprs, by)
 }
 
+func (qp *QueryProjection) GetColumnCount() int {
+	return len(qp.SelectExprs) - qp.AddedColumn
+}
+
 func checkForInvalidGroupingExpressions(expr sqlparser.Expr) error {
 	return sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) {
 		if sqlparser.IsAggregation(node) {
@@ -563,21 +615,16 @@ func checkForInvalidGroupingExpressions(expr sqlparser.Expr) error {
 	}, expr)
 }
 
-type Aggrs []Aggr
-
-// Len implements the sort.Interface
-func (a Aggrs) Len() int {
-	return len(a)
-}
-
-// Less implements the sort.Interface
-func (a Aggrs) Less(i, j int) bool {
-	return CompareRefInt(a[i].Index, a[j].Index)
+func SortAggregations(a []Aggr) {
+	sort.Slice(a, func(i, j int) bool {
+		return CompareRefInt(a[i].Index, a[j].Index)
+	})
 }
 
-// Swap implements the sort.Interface
-func (a Aggrs) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
+func SortGrouping(a []GroupBy) {
+	sort.Slice(a, func(i, j int) bool {
+		return CompareRefInt(a[i].InnerIndex, a[j].InnerIndex)
+	})
 }
 
 // CompareRefInt compares two references of integers.
@@ -591,20 +638,3 @@ func CompareRefInt(a *int, b *int) bool {
 	}
 	return *a < *b
 }
-
-type GroupBys []GroupBy
-
-// Len implements the sort.Interface
-func (gbys GroupBys) Len() int {
-	return len(gbys)
-}
-
-// Less implements the sort.Interface
-func (gbys GroupBys) Less(i, j int) bool {
-	return CompareRefInt(gbys[i].InnerIndex, gbys[j].InnerIndex)
-}
-
-// Swap implements the sort.Interface
-func (gbys GroupBys) Swap(i, j int) {
-	gbys[i], gbys[j] = gbys[j], gbys[i]
-}
diff --git a/go/vt/vtgate/planbuilder/aggregation_pushing.go b/go/vt/vtgate/planbuilder/aggregation_pushing.go
index bae6486f2b..cb937910d8 100644
--- a/go/vt/vtgate/planbuilder/aggregation_pushing.go
+++ b/go/vt/vtgate/planbuilder/aggregation_pushing.go
@@ -17,8 +17,6 @@ limitations under the License.
 package planbuilder
 
 import (
-	"sort"
-
 	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
 	"vitess.io/vitess/go/vt/sqlparser"
 	"vitess.io/vitess/go/vt/vterrors"
@@ -168,7 +166,7 @@ func addAggregationToSelect(sel *sqlparser.Select, aggregation abstract.Aggr) of
 		if !isAliasedExpr {
 			continue
 		}
-		if sqlparser.EqualsExpr(aliasedExpr.Expr, aggregation.Func) {
+		if sqlparser.EqualsExpr(aliasedExpr.Expr, aggregation.Original.Expr) {
 			return newOffset(i)
 		}
 	}
@@ -186,7 +184,6 @@ func countStarAggr() *abstract.Aggr {
 
 	return &abstract.Aggr{
 		Original: &sqlparser.AliasedExpr{Expr: f},
-		Func:     f,
 		OpCode:   engine.AggregateCount,
 		Alias:    "count(*)",
 	}
@@ -377,11 +374,11 @@ func splitAggregationsToLeftAndRight(
 	var lhsAggrs, rhsAggrs []*abstract.Aggr
 	for _, aggr := range aggregations {
 		newAggr := aggr
-		if isCountStar(aggr.Func) {
+		if isCountStar(aggr.Original.Expr) {
 			lhsAggrs = append(lhsAggrs, &newAggr)
 			rhsAggrs = append(rhsAggrs, &newAggr)
 		} else {
-			deps := ctx.SemTable.RecursiveDeps(aggr.Func)
+			deps := ctx.SemTable.RecursiveDeps(aggr.Original.Expr)
 			var other *abstract.Aggr
 			// if we are sending down min/max, we don't have to multiply the results with anything
 			if !isMinOrMax(aggr.OpCode) {
@@ -484,8 +481,8 @@ func sortOffsets(grouping []abstract.GroupBy, aggregations []abstract.Aggr) ([]a
 	originalAggr := make([]abstract.Aggr, len(aggregations))
 	copy(originalAggr, aggregations)
 	copy(originalGrouping, grouping)
-	sort.Sort(abstract.Aggrs(aggregations))
-	sort.Sort(abstract.GroupBys(grouping))
+	abstract.SortAggregations(aggregations)
+	abstract.SortGrouping(grouping)
 
 	reorg := func(groupByOffsets []offsets, aggrOffsets [][]offsets) ([]offsets, [][]offsets) {
 		orderedGroupingOffsets := make([]offsets, 0, len(originalGrouping))
@@ -501,7 +498,7 @@ func sortOffsets(grouping []abstract.GroupBy, aggregations []abstract.Aggr) ([]a
 		orderedAggrs := make([][]offsets, 0, len(originalAggr))
 		for _, og := range originalAggr {
 			for i, g := range aggregations {
-				if og.Func == g.Func {
+				if og.Original.Expr == g.Original.Expr {
 					orderedAggrs = append(orderedAggrs, aggrOffsets[i])
 					break
 				}
diff --git a/go/vt/vtgate/planbuilder/horizon_planning.go b/go/vt/vtgate/planbuilder/horizon_planning.go
index fe9f8f58c9..81945b1f41 100644
--- a/go/vt/vtgate/planbuilder/horizon_planning.go
+++ b/go/vt/vtgate/planbuilder/horizon_planning.go
@@ -17,6 +17,7 @@ limitations under the License.
 package planbuilder
 
 import (
+	"vitess.io/vitess/go/mysql/collations"
 	"vitess.io/vitess/go/sqltypes"
 	"vitess.io/vitess/go/vt/vtgate/planbuilder/abstract"
 	"vitess.io/vitess/go/vt/vtgate/planbuilder/physical"
@@ -138,18 +139,18 @@ func pushProjections(ctx *plancontext.PlanningContext, plan logicalPlan, selectE
 }
 
 func (hp *horizonPlanning) truncateColumnsIfNeeded(ctx *plancontext.PlanningContext, plan logicalPlan) (logicalPlan, error) {
-	if len(plan.OutputColumns()) == hp.sel.GetColumnCount() {
+	if len(plan.OutputColumns()) == hp.qp.GetColumnCount() {
 		return plan, nil
 	}
 	switch p := plan.(type) {
 	case *routeGen4:
-		p.eroute.SetTruncateColumnCount(hp.sel.GetColumnCount())
+		p.eroute.SetTruncateColumnCount(hp.qp.GetColumnCount())
 	case *joinGen4, *semiJoin, *hashJoin:
 		// since this is a join, we can safely add extra columns and not need to truncate them
 	case *orderedAggregate:
-		p.truncateColumnCount = hp.sel.GetColumnCount()
+		p.truncateColumnCount = hp.qp.GetColumnCount()
 	case *memorySort:
-		p.truncater.SetTruncateColumnCount(hp.sel.GetColumnCount())
+		p.truncater.SetTruncateColumnCount(hp.qp.GetColumnCount())
 	case *pulloutSubquery:
 		newUnderlyingPlan, err := hp.truncateColumnsIfNeeded(ctx, p.underlying)
 		if err != nil {
@@ -162,7 +163,8 @@ func (hp *horizonPlanning) truncateColumnsIfNeeded(ctx *plancontext.PlanningCont
 			eSimpleProj:       &engine.SimpleProjection{},
 		}
 
-		err := pushProjections(ctx, plan, hp.qp.SelectExprs)
+		exprs := hp.qp.SelectExprs[0:hp.qp.GetColumnCount()]
+		err := pushProjections(ctx, plan, exprs)
 		if err != nil {
 			return nil, err
 		}
@@ -321,7 +323,18 @@ func pushProjection(
 				return key.KeyCol, false, nil
 			}
 		}
-		return 0, false, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "cannot push projections in ordered aggregates")
+		offset, _, err := pushProjection(ctx, expr, node.input, inner, true, hasAggregation)
+		if err != nil {
+			return 0, false, err
+		}
+		node.aggregates = append(node.aggregates, &engine.AggregateParams{
+			Opcode:   engine.AggregateRandom,
+			Col:      offset,
+			Alias:    expr.ColumnName(),
+			Expr:     expr.Expr,
+			Original: expr,
+		})
+		return offset, true, nil
 	case *vindexFunc:
 		colsBefore := len(node.eVindexFunc.Cols)
 		i, err := node.SupplyProjection(expr, reuseCol)
@@ -528,6 +541,14 @@ func (hp *horizonPlanning) planAggrUsingOA(
 		})
 	}
 
+	if hp.sel.Having != nil {
+		rewriter := hp.qp.AggrRewriter()
+		sqlparser.Rewrite(hp.sel.Having.Expr, rewriter.Rewrite(), nil)
+		if rewriter.Err != nil {
+			return nil, rewriter.Err
+		}
+	}
+
 	aggregationExprs, err := hp.qp.AggregationExpressions()
 	if err != nil {
 		return nil, err
@@ -600,13 +621,13 @@ func passGroupingColumns(proj *projection, groupings []offsets, grouping []abstr
 	for idx, grp := range groupings {
 		origGrp := grouping[idx]
 		var offs offsets
-		alias := origGrp.AsAliasedExpr().ColumnName()
-		offs.col, err = proj.addColumn(origGrp.InnerIndex, sqlparser.Offset(grp.col), alias)
+		expr := origGrp.AsAliasedExpr()
+		offs.col, err = proj.addColumn(origGrp.InnerIndex, sqlparser.NewOffset(grp.col, expr.Expr), expr.ColumnName())
 		if err != nil {
 			return nil, err
 		}
 		if grp.wsCol != -1 {
-			offs.wsCol, err = proj.addColumn(nil, sqlparser.Offset(grp.wsCol), "")
+			offs.wsCol, err = proj.addColumn(nil, sqlparser.NewOffset(grp.wsCol, weightStringFor(expr.Expr)), "")
 			if err != nil {
 				return nil, err
 			}
@@ -625,7 +646,7 @@ func generateAggregateParams(aggrs []abstract.Aggr, aggrParamOffsets [][]offsets
 		if proj != nil {
 			var aggrExpr sqlparser.Expr
 			for _, ofs := range paramOffset {
-				curr := sqlparser.Offset(ofs.col)
+				curr := &sqlparser.Offset{V: ofs.col}
 				if aggrExpr == nil {
 					aggrExpr = curr
 				} else {
@@ -657,7 +678,7 @@ func generateAggregateParams(aggrs []abstract.Aggr, aggrParamOffsets [][]offsets
 			Opcode:   opcode,
 			Col:      offset,
 			Alias:    aggr.Alias,
-			Expr:     aggr.Func,
+			Expr:     aggr.Original.Expr,
 			Original: aggr.Original,
 		}
 	}
@@ -689,7 +710,11 @@ func addColumnsToOA(
 			o := groupings[count]
 			count++
 			a := aggregationExprs[offset]
-			collID := ctx.SemTable.CollationForExpr(a.Func.Exprs[0].(*sqlparser.AliasedExpr).Expr)
+			collID := collations.Unknown
+			fnc, ok := a.Original.Expr.(*sqlparser.FuncExpr)
+			if ok {
+				collID = ctx.SemTable.CollationForExpr(fnc.Exprs[0].(*sqlparser.AliasedExpr).Expr)
+			}
 			oa.aggregates = append(oa.aggregates, &engine.AggregateParams{
 				Opcode:      a.OpCode,
 				Col:         o.col,
@@ -697,7 +722,6 @@ func addColumnsToOA(
 				WAssigned:   o.wsCol >= 0,
 				WCol:        o.wsCol,
 				Alias:       a.Alias,
-				Expr:        a.Func,
 				Original:    a.Original,
 				CollationID: collID,
 			})
@@ -736,7 +760,8 @@ func (hp *horizonPlanning) handleDistinctAggr(ctx *plancontext.PlanningContext,
 			aggrs = append(aggrs, expr)
 			continue
 		}
-		aliasedExpr, ok := expr.Func.Exprs[0].(*sqlparser.AliasedExpr)
+		funcExpr := expr.Original.Expr.(*sqlparser.FuncExpr) // we wouldn't be in this method if this wasn't a function
+		aliasedExpr, ok := funcExpr.Exprs[0].(*sqlparser.AliasedExpr)
 		if !ok {
 			err = vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "syntax error: %s", sqlparser.String(expr.Original))
 			return
@@ -813,8 +838,9 @@ func (hp *horizonPlanning) createGroupingsForColumns(columns []*sqlparser.ColNam
 	return lhsGrouping, nil
 }
 
-func isCountStar(f *sqlparser.FuncExpr) bool {
-	if f == nil {
+func isCountStar(e sqlparser.Expr) bool {
+	f, ok := e.(*sqlparser.FuncExpr)
+	if !ok || !f.Name.EqualString("count") {
 		return false
 	}
 	_, isStar := f.Exprs[0].(*sqlparser.StarExpr)
@@ -1340,9 +1366,6 @@ func pushHaving(ctx *plancontext.PlanningContext, expr sqlparser.Expr, plan logi
 	case *simpleProjection:
 		return nil, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "unsupported: filtering on results of cross-shard derived table")
 	case *orderedAggregate:
-		if sqlparser.ContainsAggregation(expr) {
-			expr = sqlparser.Rewrite(expr, node.rewriteAggrExpressions(), nil).(sqlparser.Expr)
-		}
 		return newFilter(ctx, plan, expr)
 	}
 	return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] unreachable %T.filtering", plan)
diff --git a/go/vt/vtgate/planbuilder/ordered_aggregate.go b/go/vt/vtgate/planbuilder/ordered_aggregate.go
index 8a940e340d..c79120a8a8 100644
--- a/go/vt/vtgate/planbuilder/ordered_aggregate.go
+++ b/go/vt/vtgate/planbuilder/ordered_aggregate.go
@@ -387,20 +387,3 @@ func (oa *orderedAggregate) OutputColumns() []sqlparser.SelectExpr {
 func (oa *orderedAggregate) SetTruncateColumnCount(count int) {
 	oa.truncateColumnCount = count
 }
-
-// rewriteAggrExpressions is used when our predicate expression contains aggregation.
-// In these cases, we need to rewrite it, so it uses the column output from the ordered aggregate
-func (oa *orderedAggregate) rewriteAggrExpressions() func(*sqlparser.Cursor) bool {
-	return func(cursor *sqlparser.Cursor) bool {
-		sqlNode := cursor.Node()
-		if sqlparser.IsAggregation(sqlNode) {
-			fExp := sqlNode.(*sqlparser.FuncExpr)
-			for _, aggregate := range oa.aggregates {
-				if sqlparser.EqualsExpr(aggregate.Expr, fExp) {
-					cursor.Replace(sqlparser.Offset(aggregate.Col))
-				}
-			}
-		}
-		return true
-	}
-}
diff --git a/web/vtadmin/README.md b/web/vtadmin/README.md
index 06147382af..c5fd744ae2 100644
--- a/web/vtadmin/README.md
+++ b/web/vtadmin/README.md
@@ -17,7 +17,6 @@ Scripts for common and not-so-common tasks. These are always run from the `vites
 | `npm run lint` | Run all of the linters and formatters. The `package.json` file defines a bunch more scripts to run individual linters, if you prefer, like `npm run lint:eslint`. |
 | `npm run lint:fix` | Run all of the linters and fix errors (where possible) in place. Note that this will overwrite your files so you may want to consider committing your work beforehand! |
 | `npm run build` | Generates a build of vtadmin-web for production and outputs the files to the `vitess/web/vtadmin/build` folder. In most cases, you won't need to run this locally, but it _can_ be useful for debugging production-specific issues. See the create-react-app documentation about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. |
-| `npm run analyze` | Analyze and debug JavaScript build size using [source-map-explorer](https://create-react-app.dev/docs/analyzing-the-bundle-size/). In most cases, you'll first want to run `npm run build` to update the `build/` directory. |
 
 ## Toolchain
 
diff --git a/web/vtadmin/package-lock.json b/web/vtadmin/package-lock.json
index fa7d0d2bb3..19f320e4a3 100644
--- a/web/vtadmin/package-lock.json
+++ b/web/vtadmin/package-lock.json
@@ -34,7 +34,6 @@
         "react-tiny-popover": "^6.0.5",
         "react-toastify": "^8.1.0",
         "sass": "^1.43.4",
-        "source-map-explorer": "^2.5.2",
         "svgo": "^2.8.0",
         "typescript": "^4.5.4",
         "web-vitals": "^0.2.4"
@@ -3111,6 +3110,11 @@
         "node": ">=8"
       }
     },
+    "node_modules/@leichtgewicht/ip-codec": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
+      "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
+    },
     "node_modules/@mswjs/cookies": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.1.7.tgz",
@@ -4233,9 +4237,9 @@
       "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg=="
     },
     "node_modules/@types/http-proxy": {
-      "version": "1.17.8",
-      "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
-      "integrity": "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==",
+      "version": "1.17.9",
+      "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz",
+      "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==",
       "dependencies": {
         "@types/node": "*"
       }
@@ -4571,9 +4575,9 @@
       }
     },
     "node_modules/@types/ws": {
-      "version": "8.2.2",
-      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz",
-      "integrity": "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==",
+      "version": "8.5.3",
+      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
+      "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
       "dependencies": {
         "@types/node": "*"
       }
@@ -4967,12 +4971,12 @@
       "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
     },
     "node_modules/accepts": {
-      "version": "1.3.7",
-      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
-      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
       "dependencies": {
-        "mime-types": "~2.1.24",
-        "negotiator": "0.6.2"
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
       },
       "engines": {
         "node": ">= 0.6"
@@ -5055,18 +5059,6 @@
         "node": ">= 6.0.0"
       }
     },
-    "node_modules/aggregate-error": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
-      "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
-      "dependencies": {
-        "clean-stack": "^2.0.0",
-        "indent-string": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/ajv": {
       "version": "6.12.6",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -5333,14 +5325,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/async": {
-      "version": "2.6.3",
-      "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
-      "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
-      "dependencies": {
-        "lodash": "^4.17.14"
-      }
-    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -5822,23 +5806,26 @@
       "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
     },
     "node_modules/body-parser": {
-      "version": "1.19.1",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz",
-      "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==",
+      "version": "1.20.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
+      "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
       "dependencies": {
-        "bytes": "3.1.1",
+        "bytes": "3.1.2",
         "content-type": "~1.0.4",
         "debug": "2.6.9",
-        "depd": "~1.1.2",
-        "http-errors": "1.8.1",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
-        "on-finished": "~2.3.0",
-        "qs": "6.9.6",
-        "raw-body": "2.4.2",
-        "type-is": "~1.6.18"
+        "on-finished": "2.4.1",
+        "qs": "6.10.3",
+        "raw-body": "2.5.1",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
       },
       "engines": {
-        "node": ">= 0.8"
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
       }
     },
     "node_modules/body-parser/node_modules/debug": {
@@ -5849,6 +5836,14 @@
         "ms": "2.0.0"
       }
     },
+    "node_modules/body-parser/node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/body-parser/node_modules/iconv-lite": {
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -5865,17 +5860,15 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
     },
-    "node_modules/bonjour": {
-      "version": "3.5.0",
-      "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
-      "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
+    "node_modules/bonjour-service": {
+      "version": "1.0.12",
+      "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.12.tgz",
+      "integrity": "sha512-pMmguXYCu63Ug37DluMKEHdxc+aaIf/ay4YbF8Gxtba+9d3u+rmEWy61VK3Z3hp8Rskok3BunHYnG0dUHAsblw==",
       "dependencies": {
-        "array-flatten": "^2.1.0",
-        "deep-equal": "^1.0.1",
+        "array-flatten": "^2.1.2",
         "dns-equal": "^1.0.0",
-        "dns-txt": "^2.0.2",
-        "multicast-dns": "^6.0.1",
-        "multicast-dns-service-types": "^1.1.0"
+        "fast-deep-equal": "^3.1.3",
+        "multicast-dns": "^7.2.4"
       }
     },
     "node_modules/boolbase": {
@@ -6052,17 +6045,6 @@
         "node-int64": "^0.4.0"
       }
     },
-    "node_modules/btoa": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
-      "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
-      "bin": {
-        "btoa": "bin/btoa.js"
-      },
-      "engines": {
-        "node": ">= 0.4.0"
-      }
-    },
     "node_modules/buffer": {
       "version": "5.7.1",
       "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -6092,11 +6074,6 @@
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
       "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
     },
-    "node_modules/buffer-indexof": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
-      "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g=="
-    },
     "node_modules/builtin-modules": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
@@ -6117,9 +6094,9 @@
       }
     },
     "node_modules/bytes": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz",
-      "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==",
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
       "engines": {
         "node": ">= 0.8"
       }
@@ -6334,14 +6311,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/clean-stack": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
-      "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/cli-boxes": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
@@ -6764,6 +6733,7 @@
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
       "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+      "dev": true,
       "engines": {
         "node": ">= 0.6"
       }
@@ -7846,22 +7816,6 @@
       "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
       "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw="
     },
-    "node_modules/deep-equal": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
-      "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
-      "dependencies": {
-        "is-arguments": "^1.0.4",
-        "is-date-object": "^1.0.1",
-        "is-regex": "^1.0.4",
-        "object-is": "^1.0.1",
-        "object-keys": "^1.1.1",
-        "regexp.prototype.flags": "^1.2.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/deep-extend": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -7928,27 +7882,6 @@
       "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
       "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
     },
-    "node_modules/del": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz",
-      "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==",
-      "dependencies": {
-        "globby": "^11.0.1",
-        "graceful-fs": "^4.2.4",
-        "is-glob": "^4.0.1",
-        "is-path-cwd": "^2.2.0",
-        "is-path-inside": "^3.0.2",
-        "p-map": "^4.0.0",
-        "rimraf": "^3.0.2",
-        "slash": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -7966,9 +7899,13 @@
       }
     },
     "node_modules/destroy": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
-      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
     },
     "node_modules/detect-newline": {
       "version": "3.1.0",
@@ -8073,20 +8010,14 @@
       "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0="
     },
     "node_modules/dns-packet": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz",
-      "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==",
-      "dependencies": {
-        "ip": "^1.1.0",
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "node_modules/dns-txt": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
-      "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.3.1.tgz",
+      "integrity": "sha512-spBwIj0TK0Ey3666GwIdWVfUpLyubpU53BTCu8iPn4r4oXd9O14Hjg3EHw3ts2oed77/SeckunUYCyRlSngqHw==",
       "dependencies": {
-        "buffer-indexof": "^1.0.0"
+        "@leichtgewicht/ip-codec": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=6"
       }
     },
     "node_modules/doctrine": {
@@ -8220,11 +8151,11 @@
       "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
     },
     "node_modules/ejs": {
-      "version": "3.1.6",
-      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz",
-      "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==",
+      "version": "3.1.8",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
+      "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
       "dependencies": {
-        "jake": "^10.6.1"
+        "jake": "^10.8.5"
       },
       "bin": {
         "ejs": "bin/cli.js"
@@ -9339,37 +9270,38 @@
       }
     },
     "node_modules/express": {
-      "version": "4.17.2",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz",
-      "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==",
+      "version": "4.18.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
+      "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
       "dependencies": {
-        "accepts": "~1.3.7",
+        "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
-        "body-parser": "1.19.1",
+        "body-parser": "1.20.0",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
-        "cookie": "0.4.1",
+        "cookie": "0.5.0",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
-        "depd": "~1.1.2",
+        "depd": "2.0.0",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
-        "finalhandler": "~1.1.2",
+        "finalhandler": "1.2.0",
         "fresh": "0.5.2",
+        "http-errors": "2.0.0",
         "merge-descriptors": "1.0.1",
         "methods": "~1.1.2",
-        "on-finished": "~2.3.0",
+        "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
         "path-to-regexp": "0.1.7",
         "proxy-addr": "~2.0.7",
-        "qs": "6.9.6",
+        "qs": "6.10.3",
         "range-parser": "~1.2.1",
         "safe-buffer": "5.2.1",
-        "send": "0.17.2",
-        "serve-static": "1.14.2",
+        "send": "0.18.0",
+        "serve-static": "1.15.0",
         "setprototypeof": "1.2.0",
-        "statuses": "~1.5.0",
+        "statuses": "2.0.1",
         "type-is": "~1.6.18",
         "utils-merge": "1.0.1",
         "vary": "~1.1.2"
@@ -9381,7 +9313,15 @@
     "node_modules/express/node_modules/array-flatten": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
-      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+    },
+    "node_modules/express/node_modules/cookie": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+      "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+      "engines": {
+        "node": ">= 0.6"
+      }
     },
     "node_modules/express/node_modules/debug": {
       "version": "2.6.9",
@@ -9391,6 +9331,14 @@
         "ms": "2.0.0"
       }
     },
+    "node_modules/express/node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/express/node_modules/ms": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -9420,6 +9368,14 @@
         }
       ]
     },
+    "node_modules/express/node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/external-editor": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -9570,11 +9526,30 @@
       }
     },
     "node_modules/filelist": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
-      "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==",
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
+      "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
       "dependencies": {
-        "minimatch": "^3.0.4"
+        "minimatch": "^5.0.1"
+      }
+    },
+    "node_modules/filelist/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/filelist/node_modules/minimatch": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
+      "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=10"
       }
     },
     "node_modules/filesize": {
@@ -9614,16 +9589,16 @@
       }
     },
     "node_modules/finalhandler": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
-      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
       "dependencies": {
         "debug": "2.6.9",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
-        "on-finished": "~2.3.0",
+        "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "statuses": "~1.5.0",
+        "statuses": "2.0.1",
         "unpipe": "~1.0.0"
       },
       "engines": {
@@ -9643,6 +9618,14 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
     },
+    "node_modules/finalhandler/node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/find-cache-dir": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
@@ -9689,9 +9672,9 @@
       "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA=="
     },
     "node_modules/follow-redirects": {
-      "version": "1.14.7",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
-      "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==",
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz",
+      "integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==",
       "funding": [
         {
           "type": "individual",
@@ -10422,18 +10405,34 @@
       "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc="
     },
     "node_modules/http-errors": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
-      "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
       "dependencies": {
-        "depd": "~1.1.2",
+        "depd": "2.0.0",
         "inherits": "2.0.4",
         "setprototypeof": "1.2.0",
-        "statuses": ">= 1.5.0 < 2",
+        "statuses": "2.0.1",
         "toidentifier": "1.0.1"
       },
       "engines": {
-        "node": ">= 0.6"
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/http-errors/node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/http-errors/node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "engines": {
+        "node": ">= 0.8"
       }
     },
     "node_modules/http-parser-js": {
@@ -10468,11 +10467,11 @@
       }
     },
     "node_modules/http-proxy-middleware": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz",
-      "integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==",
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz",
+      "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==",
       "dependencies": {
-        "@types/http-proxy": "^1.17.5",
+        "@types/http-proxy": "^1.17.8",
         "http-proxy": "^1.18.1",
         "is-glob": "^4.0.1",
         "is-plain-obj": "^3.0.0",
@@ -10480,6 +10479,14 @@
       },
       "engines": {
         "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "@types/express": "^4.17.13"
+      },
+      "peerDependenciesMeta": {
+        "@types/express": {
+          "optional": true
+        }
       }
     },
     "node_modules/http-proxy-middleware/node_modules/is-plain-obj": {
@@ -10636,6 +10643,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
       "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+      "dev": true,
       "engines": {
         "node": ">=8"
       }
@@ -10767,11 +10775,6 @@
         "node": ">= 0.4"
       }
     },
-    "node_modules/ip": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
-      "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
-    },
     "node_modules/ipaddr.js": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz",
@@ -10780,21 +10783,6 @@
         "node": ">= 10"
       }
     },
-    "node_modules/is-arguments": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
-      "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "has-tostringtag": "^1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/is-arrayish": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -10980,22 +10968,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/is-path-cwd": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
-      "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/is-path-inside": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
-      "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/is-plain-obj": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
@@ -11251,12 +11223,12 @@
       }
     },
     "node_modules/jake": {
-      "version": "10.8.2",
-      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz",
-      "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==",
+      "version": "10.8.5",
+      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
+      "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
       "dependencies": {
-        "async": "0.9.x",
-        "chalk": "^2.4.2",
+        "async": "^3.2.3",
+        "chalk": "^4.0.2",
         "filelist": "^1.0.1",
         "minimatch": "^3.0.4"
       },
@@ -11264,13 +11236,77 @@
         "jake": "bin/cli.js"
       },
       "engines": {
-        "node": "*"
+        "node": ">=10"
+      }
+    },
+    "node_modules/jake/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
     "node_modules/jake/node_modules/async": {
-      "version": "0.9.2",
-      "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
-      "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
+      "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
+    },
+    "node_modules/jake/node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/jake/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/jake/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+    },
+    "node_modules/jake/node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/jake/node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
     },
     "node_modules/jest": {
       "version": "27.4.7",
@@ -14660,9 +14696,9 @@
       }
     },
     "node_modules/memfs": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz",
-      "integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==",
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz",
+      "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==",
       "dependencies": {
         "fs-monkey": "1.0.3"
       },
@@ -15198,22 +15234,17 @@
       }
     },
     "node_modules/multicast-dns": {
-      "version": "6.2.3",
-      "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
-      "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
+      "version": "7.2.5",
+      "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz",
+      "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==",
       "dependencies": {
-        "dns-packet": "^1.3.1",
+        "dns-packet": "^5.2.2",
         "thunky": "^1.0.2"
       },
       "bin": {
         "multicast-dns": "cli.js"
       }
     },
-    "node_modules/multicast-dns-service-types": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
-      "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
-    },
     "node_modules/mute-stream": {
       "version": "0.0.8",
       "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@@ -15237,9 +15268,9 @@
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc="
     },
     "node_modules/negotiator": {
-      "version": "0.6.2",
-      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
-      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
       "engines": {
         "node": ">= 0.6"
       }
@@ -15307,9 +15338,9 @@
       }
     },
     "node_modules/node-forge": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz",
-      "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==",
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
+      "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
       "engines": {
         "node": ">= 6.13.0"
       }
@@ -15403,21 +15434,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/object-is": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
-      "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
-      "dependencies": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/object-keys": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -15522,9 +15538,9 @@
       "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
     },
     "node_modules/on-finished": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
-      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
       "dependencies": {
         "ee-first": "1.1.1"
       },
@@ -15562,21 +15578,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/open": {
-      "version": "7.4.2",
-      "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
-      "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
-      "dependencies": {
-        "is-docker": "^2.0.0",
-        "is-wsl": "^2.1.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/optionator": {
       "version": "0.9.1",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -15735,20 +15736,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/p-map": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
-      "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
-      "dependencies": {
-        "aggregate-error": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/p-retry": {
       "version": "4.6.1",
       "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz",
@@ -15977,27 +15964,6 @@
         "node": ">=4"
       }
     },
-    "node_modules/portfinder": {
-      "version": "1.0.28",
-      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
-      "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==",
-      "dependencies": {
-        "async": "^2.6.2",
-        "debug": "^3.1.1",
-        "mkdirp": "^0.5.5"
-      },
-      "engines": {
-        "node": ">= 0.12.0"
-      }
-    },
-    "node_modules/portfinder/node_modules/debug": {
-      "version": "3.2.7",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
-      "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
-      "dependencies": {
-        "ms": "^2.1.1"
-      }
-    },
     "node_modules/postcss": {
       "version": "8.4.6",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz",
@@ -16751,9 +16717,12 @@
       }
     },
     "node_modules/qs": {
-      "version": "6.9.6",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz",
-      "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==",
+      "version": "6.10.3",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+      "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+      "dependencies": {
+        "side-channel": "^1.0.4"
+      },
       "engines": {
         "node": ">=0.6"
       },
@@ -16812,12 +16781,12 @@
       }
     },
     "node_modules/raw-body": {
-      "version": "2.4.2",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz",
-      "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==",
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+      "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
       "dependencies": {
-        "bytes": "3.1.1",
-        "http-errors": "1.8.1",
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
         "unpipe": "1.0.0"
       },
@@ -18048,11 +18017,14 @@
       "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo="
     },
     "node_modules/selfsigned": {
-      "version": "1.10.13",
-      "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.13.tgz",
-      "integrity": "sha512-UmLwTKZwNmXYDAlRFhaEdgEg0dp9k5gfJXuO7uKvSqioN1M0+Mgf4V39IlVZMSuqGoCi6h5legkhNXvWy0rFSg==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz",
+      "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==",
       "dependencies": {
-        "node-forge": "^1.2.0"
+        "node-forge": "^1"
+      },
+      "engines": {
+        "node": ">=10"
       }
     },
     "node_modules/semver": {
@@ -18070,23 +18042,23 @@
       }
     },
     "node_modules/send": {
-      "version": "0.17.2",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
-      "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
       "dependencies": {
         "debug": "2.6.9",
-        "depd": "~1.1.2",
-        "destroy": "~1.0.4",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
         "fresh": "0.5.2",
-        "http-errors": "1.8.1",
+        "http-errors": "2.0.0",
         "mime": "1.6.0",
         "ms": "2.1.3",
-        "on-finished": "~2.3.0",
+        "on-finished": "2.4.1",
         "range-parser": "~1.2.1",
-        "statuses": "~1.5.0"
+        "statuses": "2.0.1"
       },
       "engines": {
         "node": ">= 0.8.0"
@@ -18105,11 +18077,27 @@
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
       "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
     },
+    "node_modules/send/node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/send/node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
     },
+    "node_modules/send/node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/serialize-javascript": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
@@ -18263,14 +18251,14 @@
       "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
     },
     "node_modules/serve-static": {
-      "version": "1.14.2",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
-      "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
       "dependencies": {
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "parseurl": "~1.3.3",
-        "send": "0.17.2"
+        "send": "0.18.0"
       },
       "engines": {
         "node": ">= 0.8.0"
@@ -18477,104 +18465,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/source-map-explorer": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-2.5.2.tgz",
-      "integrity": "sha512-gBwOyCcHPHcdLbgw6Y6kgoH1uLKL6hN3zz0xJcNI2lpnElZliIlmSYAjUVwAWnc7+HscoTyh1ScR7ITtFuEnxg==",
-      "dependencies": {
-        "btoa": "^1.2.1",
-        "chalk": "^4.1.0",
-        "convert-source-map": "^1.7.0",
-        "ejs": "^3.1.5",
-        "escape-html": "^1.0.3",
-        "glob": "^7.1.6",
-        "gzip-size": "^6.0.0",
-        "lodash": "^4.17.20",
-        "open": "^7.3.1",
-        "source-map": "^0.7.3",
-        "temp": "^0.9.4",
-        "yargs": "^16.2.0"
-      },
-      "bin": {
-        "sme": "bin/cli.js",
-        "source-map-explorer": "bin/cli.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/source-map-explorer/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/source-map-explorer/node_modules/chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/chalk?sponsor=1"
-      }
-    },
-    "node_modules/source-map-explorer/node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/source-map-explorer/node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
-    },
-    "node_modules/source-map-explorer/node_modules/has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/source-map-explorer/node_modules/source-map": {
-      "version": "0.7.3",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
-      "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/source-map-explorer/node_modules/supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dependencies": {
-        "has-flag": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/source-map-js": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -19462,18 +19352,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/temp": {
-      "version": "0.9.4",
-      "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
-      "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
-      "dependencies": {
-        "mkdirp": "^0.5.1",
-        "rimraf": "~2.6.2"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
     "node_modules/temp-dir": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
@@ -19482,17 +19360,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/temp/node_modules/rimraf": {
-      "version": "2.6.3",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
-      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
-      "dependencies": {
-        "glob": "^7.1.3"
-      },
-      "bin": {
-        "rimraf": "bin.js"
-      }
-    },
     "node_modules/tempy": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz",
@@ -20268,12 +20135,12 @@
       }
     },
     "node_modules/webpack-dev-middleware": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.0.tgz",
-      "integrity": "sha512-MouJz+rXAm9B1OTOYaJnn6rtD/lWZPy2ufQCH3BPs8Rloh/Du6Jze4p7AeLYHkVi0giJnYLaSGDC7S+GM9arhg==",
+      "version": "5.3.3",
+      "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz",
+      "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==",
       "dependencies": {
         "colorette": "^2.0.10",
-        "memfs": "^3.2.2",
+        "memfs": "^3.4.3",
         "mime-types": "^2.1.31",
         "range-parser": "^1.2.1",
         "schema-utils": "^4.0.0"
@@ -20290,9 +20157,9 @@
       }
     },
     "node_modules/webpack-dev-middleware/node_modules/ajv": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz",
-      "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==",
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
+      "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
         "json-schema-traverse": "^1.0.0",
@@ -20339,39 +20206,38 @@
       }
     },
     "node_modules/webpack-dev-server": {
-      "version": "4.7.2",
-      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.7.2.tgz",
-      "integrity": "sha512-s6yEOSfPpB6g1T2+C5ZOUt5cQOMhjI98IVmmvMNb5cdiqHoxSUfACISHqU/wZy+q4ar/A9jW0pbNj7sa50XRVA==",
+      "version": "4.9.0",
+      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.9.0.tgz",
+      "integrity": "sha512-+Nlb39iQSOSsFv0lWUuUTim3jDQO8nhK3E68f//J2r5rIcp4lULHXz2oZ0UVdEeWXEh5lSzYUlzarZhDAeAVQw==",
       "dependencies": {
         "@types/bonjour": "^3.5.9",
         "@types/connect-history-api-fallback": "^1.3.5",
+        "@types/express": "^4.17.13",
         "@types/serve-index": "^1.9.1",
         "@types/sockjs": "^0.3.33",
-        "@types/ws": "^8.2.2",
+        "@types/ws": "^8.5.1",
         "ansi-html-community": "^0.0.8",
-        "bonjour": "^3.5.0",
-        "chokidar": "^3.5.2",
+        "bonjour-service": "^1.0.11",
+        "chokidar": "^3.5.3",
         "colorette": "^2.0.10",
         "compression": "^1.7.4",
         "connect-history-api-fallback": "^1.6.0",
         "default-gateway": "^6.0.3",
-        "del": "^6.0.0",
-        "express": "^4.17.1",
+        "express": "^4.17.3",
         "graceful-fs": "^4.2.6",
         "html-entities": "^2.3.2",
-        "http-proxy-middleware": "^2.0.0",
+        "http-proxy-middleware": "^2.0.3",
         "ipaddr.js": "^2.0.1",
         "open": "^8.0.9",
         "p-retry": "^4.5.0",
-        "portfinder": "^1.0.28",
+        "rimraf": "^3.0.2",
         "schema-utils": "^4.0.0",
-        "selfsigned": "^1.10.11",
+        "selfsigned": "^2.0.1",
         "serve-index": "^1.9.1",
         "sockjs": "^0.3.21",
         "spdy": "^4.0.2",
-        "strip-ansi": "^7.0.0",
-        "webpack-dev-middleware": "^5.3.0",
-        "ws": "^8.1.0"
+        "webpack-dev-middleware": "^5.3.1",
+        "ws": "^8.4.2"
       },
       "bin": {
         "webpack-dev-server": "bin/webpack-dev-server.js"
@@ -20414,17 +20280,6 @@
         "ajv": "^8.8.2"
       }
     },
-    "node_modules/webpack-dev-server/node_modules/ansi-regex": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
-      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
-      }
-    },
     "node_modules/webpack-dev-server/node_modules/json-schema-traverse": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -20464,24 +20319,10 @@
         "url": "https://opencollective.com/webpack"
       }
     },
-    "node_modules/webpack-dev-server/node_modules/strip-ansi": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
-      "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
-      "dependencies": {
-        "ansi-regex": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
-      }
-    },
     "node_modules/webpack-dev-server/node_modules/ws": {
-      "version": "8.4.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.0.tgz",
-      "integrity": "sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==",
+      "version": "8.6.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz",
+      "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==",
       "engines": {
         "node": ">=10.0.0"
       },
@@ -23316,6 +23157,11 @@
         }
       }
     },
+    "@leichtgewicht/ip-codec": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
+      "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
+    },
     "@mswjs/cookies": {
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.1.7.tgz",
@@ -24144,9 +23990,9 @@
       "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg=="
     },
     "@types/http-proxy": {
-      "version": "1.17.8",
-      "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz",
-      "integrity": "sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA==",
+      "version": "1.17.9",
+      "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz",
+      "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==",
       "requires": {
         "@types/node": "*"
       }
@@ -24479,9 +24325,9 @@
       }
     },
     "@types/ws": {
-      "version": "8.2.2",
-      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz",
-      "integrity": "sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==",
+      "version": "8.5.3",
+      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
+      "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
       "requires": {
         "@types/node": "*"
       }
@@ -24769,12 +24615,12 @@
       "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
     },
     "accepts": {
-      "version": "1.3.7",
-      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
-      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
       "requires": {
-        "mime-types": "~2.1.24",
-        "negotiator": "0.6.2"
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
       }
     },
     "acorn": {
@@ -24834,15 +24680,6 @@
         "debug": "4"
       }
     },
-    "aggregate-error": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
-      "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
-      "requires": {
-        "clean-stack": "^2.0.0",
-        "indent-string": "^4.0.0"
-      }
-    },
     "ajv": {
       "version": "6.12.6",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -25028,14 +24865,6 @@
       "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
       "dev": true
     },
-    "async": {
-      "version": "2.6.3",
-      "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
-      "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
-      "requires": {
-        "lodash": "^4.17.14"
-      }
-    },
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -25394,20 +25223,22 @@
       "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
     },
     "body-parser": {
-      "version": "1.19.1",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz",
-      "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==",
+      "version": "1.20.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
+      "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
       "requires": {
-        "bytes": "3.1.1",
+        "bytes": "3.1.2",
         "content-type": "~1.0.4",
         "debug": "2.6.9",
-        "depd": "~1.1.2",
-        "http-errors": "1.8.1",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
-        "on-finished": "~2.3.0",
-        "qs": "6.9.6",
-        "raw-body": "2.4.2",
-        "type-is": "~1.6.18"
+        "on-finished": "2.4.1",
+        "qs": "6.10.3",
+        "raw-body": "2.5.1",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
       },
       "dependencies": {
         "debug": {
@@ -25418,6 +25249,11 @@
             "ms": "2.0.0"
           }
         },
+        "depd": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+        },
         "iconv-lite": {
           "version": "0.4.24",
           "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -25433,17 +25269,15 @@
         }
       }
     },
-    "bonjour": {
-      "version": "3.5.0",
-      "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
-      "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
+    "bonjour-service": {
+      "version": "1.0.12",
+      "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.0.12.tgz",
+      "integrity": "sha512-pMmguXYCu63Ug37DluMKEHdxc+aaIf/ay4YbF8Gxtba+9d3u+rmEWy61VK3Z3hp8Rskok3BunHYnG0dUHAsblw==",
       "requires": {
-        "array-flatten": "^2.1.0",
-        "deep-equal": "^1.0.1",
+        "array-flatten": "^2.1.2",
         "dns-equal": "^1.0.0",
-        "dns-txt": "^2.0.2",
-        "multicast-dns": "^6.0.1",
-        "multicast-dns-service-types": "^1.1.0"
+        "fast-deep-equal": "^3.1.3",
+        "multicast-dns": "^7.2.4"
       }
     },
     "boolbase": {
@@ -25578,11 +25412,6 @@
         "node-int64": "^0.4.0"
       }
     },
-    "btoa": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
-      "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
-    },
     "buffer": {
       "version": "5.7.1",
       "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -25598,11 +25427,6 @@
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
       "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
     },
-    "buffer-indexof": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
-      "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g=="
-    },
     "builtin-modules": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
@@ -25614,9 +25438,9 @@
       "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE="
     },
     "bytes": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz",
-      "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg=="
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
     },
     "call-bind": {
       "version": "1.0.2",
@@ -25772,11 +25596,6 @@
         }
       }
     },
-    "clean-stack": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
-      "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="
-    },
     "cli-boxes": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
@@ -26098,7 +25917,8 @@
     "cookie": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
-      "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
+      "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+      "dev": true
     },
     "cookie-signature": {
       "version": "1.0.6",
@@ -26802,19 +26622,6 @@
       "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
       "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw="
     },
-    "deep-equal": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
-      "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
-      "requires": {
-        "is-arguments": "^1.0.4",
-        "is-date-object": "^1.0.1",
-        "is-regex": "^1.0.4",
-        "object-is": "^1.0.1",
-        "object-keys": "^1.1.1",
-        "regexp.prototype.flags": "^1.2.0"
-      }
-    },
     "deep-extend": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -26866,21 +26673,6 @@
       "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
       "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
     },
-    "del": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz",
-      "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==",
-      "requires": {
-        "globby": "^11.0.1",
-        "graceful-fs": "^4.2.4",
-        "is-glob": "^4.0.1",
-        "is-path-cwd": "^2.2.0",
-        "is-path-inside": "^3.0.2",
-        "p-map": "^4.0.0",
-        "rimraf": "^3.0.2",
-        "slash": "^3.0.0"
-      }
-    },
     "delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -26892,9 +26684,9 @@
       "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
     },
     "destroy": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
-      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
     },
     "detect-newline": {
       "version": "3.1.0",
@@ -26976,20 +26768,11 @@
       "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0="
     },
     "dns-packet": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz",
-      "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==",
-      "requires": {
-        "ip": "^1.1.0",
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "dns-txt": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
-      "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.3.1.tgz",
+      "integrity": "sha512-spBwIj0TK0Ey3666GwIdWVfUpLyubpU53BTCu8iPn4r4oXd9O14Hjg3EHw3ts2oed77/SeckunUYCyRlSngqHw==",
       "requires": {
-        "buffer-indexof": "^1.0.0"
+        "@leichtgewicht/ip-codec": "^2.0.1"
       }
     },
     "doctrine": {
@@ -27108,11 +26891,11 @@
       "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
     },
     "ejs": {
-      "version": "3.1.6",
-      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz",
-      "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==",
+      "version": "3.1.8",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
+      "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
       "requires": {
-        "jake": "^10.6.1"
+        "jake": "^10.8.5"
       }
     },
     "electron-to-chromium": {
@@ -27918,37 +27701,38 @@
       }
     },
     "express": {
-      "version": "4.17.2",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz",
-      "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==",
+      "version": "4.18.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
+      "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
       "requires": {
-        "accepts": "~1.3.7",
+        "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
-        "body-parser": "1.19.1",
+        "body-parser": "1.20.0",
         "content-disposition": "0.5.4",
         "content-type": "~1.0.4",
-        "cookie": "0.4.1",
+        "cookie": "0.5.0",
         "cookie-signature": "1.0.6",
         "debug": "2.6.9",
-        "depd": "~1.1.2",
+        "depd": "2.0.0",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
-        "finalhandler": "~1.1.2",
+        "finalhandler": "1.2.0",
         "fresh": "0.5.2",
+        "http-errors": "2.0.0",
         "merge-descriptors": "1.0.1",
         "methods": "~1.1.2",
-        "on-finished": "~2.3.0",
+        "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
         "path-to-regexp": "0.1.7",
         "proxy-addr": "~2.0.7",
-        "qs": "6.9.6",
+        "qs": "6.10.3",
         "range-parser": "~1.2.1",
         "safe-buffer": "5.2.1",
-        "send": "0.17.2",
-        "serve-static": "1.14.2",
+        "send": "0.18.0",
+        "serve-static": "1.15.0",
         "setprototypeof": "1.2.0",
-        "statuses": "~1.5.0",
+        "statuses": "2.0.1",
         "type-is": "~1.6.18",
         "utils-merge": "1.0.1",
         "vary": "~1.1.2"
@@ -27957,7 +27741,12 @@
         "array-flatten": {
           "version": "1.1.1",
           "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
-          "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+          "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+        },
+        "cookie": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+          "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
         },
         "debug": {
           "version": "2.6.9",
@@ -27967,6 +27756,11 @@
             "ms": "2.0.0"
           }
         },
+        "depd": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+        },
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -27981,6 +27775,11 @@
           "version": "5.2.1",
           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
           "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+        },
+        "statuses": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+          "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
         }
       }
     },
@@ -28107,11 +27906,29 @@
       }
     },
     "filelist": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
-      "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==",
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
+      "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
       "requires": {
-        "minimatch": "^3.0.4"
+        "minimatch": "^5.0.1"
+      },
+      "dependencies": {
+        "brace-expansion": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+          "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+          "requires": {
+            "balanced-match": "^1.0.0"
+          }
+        },
+        "minimatch": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
+          "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
+          "requires": {
+            "brace-expansion": "^2.0.1"
+          }
+        }
       }
     },
     "filesize": {
@@ -28139,16 +27956,16 @@
       "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs="
     },
     "finalhandler": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
-      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+      "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
       "requires": {
         "debug": "2.6.9",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
-        "on-finished": "~2.3.0",
+        "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "statuses": "~1.5.0",
+        "statuses": "2.0.1",
         "unpipe": "~1.0.0"
       },
       "dependencies": {
@@ -28164,6 +27981,11 @@
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        },
+        "statuses": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+          "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
         }
       }
     },
@@ -28201,9 +28023,9 @@
       "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA=="
     },
     "follow-redirects": {
-      "version": "1.14.7",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz",
-      "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ=="
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz",
+      "integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ=="
     },
     "fork-ts-checker-webpack-plugin": {
       "version": "6.5.1",
@@ -28712,15 +28534,27 @@
       "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc="
     },
     "http-errors": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
-      "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
       "requires": {
-        "depd": "~1.1.2",
+        "depd": "2.0.0",
         "inherits": "2.0.4",
         "setprototypeof": "1.2.0",
-        "statuses": ">= 1.5.0 < 2",
+        "statuses": "2.0.1",
         "toidentifier": "1.0.1"
+      },
+      "dependencies": {
+        "depd": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+        },
+        "statuses": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+          "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
+        }
       }
     },
     "http-parser-js": {
@@ -28749,11 +28583,11 @@
       }
     },
     "http-proxy-middleware": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz",
-      "integrity": "sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg==",
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz",
+      "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==",
       "requires": {
-        "@types/http-proxy": "^1.17.5",
+        "@types/http-proxy": "^1.17.8",
         "http-proxy": "^1.18.1",
         "is-glob": "^4.0.1",
         "is-plain-obj": "^3.0.0",
@@ -28855,7 +28689,8 @@
     "indent-string": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
-      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="
+      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+      "dev": true
     },
     "inflight": {
       "version": "1.0.6",
@@ -28959,25 +28794,11 @@
         "side-channel": "^1.0.4"
       }
     },
-    "ip": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
-      "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
-    },
     "ipaddr.js": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz",
       "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng=="
     },
-    "is-arguments": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
-      "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
-      "requires": {
-        "call-bind": "^1.0.2",
-        "has-tostringtag": "^1.0.0"
-      }
-    },
     "is-arrayish": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -29094,16 +28915,6 @@
       "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
       "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
     },
-    "is-path-cwd": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
-      "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ=="
-    },
-    "is-path-inside": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
-      "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="
-    },
     "is-plain-obj": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
@@ -29287,20 +29098,63 @@
       }
     },
     "jake": {
-      "version": "10.8.2",
-      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz",
-      "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==",
+      "version": "10.8.5",
+      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
+      "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
       "requires": {
-        "async": "0.9.x",
-        "chalk": "^2.4.2",
+        "async": "^3.2.3",
+        "chalk": "^4.0.2",
         "filelist": "^1.0.1",
         "minimatch": "^3.0.4"
       },
       "dependencies": {
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
         "async": {
-          "version": "0.9.2",
-          "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
-          "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
+          "version": "3.2.3",
+          "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
+          "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
+        },
+        "chalk": {
+          "version": "4.1.2",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
         }
       }
     },
@@ -31836,9 +31690,9 @@
       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
     },
     "memfs": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz",
-      "integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==",
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz",
+      "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==",
       "requires": {
         "fs-monkey": "1.0.3"
       }
@@ -32236,19 +32090,14 @@
       }
     },
     "multicast-dns": {
-      "version": "6.2.3",
-      "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
-      "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
+      "version": "7.2.5",
+      "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz",
+      "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==",
       "requires": {
-        "dns-packet": "^1.3.1",
+        "dns-packet": "^5.2.2",
         "thunky": "^1.0.2"
       }
     },
-    "multicast-dns-service-types": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
-      "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
-    },
     "mute-stream": {
       "version": "0.0.8",
       "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@@ -32266,9 +32115,9 @@
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc="
     },
     "negotiator": {
-      "version": "0.6.2",
-      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
-      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
     },
     "neo-async": {
       "version": "2.6.2",
@@ -32324,9 +32173,9 @@
       }
     },
     "node-forge": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz",
-      "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA=="
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
+      "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
     },
     "node-int64": {
       "version": "0.4.0",
@@ -32390,15 +32239,6 @@
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz",
       "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g=="
     },
-    "object-is": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
-      "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
-      "requires": {
-        "call-bind": "^1.0.2",
-        "define-properties": "^1.1.3"
-      }
-    },
     "object-keys": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -32470,9 +32310,9 @@
       "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
     },
     "on-finished": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
-      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
       "requires": {
         "ee-first": "1.1.1"
       }
@@ -32498,15 +32338,6 @@
         "mimic-fn": "^2.1.0"
       }
     },
-    "open": {
-      "version": "7.4.2",
-      "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
-      "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
-      "requires": {
-        "is-docker": "^2.0.0",
-        "is-wsl": "^2.1.1"
-      }
-    },
     "optionator": {
       "version": "0.9.1",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -32622,14 +32453,6 @@
         "p-limit": "^2.2.0"
       }
     },
-    "p-map": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
-      "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
-      "requires": {
-        "aggregate-error": "^3.0.0"
-      }
-    },
     "p-retry": {
       "version": "4.6.1",
       "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz",
@@ -32805,26 +32628,6 @@
         }
       }
     },
-    "portfinder": {
-      "version": "1.0.28",
-      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
-      "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==",
-      "requires": {
-        "async": "^2.6.2",
-        "debug": "^3.1.1",
-        "mkdirp": "^0.5.5"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "3.2.7",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
-          "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
-          "requires": {
-            "ms": "^2.1.1"
-          }
-        }
-      }
-    },
     "postcss": {
       "version": "8.4.6",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz",
@@ -33324,9 +33127,12 @@
       "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
     },
     "qs": {
-      "version": "6.9.6",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz",
-      "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ=="
+      "version": "6.10.3",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
+      "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
+      "requires": {
+        "side-channel": "^1.0.4"
+      }
     },
     "query-string": {
       "version": "6.14.1",
@@ -33367,12 +33173,12 @@
       "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
     },
     "raw-body": {
-      "version": "2.4.2",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz",
-      "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==",
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+      "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
       "requires": {
-        "bytes": "3.1.1",
-        "http-errors": "1.8.1",
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
         "iconv-lite": "0.4.24",
         "unpipe": "1.0.0"
       },
@@ -34242,11 +34048,11 @@
       "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo="
     },
     "selfsigned": {
-      "version": "1.10.13",
-      "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.13.tgz",
-      "integrity": "sha512-UmLwTKZwNmXYDAlRFhaEdgEg0dp9k5gfJXuO7uKvSqioN1M0+Mgf4V39IlVZMSuqGoCi6h5legkhNXvWy0rFSg==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz",
+      "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==",
       "requires": {
-        "node-forge": "^1.2.0"
+        "node-forge": "^1"
       }
     },
     "semver": {
@@ -34258,23 +34064,23 @@
       }
     },
     "send": {
-      "version": "0.17.2",
-      "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
-      "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+      "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
       "requires": {
         "debug": "2.6.9",
-        "depd": "~1.1.2",
-        "destroy": "~1.0.4",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "etag": "~1.8.1",
         "fresh": "0.5.2",
-        "http-errors": "1.8.1",
+        "http-errors": "2.0.0",
         "mime": "1.6.0",
         "ms": "2.1.3",
-        "on-finished": "~2.3.0",
+        "on-finished": "2.4.1",
         "range-parser": "~1.2.1",
-        "statuses": "~1.5.0"
+        "statuses": "2.0.1"
       },
       "dependencies": {
         "debug": {
@@ -34292,10 +34098,20 @@
             }
           }
         },
+        "depd": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+          "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+        },
         "ms": {
           "version": "2.1.3",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
           "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+        },
+        "statuses": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+          "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
         }
       }
     },
@@ -34487,14 +34303,14 @@
       }
     },
     "serve-static": {
-      "version": "1.14.2",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
-      "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+      "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
       "requires": {
         "encodeurl": "~1.0.2",
         "escape-html": "~1.0.3",
         "parseurl": "~1.3.3",
-        "send": "0.17.2"
+        "send": "0.18.0"
       }
     },
     "set-cookie-parser": {
@@ -34608,75 +34424,6 @@
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
       "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
     },
-    "source-map-explorer": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-2.5.2.tgz",
-      "integrity": "sha512-gBwOyCcHPHcdLbgw6Y6kgoH1uLKL6hN3zz0xJcNI2lpnElZliIlmSYAjUVwAWnc7+HscoTyh1ScR7ITtFuEnxg==",
-      "requires": {
-        "btoa": "^1.2.1",
-        "chalk": "^4.1.0",
-        "convert-source-map": "^1.7.0",
-        "ejs": "^3.1.5",
-        "escape-html": "^1.0.3",
-        "glob": "^7.1.6",
-        "gzip-size": "^6.0.0",
-        "lodash": "^4.17.20",
-        "open": "^7.3.1",
-        "source-map": "^0.7.3",
-        "temp": "^0.9.4",
-        "yargs": "^16.2.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-          "requires": {
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "4.1.2",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
-        },
-        "source-map": {
-          "version": "0.7.3",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
-          "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
-        },
-        "supports-color": {
-          "version": "7.2.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        }
-      }
-    },
     "source-map-js": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -35343,25 +35090,6 @@
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
       "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
     },
-    "temp": {
-      "version": "0.9.4",
-      "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
-      "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
-      "requires": {
-        "mkdirp": "^0.5.1",
-        "rimraf": "~2.6.2"
-      },
-      "dependencies": {
-        "rimraf": {
-          "version": "2.6.3",
-          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
-          "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
-          "requires": {
-            "glob": "^7.1.3"
-          }
-        }
-      }
-    },
     "temp-dir": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
@@ -35959,21 +35687,21 @@
       }
     },
     "webpack-dev-middleware": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.0.tgz",
-      "integrity": "sha512-MouJz+rXAm9B1OTOYaJnn6rtD/lWZPy2ufQCH3BPs8Rloh/Du6Jze4p7AeLYHkVi0giJnYLaSGDC7S+GM9arhg==",
+      "version": "5.3.3",
+      "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz",
+      "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==",
       "requires": {
         "colorette": "^2.0.10",
-        "memfs": "^3.2.2",
+        "memfs": "^3.4.3",
         "mime-types": "^2.1.31",
         "range-parser": "^1.2.1",
         "schema-utils": "^4.0.0"
       },
       "dependencies": {
         "ajv": {
-          "version": "8.8.2",
-          "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz",
-          "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==",
+          "version": "8.11.0",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
+          "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
           "requires": {
             "fast-deep-equal": "^3.1.1",
             "json-schema-traverse": "^1.0.0",
@@ -36008,39 +35736,38 @@
       }
     },
     "webpack-dev-server": {
-      "version": "4.7.2",
-      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.7.2.tgz",
-      "integrity": "sha512-s6yEOSfPpB6g1T2+C5ZOUt5cQOMhjI98IVmmvMNb5cdiqHoxSUfACISHqU/wZy+q4ar/A9jW0pbNj7sa50XRVA==",
+      "version": "4.9.0",
+      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.9.0.tgz",
+      "integrity": "sha512-+Nlb39iQSOSsFv0lWUuUTim3jDQO8nhK3E68f//J2r5rIcp4lULHXz2oZ0UVdEeWXEh5lSzYUlzarZhDAeAVQw==",
       "requires": {
         "@types/bonjour": "^3.5.9",
         "@types/connect-history-api-fallback": "^1.3.5",
+        "@types/express": "^4.17.13",
         "@types/serve-index": "^1.9.1",
         "@types/sockjs": "^0.3.33",
-        "@types/ws": "^8.2.2",
+        "@types/ws": "^8.5.1",
         "ansi-html-community": "^0.0.8",
-        "bonjour": "^3.5.0",
-        "chokidar": "^3.5.2",
+        "bonjour-service": "^1.0.11",
+        "chokidar": "^3.5.3",
         "colorette": "^2.0.10",
         "compression": "^1.7.4",
         "connect-history-api-fallback": "^1.6.0",
         "default-gateway": "^6.0.3",
-        "del": "^6.0.0",
-        "express": "^4.17.1",
+        "express": "^4.17.3",
         "graceful-fs": "^4.2.6",
         "html-entities": "^2.3.2",
-        "http-proxy-middleware": "^2.0.0",
+        "http-proxy-middleware": "^2.0.3",
         "ipaddr.js": "^2.0.1",
         "open": "^8.0.9",
         "p-retry": "^4.5.0",
-        "portfinder": "^1.0.28",
+        "rimraf": "^3.0.2",
         "schema-utils": "^4.0.0",
-        "selfsigned": "^1.10.11",
+        "selfsigned": "^2.0.1",
         "serve-index": "^1.9.1",
         "sockjs": "^0.3.21",
         "spdy": "^4.0.2",
-        "strip-ansi": "^7.0.0",
-        "webpack-dev-middleware": "^5.3.0",
-        "ws": "^8.1.0"
+        "webpack-dev-middleware": "^5.3.1",
+        "ws": "^8.4.2"
       },
       "dependencies": {
         "ajv": {
@@ -36062,11 +35789,6 @@
             "fast-deep-equal": "^3.1.3"
           }
         },
-        "ansi-regex": {
-          "version": "6.0.1",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
-          "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="
-        },
         "json-schema-traverse": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -36093,18 +35815,10 @@
             "ajv-keywords": "^5.0.0"
           }
         },
-        "strip-ansi": {
-          "version": "7.0.1",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
-          "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
-          "requires": {
-            "ansi-regex": "^6.0.1"
-          }
-        },
         "ws": {
-          "version": "8.4.0",
-          "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.0.tgz",
-          "integrity": "sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==",
+          "version": "8.6.0",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz",
+          "integrity": "sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==",
           "requires": {}
         }
       }
diff --git a/web/vtadmin/package.json b/web/vtadmin/package.json
index 4ff0e8023f..a2c2601934 100644
--- a/web/vtadmin/package.json
+++ b/web/vtadmin/package.json
@@ -33,7 +33,6 @@
     "react-tiny-popover": "^6.0.5",
     "react-toastify": "^8.1.0",
     "sass": "^1.43.4",
-    "source-map-explorer": "^2.5.2",
     "svgo": "^2.8.0",
     "typescript": "^4.5.4",
     "web-vitals": "^0.2.4"
@@ -51,7 +50,6 @@
     "lint:prettier:fix": "npm run lint:prettier -- --write",
     "lint:stylelint": "stylelint \"**/*.{css,scss}\"",
     "lint:stylelint:fix": "npm run lint:stylelint -- --fix",
-    "analyze": "source-map-explorer 'build/static/js/*.js'",
     "eject": "react-scripts eject"
   },
   "eslintConfig": {
diff --git a/web/vtadmin/src/api/http.ts b/web/vtadmin/src/api/http.ts
index 6d68d4fadf..9d3650dbe8 100644
--- a/web/vtadmin/src/api/http.ts
+++ b/web/vtadmin/src/api/http.ts
@@ -518,3 +518,46 @@ export const fetchShardReplicationPositions = async ({
 
     return pb.GetShardReplicationPositionsResponse.create(result);
 };
+
+export interface ReloadSchemaParams {
+    clusterIDs?: (string | null | undefined)[];
+    concurrency?: number;
+    includePrimary?: boolean;
+    keyspaces?: (string | null | undefined)[];
+
+    // e.g., ["commerce/0"]
+    keyspaceShards?: (string | null | undefined)[];
+
+    // A list of tablet aliases; e.g., ["zone1-101", "zone1-102"]
+    tablets?: (string | null | undefined)[];
+
+    waitPosition?: string;
+}
+
+export const reloadSchema = async (params: ReloadSchemaParams) => {
+    const req = new URLSearchParams();
+
+    (params.clusterIDs || []).forEach((c) => c && req.append('cluster', c));
+    (params.keyspaces || []).forEach((k) => k && req.append('keyspace', k));
+    (params.keyspaceShards || []).forEach((k) => k && req.append('keyspaceShard', k));
+    (params.tablets || []).forEach((t) => t && req.append('tablet', t));
+
+    if (typeof params.concurrency === 'number') {
+        req.append('concurrency', params.concurrency.toString());
+    }
+
+    if (typeof params.includePrimary === 'boolean') {
+        req.append('include_primary', params.includePrimary.toString());
+    }
+
+    if (typeof params.waitPosition === 'string') {
+        req.append('wait_position', params.waitPosition);
+    }
+
+    const { result } = await vtfetch(`/api/schemas/reload?${req}`, { method: 'put' });
+
+    const err = pb.ReloadSchemasResponse.verify(result);
+    if (err) throw Error(err);
+
+    return pb.ReloadSchemasResponse.create(result);
+};
diff --git a/web/vtadmin/src/components/routes/keyspace/Advanced.tsx b/web/vtadmin/src/components/routes/keyspace/Advanced.tsx
new file mode 100644
index 0000000000..b89d180410
--- /dev/null
+++ b/web/vtadmin/src/components/routes/keyspace/Advanced.tsx
@@ -0,0 +1,74 @@
+/**
+ * Copyright 2022 The Vitess Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { UseMutationResult } from 'react-query';
+
+import { useKeyspace, useReloadSchema } from '../../../hooks/api';
+import ActionPanel from '../../ActionPanel';
+import { QueryLoadingPlaceholder } from '../../placeholders/QueryLoadingPlaceholder';
+import { success, warn } from '../../Snackbar';
+
+interface Props {
+    clusterID: string;
+    name: string;
+}
+
+export const Advanced: React.FC<Props> = ({ clusterID, name }) => {
+    const kq = useKeyspace({ clusterID, name });
+
+    const { data: keyspace } = kq;
+
+    const reloadSchemaMutation = useReloadSchema(
+        {
+            clusterIDs: [clusterID],
+            keyspaces: [name],
+        },
+        {
+            onError: (error) => warn(`There was an error reloading the schemas in the ${name} keyspace: ${error}`),
+            onSuccess: () => {
+                success(`Successfully reloaded schemas in the ${name} keyspace.`, { autoClose: 1600 });
+            },
+        }
+    );
+
+    return (
+        <div className="pt-4">
+            <div className="my-8">
+                <h3 className="mb-4">Schemas</h3>
+
+                <QueryLoadingPlaceholder query={kq} />
+
+                {keyspace && (
+                    <div>
+                        <ActionPanel
+                            description={
+                                <>
+                                    Reloads the schema on all the tablets, except the primary tablet, in the{' '}
+                                    <span className="font-bold">{name}</span> keyspace.
+                                </>
+                            }
+                            documentationLink="https://vitess.io/docs/13.0/reference/programs/vtctl/schema-version-permissions/#reloadschemakeyspace"
+                            loadedText="Reload Schema"
+                            loadingText="Reloading Schema..."
+                            mutation={reloadSchemaMutation as UseMutationResult}
+                            title="Reload Schema"
+                        />
+                    </div>
+                )}
+            </div>
+        </div>
+    );
+};
diff --git a/web/vtadmin/src/components/routes/keyspace/Keyspace.tsx b/web/vtadmin/src/components/routes/keyspace/Keyspace.tsx
index 87c33de2f6..61bf52bb18 100644
--- a/web/vtadmin/src/components/routes/keyspace/Keyspace.tsx
+++ b/web/vtadmin/src/components/routes/keyspace/Keyspace.tsx
@@ -18,14 +18,17 @@ import { Link, Redirect, Route } from 'react-router-dom';
 
 import { useKeyspace } from '../../../hooks/api';
 import { useDocumentTitle } from '../../../hooks/useDocumentTitle';
+import { isReadOnlyMode } from '../../../util/env';
 import { Code } from '../../Code';
 import { ContentContainer } from '../../layout/ContentContainer';
 import { NavCrumbs } from '../../layout/NavCrumbs';
 import { WorkspaceHeader } from '../../layout/WorkspaceHeader';
 import { WorkspaceTitle } from '../../layout/WorkspaceTitle';
 import { QueryLoadingPlaceholder } from '../../placeholders/QueryLoadingPlaceholder';
+import { ReadOnlyGate } from '../../ReadOnlyGate';
 import { Tab } from '../../tabs/Tab';
 import { TabContainer } from '../../tabs/TabContainer';
+import { Advanced } from './Advanced';
 import style from './Keyspace.module.scss';
 import { KeyspaceShards } from './KeyspaceShards';
 import { KeyspaceVSchema } from './KeyspaceVSchema';
@@ -91,6 +94,10 @@ export const Keyspace = () => {
                     <Tab text="Shards" to={`${url}/shards`} />
                     <Tab text="VSchema" to={`${url}/vschema`} />
                     <Tab text="JSON" to={`${url}/json`} />
+
+                    <ReadOnlyGate>
+                        <Tab text="Advanced" to={`${url}/advanced`} />
+                    </ReadOnlyGate>
                 </TabContainer>
 
                 <Switch>
@@ -107,6 +114,12 @@ export const Keyspace = () => {
                         <Code code={JSON.stringify(keyspace, null, 2)} />
                     </Route>
 
+                    {!isReadOnlyMode() && (
+                        <Route path={`${path}/advanced`}>
+                            <Advanced clusterID={clusterID} name={name} />
+                        </Route>
+                    )}
+
                     <Redirect exact from={path} to={{ pathname: `${path}/shards`, search }} />
                 </Switch>
             </ContentContainer>
diff --git a/web/vtadmin/src/hooks/api.ts b/web/vtadmin/src/hooks/api.ts
index a57d7076d8..bad268acac 100644
--- a/web/vtadmin/src/hooks/api.ts
+++ b/web/vtadmin/src/hooks/api.ts
@@ -59,6 +59,7 @@ import {
     validateVersionKeyspace,
     fetchShardReplicationPositions,
     createKeyspace,
+    reloadSchema,
 } from '../api/http';
 import { vtadmin as pb } from '../proto/vtadmin';
 import { formatAlias } from '../util/tablets';
@@ -429,3 +430,16 @@ export const useWorkflow = (
         ...options,
     });
 };
+
+/**
+ * useReloadSchema is a mutate hook that reloads schemas in one or more
+ * keyspaces, shards, or tablets in the cluster, depending on the request parameters.
+ */
+export const useReloadSchema = (
+    params: Parameters<typeof reloadSchema>[0],
+    options?: UseMutationOptions<Awaited<ReturnType<typeof reloadSchema>>, Error>
+) => {
+    return useMutation<Awaited<ReturnType<typeof reloadSchema>>, Error>(() => {
+        return reloadSchema(params);
+    }, options);
+};


Test patch:
diff --git a/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go b/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go
index 55b3ee8c69..b0a69fcf57 100644
--- a/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go
+++ b/go/test/endtoend/onlineddl/vrepl/onlineddl_vrepl_test.go
@@ -603,6 +603,86 @@ func TestSchemaChange(t *testing.T) {
 		onlineddl.CheckCancelMigration(t, &vtParams, shards, uuid, false)
 		onlineddl.CheckRetryMigration(t, &vtParams, shards, uuid, false)
 	})
+
+	// Technically the next test should belong in onlineddl_revert suite. But we're tking advantage of setup and functionality existing in this tets:
+	// - two shards as opposed to one
+	// - tablet throttling
+	t.Run("Revert a migration completed on one shard and cancelled on another", func(t *testing.T) {
+		// shard 0 will run normally, shard 1 will be throttled
+		defer unthrottleApp(shards[1].Vttablets[0], throttlerAppName)
+		t.Run("throttle shard 1", func(t *testing.T) {
+			_, body, err := throttleApp(shards[1].Vttablets[0], throttlerAppName)
+			assert.NoError(t, err)
+			assert.Contains(t, body, throttlerAppName)
+		})
+
+		var uuid string
+		t.Run("run migrations, expect 1st to complete, 2nd to be running", func(t *testing.T) {
+			uuid = testOnlineDDLStatement(t, alterTableTrivialStatement, "vitess", providedUUID, providedMigrationContext, "vtgate", "test_val", "", true)
+			{
+				status := onlineddl.WaitForMigrationStatus(t, &vtParams, shards[:1], uuid, normalMigrationWait, schema.OnlineDDLStatusComplete, schema.OnlineDDLStatusFailed)
+				fmt.Printf("# Migration status (for debug purposes): <%s>\n", status)
+				onlineddl.CheckMigrationStatus(t, &vtParams, shards[:1], uuid, schema.OnlineDDLStatusComplete)
+			}
+			{
+				// shard 1 is throttled
+				status := onlineddl.WaitForMigrationStatus(t, &vtParams, shards[1:], uuid, normalMigrationWait, schema.OnlineDDLStatusRunning)
+				fmt.Printf("# Migration status (for debug purposes): <%s>\n", status)
+				onlineddl.CheckMigrationStatus(t, &vtParams, shards[1:], uuid, schema.OnlineDDLStatusRunning)
+			}
+		})
+		t.Run("check cancel migration", func(t *testing.T) {
+			onlineddl.CheckCancelAllMigrations(t, &vtParams, 1)
+		})
+		t.Run("unthrottle shard 1", func(t *testing.T) {
+			_, body, err := unthrottleApp(shards[1].Vttablets[0], throttlerAppName)
+			assert.NoError(t, err)
+			assert.Contains(t, body, throttlerAppName)
+		})
+		var revertUUID string
+		t.Run("issue revert migration", func(t *testing.T) {
+			revertQuery := fmt.Sprintf("revert vitess_migration '%s'", uuid)
+			rs := onlineddl.VtgateExecQuery(t, &vtParams, revertQuery, "")
+			require.NotNil(t, rs)
+			row := rs.Named().Row()
+			require.NotNil(t, row)
+			revertUUID = row.AsString("uuid", "")
+			assert.NotEmpty(t, revertUUID)
+		})
+		t.Run("expect one revert successful, another failed", func(t *testing.T) {
+			{
+				// shard 0 migration was complete. Revert should be successful
+				status := onlineddl.WaitForMigrationStatus(t, &vtParams, shards[:1], revertUUID, normalMigrationWait, schema.OnlineDDLStatusComplete, schema.OnlineDDLStatusFailed)
+				fmt.Printf("# Migration status (for debug purposes): <%s>\n", status)
+				onlineddl.CheckMigrationStatus(t, &vtParams, shards[:1], revertUUID, schema.OnlineDDLStatusComplete)
+			}
+			{
+				// shard 0 migration was cancelled. Revert should not be possible
+				status := onlineddl.WaitForMigrationStatus(t, &vtParams, shards[1:], revertUUID, normalMigrationWait, schema.OnlineDDLStatusComplete, schema.OnlineDDLStatusFailed)
+				fmt.Printf("# Migration status (for debug purposes): <%s>\n", status)
+				onlineddl.CheckMigrationStatus(t, &vtParams, shards[1:], revertUUID, schema.OnlineDDLStatusFailed)
+			}
+		})
+		t.Run("expect two rows in SHOW VITESS_MIGRATIONS", func(t *testing.T) {
+			// This validates that the shards are reflected correctly in output of SHOW VITESS_MIGRATIONS
+			rs := onlineddl.ReadMigrations(t, &vtParams, revertUUID)
+			require.NotNil(t, rs)
+			require.Equal(t, 2, len(rs.Rows))
+			for _, row := range rs.Named().Rows {
+				shard := row["shard"].ToString()
+				status := row["migration_status"].ToString()
+
+				switch shard {
+				case "-80":
+					require.Equal(t, string(schema.OnlineDDLStatusComplete), status)
+				case "80-":
+					require.Equal(t, string(schema.OnlineDDLStatusFailed), status)
+				default:
+					require.NoError(t, fmt.Errorf("unexpected shard name: %s", shard))
+				}
+			}
+		})
+	})
 }
 
 func insertRow(t *testing.T) {
diff --git a/go/test/endtoend/onlineddl/vtgate_util.go b/go/test/endtoend/onlineddl/vtgate_util.go
index 4fabee112d..1034fd6f67 100644
--- a/go/test/endtoend/onlineddl/vtgate_util.go
+++ b/go/test/endtoend/onlineddl/vtgate_util.go
@@ -172,6 +172,10 @@ func CheckMigrationStatus(t *testing.T, vtParams *mysql.ConnParams, shards []clu
 
 // WaitForMigrationStatus waits for a migration to reach either provided statuses (returns immediately), or eventually time out
 func WaitForMigrationStatus(t *testing.T, vtParams *mysql.ConnParams, shards []cluster.Shard, uuid string, timeout time.Duration, expectStatuses ...schema.OnlineDDLStatus) schema.OnlineDDLStatus {
+	shardNames := map[string]bool{}
+	for _, shard := range shards {
+		shardNames[shard.Name] = true
+	}
 	query, err := sqlparser.ParseAndBind("show vitess_migrations like %a",
 		sqltypes.StringBindVariable(uuid),
 	)
@@ -187,6 +191,11 @@ func WaitForMigrationStatus(t *testing.T, vtParams *mysql.ConnParams, shards []c
 		countMatchedShards := 0
 		r := VtgateExecQuery(t, vtParams, query, "")
 		for _, row := range r.Named().Rows {
+			shardName := row["shard"].ToString()
+			if !shardNames[shardName] {
+				// irrelevant shard
+				continue
+			}
 			lastKnownStatus = row["migration_status"].ToString()
 			if row["migration_uuid"].ToString() == uuid && statusesMap[lastKnownStatus] {
 				countMatchedShards++
diff --git a/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go b/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go
index fc5d1915b7..0e20922288 100644
--- a/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go
+++ b/go/test/endtoend/vtgate/queries/aggregation/aggregation_test.go
@@ -120,8 +120,7 @@ func TestEqualFilterOnScatter(t *testing.T) {
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a = \"5\"", `[[INT64(5)]]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a = 5.00", `[[INT64(5)]]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a, val1 from aggr_test group by val1 having a = 1.00", `[[INT64(1) VARCHAR("a")] [INT64(1) VARCHAR("b")] [INT64(1) VARCHAR("c")] [INT64(1) VARCHAR("d")] [INT64(1) VARCHAR("e")]]`)
-
-			utils.AssertContainsError(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) = 5", `expr cannot be translated, not supported`) // will fail since `count(*)` is a FuncExpr
+			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) = 5", `[[INT64(1)]]`)
 		})
 	}
 }
@@ -179,6 +178,9 @@ func TestAggrOnJoin(t *testing.T) {
 
 	mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ a.val1, count(*) as leCount from aggr_test a join t3 t on a.val2 = t.id7 group by a.val1 having leCount = 4",
 		`[[VARCHAR("a") INT64(4)]]`)
+
+	mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ a.val1 from aggr_test a join t3 t on a.val2 = t.id7 group by a.val1 having count(*) = 4",
+		`[[VARCHAR("a")]]`)
 }
 
 func TestNotEqualFilterOnScatter(t *testing.T) {
@@ -200,8 +202,7 @@ func TestNotEqualFilterOnScatter(t *testing.T) {
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a != \"1\"", `[[INT64(5)]]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a != \"5\"", `[]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a != 5.00", `[]`)
-
-			utils.AssertContainsError(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) != 5", `expr cannot be translated, not supported`) // will fail since `count(*)` is a FuncExpr
+			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) != 5", `[]`)
 		})
 	}
 }
@@ -224,8 +225,7 @@ func TestLessFilterOnScatter(t *testing.T) {
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a < \"10\"", `[[INT64(5)]]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a < \"5\"", `[]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a < 6.00", `[[INT64(5)]]`)
-
-			utils.AssertContainsError(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) < 5", `expr cannot be translated, not supported`) // will fail since `count(*)` is a FuncExpr
+			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) < 5", `[]`)
 		})
 	}
 }
@@ -249,8 +249,7 @@ func TestLessEqualFilterOnScatter(t *testing.T) {
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a <= \"10\"", `[[INT64(5)]]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a <= \"5\"", `[[INT64(5)]]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a <= 5.00", `[[INT64(5)]]`)
-
-			utils.AssertContainsError(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) <= 5", `expr cannot be translated, not supported`) // will fail since `count(*)` is a FuncExpr
+			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) <= 5", `[[INT64(1)]]`)
 		})
 	}
 }
@@ -274,8 +273,7 @@ func TestGreaterFilterOnScatter(t *testing.T) {
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a > \"1\"", `[[INT64(5)]]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a > \"5\"", `[]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a > 4.00", `[[INT64(5)]]`)
-
-			utils.AssertContainsError(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) > 5", `expr cannot be translated, not supported`) // will fail since `count(*)` is a FuncExpr
+			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) > 5", `[]`)
 		})
 	}
 }
@@ -299,8 +297,7 @@ func TestGreaterEqualFilterOnScatter(t *testing.T) {
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a >= \"1\"", `[[INT64(5)]]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a >= \"5\"", `[[INT64(5)]]`)
 			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ count(*) as a from aggr_test having a >= 5.00", `[[INT64(5)]]`)
-
-			utils.AssertContainsError(t, mcmp.VtConn, "select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) >= 5", `expr cannot be translated, not supported`) // will fail since `count(*)` is a FuncExpr
+			mcmp.AssertMatches("select /*vt+ PLANNER=gen4 */ 1 from aggr_test having count(*) >= 5", `[[INT64(1)]]`)
 		})
 	}
 }
diff --git a/go/vt/schemadiff/diff_test.go b/go/vt/schemadiff/diff_test.go
index f7477a1d7a..8b49a3a9f3 100644
--- a/go/vt/schemadiff/diff_test.go
+++ b/go/vt/schemadiff/diff_test.go
@@ -308,14 +308,25 @@ func TestDiffSchemas(t *testing.T) {
 			},
 		},
 		{
-			name: "change of table column int length",
-			from: "create table t(id int primary key, i int(10))",
-			to:   "create table t(id int primary key, i int(11))",
+			name: "change of table column tinyint 1 to longer",
+			from: "create table t(id int primary key, i tinyint(1))",
+			to:   "create table t(id int primary key, i tinyint(2))",
 			diffs: []string{
-				"alter table t modify column i int(11)",
+				"alter table t modify column i tinyint",
 			},
 			cdiffs: []string{
-				"ALTER TABLE `t` MODIFY COLUMN `i` int(11)",
+				"ALTER TABLE `t` MODIFY COLUMN `i` tinyint",
+			},
+		},
+		{
+			name: "change of table column tinyint 2 to 1",
+			from: "create table t(id int primary key, i tinyint(2))",
+			to:   "create table t(id int primary key, i tinyint(1))",
+			diffs: []string{
+				"alter table t modify column i tinyint(1)",
+			},
+			cdiffs: []string{
+				"ALTER TABLE `t` MODIFY COLUMN `i` tinyint(1)",
 			},
 		},
 		{
diff --git a/go/vt/schemadiff/table_test.go b/go/vt/schemadiff/table_test.go
index b649fe1a28..72c3f86469 100644
--- a/go/vt/schemadiff/table_test.go
+++ b/go/vt/schemadiff/table_test.go
@@ -1193,19 +1193,34 @@ func TestNormalize(t *testing.T) {
 			to:   "CREATE TABLE `t` (\n\t`id` int PRIMARY KEY,\n\t`i` int\n)",
 		},
 		{
-			name: "removes int sizes",
-			from: "create table t (id int primary key, i int(11) default null)",
-			to:   "CREATE TABLE `t` (\n\t`id` int PRIMARY KEY,\n\t`i` int(11)\n)",
+			name: "does not remove tinyint(1) size",
+			from: "create table t (id int primary key, i tinyint(1) default null)",
+			to:   "CREATE TABLE `t` (\n\t`id` int PRIMARY KEY,\n\t`i` tinyint(1)\n)",
 		},
 		{
-			name: "removes zerofill and maps to unsigned",
+			name: "removes other tinyint size",
+			from: "create table t (id int primary key, i tinyint(2) default null)",
+			to:   "CREATE TABLE `t` (\n\t`id` int PRIMARY KEY,\n\t`i` tinyint\n)",
+		},
+		{
+			name: "removes int size",
+			from: "create table t (id int primary key, i int(1) default null)",
+			to:   "CREATE TABLE `t` (\n\t`id` int PRIMARY KEY,\n\t`i` int\n)",
+		},
+		{
+			name: "removes bigint size",
+			from: "create table t (id int primary key, i bigint(1) default null)",
+			to:   "CREATE TABLE `t` (\n\t`id` int PRIMARY KEY,\n\t`i` bigint\n)",
+		},
+		{
+			name: "keeps zerofill",
 			from: "create table t (id int primary key, i int zerofill default null)",
 			to:   "CREATE TABLE `t` (\n\t`id` int PRIMARY KEY,\n\t`i` int zerofill\n)",
 		},
 		{
 			name: "removes int sizes case insensitive",
 			from: "create table t (id int primary key, i INT(11) default null)",
-			to:   "CREATE TABLE `t` (\n\t`id` int PRIMARY KEY,\n\t`i` int(11)\n)",
+			to:   "CREATE TABLE `t` (\n\t`id` int PRIMARY KEY,\n\t`i` int\n)",
 		},
 		{
 			name: "removes matching charset",
diff --git a/go/vt/vtgate/engine/projection_test.go b/go/vt/vtgate/engine/projection_test.go
index 09bc9ff3e4..42bc7555ed 100644
--- a/go/vt/vtgate/engine/projection_test.go
+++ b/go/vt/vtgate/engine/projection_test.go
@@ -32,8 +32,8 @@ import (
 func TestMultiply(t *testing.T) {
 	expr := &sqlparser.BinaryExpr{
 		Operator: sqlparser.MultOp,
-		Left:     sqlparser.Offset(0),
-		Right:    sqlparser.Offset(1),
+		Left:     &sqlparser.Offset{V: 0},
+		Right:    &sqlparser.Offset{V: 1},
 	}
 	evalExpr, err := evalengine.Translate(expr, nil)
 	require.NoError(t, err)
diff --git a/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt b/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt
index f08c0f2e72..a8ca9c018d 100644
--- a/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/aggr_cases.txt
@@ -2112,7 +2112,35 @@ Gen4 plan same as above
     ]
   }
 }
-Gen4 error: [BUG] unable to plan distinct query as the column is not projected: a
+{
+  "QueryType": "SELECT",
+  "Original": "select distinct a, count(*) from user",
+  "Instructions": {
+    "OperatorType": "Aggregate",
+    "Variant": "Ordered",
+    "GroupBy": "0, 1",
+    "Inputs": [
+      {
+        "OperatorType": "Aggregate",
+        "Variant": "Scalar",
+        "Aggregates": "random(0) AS a, sum(1) AS count(*)",
+        "Inputs": [
+          {
+            "OperatorType": "Route",
+            "Variant": "Scatter",
+            "Keyspace": {
+              "Name": "user",
+              "Sharded": true
+            },
+            "FieldQuery": "select a, count(*) from `user` where 1 != 1",
+            "Query": "select a, count(*) from `user`",
+            "Table": "`user`"
+          }
+        ]
+      }
+    ]
+  }
+}
 
 # distinct and aggregate functions
 "select distinct a, count(*) from user group by a"
@@ -3243,7 +3271,46 @@ Gen4 plan same as above
     "Vindex": "name_user_map"
   }
 }
-Gen4 error: cannot push projections in ordered aggregates
+{
+  "QueryType": "SELECT",
+  "Original": "select 1 from user having count(id) = 10 and name = 'a'",
+  "Instructions": {
+    "OperatorType": "SimpleProjection",
+    "Columns": [
+      0
+    ],
+    "Inputs": [
+      {
+        "OperatorType": "Filter",
+        "Predicate": "OFFSET(1, 'count(id)') = 10",
+        "Inputs": [
+          {
+            "OperatorType": "Aggregate",
+            "Variant": "Scalar",
+            "Aggregates": "random(0) AS 1, sum(1) AS count(id)",
+            "Inputs": [
+              {
+                "OperatorType": "Route",
+                "Variant": "Equal",
+                "Keyspace": {
+                  "Name": "user",
+                  "Sharded": true
+                },
+                "FieldQuery": "select 1, count(id) from `user` where 1 != 1",
+                "Query": "select 1, count(id) from `user` where `name` = 'a'",
+                "Table": "`user`",
+                "Values": [
+                  "VARCHAR(\"a\")"
+                ],
+                "Vindex": "name_user_map"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
 
 # Aggregates and joins
 "select count(*) from user join user_extra"
@@ -3315,7 +3382,42 @@ Gen4 error: cannot push projections in ordered aggregates
     "Table": "`user`"
   }
 }
-Gen4 error: cannot push projections in ordered aggregates
+{
+  "QueryType": "SELECT",
+  "Original": "select 1 from user having count(id) = 10",
+  "Instructions": {
+    "OperatorType": "SimpleProjection",
+    "Columns": [
+      0
+    ],
+    "Inputs": [
+      {
+        "OperatorType": "Filter",
+        "Predicate": "OFFSET(1, 'count(id)') = 10",
+        "Inputs": [
+          {
+            "OperatorType": "Aggregate",
+            "Variant": "Scalar",
+            "Aggregates": "random(0) AS 1, sum(1) AS count(id)",
+            "Inputs": [
+              {
+                "OperatorType": "Route",
+                "Variant": "Scatter",
+                "Keyspace": {
+                  "Name": "user",
+                  "Sharded": true
+                },
+                "FieldQuery": "select 1, count(id) from `user` where 1 != 1",
+                "Query": "select 1, count(id) from `user`",
+                "Table": "`user`"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
 
 # Aggregate on join
 "select user.a, count(*) from user join user_extra group by user.a"
@@ -4056,7 +4158,7 @@ Gen4 error: aggregate functions take a single argument 'count(distinct user_id,
     "Inputs": [
       {
         "OperatorType": "Filter",
-        "Predicate": "[1] = 3",
+        "Predicate": "OFFSET(1, 'count(*)') = 3",
         "Inputs": [
           {
             "OperatorType": "Aggregate",
@@ -4100,7 +4202,7 @@ Gen4 error: aggregate functions take a single argument 'count(distinct user_id,
     "Inputs": [
       {
         "OperatorType": "Filter",
-        "Predicate": "[1] + [2] = 42",
+        "Predicate": "OFFSET(1, 'sum(foo)') + OFFSET(2, 'sum(bar)') = 42",
         "Inputs": [
           {
             "OperatorType": "Aggregate",
@@ -4144,7 +4246,7 @@ Gen4 error: aggregate functions take a single argument 'count(distinct user_id,
     "Inputs": [
       {
         "OperatorType": "Filter",
-        "Predicate": "fooSum + [2] = 42",
+        "Predicate": "fooSum + OFFSET(2, 'sum(bar)') = 42",
         "Inputs": [
           {
             "OperatorType": "Aggregate",
@@ -4171,3 +4273,498 @@ Gen4 error: aggregate functions take a single argument 'count(distinct user_id,
     ]
   }
 }
+
+# having should be able to add new aggregation expressions in having
+"select foo from user group by foo having count(*) = 3"
+"unsupported: filtering on results of aggregates"
+{
+  "QueryType": "SELECT",
+  "Original": "select foo from user group by foo having count(*) = 3",
+  "Instructions": {
+    "OperatorType": "SimpleProjection",
+    "Columns": [
+      0
+    ],
+    "Inputs": [
+      {
+        "OperatorType": "Filter",
+        "Predicate": "OFFSET(1, 'count(*)') = 3",
+        "Inputs": [
+          {
+            "OperatorType": "Aggregate",
+            "Variant": "Ordered",
+            "Aggregates": "sum(1) AS count(*)",
+            "GroupBy": "(0|2)",
+            "Inputs": [
+              {
+                "OperatorType": "Route",
+                "Variant": "Scatter",
+                "Keyspace": {
+                  "Name": "user",
+                  "Sharded": true
+                },
+                "FieldQuery": "select foo, count(*), weight_string(foo) from `user` where 1 != 1 group by foo, weight_string(foo)",
+                "OrderBy": "(0|2) ASC",
+                "Query": "select foo, count(*), weight_string(foo) from `user` group by foo, weight_string(foo) order by foo asc",
+                "Table": "`user`"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
+
+"select u.id from user u join user_extra ue on ue.id = u.id group by u.id having count(u.name) = 3"
+"unsupported: cross-shard query with aggregates"
+{
+  "QueryType": "SELECT",
+  "Original": "select u.id from user u join user_extra ue on ue.id = u.id group by u.id having count(u.name) = 3",
+  "Instructions": {
+    "OperatorType": "SimpleProjection",
+    "Columns": [
+      0
+    ],
+    "Inputs": [
+      {
+        "OperatorType": "Filter",
+        "Predicate": "OFFSET(1, 'count(u.`name`)') = 3",
+        "Inputs": [
+          {
+            "OperatorType": "Aggregate",
+            "Variant": "Ordered",
+            "Aggregates": "sum(1) AS count(u.`name`)",
+            "GroupBy": "(0|2)",
+            "Inputs": [
+              {
+                "OperatorType": "Projection",
+                "Expressions": [
+                  "[COLUMN 0] as id",
+                  "[COLUMN 2] * [COLUMN 3] as count(u.`name`)",
+                  "[COLUMN 1]"
+                ],
+                "Inputs": [
+                  {
+                    "OperatorType": "Sort",
+                    "Variant": "Memory",
+                    "OrderBy": "(0|1) ASC",
+                    "Inputs": [
+                      {
+                        "OperatorType": "Join",
+                        "Variant": "Join",
+                        "JoinColumnIndexes": "R:1,R:2,L:1,R:0",
+                        "JoinVars": {
+                          "ue_id": 0
+                        },
+                        "TableName": "user_extra_`user`",
+                        "Inputs": [
+                          {
+                            "OperatorType": "Route",
+                            "Variant": "Scatter",
+                            "Keyspace": {
+                              "Name": "user",
+                              "Sharded": true
+                            },
+                            "FieldQuery": "select ue.id, count(*), weight_string(ue.id) from user_extra as ue where 1 != 1 group by ue.id, weight_string(ue.id)",
+                            "Query": "select ue.id, count(*), weight_string(ue.id) from user_extra as ue group by ue.id, weight_string(ue.id)",
+                            "Table": "user_extra"
+                          },
+                          {
+                            "OperatorType": "Route",
+                            "Variant": "EqualUnique",
+                            "Keyspace": {
+                              "Name": "user",
+                              "Sharded": true
+                            },
+                            "FieldQuery": "select count(u.`name`), u.id, weight_string(u.id) from `user` as u where 1 != 1 group by u.id, weight_string(u.id)",
+                            "Query": "select count(u.`name`), u.id, weight_string(u.id) from `user` as u where u.id = :ue_id group by u.id, weight_string(u.id)",
+                            "Table": "`user`",
+                            "Values": [
+                              ":ue_id"
+                            ],
+                            "Vindex": "user_index"
+                          }
+                        ]
+                      }
+                    ]
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
+
+"select u.id from user u join user_extra ue on ue.user_id = u.id group by u.id having count(u.name) = 3"
+{
+  "QueryType": "SELECT",
+  "Original": "select u.id from user u join user_extra ue on ue.user_id = u.id group by u.id having count(u.name) = 3",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "Scatter",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select u.id from `user` as u join user_extra as ue on ue.user_id = u.id where 1 != 1 group by u.id",
+    "Query": "select u.id from `user` as u join user_extra as ue on ue.user_id = u.id group by u.id having count(u.`name`) = 3",
+    "Table": "`user`, user_extra"
+  }
+}
+{
+  "QueryType": "SELECT",
+  "Original": "select u.id from user u join user_extra ue on ue.user_id = u.id group by u.id having count(u.name) = 3",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "Scatter",
+    "Keyspace": {
+      "Name": "user",
+      "Sharded": true
+    },
+    "FieldQuery": "select u.id from `user` as u, user_extra as ue where 1 != 1 group by u.id",
+    "Query": "select u.id from `user` as u, user_extra as ue where ue.user_id = u.id group by u.id having count(u.`name`) = 3",
+    "Table": "`user`, user_extra"
+  }
+}
+
+# only extract the aggregation once, even if used twice
+"select u.id from user u join user_extra ue on ue.id = u.id group by u.id having count(*) < 3 and count(*) > 5"
+"unsupported: cross-shard query with aggregates"
+{
+  "QueryType": "SELECT",
+  "Original": "select u.id from user u join user_extra ue on ue.id = u.id group by u.id having count(*) \u003c 3 and count(*) \u003e 5",
+  "Instructions": {
+    "OperatorType": "SimpleProjection",
+    "Columns": [
+      0
+    ],
+    "Inputs": [
+      {
+        "OperatorType": "Filter",
+        "Predicate": "OFFSET(1, 'count(*)') \u003c 3 and OFFSET(1, 'count(*)') \u003e 5",
+        "Inputs": [
+          {
+            "OperatorType": "Aggregate",
+            "Variant": "Ordered",
+            "Aggregates": "sum(1) AS count(*)",
+            "GroupBy": "(0|2)",
+            "Inputs": [
+              {
+                "OperatorType": "Projection",
+                "Expressions": [
+                  "[COLUMN 0] as id",
+                  "[COLUMN 2] * [COLUMN 3] as count(*)",
+                  "[COLUMN 1]"
+                ],
+                "Inputs": [
+                  {
+                    "OperatorType": "Sort",
+                    "Variant": "Memory",
+                    "OrderBy": "(0|1) ASC",
+                    "Inputs": [
+                      {
+                        "OperatorType": "Join",
+                        "Variant": "Join",
+                        "JoinColumnIndexes": "R:1,R:2,L:1,R:0",
+                        "JoinVars": {
+                          "ue_id": 0
+                        },
+                        "TableName": "user_extra_`user`",
+                        "Inputs": [
+                          {
+                            "OperatorType": "Route",
+                            "Variant": "Scatter",
+                            "Keyspace": {
+                              "Name": "user",
+                              "Sharded": true
+                            },
+                            "FieldQuery": "select ue.id, count(*), weight_string(ue.id) from user_extra as ue where 1 != 1 group by ue.id, weight_string(ue.id)",
+                            "Query": "select ue.id, count(*), weight_string(ue.id) from user_extra as ue group by ue.id, weight_string(ue.id)",
+                            "Table": "user_extra"
+                          },
+                          {
+                            "OperatorType": "Route",
+                            "Variant": "EqualUnique",
+                            "Keyspace": {
+                              "Name": "user",
+                              "Sharded": true
+                            },
+                            "FieldQuery": "select count(*), u.id, weight_string(u.id) from `user` as u where 1 != 1 group by u.id, weight_string(u.id)",
+                            "Query": "select count(*), u.id, weight_string(u.id) from `user` as u where u.id = :ue_id group by u.id, weight_string(u.id)",
+                            "Table": "`user`",
+                            "Values": [
+                              ":ue_id"
+                            ],
+                            "Vindex": "user_index"
+                          }
+                        ]
+                      }
+                    ]
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
+
+"select (select 1 from user u having count(ue.col) > 10) from user_extra ue"
+"symbol ue.col not found in subquery"
+{
+  "QueryType": "SELECT",
+  "Original": "select (select 1 from user u having count(ue.col) \u003e 10) from user_extra ue",
+  "Instructions": {
+    "OperatorType": "Subquery",
+    "Variant": "PulloutValue",
+    "PulloutVars": [
+      "__sq1"
+    ],
+    "Inputs": [
+      {
+        "OperatorType": "SimpleProjection",
+        "Columns": [
+          0
+        ],
+        "Inputs": [
+          {
+            "OperatorType": "Filter",
+            "Predicate": "OFFSET(1, 'count(ue.col)') \u003e 10",
+            "Inputs": [
+              {
+                "OperatorType": "Aggregate",
+                "Variant": "Scalar",
+                "Aggregates": "random(0) AS 1, sum(1) AS count(ue.col)",
+                "Inputs": [
+                  {
+                    "OperatorType": "Route",
+                    "Variant": "Scatter",
+                    "Keyspace": {
+                      "Name": "user",
+                      "Sharded": true
+                    },
+                    "FieldQuery": "select 1, count(ue.col) from `user` as u where 1 != 1",
+                    "Query": "select 1, count(ue.col) from `user` as u",
+                    "Table": "`user`"
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "OperatorType": "Route",
+        "Variant": "Scatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select :__sq1 from user_extra as ue where 1 != 1",
+        "Query": "select :__sq1 from user_extra as ue",
+        "Table": "user_extra"
+      }
+    ]
+  }
+}
+
+# group by and ',' joins with condition
+"select user.col from user join user_extra on user_extra.col = user.col group by user.id"
+"unsupported: cross-shard query with aggregates"
+{
+  "QueryType": "SELECT",
+  "Original": "select user.col from user join user_extra on user_extra.col = user.col group by user.id",
+  "Instructions": {
+    "OperatorType": "Aggregate",
+    "Variant": "Ordered",
+    "Aggregates": "random(0) AS col",
+    "GroupBy": "(2|1)",
+    "ResultColumns": 1,
+    "Inputs": [
+      {
+        "OperatorType": "Projection",
+        "Expressions": [
+          "[COLUMN 2] * [COLUMN 3] as col",
+          "[COLUMN 1]",
+          "[COLUMN 0] as id"
+        ],
+        "Inputs": [
+          {
+            "OperatorType": "Join",
+            "Variant": "Join",
+            "JoinColumnIndexes": "L:1,L:2,L:0,R:0",
+            "JoinVars": {
+              "user_col": 0
+            },
+            "TableName": "`user`_user_extra",
+            "Inputs": [
+              {
+                "OperatorType": "Route",
+                "Variant": "Scatter",
+                "Keyspace": {
+                  "Name": "user",
+                  "Sharded": true
+                },
+                "FieldQuery": "select `user`.col, `user`.id, weight_string(`user`.id) from `user` where 1 != 1 group by `user`.col, `user`.id, weight_string(`user`.id)",
+                "OrderBy": "(1|2) ASC",
+                "Query": "select `user`.col, `user`.id, weight_string(`user`.id) from `user` group by `user`.col, `user`.id, weight_string(`user`.id) order by `user`.id asc",
+                "Table": "`user`"
+              },
+              {
+                "OperatorType": "Route",
+                "Variant": "Scatter",
+                "Keyspace": {
+                  "Name": "user",
+                  "Sharded": true
+                },
+                "FieldQuery": "select count(*) from user_extra where 1 != 1",
+                "Query": "select count(*) from user_extra where user_extra.col = :user_col",
+                "Table": "user_extra"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
+
+
+# scatter aggregate symtab lookup error
+"select id, b as id, count(*) from user order by id"
+"ambiguous symbol reference: id"
+{
+  "QueryType": "SELECT",
+  "Original": "select id, b as id, count(*) from user order by id",
+  "Instructions": {
+    "OperatorType": "Aggregate",
+    "Variant": "Scalar",
+    "Aggregates": "random(0) AS id, random(1) AS id, sum(2) AS count(*)",
+    "ResultColumns": 3,
+    "Inputs": [
+      {
+        "OperatorType": "Route",
+        "Variant": "Scatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select id, b as id, count(*), weight_string(b) from `user` where 1 != 1",
+        "OrderBy": "(1|3) ASC",
+        "Query": "select id, b as id, count(*), weight_string(b) from `user` order by id asc",
+        "Table": "`user`"
+      }
+    ]
+  }
+}
+
+# aggr and non-aggr without group by (with query does not give useful result out)
+"select id, count(*) from user"
+{
+  "QueryType": "SELECT",
+  "Original": "select id, count(*) from user",
+  "Instructions": {
+    "OperatorType": "Aggregate",
+    "Variant": "Scalar",
+    "Aggregates": "count(1)",
+    "Inputs": [
+      {
+        "OperatorType": "Route",
+        "Variant": "Scatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select id, count(*) from `user` where 1 != 1",
+        "Query": "select id, count(*) from `user`",
+        "Table": "`user`"
+      }
+    ]
+  }
+}
+{
+  "QueryType": "SELECT",
+  "Original": "select id, count(*) from user",
+  "Instructions": {
+    "OperatorType": "Aggregate",
+    "Variant": "Scalar",
+    "Aggregates": "random(0) AS id, sum(1) AS count(*)",
+    "Inputs": [
+      {
+        "OperatorType": "Route",
+        "Variant": "Scatter",
+        "Keyspace": {
+          "Name": "user",
+          "Sharded": true
+        },
+        "FieldQuery": "select id, count(*) from `user` where 1 != 1",
+        "Query": "select id, count(*) from `user`",
+        "Table": "`user`"
+      }
+    ]
+  }
+}
+
+# group by and ',' joins
+"select user.id from user, user_extra group by id"
+"unsupported: cross-shard query with aggregates"
+{
+  "QueryType": "SELECT",
+  "Original": "select user.id from user, user_extra group by id",
+  "Instructions": {
+    "OperatorType": "Aggregate",
+    "Variant": "Ordered",
+    "Aggregates": "random(0) AS id",
+    "GroupBy": "(2|1)",
+    "ResultColumns": 1,
+    "Inputs": [
+      {
+        "OperatorType": "Projection",
+        "Expressions": [
+          "[COLUMN 2] * [COLUMN 3] as id",
+          "[COLUMN 1]",
+          "[COLUMN 0] as id"
+        ],
+        "Inputs": [
+          {
+            "OperatorType": "Join",
+            "Variant": "Join",
+            "JoinColumnIndexes": "L:0,L:1,L:0,R:0",
+            "TableName": "`user`_user_extra",
+            "Inputs": [
+              {
+                "OperatorType": "Route",
+                "Variant": "Scatter",
+                "Keyspace": {
+                  "Name": "user",
+                  "Sharded": true
+                },
+                "FieldQuery": "select `user`.id, weight_string(id) from `user` where 1 != 1 group by id, weight_string(id)",
+                "OrderBy": "(0|1) ASC",
+                "Query": "select `user`.id, weight_string(id) from `user` group by id, weight_string(id) order by id asc",
+                "Table": "`user`"
+              },
+              {
+                "OperatorType": "Route",
+                "Variant": "Scatter",
+                "Keyspace": {
+                  "Name": "user",
+                  "Sharded": true
+                },
+                "FieldQuery": "select count(*) from user_extra where 1 != 1",
+                "Query": "select count(*) from user_extra",
+                "Table": "user_extra"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/go/vt/vtgate/planbuilder/testdata/systemtables_cases.txt b/go/vt/vtgate/planbuilder/testdata/systemtables_cases.txt
index 86029491aa..465354d28f 100644
--- a/go/vt/vtgate/planbuilder/testdata/systemtables_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/systemtables_cases.txt
@@ -1431,3 +1431,23 @@ Gen4 plan same as above
     "Table": "information_schema.key_column_usage"
   }
 }
+
+# table_schema OR predicate
+# It is unsupported because we do not route queries to multiple keyspaces right now
+"SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'ks' OR TABLE_SCHEMA = 'main'"
+{
+  "QueryType": "SELECT",
+  "Original": "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'ks' OR TABLE_SCHEMA = 'main'",
+  "Instructions": {
+    "OperatorType": "Route",
+    "Variant": "DBA",
+    "Keyspace": {
+      "Name": "main",
+      "Sharded": false
+    },
+    "FieldQuery": "select * from INFORMATION_SCHEMA.`TABLES` where 1 != 1",
+    "Query": "select * from INFORMATION_SCHEMA.`TABLES` where TABLE_SCHEMA = 'ks' or TABLE_SCHEMA = 'main'",
+    "Table": "INFORMATION_SCHEMA.`TABLES`"
+  }
+}
+Gen4 plan same as above
diff --git a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.txt b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.txt
index 7e84405e17..4124d264a2 100644
--- a/go/vt/vtgate/planbuilder/testdata/unsupported_cases.txt
+++ b/go/vt/vtgate/planbuilder/testdata/unsupported_cases.txt
@@ -68,35 +68,6 @@ Gen4 plan same as above
 "unsupported: '*' expression in cross-shard query"
 Gen4 error: cannot use column offsets in group statement when using `*`
 
-# group by must reference select list
-"select a from user group by b"
-"unsupported: in scatter query: group by column must reference column in SELECT list"
-{
-  "QueryType": "SELECT",
-  "Original": "select a from user group by b",
-  "Instructions": {
-    "OperatorType": "Aggregate",
-    "Variant": "Ordered",
-    "Aggregates": "random(0) AS a",
-    "GroupBy": "(1|2)",
-    "ResultColumns": 1,
-    "Inputs": [
-      {
-        "OperatorType": "Route",
-        "Variant": "Scatter",
-        "Keyspace": {
-          "Name": "user",
-          "Sharded": true
-        },
-        "FieldQuery": "select a, b, weight_string(b) from `user` where 1 != 1 group by b, weight_string(b)",
-        "OrderBy": "(1|2) ASC",
-        "Query": "select a, b, weight_string(b) from `user` group by b, weight_string(b) order by b asc",
-        "Table": "`user`"
-      }
-    ]
-  }
-}
-
 # complex group by expression
 "select a from user group by a+1"
 "unsupported: in scatter query: only simple references allowed"
@@ -112,34 +83,6 @@ Gen4 plan same as above
 "unsupported: only one expression allowed inside aggregates: count(a, b)"
 Gen4 error: aggregate functions take a single argument 'count(a, b)'
 
-# scatter aggregate symtab lookup error
-"select id, b as id, count(*) from user order by id"
-"ambiguous symbol reference: id"
-{
-  "QueryType": "SELECT",
-  "Original": "select id, b as id, count(*) from user order by id",
-  "Instructions": {
-    "OperatorType": "Aggregate",
-    "Variant": "Scalar",
-    "Aggregates": "random(0) AS id, random(0) AS id, sum(2) AS count(*)",
-    "ResultColumns": 3,
-    "Inputs": [
-      {
-        "OperatorType": "Route",
-        "Variant": "Scatter",
-        "Keyspace": {
-          "Name": "user",
-          "Sharded": true
-        },
-        "FieldQuery": "select id, b as id, count(*), weight_string(b) from `user` where 1 != 1",
-        "OrderBy": "(1|3) ASC",
-        "Query": "select id, b as id, count(*), weight_string(b) from `user` order by id asc",
-        "Table": "`user`"
-      }
-    ]
-  }
-}
-
 # scatter aggregate complex order by
 "select id from user group by id order by id+1"
 "unsupported: in scatter query: complex order by expression: id + 1"
@@ -155,125 +98,6 @@ Gen4 plan same as above
 "unsupported: cross-shard query with aggregates"
 Gen4 error: unsupported: in scatter query: complex aggregate expression
 
-# group by and ',' joins
-"select user.id from user, user_extra group by id"
-"unsupported: cross-shard query with aggregates"
-{
-  "QueryType": "SELECT",
-  "Original": "select user.id from user, user_extra group by id",
-  "Instructions": {
-    "OperatorType": "Aggregate",
-    "Variant": "Ordered",
-    "Aggregates": "random(0) AS id",
-    "GroupBy": "(2|1)",
-    "ResultColumns": 1,
-    "Inputs": [
-      {
-        "OperatorType": "Projection",
-        "Expressions": [
-          "[COLUMN 2] * [COLUMN 3] as id",
-          "[COLUMN 1]",
-          "[COLUMN 0] as id"
-        ],
-        "Inputs": [
-          {
-            "OperatorType": "Join",
-            "Variant": "Join",
-            "JoinColumnIndexes": "L:0,L:1,L:0,R:0",
-            "TableName": "`user`_user_extra",
-            "Inputs": [
-              {
-                "OperatorType": "Route",
-                "Variant": "Scatter",
-                "Keyspace": {
-                  "Name": "user",
-                  "Sharded": true
-                },
-                "FieldQuery": "select `user`.id, weight_string(id) from `user` where 1 != 1 group by id, weight_string(id)",
-                "OrderBy": "(0|1) ASC",
-                "Query": "select `user`.id, weight_string(id) from `user` group by id, weight_string(id) order by id asc",
-                "Table": "`user`"
-              },
-              {
-                "OperatorType": "Route",
-                "Variant": "Scatter",
-                "Keyspace": {
-                  "Name": "user",
-                  "Sharded": true
-                },
-                "FieldQuery": "select count(*) from user_extra where 1 != 1",
-                "Query": "select count(*) from user_extra",
-                "Table": "user_extra"
-              }
-            ]
-          }
-        ]
-      }
-    ]
-  }
-}
-
-# group by and ',' joins with condition
-"select user.col from user join user_extra on user_extra.col = user.col group by user.id"
-"unsupported: cross-shard query with aggregates"
-{
-  "QueryType": "SELECT",
-  "Original": "select user.col from user join user_extra on user_extra.col = user.col group by user.id",
-  "Instructions": {
-    "OperatorType": "Aggregate",
-    "Variant": "Ordered",
-    "Aggregates": "random(0) AS col",
-    "GroupBy": "(2|1)",
-    "ResultColumns": 1,
-    "Inputs": [
-      {
-        "OperatorType": "Projection",
-        "Expressions": [
-          "[COLUMN 2] * [COLUMN 3] as col",
-          "[COLUMN 1]",
-          "[COLUMN 0] as id"
-        ],
-        "Inputs": [
-          {
-            "OperatorType": "Join",
-            "Variant": "Join",
-            "JoinColumnIndexes": "L:2,L:3,L:1,R:0",
-            "JoinVars": {
-              "user_col": 0
-            },
-            "TableName": "`user`_user_extra",
-            "Inputs": [
-              {
-                "OperatorType": "Route",
-                "Variant": "Scatter",
-                "Keyspace": {
-                  "Name": "user",
-                  "Sharded": true
-                },
-                "FieldQuery": "select `user`.col, `user`.col, `user`.id, weight_string(`user`.id) from `user` where 1 != 1 group by `user`.col, `user`.id, weight_string(`user`.id)",
-                "OrderBy": "(2|3) ASC",
-                "Query": "select `user`.col, `user`.col, `user`.id, weight_string(`user`.id) from `user` group by `user`.col, `user`.id, weight_string(`user`.id) order by `user`.id asc",
-                "Table": "`user`"
-              },
-              {
-                "OperatorType": "Route",
-                "Variant": "Scatter",
-                "Keyspace": {
-                  "Name": "user",
-                  "Sharded": true
-                },
-                "FieldQuery": "select count(*) from user_extra where 1 != 1",
-                "Query": "select count(*) from user_extra where user_extra.col = :user_col",
-                "Table": "user_extra"
-              }
-            ]
-          }
-        ]
-      }
-    ]
-  }
-}
-
 # subqueries not supported in group by
 "select id from user group by id, (select id from user_extra)"
 "unsupported: subqueries disallowed in GROUP or ORDER BY"
@@ -542,73 +366,6 @@ Gen4 plan same as above
 "Select query does not belong to the same keyspace as the view statement"
 Gen4 plan same as above
 
-# table_schema OR predicate
-# It is unsupported because we do not route queries to multiple keyspaces right now
-"SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'ks' OR TABLE_SCHEMA = 'main'"
-{
-  "QueryType": "SELECT",
-  "Original": "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'ks' OR TABLE_SCHEMA = 'main'",
-  "Instructions": {
-    "OperatorType": "Route",
-    "Variant": "DBA",
-    "Keyspace": {
-      "Name": "main",
-      "Sharded": false
-    },
-    "FieldQuery": "select * from INFORMATION_SCHEMA.`TABLES` where 1 != 1",
-    "Query": "select * from INFORMATION_SCHEMA.`TABLES` where TABLE_SCHEMA = 'ks' or TABLE_SCHEMA = 'main'",
-    "Table": "INFORMATION_SCHEMA.`TABLES`"
-  }
-}
-Gen4 plan same as above
-
-# aggr and non-aggr without group by (with query does not give useful result out)
-"select id, count(*) from user"
-{
-  "QueryType": "SELECT",
-  "Original": "select id, count(*) from user",
-  "Instructions": {
-    "OperatorType": "Aggregate",
-    "Variant": "Scalar",
-    "Aggregates": "count(1)",
-    "Inputs": [
-      {
-        "OperatorType": "Route",
-        "Variant": "Scatter",
-        "Keyspace": {
-          "Name": "user",
-          "Sharded": true
-        },
-        "FieldQuery": "select id, count(*) from `user` where 1 != 1",
-        "Query": "select id, count(*) from `user`",
-        "Table": "`user`"
-      }
-    ]
-  }
-}
-{
-  "QueryType": "SELECT",
-  "Original": "select id, count(*) from user",
-  "Instructions": {
-    "OperatorType": "Aggregate",
-    "Variant": "Scalar",
-    "Aggregates": "random(0) AS id, sum(1) AS count(*)",
-    "Inputs": [
-      {
-        "OperatorType": "Route",
-        "Variant": "Scatter",
-        "Keyspace": {
-          "Name": "user",
-          "Sharded": true
-        },
-        "FieldQuery": "select id, count(*) from `user` where 1 != 1",
-        "Query": "select id, count(*) from `user`",
-        "Table": "`user`"
-      }
-    ]
-  }
-}
-
 # avg function on scatter query
 "select avg(id) from user"
 "unsupported: in scatter query: complex aggregate expression"
@@ -619,15 +376,6 @@ Gen4 error: unsupported: in scatter query: aggregation function 'avg'
 "generating order by clause: ambiguous symbol reference: a"
 Gen4 plan same as above
 
-# TODO this should be planned without using OA and MS
-"select u.id from user u join user_extra ue on ue.id = u.id group by u.id having count(u.name) = 3"
-"unsupported: cross-shard query with aggregates"
-Gen4 error: cannot push projections in ordered aggregates
-
-"select (select 1 from user u having count(ue.col) > 10) from user_extra ue"
-"symbol ue.col not found in subquery"
-Gen4 error: cannot push projections in ordered aggregates
-
 # subquery of information_schema with itself and star expression in outer select
 "select a.*, u.id from information_schema.a a, user u where a.id in (select * from information_schema.b)"
 "unsupported: '*' expression in cross-shard query"
diff --git a/web/vtadmin/src/components/routes/keyspace/Advanced.test.tsx b/web/vtadmin/src/components/routes/keyspace/Advanced.test.tsx
new file mode 100644
index 0000000000..1d40c2d511
--- /dev/null
+++ b/web/vtadmin/src/components/routes/keyspace/Advanced.test.tsx
@@ -0,0 +1,109 @@
+/**
+ * Copyright 2022 The Vitess Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { rest } from 'msw';
+import { setupServer } from 'msw/node';
+import { render, screen, waitFor, within } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { QueryClient, QueryClientProvider } from 'react-query';
+import { Advanced } from './Advanced';
+import { vtadmin } from '../../../proto/vtadmin';
+
+const ORIGINAL_PROCESS_ENV = process.env;
+const TEST_PROCESS_ENV = {
+    ...process.env,
+    REACT_APP_VTADMIN_API_ADDRESS: '',
+};
+
+describe('Advanced keyspace actions', () => {
+    const keyspace: vtadmin.IKeyspace = {
+        cluster: { id: 'some-cluster', name: 'some-cluster' },
+        keyspace: {
+            name: 'some-keyspace',
+        },
+    };
+
+    const server = setupServer(
+        rest.get('/api/keyspace/:clusterID/:keyspace', (req, res, ctx) => {
+            return res(ctx.json({ ok: true, result: keyspace }));
+        }),
+        rest.put('/api/schemas/reload', (req, res, ctx) => {
+            return res(ctx.json({ ok: true }));
+        })
+    );
+
+    const queryClient = new QueryClient({
+        defaultOptions: { queries: { retry: false } },
+    });
+
+    beforeAll(() => {
+        process.env = { ...TEST_PROCESS_ENV } as NodeJS.ProcessEnv;
+        server.listen();
+    });
+
+    beforeEach(() => {
+        process.env = { ...TEST_PROCESS_ENV } as NodeJS.ProcessEnv;
+        jest.clearAllMocks();
+    });
+
+    afterAll(() => {
+        process.env = { ...ORIGINAL_PROCESS_ENV };
+        server.close();
+    });
+
+    describe('Reload Schema', () => {
+        it('reloads the schema', async () => {
+            jest.spyOn(global, 'fetch');
+
+            render(
+                <QueryClientProvider client={queryClient}>
+                    <Advanced clusterID="some-cluster" name="some-keyspace" />
+                </QueryClientProvider>
+            );
+
+            expect(screen.getByText('Loading...')).not.toBeNull();
+
+            await waitFor(async () => {
+                expect(screen.queryByText('Loading...')).toBeNull();
+            });
+
+            expect(global.fetch).toHaveBeenCalledTimes(1);
+            expect(global.fetch).toHaveBeenCalledWith(`/api/keyspace/some-cluster/some-keyspace`, {
+                credentials: undefined,
+            });
+
+            jest.clearAllMocks();
+
+            const container = screen.getByTitle('Reload Schema');
+            const button = within(container).getByRole('button');
+            expect(button).not.toHaveAttribute('disabled');
+
+            const user = userEvent.setup();
+            await user.click(button);
+            await waitFor(() => {
+                expect(global.fetch).toHaveBeenCalledTimes(1);
+            });
+
+            expect(global.fetch).toHaveBeenCalledWith(
+                `/api/schemas/reload?cluster=some-cluster&keyspace=some-keyspace`,
+                {
+                    credentials: undefined,
+                    method: 'put',
+                }
+            );
+        });
+    });
+});