diff --git a/admin/src/features/intercept/components/EditRequest.tsx b/admin/src/features/intercept/components/EditRequest.tsx index b9e3140..a15086e 100644 --- a/admin/src/features/intercept/components/EditRequest.tsx +++ b/admin/src/features/intercept/components/EditRequest.tsx @@ -1,3 +1,4 @@ +import CancelIcon from "@mui/icons-material/Cancel"; import SendIcon from "@mui/icons-material/Send"; import { Alert, Box, Button, CircularProgress, Typography } from "@mui/material"; import { useRouter } from "next/router"; @@ -12,6 +13,7 @@ import UrlBar, { HttpMethod, HttpProto, httpProtoMap } from "lib/components/UrlB import { HttpProtocol, HttpRequest, + useCancelRequestMutation, useGetInterceptedRequestQuery, useModifyRequestMutation, } from "lib/graphql/generated"; @@ -27,7 +29,6 @@ function EditRequest(): JSX.Element { // If there's no request selected and there are pending reqs, navigate to // the first one in the list. This helps you quickly review/handle reqs // without having to manually select the next one in the requests table. - console.log(router.isReady, router.query.id, interceptedRequests?.length); if (router.isReady && !router.query.id && interceptedRequests?.length) { const req = interceptedRequests[0]; router.replace(`/proxy/intercept?id=${req.id}`); @@ -104,6 +105,16 @@ function EditRequest(): JSX.Element { const interceptedReq = reqId ? getReqResult?.data?.interceptedRequest : undefined; const [modifyRequest, modifyResult] = useModifyRequestMutation(); + const [cancelRequest, cancelResult] = useCancelRequestMutation(); + + const onActionCompleted = () => { + setURL(""); + setMethod(HttpMethod.Get); + setBody(""); + setQueryParams([]); + setHeaders([]); + router.replace(`/proxy/intercept`); + }; const handleFormSubmit: React.FormEventHandler = (e) => { e.preventDefault(); @@ -132,15 +143,29 @@ function EditRequest(): JSX.Element { }, }); }, - onCompleted: () => { - setURL(""); - setMethod(HttpMethod.Get); - setBody(""); - setQueryParams([]); - setHeaders([]); - console.log("done!"); - router.replace(`/proxy/intercept`); + onCompleted: onActionCompleted, + }); + }; + + const handleCancelClick = () => { + if (!interceptedReq) { + return; + } + + cancelRequest({ + variables: { + id: interceptedReq.id, }, + update(cache) { + cache.modify({ + fields: { + interceptedRequests(existing: HttpRequest[], { readField }) { + return existing.filter((ref) => interceptedReq.id !== readField("id", ref)); + }, + }, + }); + }, + onCompleted: onActionCompleted, }); }; @@ -161,17 +186,32 @@ function EditRequest(): JSX.Element { variant="contained" disableElevation type="submit" - disabled={!interceptedReq || modifyResult.loading} + disabled={!interceptedReq || modifyResult.loading || cancelResult.loading} startIcon={modifyResult.loading ? : } > Send + {modifyResult.error && ( {modifyResult.error.message} )} + {cancelResult.error && ( + + {cancelResult.error.message} + + )} diff --git a/admin/src/features/intercept/graphql/cancelRequest.graphql b/admin/src/features/intercept/graphql/cancelRequest.graphql new file mode 100644 index 0000000..3658c52 --- /dev/null +++ b/admin/src/features/intercept/graphql/cancelRequest.graphql @@ -0,0 +1,5 @@ +mutation CancelRequest($id: ID!) { + cancelRequest(id: $id) { + success + } +} diff --git a/admin/src/lib/graphql/generated.tsx b/admin/src/lib/graphql/generated.tsx index 14ff524..79d9461 100644 --- a/admin/src/lib/graphql/generated.tsx +++ b/admin/src/lib/graphql/generated.tsx @@ -18,6 +18,11 @@ export type Scalars = { URL: any; }; +export type CancelRequestResult = { + __typename?: 'CancelRequestResult'; + success: Scalars['Boolean']; +}; + export type ClearHttpRequestLogResult = { __typename?: 'ClearHTTPRequestLogResult'; success: Scalars['Boolean']; @@ -127,6 +132,7 @@ export type ModifyRequestResult = { export type Mutation = { __typename?: 'Mutation'; + cancelRequest: CancelRequestResult; clearHTTPRequestLog: ClearHttpRequestLogResult; closeProject: CloseProjectResult; createOrUpdateSenderRequest: SenderRequest; @@ -143,6 +149,11 @@ export type Mutation = { }; +export type MutationCancelRequestArgs = { + id: Scalars['ID']; +}; + + export type MutationCreateOrUpdateSenderRequestArgs = { request: SenderRequestInput; }; @@ -285,6 +296,13 @@ export type SenderRequestInput = { url: Scalars['URL']; }; +export type CancelRequestMutationVariables = Exact<{ + id: Scalars['ID']; +}>; + + +export type CancelRequestMutation = { __typename?: 'Mutation', cancelRequest: { __typename?: 'CancelRequestResult', success: boolean } }; + export type GetInterceptedRequestQueryVariables = Exact<{ id: Scalars['ID']; }>; @@ -410,6 +428,39 @@ export type GetInterceptedRequestsQueryVariables = Exact<{ [key: string]: never; export type GetInterceptedRequestsQuery = { __typename?: 'Query', interceptedRequests: Array<{ __typename?: 'HttpRequest', id: string, url: any, method: HttpMethod }> }; +export const CancelRequestDocument = gql` + mutation CancelRequest($id: ID!) { + cancelRequest(id: $id) { + success + } +} + `; +export type CancelRequestMutationFn = Apollo.MutationFunction; + +/** + * __useCancelRequestMutation__ + * + * To run a mutation, you first call `useCancelRequestMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCancelRequestMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [cancelRequestMutation, { data, loading, error }] = useCancelRequestMutation({ + * variables: { + * id: // value for 'id' + * }, + * }); + */ +export function useCancelRequestMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CancelRequestDocument, options); + } +export type CancelRequestMutationHookResult = ReturnType; +export type CancelRequestMutationResult = Apollo.MutationResult; +export type CancelRequestMutationOptions = Apollo.BaseMutationOptions; export const GetInterceptedRequestDocument = gql` query GetInterceptedRequest($id: ID!) { interceptedRequest(id: $id) { diff --git a/pkg/api/generated.go b/pkg/api/generated.go index b671d3d..5ad3515 100644 --- a/pkg/api/generated.go +++ b/pkg/api/generated.go @@ -45,6 +45,10 @@ type DirectiveRoot struct { } type ComplexityRoot struct { + CancelRequestResult struct { + Success func(childComplexity int) int + } + ClearHTTPRequestLogResult struct { Success func(childComplexity int) int } @@ -105,6 +109,7 @@ type ComplexityRoot struct { } Mutation struct { + CancelRequest func(childComplexity int, id ulid.ULID) int ClearHTTPRequestLog func(childComplexity int) int CloseProject func(childComplexity int) int CreateOrUpdateSenderRequest func(childComplexity int, request SenderRequestInput) int @@ -182,6 +187,7 @@ type MutationResolver interface { SendRequest(ctx context.Context, id ulid.ULID) (*SenderRequest, error) DeleteSenderRequests(ctx context.Context) (*DeleteSenderRequestsResult, error) ModifyRequest(ctx context.Context, request ModifyRequestInput) (*ModifyRequestResult, error) + CancelRequest(ctx context.Context, id ulid.ULID) (*CancelRequestResult, error) } type QueryResolver interface { HTTPRequestLog(ctx context.Context, id ulid.ULID) (*HTTPRequestLog, error) @@ -211,6 +217,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in _ = ec switch typeName + "." + field { + case "CancelRequestResult.success": + if e.complexity.CancelRequestResult.Success == nil { + break + } + + return e.complexity.CancelRequestResult.Success(childComplexity), true + case "ClearHTTPRequestLogResult.success": if e.complexity.ClearHTTPRequestLogResult.Success == nil { break @@ -414,6 +427,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ModifyRequestResult.Success(childComplexity), true + case "Mutation.cancelRequest": + if e.complexity.Mutation.CancelRequest == nil { + break + } + + args, err := ec.field_Mutation_cancelRequest_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.CancelRequest(childComplexity, args["id"].(ulid.ULID)), true + case "Mutation.clearHTTPRequestLog": if e.complexity.Mutation.ClearHTTPRequestLog == nil { break @@ -977,6 +1002,10 @@ type ModifyRequestResult { success: Boolean! } +type CancelRequestResult { + success: Boolean! +} + type Query { httpRequestLog(id: ID!): HttpRequestLog httpRequestLogs: [HttpRequestLog!]! @@ -1006,6 +1035,7 @@ type Mutation { sendRequest(id: ID!): SenderRequest! deleteSenderRequests: DeleteSenderRequestsResult! modifyRequest(request: ModifyRequestInput!): ModifyRequestResult! + cancelRequest(id: ID!): CancelRequestResult! } enum HttpMethod { @@ -1037,6 +1067,21 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) // region ***************************** args.gotpl ***************************** +func (ec *executionContext) field_Mutation_cancelRequest_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 ulid.ULID + if tmp, ok := rawArgs["id"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + arg0, err = ec.unmarshalNID2githubᚗcomᚋoklogᚋulidᚐULID(ctx, tmp) + if err != nil { + return nil, err + } + } + args["id"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_createOrUpdateSenderRequest_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1285,6 +1330,41 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg // region **************************** field.gotpl ***************************** +func (ec *executionContext) _CancelRequestResult_success(ctx context.Context, field graphql.CollectedField, obj *CancelRequestResult) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "CancelRequestResult", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Success, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _ClearHTTPRequestLogResult_success(ctx context.Context, field graphql.CollectedField, obj *ClearHTTPRequestLogResult) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2798,6 +2878,48 @@ func (ec *executionContext) _Mutation_modifyRequest(ctx context.Context, field g return ec.marshalNModifyRequestResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐModifyRequestResult(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_cancelRequest(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_cancelRequest_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CancelRequest(rctx, args["id"].(ulid.ULID)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*CancelRequestResult) + fc.Result = res + return ec.marshalNCancelRequestResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCancelRequestResult(ctx, field.Selections, res) +} + func (ec *executionContext) _Project_id(ctx context.Context, field graphql.CollectedField, obj *Project) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5279,6 +5401,33 @@ func (ec *executionContext) unmarshalInputSenderRequestInput(ctx context.Context // region **************************** object.gotpl **************************** +var cancelRequestResultImplementors = []string{"CancelRequestResult"} + +func (ec *executionContext) _CancelRequestResult(ctx context.Context, sel ast.SelectionSet, obj *CancelRequestResult) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, cancelRequestResultImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("CancelRequestResult") + case "success": + out.Values[i] = ec._CancelRequestResult_success(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var clearHTTPRequestLogResultImplementors = []string{"ClearHTTPRequestLogResult"} func (ec *executionContext) _ClearHTTPRequestLogResult(ctx context.Context, sel ast.SelectionSet, obj *ClearHTTPRequestLogResult) graphql.Marshaler { @@ -5697,6 +5846,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "cancelRequest": + out.Values[i] = ec._Mutation_cancelRequest(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -6303,6 +6457,20 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) marshalNCancelRequestResult2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCancelRequestResult(ctx context.Context, sel ast.SelectionSet, v CancelRequestResult) graphql.Marshaler { + return ec._CancelRequestResult(ctx, sel, &v) +} + +func (ec *executionContext) marshalNCancelRequestResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCancelRequestResult(ctx context.Context, sel ast.SelectionSet, v *CancelRequestResult) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._CancelRequestResult(ctx, sel, v) +} + func (ec *executionContext) marshalNClearHTTPRequestLogResult2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐClearHTTPRequestLogResult(ctx context.Context, sel ast.SelectionSet, v ClearHTTPRequestLogResult) graphql.Marshaler { return ec._ClearHTTPRequestLogResult(ctx, sel, &v) } diff --git a/pkg/api/models_gen.go b/pkg/api/models_gen.go index 6cc66b5..09b8749 100644 --- a/pkg/api/models_gen.go +++ b/pkg/api/models_gen.go @@ -12,6 +12,10 @@ import ( "github.com/oklog/ulid" ) +type CancelRequestResult struct { + Success bool `json:"success"` +} + type ClearHTTPRequestLogResult struct { Success bool `json:"success"` } diff --git a/pkg/api/resolvers.go b/pkg/api/resolvers.go index 81cb17f..588c188 100644 --- a/pkg/api/resolvers.go +++ b/pkg/api/resolvers.go @@ -581,6 +581,15 @@ func (r *mutationResolver) ModifyRequest(ctx context.Context, input ModifyReques return &ModifyRequestResult{Success: true}, nil } +func (r *mutationResolver) CancelRequest(ctx context.Context, id ulid.ULID) (*CancelRequestResult, error) { + err := r.InterceptService.CancelRequest(id) + if err != nil { + return nil, fmt.Errorf("could not cancel http request: %w", err) + } + + return &CancelRequestResult{Success: true}, nil +} + func parseSenderRequest(req sender.Request) (SenderRequest, error) { method := HTTPMethod(req.Method) if method != "" && !method.IsValid() { diff --git a/pkg/api/schema.graphql b/pkg/api/schema.graphql index 0f001ca..9c4aa48 100644 --- a/pkg/api/schema.graphql +++ b/pkg/api/schema.graphql @@ -138,6 +138,10 @@ type ModifyRequestResult { success: Boolean! } +type CancelRequestResult { + success: Boolean! +} + type Query { httpRequestLog(id: ID!): HttpRequestLog httpRequestLogs: [HttpRequestLog!]! @@ -167,6 +171,7 @@ type Mutation { sendRequest(id: ID!): SenderRequest! deleteSenderRequests: DeleteSenderRequestsResult! modifyRequest(request: ModifyRequestInput!): ModifyRequestResult! + cancelRequest(id: ID!): CancelRequestResult! } enum HttpMethod { diff --git a/pkg/proxy/intercept/intercept.go b/pkg/proxy/intercept/intercept.go index 7fd50fa..201b3f5 100644 --- a/pkg/proxy/intercept/intercept.go +++ b/pkg/proxy/intercept/intercept.go @@ -144,6 +144,11 @@ func (svc *Service) ModifyRequest(reqID ulid.ULID, modReq *http.Request) error { } } +// CancelRequest ensures an intercepted request is dropped. +func (svc *Service) CancelRequest(reqID ulid.ULID) error { + return svc.ModifyRequest(reqID, nil) +} + func (svc *Service) ClearRequests() { svc.mu.Lock() defer svc.mu.Unlock()