Skip to content

Commit

Permalink
feat: add cypress trigger type (#3439)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Dias <danielbpdias@gmail.com>
  • Loading branch information
jorgeepc and danielbdias committed Dec 14, 2023
1 parent 82d44e0 commit ddb68b4
Show file tree
Hide file tree
Showing 31 changed files with 189 additions and 66 deletions.
4 changes: 4 additions & 0 deletions agent/workers/trigger/triggerer.go
Expand Up @@ -55,6 +55,10 @@ func (r *Registry) Get(triggererType trigger.TriggerType) (Triggerer, error) {
r.Lock()
defer r.Unlock()

if triggererType.IsTraceIDBased() {
triggererType = trigger.TriggerTypeTraceID
}

t, found := r.reg[triggererType]
if !found {
return nil, fmt.Errorf(`cannot get trigger type "%s": %w`, triggererType, ErrTriggererTypeNotRegistered)
Expand Down
4 changes: 2 additions & 2 deletions api/triggers.yaml
Expand Up @@ -6,7 +6,7 @@ components:
properties:
type:
type: string
enum: ["http", "grpc", "traceid", "kafka"]
enum: ["http", "grpc", "traceid", "kafka", "cypress"]
httpRequest:
$ref: "./http.yaml#/components/schemas/HTTPRequest"
grpc:
Expand All @@ -21,7 +21,7 @@ components:
properties:
type:
type: string
enum: ["http", "grpc", "traceid", "kafka"]
enum: ["http", "grpc", "traceid", "kafka", "cypress"]
triggerResult:
type: object
properties:
Expand Down
8 changes: 7 additions & 1 deletion server/executor/trigger/traceid.go
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/kubeshop/tracetest/server/test/trigger"
)

const TRACEID_EXPRESSION = "${env:TRACE_ID}"

func TRACEID() Triggerer {
return &traceidTriggerer{}
}
Expand All @@ -17,7 +19,7 @@ type traceidTriggerer struct{}
func (t *traceidTriggerer) Trigger(ctx context.Context, test test.Test, opts *TriggerOptions) (Response, error) {
response := Response{
Result: trigger.TriggerResult{
Type: t.Type(),
Type: test.Trigger.Type,
TraceID: &trigger.TraceIDResponse{ID: test.Trigger.TraceID.ID},
},
}
Expand All @@ -30,6 +32,10 @@ func (t *traceidTriggerer) Type() trigger.TriggerType {
}

func (t *traceidTriggerer) Resolve(ctx context.Context, test test.Test, opts *ResolveOptions) (test.Test, error) {
if test.Trigger.Type.IsFrontendE2EBased() {
test.Trigger.TraceID = &trigger.TraceIDRequest{ID: TRACEID_EXPRESSION}
}

traceid := test.Trigger.TraceID
if traceid == nil {
return test, fmt.Errorf("no settings provided for TRACEID triggerer")
Expand Down
4 changes: 4 additions & 0 deletions server/executor/trigger/triggerer.go
Expand Up @@ -61,6 +61,10 @@ func (r *Registry) Get(triggererType trigger.TriggerType) (Triggerer, error) {
r.Lock()
defer r.Unlock()

if triggererType.IsTraceIDBased() {
triggererType = trigger.TriggerTypeTraceID
}

t, found := r.reg[triggererType]
if !found {
return nil, fmt.Errorf(`cannot get trigger type "%s": %w`, triggererType, ErrTriggererTypeNotRegistered)
Expand Down
5 changes: 2 additions & 3 deletions server/executor/trigger_resolver_worker.go
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/kubeshop/tracetest/server/model/events"
"github.com/kubeshop/tracetest/server/pkg/pipeline"
"github.com/kubeshop/tracetest/server/test"
"github.com/kubeshop/tracetest/server/test/trigger"
"github.com/kubeshop/tracetest/server/tracedb"
"go.opentelemetry.io/otel/trace"
)
Expand Down Expand Up @@ -132,8 +131,8 @@ func (r triggerResolverWorker) ProcessItem(ctx context.Context, job Job) {
r.handleError(job.Run, err)
}

if job.Test.Trigger.Type == trigger.TriggerTypeTraceID {
traceIDFromParam, err := trace.TraceIDFromHex(job.Test.Trigger.TraceID.ID)
if job.Test.Trigger.Type.IsTraceIDBased() {
traceIDFromParam, err := trace.TraceIDFromHex(resolvedTest.Trigger.TraceID.ID)
if err == nil {
run.TraceID = traceIDFromParam
}
Expand Down
8 changes: 6 additions & 2 deletions server/http/mappings/tests.go
Expand Up @@ -465,13 +465,17 @@ func (m Model) TriggerResult(in openapi.TriggerResult) trigger.TriggerResult {
Type: trigger.TriggerType(in.Type),
Error: m.TriggerError(in.TriggerResult.Error),
}

if tr.Type.IsTraceIDBased() {
tr.TraceID = m.TraceIDResponse(in.TriggerResult.Traceid)
return tr
}

switch in.Type {
case "http":
tr.HTTP = m.HTTPResponse(in.TriggerResult.Http)
case "grpc":
tr.GRPC = m.GRPCResponse(in.TriggerResult.Grpc)
case "traceid":
tr.TraceID = m.TraceIDResponse(in.TriggerResult.Traceid)
case "kafka":
tr.Kafka = m.KafkaResponse(in.TriggerResult.Kafka)
}
Expand Down
3 changes: 3 additions & 0 deletions server/test/trigger/traceid.go
@@ -1,6 +1,9 @@
package trigger

const TriggerTypeTraceID TriggerType = "traceid"
const TriggerTypeCypress TriggerType = "cypress"

var traceIDBasedTriggers = []TriggerType{TriggerTypeTraceID, TriggerTypeCypress}

type TraceIDRequest struct {
ID string `json:"id,omitempty" expr_enabled:"true"`
Expand Down
14 changes: 13 additions & 1 deletion server/test/trigger/trigger.go
@@ -1,6 +1,10 @@
package trigger

import "errors"
import (
"errors"

"golang.org/x/exp/slices"
)

type (
TriggerType string
Expand Down Expand Up @@ -33,3 +37,11 @@ type (
func (e TriggerError) Error() error {
return errors.New(e.ErrorMessage)
}

func (t TriggerType) IsTraceIDBased() bool {
return slices.Contains(traceIDBasedTriggers, t)
}

func (t TriggerType) IsFrontendE2EBased() bool {
return t == TriggerTypeCypress
}
4 changes: 2 additions & 2 deletions server/test/trigger/trigger_json.go
Expand Up @@ -17,11 +17,11 @@ type triggerJSONV3 struct {

func (v3 triggerJSONV3) valid() bool {
// has a valid type and at least one not nil trigger type settings
return v3.Type != "" &&
return (v3.Type != "" &&
(v3.HTTP != nil ||
v3.GRPC != nil ||
v3.TraceID != nil ||
v3.Kafka != nil)
v3.Kafka != nil)) || v3.Type == TriggerTypeCypress
}

type triggerJSONV2 struct {
Expand Down
27 changes: 15 additions & 12 deletions web/src/components/CreateTest/Header.tsx
Expand Up @@ -3,6 +3,7 @@ import AllowButton, {Operation} from 'components/AllowButton';
import CreateButton from 'components/CreateButton';
import {TriggerTypes} from 'constants/Test.constants';
import EntryPointFactory from 'components/TestPlugins/EntryPointFactory';
import Test from 'models/Test.model';
import * as S from './CreateTest.styled';

interface IProps {
Expand All @@ -21,18 +22,20 @@ const Header = ({isLoading, isValid, triggerType}: IProps) => {
</S.HeaderLeft>

<S.HeaderRight>
<AllowButton
block
ButtonComponent={CreateButton}
data-cy="run-test-submit"
disabled={!isValid}
loading={isLoading}
onClick={() => form.submit()}
operation={Operation.Edit}
type="primary"
>
Run
</AllowButton>
{Test.shouldAllowRun(triggerType) && (
<AllowButton
block
ButtonComponent={CreateButton}
data-cy="run-test-submit"
disabled={!isValid}
loading={isLoading}
onClick={() => form.submit()}
operation={Operation.Edit}
type="primary"
>
Run
</AllowButton>
)}
</S.HeaderRight>
</S.Header>
);
Expand Down
24 changes: 13 additions & 11 deletions web/src/components/ResourceCard/TestCard.tsx
Expand Up @@ -50,17 +50,19 @@ const TestCard = ({onEdit, onDelete, onDuplicate, onRun, onViewAll, test}: IProp
<ResourceCardSummary summary={test.summary} />

<S.Row $gap={12}>
<CreateButton
data-cy={`test-run-button-${test.id}`}
ghost
onClick={event => {
event.stopPropagation();
onRun(test, ResourceType.Test);
}}
type="primary"
>
Run
</CreateButton>
{Test.shouldAllowRun(test.trigger.type) && (
<CreateButton
data-cy={`test-run-button-${test.id}`}
ghost
onClick={event => {
event.stopPropagation();
onRun(test, ResourceType.Test);
}}
type="primary"
>
Run
</CreateButton>
)}
<ResourceCardActions
id={test.id}
shouldEdit={shouldEdit}
Expand Down
7 changes: 5 additions & 2 deletions web/src/components/RunDetailLayout/HeaderRight.tsx
Expand Up @@ -2,7 +2,9 @@ import CreateButton from 'components/CreateButton';
import RunActionsMenu from 'components/RunActionsMenu';
import TestActions from 'components/TestActions';
import TestState from 'components/TestState';
import {TriggerTypes} from 'constants/Test.constants';
import {TestState as TestStateEnum} from 'constants/TestRun.constants';
import Test from 'models/Test.model';
import {isRunPollingState, isRunStateFinished, isRunStateStopped, isRunStateSucceeded} from 'models/TestRun.model';
import {useTest} from 'providers/Test/Test.provider';
import {useTestRun} from 'providers/TestRun/TestRun.provider';
Expand All @@ -17,9 +19,10 @@ import useSkipPolling from './hooks/useSkipPolling';

interface IProps {
testId: string;
triggerType: TriggerTypes;
}

const HeaderRight = ({testId}: IProps) => {
const HeaderRight = ({testId, triggerType}: IProps) => {
const {isDraftMode: isTestSpecsDraftMode} = useTestSpecs();
const {isDraftMode: isTestOutputsDraftMode} = useTestOutput();
const isDraftMode = isTestSpecsDraftMode || isTestOutputsDraftMode;
Expand Down Expand Up @@ -48,7 +51,7 @@ const HeaderRight = ({testId}: IProps) => {
<RunStatusIcon state={state} requiredGatesResult={requiredGatesResult} />
)}
<VariableSetSelector />
{!isDraftMode && state && isRunStateFinished(state) && (
{!isDraftMode && state && isRunStateFinished(state) && Test.shouldAllowRun(triggerType) && (
<CreateButton data-cy="run-test-button" ghost onClick={() => onRun()} type="primary">
Run Test
</CreateButton>
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/RunDetailLayout/RunDetailLayout.tsx
Expand Up @@ -63,7 +63,7 @@ const RunDetailLayout = ({test: {id, name, trigger, skipTraceCollection}, test}:
const tabBarExtraContent = useMemo(
() => ({
left: <HeaderLeft name={name} triggerType={trigger.type.toUpperCase()} origin={runOriginPath} />,
right: <HeaderRight testId={id} />,
right: <HeaderRight testId={id} triggerType={trigger.type} />,
}),
[id, name, trigger.type, runOriginPath]
);
Expand Down
Expand Up @@ -15,8 +15,9 @@ export interface IPropsComponent {
const ComponentMap: Record<TriggerTypes, (props: IPropsComponent) => React.ReactElement> = {
[TriggerTypes.http]: RunDetailTriggerResponse,
[TriggerTypes.grpc]: RunDetailTriggerResponse,
[TriggerTypes.traceid]: RunDetailTriggerData,
[TriggerTypes.kafka]: RunDetailTriggerResponse,
[TriggerTypes.traceid]: RunDetailTriggerData,
[TriggerTypes.cypress]: RunDetailTriggerData,
};

interface IProps extends IPropsComponent {
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/TestHeader/TestHeader.tsx
Expand Up @@ -11,7 +11,7 @@ interface IProps {
onDelete(): void;
onDuplicate(): void;
title: string;
runButton: React.ReactElement;
runButton: React.ReactNode;
}

const TestHeader = ({description, id, shouldEdit, onEdit, onDelete, onDuplicate, title, runButton}: IProps) => {
Expand Down
3 changes: 2 additions & 1 deletion web/src/components/TestPlugins/EntryPointFactory.tsx
Expand Up @@ -7,8 +7,9 @@ import TriggerHeaderBarKafka from './EntryPoint/Kafka';
const EntryPointFactoryMap = {
[TriggerTypes.http]: TriggerHeaderBarHttp,
[TriggerTypes.grpc]: TriggerHeaderBarGrpc,
[TriggerTypes.traceid]: TriggerHeaderBarTraceID,
[TriggerTypes.kafka]: TriggerHeaderBarKafka,
[TriggerTypes.traceid]: TriggerHeaderBarTraceID,
[TriggerTypes.cypress]: () => null,
};

interface IProps {
Expand Down
3 changes: 2 additions & 1 deletion web/src/components/TestPlugins/FormFactory.tsx
Expand Up @@ -7,8 +7,9 @@ import {TDraftTestForm} from 'types/Test.types';
const FormFactoryMap = {
[TriggerTypes.http]: Rest,
[TriggerTypes.grpc]: Grpc,
[TriggerTypes.traceid]: () => null,
[TriggerTypes.kafka]: Kafka,
[TriggerTypes.traceid]: () => null,
[TriggerTypes.cypress]: () => null,
};

export interface IFormProps {
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/TriggerTypeModal/TriggerTypeModal.tsx
@@ -1,10 +1,10 @@
import {TriggerTypeToPlugin} from 'constants/Plugins.constants';
import {CreateTriggerTypeToPlugin} from 'constants/Plugins.constants';
import {useDashboard} from 'providers/Dashboard/Dashboard.provider';
import CreateTestAnalyticsService from 'services/Analytics/CreateTestAnalytics.service';
import TriggerTypeCard from './TriggerTypeCard';
import * as S from './TriggerTypeModal.styled';

const pluginList = Object.values(TriggerTypeToPlugin);
const pluginList = Object.values(CreateTriggerTypeToPlugin);

interface IProps {
isOpen: boolean;
Expand Down
3 changes: 2 additions & 1 deletion web/src/constants/Common.constants.ts
Expand Up @@ -66,6 +66,7 @@ export enum RouterSearchFields {
export enum SupportedPlugins {
REST = 'REST',
GRPC = 'GRPC',
TraceID = 'TraceID',
Kafka = 'Kafka',
TraceID = 'TraceID',
Cypress = 'Cypress',
}
1 change: 1 addition & 0 deletions web/src/constants/Demo.constants.ts
Expand Up @@ -228,6 +228,7 @@ export function getDemoByPluginMap(demos: Demo[]) {
...((otelDemoMap && otelDemoMap[SupportedPlugins.GRPC]) || []),
],
[SupportedPlugins.TraceID]: [],
[SupportedPlugins.Cypress]: [],
[SupportedPlugins.Kafka]: (pokeshopDemoMap && pokeshopDemoMap[SupportedPlugins.Kafka]) || [],
};
}
19 changes: 19 additions & 0 deletions web/src/constants/Plugins.constants.ts
Expand Up @@ -48,16 +48,35 @@ const TraceID: IPlugin = {
type: TriggerTypes.traceid,
};

const Cypress: IPlugin = {
name: SupportedPlugins.Cypress,
title: 'Cypress',
description: 'Define your test via Cypress',
isActive: true,
demoList: [],
type: TriggerTypes.cypress,
requestType: TriggerTypes.traceid,
};

export const Plugins = {
[SupportedPlugins.REST]: Rest,
[SupportedPlugins.GRPC]: GRPC,
[SupportedPlugins.Kafka]: Kafka,
[SupportedPlugins.TraceID]: TraceID,
[SupportedPlugins.Cypress]: Cypress,
} as const;

export const TriggerTypeToPlugin = {
[TriggerTypes.http]: Plugins.REST,
[TriggerTypes.grpc]: Plugins.GRPC,
[TriggerTypes.kafka]: Plugins.Kafka,
[TriggerTypes.traceid]: Plugins.TraceID,
[TriggerTypes.cypress]: Plugins.Cypress,
} as const;

export const CreateTriggerTypeToPlugin = {
[TriggerTypes.http]: Plugins.REST,
[TriggerTypes.grpc]: Plugins.GRPC,
[TriggerTypes.kafka]: Plugins.Kafka,
[TriggerTypes.traceid]: Plugins.TraceID,
} as const;
1 change: 1 addition & 0 deletions web/src/constants/Test.constants.ts
Expand Up @@ -10,6 +10,7 @@ export enum TriggerTypes {
grpc = 'grpc',
traceid = 'traceid',
kafka = 'kafka',
cypress = 'cypress',
}

export enum ImportTypes {
Expand Down
5 changes: 5 additions & 0 deletions web/src/models/Test.model.ts
@@ -1,3 +1,4 @@
import {TriggerTypes} from 'constants/Test.constants';
import {load} from 'js-yaml';
import {Model, TTestSchemas} from 'types/Common.types';
import TestOutput from './TestOutput.model';
Expand Down Expand Up @@ -54,4 +55,8 @@ Test.FromDefinition = (definition: string): Test => {
return Test(raw);
};

Test.shouldAllowRun = (triggerType: TriggerTypes): boolean => {
return triggerType !== TriggerTypes.cypress;
};

export default Test;

0 comments on commit ddb68b4

Please sign in to comment.