From 50d0d213d8e15b63cc47109851297506c9796183 Mon Sep 17 00:00:00 2001 From: Erik Seliger Date: Wed, 15 Jul 2020 21:25:10 +0200 Subject: [PATCH] Implement minimal resolvers to incorporate schema changes (#12215) --- cmd/frontend/graphqlbackend/campaigns.go | 56 +++++++--- cmd/frontend/graphqlbackend/graphqlbackend.go | 16 ++- cmd/frontend/graphqlbackend/schema.go | 105 +++++++++++++----- cmd/frontend/graphqlbackend/schema.graphql | 105 +++++++++++++----- .../campaigns/resolvers/apitest/types.go | 17 ++- .../campaigns/resolvers/campaign_spec.go | 54 ++++++++- .../campaigns/resolvers/campaign_spec_test.go | 2 +- .../campaigns/resolvers/changeset_spec.go | 72 ++++++++---- .../resolvers/changeset_spec_test.go | 48 +++++--- .../campaigns/resolvers/resolver_test.go | 22 ++-- internal/campaigns/types.go | 14 +++ 11 files changed, 384 insertions(+), 127 deletions(-) diff --git a/cmd/frontend/graphqlbackend/campaigns.go b/cmd/frontend/graphqlbackend/campaigns.go index 4b6506b75a4ce..25d8c27a4485b 100644 --- a/cmd/frontend/graphqlbackend/campaigns.go +++ b/cmd/frontend/graphqlbackend/campaigns.go @@ -60,6 +60,11 @@ type CreateCampaignSpecArgs struct { ChangesetSpecs []graphql.ID } +type ChangesetSpecsConnectionArgs struct { + First *int32 + After *string +} + type CampaignsResolver interface { // Mutations CreateCampaign(ctx context.Context, args *CreateCampaignArgs) (CampaignResolver, error) @@ -69,7 +74,6 @@ type CampaignsResolver interface { DeleteCampaign(ctx context.Context, args *DeleteCampaignArgs) (*EmptyResponse, error) CreateChangesetSpec(ctx context.Context, args *CreateChangesetSpecArgs) (ChangesetSpecResolver, error) CreateCampaignSpec(ctx context.Context, args *CreateCampaignSpecArgs) (CampaignSpecResolver, error) - // ComputeCampaignDelta(ctx context.Context, args *ComputeCampaignDeltaArgs) (CampaignDeltaResolver, error) SyncChangeset(ctx context.Context, args *SyncChangesetArgs) (*EmptyResponse, error) // Queries @@ -86,23 +90,51 @@ type CampaignSpecResolver interface { OriginalInput() (string, error) ParsedInput() (JSONValue, error) - ChangesetSpecs(context.Context) ([]ChangesetSpecResolver, error) + ChangesetSpecs(ctx context.Context, args *ChangesetSpecsConnectionArgs) (ChangesetSpecConnectionResolver, error) + + Description() CampaignDescriptionResolver Creator(context.Context) (*UserResolver, error) - CreatedAt() *DateTime + CreatedAt() DateTime Namespace(context.Context) (*NamespaceResolver, error) ExpiresAt() *DateTime PreviewURL() (string, error) + + ViewerCanAdminister() bool +} + +type CampaignDescriptionResolver interface { + Name() string + Description() string +} + +type ChangesetSpecConnectionResolver interface { + TotalCount(ctx context.Context) (int32, error) + PageInfo(ctx context.Context) (*graphqlutil.PageInfo, error) + Nodes(ctx context.Context) ([]ChangesetSpecResolver, error) } type ChangesetSpecResolver interface { ID() graphql.ID - Description() ChangesetDescription + Type() campaigns.ChangesetSpecType ExpiresAt() *DateTime + + ToHiddenChangesetSpec() (HiddenChangesetSpecResolver, bool) + ToVisibleChangesetSpec() (VisibleChangesetSpecResolver, bool) +} + +type HiddenChangesetSpecResolver interface { + ChangesetSpecResolver +} + +type VisibleChangesetSpecResolver interface { + ChangesetSpecResolver + + Description(ctx context.Context) (ChangesetDescription, error) } type ChangesetDescription interface { @@ -111,21 +143,23 @@ type ChangesetDescription interface { } type ExistingChangesetReferenceResolver interface { - BaseRepository() graphql.ID + BaseRepository() *RepositoryResolver ExternalID() string } type GitBranchChangesetDescriptionResolver interface { - BaseRepository() graphql.ID + BaseRepository() *RepositoryResolver BaseRef() string BaseRev() string - HeadRepository() graphql.ID + HeadRepository() *RepositoryResolver HeadRef() string Title() string Body() string + Diff(ctx context.Context) (*RepositoryComparisonResolver, error) + Commits() []GitCommitDescriptionResolver Published() bool @@ -136,14 +170,6 @@ type GitCommitDescriptionResolver interface { Diff() string } -type CampaignDeltaResolver interface { - ID() (graphql.ID, error) - - // TODO: More fields, see PR - - CreatedAt() DateTime -} - type ChangesetCountsArgs struct { From *DateTime To *DateTime diff --git a/cmd/frontend/graphqlbackend/graphqlbackend.go b/cmd/frontend/graphqlbackend/graphqlbackend.go index 6abef1ae0a74d..d642fe4a8c44d 100644 --- a/cmd/frontend/graphqlbackend/graphqlbackend.go +++ b/cmd/frontend/graphqlbackend/graphqlbackend.go @@ -412,14 +412,20 @@ func (r *NodeResolver) ToCampaignSpec() (CampaignSpecResolver, bool) { return n, ok } -func (r *NodeResolver) ToChangesetSpec() (ChangesetSpecResolver, bool) { +func (r *NodeResolver) ToHiddenChangesetSpec() (HiddenChangesetSpecResolver, bool) { n, ok := r.Node.(ChangesetSpecResolver) - return n, ok + if !ok { + return nil, ok + } + return n.ToHiddenChangesetSpec() } -func (r *NodeResolver) ToCampaignDelta() (CampaignDeltaResolver, bool) { - n, ok := r.Node.(CampaignDeltaResolver) - return n, ok +func (r *NodeResolver) ToVisibleChangesetSpec() (VisibleChangesetSpecResolver, bool) { + n, ok := r.Node.(ChangesetSpecResolver) + if !ok { + return nil, ok + } + return n.ToVisibleChangesetSpec() } func (r *NodeResolver) ToProductLicense() (ProductLicense, bool) { diff --git a/cmd/frontend/graphqlbackend/schema.go b/cmd/frontend/graphqlbackend/schema.go index 9c5c26bbc9798..cc0474fb7a9b8 100644 --- a/cmd/frontend/graphqlbackend/schema.go +++ b/cmd/frontend/graphqlbackend/schema.go @@ -423,8 +423,7 @@ type Mutation { ): ChangesetSpec! # Create a campaign spec that will be used to create a campaign (with the createCampaign - # mutation), to update to a campaign (with the applyCampaign mutation), or to preview either - # operation (with the campaignDelta query). + # mutation), or to update a campaign (with the applyCampaign mutation). # # The returned CampaignSpec is immutable and expires after a certain period of time (if not used # in a call to applyCampaign), which can be queried on CampaignSpec.expiresAt. @@ -444,9 +443,49 @@ type Mutation { syncChangeset(changeset: ID!): EmptyResponse! } +# The type of the changeset spec. +enum ChangesetSpecType { + # References an existing changeset on a code host to be imported. + EXISTING + # References a branch and a patch to be applied to create the changeset from. + BRANCH +} + +# A changeset spec is an immutable description of the desired state of a changeset in a campaign. To +# create a changeset spec, use the createChangesetSpec mutation. +interface ChangesetSpec { + # The type of changeset spec. + type: ChangesetSpecType! + + # The date, if any, when this changeset spec expires and is automatically purged. A changeset + # spec never expires (and this field is null) if its campaign spec has been applied. + expiresAt: DateTime +} + +# A changeset spec is an immutable description of the desired state of a changeset in a campaign. To +# create a changeset spec, use the createChangesetSpec mutation. +type HiddenChangesetSpec implements ChangesetSpec & Node { + # The unique ID for a changeset spec. + # + # The ID is unguessable (i.e., long and randomly generated, not sequential). This is important + # even though repository permissions also apply to viewers of changeset specs, because being + # allowed to view a repository should not entitle a person to view all not-yet-published + # changesets for that repository. Consider a campaign to fix a security vulnerability: the + # campaign author may prefer to prepare all of the changesets in private so that the window + # between revealing the problem and merging the fixes is as short as possible. + id: ID! + + # The type of changeset spec. + type: ChangesetSpecType! + + # The date, if any, when this changeset spec expires and is automatically purged. A changeset + # spec never expires (and this field is null) if its campaign spec has been applied. + expiresAt: DateTime +} + # A changeset spec is an immutable description of the desired state of a changeset in a campaign. To # create a changeset spec, use the createChangesetSpec mutation. -type ChangesetSpec implements Node { +type VisibleChangesetSpec implements ChangesetSpec & Node { # The unique ID for a changeset spec. # # The ID is unguessable (i.e., long and randomly generated, not sequential). This is important @@ -457,6 +496,9 @@ type ChangesetSpec implements Node { # between revealing the problem and merging the fixes is as short as possible. id: ID! + # The type of changeset spec. + type: ChangesetSpecType! + # The description of the changeset. description: ChangesetDescription! @@ -472,7 +514,7 @@ union ChangesetDescription = ExistingChangesetReference | GitBranchChangesetDesc # campaign). type ExistingChangesetReference { # The repository that contains the existing changeset on the code host. - baseRepository: ID! + baseRepository: Repository! # The ID that uniquely identifies the existing changeset on the code host. # @@ -486,7 +528,7 @@ type ExistingChangesetReference { # This is used to describe a pull request (on GitHub and Bitbucket Server). type GitBranchChangesetDescription { # The repository that this changeset spec is proposing to change. - baseRepository: ID! + baseRepository: Repository! # The full name of the Git ref in the base repository that this changeset is based on (and is # proposing to be merged into). This ref must exist on the base repository. For example, @@ -502,7 +544,7 @@ type GitBranchChangesetDescription { # # Fork repositories and cross-repository changesets are not yet supported. Therefore, # headRepository must be equal to baseRepository. - headRepository: ID! + headRepository: Repository! # The full name of the Git ref that holds the changes proposed by this changeset. This ref will # be created or updated with the commits. For example, "refs/heads/fix-foo" (for @@ -524,6 +566,9 @@ type GitBranchChangesetDescription { # Only 1 commit is supported. commits: [GitCommitDescription!]! + # The total diff of the changeset diff. + diff: PreviewRepositoryComparison! + # Whether or not the changeset described here should be created right after # applying the ChangesetSpec this description belongs to. # @@ -549,6 +594,25 @@ type GitCommitDescription { diff: String! } +# A list of changeset specs. +type ChangesetSpecConnection { + # The total number of changeset specs in the connection. + totalCount: Int! + # Pagination information. + pageInfo: PageInfo! + # A list of changeset specs. + nodes: [ChangesetSpec!]! +} + +# A CampaignDescription describes a campaign. +type CampaignDescription { + # The name as parsed from the input. + name: String! + + # The description as parsed from the input. + description: String! +} + # A campaign spec is an immutable description of the desired state of a campaign. To create a # campaign spec, use the createCampaignSpec mutation. type CampaignSpec implements Node { @@ -564,14 +628,17 @@ type CampaignSpec implements Node { # converted to the equivalent JSON. parsedInput: JSONValue! + # The CampaignDescription that describes this campaign. + description: CampaignDescription! + # The specs for changesets associated with this campaign. - changesetSpecs: [ChangesetSpec!]! + changesetSpecs(first: Int, after: String): ChangesetSpecConnection! # The user who created this campaign spec (or null if the user no longer exists). creator: User # The date when this campaign spec was created. - createdAt: DateTime + createdAt: DateTime! # The namespace (either a user or organization) of the campaign spec. namespace: Namespace @@ -580,27 +647,11 @@ type CampaignSpec implements Node { # never expires if it has been applied. expiresAt: DateTime - # The URL of a web page that displays a preview of applying this campaign spec. If the campaign - # spec's name refers to an existing campaign in the namespace, the preview shows what will be - # updated. Otherwise it shows what will be created. + # The URL of a web page that displays a preview of creating a campaign from this spec. previewURL: String! -} - -# The delta (difference) between a campaign's desired state (spec) and its actual state (status) at -# a given point in time. Because the campaign delta is computed based on external state, it may -# become out-of-date immediately after creation (e.g., if the external state changes), so it must be -# treated as a snapshot only and not as a definitive plan. -type CampaignDelta { - # TODO(sqs): add more fields here to describe the delta - - # The point in time when the delta was created and the actual state was captured. Since this - # time, the actual state may have changed; those changes are not captured in this delta. - createdAt: DateTime! - # The date when this campaign delta expires and is automatically purged. Campaign deltas are - # temporary to avoid consuming resources when they are no longer relevant. Purging a campaign - # delta does not modify the campaign or any changesets. - expiresAt: DateTime! + # When true, the viewing user can apply this spec. + viewerCanAdminister: Boolean! } # A user (identified either by username or email address) with its repository permission. diff --git a/cmd/frontend/graphqlbackend/schema.graphql b/cmd/frontend/graphqlbackend/schema.graphql index a31e3d7d4b30a..8c058b218adb8 100755 --- a/cmd/frontend/graphqlbackend/schema.graphql +++ b/cmd/frontend/graphqlbackend/schema.graphql @@ -430,8 +430,7 @@ type Mutation { ): ChangesetSpec! # Create a campaign spec that will be used to create a campaign (with the createCampaign - # mutation), to update to a campaign (with the applyCampaign mutation), or to preview either - # operation (with the campaignDelta query). + # mutation), or to update a campaign (with the applyCampaign mutation). # # The returned CampaignSpec is immutable and expires after a certain period of time (if not used # in a call to applyCampaign), which can be queried on CampaignSpec.expiresAt. @@ -451,9 +450,49 @@ type Mutation { syncChangeset(changeset: ID!): EmptyResponse! } +# The type of the changeset spec. +enum ChangesetSpecType { + # References an existing changeset on a code host to be imported. + EXISTING + # References a branch and a patch to be applied to create the changeset from. + BRANCH +} + +# A changeset spec is an immutable description of the desired state of a changeset in a campaign. To +# create a changeset spec, use the createChangesetSpec mutation. +interface ChangesetSpec { + # The type of changeset spec. + type: ChangesetSpecType! + + # The date, if any, when this changeset spec expires and is automatically purged. A changeset + # spec never expires (and this field is null) if its campaign spec has been applied. + expiresAt: DateTime +} + +# A changeset spec is an immutable description of the desired state of a changeset in a campaign. To +# create a changeset spec, use the createChangesetSpec mutation. +type HiddenChangesetSpec implements ChangesetSpec & Node { + # The unique ID for a changeset spec. + # + # The ID is unguessable (i.e., long and randomly generated, not sequential). This is important + # even though repository permissions also apply to viewers of changeset specs, because being + # allowed to view a repository should not entitle a person to view all not-yet-published + # changesets for that repository. Consider a campaign to fix a security vulnerability: the + # campaign author may prefer to prepare all of the changesets in private so that the window + # between revealing the problem and merging the fixes is as short as possible. + id: ID! + + # The type of changeset spec. + type: ChangesetSpecType! + + # The date, if any, when this changeset spec expires and is automatically purged. A changeset + # spec never expires (and this field is null) if its campaign spec has been applied. + expiresAt: DateTime +} + # A changeset spec is an immutable description of the desired state of a changeset in a campaign. To # create a changeset spec, use the createChangesetSpec mutation. -type ChangesetSpec implements Node { +type VisibleChangesetSpec implements ChangesetSpec & Node { # The unique ID for a changeset spec. # # The ID is unguessable (i.e., long and randomly generated, not sequential). This is important @@ -464,6 +503,9 @@ type ChangesetSpec implements Node { # between revealing the problem and merging the fixes is as short as possible. id: ID! + # The type of changeset spec. + type: ChangesetSpecType! + # The description of the changeset. description: ChangesetDescription! @@ -479,7 +521,7 @@ union ChangesetDescription = ExistingChangesetReference | GitBranchChangesetDesc # campaign). type ExistingChangesetReference { # The repository that contains the existing changeset on the code host. - baseRepository: ID! + baseRepository: Repository! # The ID that uniquely identifies the existing changeset on the code host. # @@ -493,7 +535,7 @@ type ExistingChangesetReference { # This is used to describe a pull request (on GitHub and Bitbucket Server). type GitBranchChangesetDescription { # The repository that this changeset spec is proposing to change. - baseRepository: ID! + baseRepository: Repository! # The full name of the Git ref in the base repository that this changeset is based on (and is # proposing to be merged into). This ref must exist on the base repository. For example, @@ -509,7 +551,7 @@ type GitBranchChangesetDescription { # # Fork repositories and cross-repository changesets are not yet supported. Therefore, # headRepository must be equal to baseRepository. - headRepository: ID! + headRepository: Repository! # The full name of the Git ref that holds the changes proposed by this changeset. This ref will # be created or updated with the commits. For example, "refs/heads/fix-foo" (for @@ -531,6 +573,9 @@ type GitBranchChangesetDescription { # Only 1 commit is supported. commits: [GitCommitDescription!]! + # The total diff of the changeset diff. + diff: PreviewRepositoryComparison! + # Whether or not the changeset described here should be created right after # applying the ChangesetSpec this description belongs to. # @@ -556,6 +601,25 @@ type GitCommitDescription { diff: String! } +# A list of changeset specs. +type ChangesetSpecConnection { + # The total number of changeset specs in the connection. + totalCount: Int! + # Pagination information. + pageInfo: PageInfo! + # A list of changeset specs. + nodes: [ChangesetSpec!]! +} + +# A CampaignDescription describes a campaign. +type CampaignDescription { + # The name as parsed from the input. + name: String! + + # The description as parsed from the input. + description: String! +} + # A campaign spec is an immutable description of the desired state of a campaign. To create a # campaign spec, use the createCampaignSpec mutation. type CampaignSpec implements Node { @@ -571,14 +635,17 @@ type CampaignSpec implements Node { # converted to the equivalent JSON. parsedInput: JSONValue! + # The CampaignDescription that describes this campaign. + description: CampaignDescription! + # The specs for changesets associated with this campaign. - changesetSpecs: [ChangesetSpec!]! + changesetSpecs(first: Int, after: String): ChangesetSpecConnection! # The user who created this campaign spec (or null if the user no longer exists). creator: User # The date when this campaign spec was created. - createdAt: DateTime + createdAt: DateTime! # The namespace (either a user or organization) of the campaign spec. namespace: Namespace @@ -587,27 +654,11 @@ type CampaignSpec implements Node { # never expires if it has been applied. expiresAt: DateTime - # The URL of a web page that displays a preview of applying this campaign spec. If the campaign - # spec's name refers to an existing campaign in the namespace, the preview shows what will be - # updated. Otherwise it shows what will be created. + # The URL of a web page that displays a preview of creating a campaign from this spec. previewURL: String! -} - -# The delta (difference) between a campaign's desired state (spec) and its actual state (status) at -# a given point in time. Because the campaign delta is computed based on external state, it may -# become out-of-date immediately after creation (e.g., if the external state changes), so it must be -# treated as a snapshot only and not as a definitive plan. -type CampaignDelta { - # TODO(sqs): add more fields here to describe the delta - - # The point in time when the delta was created and the actual state was captured. Since this - # time, the actual state may have changed; those changes are not captured in this delta. - createdAt: DateTime! - # The date when this campaign delta expires and is automatically purged. Campaign deltas are - # temporary to avoid consuming resources when they are no longer relevant. Purging a campaign - # delta does not modify the campaign or any changesets. - expiresAt: DateTime! + # When true, the viewing user can apply this spec. + viewerCanAdminister: Boolean! } # A user (identified either by username or email address) with its repository permission. diff --git a/enterprise/internal/campaigns/resolvers/apitest/types.go b/enterprise/internal/campaigns/resolvers/apitest/types.go index 2a004cf1e39d9..791b39e83dfd3 100644 --- a/enterprise/internal/campaigns/resolvers/apitest/types.go +++ b/enterprise/internal/campaigns/resolvers/apitest/types.go @@ -198,9 +198,9 @@ type CampaignSpec struct { Namespace UserOrg Creator User - ChangesetSpecs []ChangesetSpec + ChangesetSpecs ChangesetSpecConnection - CreatedAt *graphqlbackend.DateTime + CreatedAt graphqlbackend.DateTime ExpiresAt *graphqlbackend.DateTime } @@ -213,14 +213,23 @@ type ChangesetSpec struct { ExpiresAt *graphqlbackend.DateTime } +type ChangesetSpecConnection struct { + Nodes []ChangesetSpec + TotalCount int + PageInfo struct { + HasNextPage bool + EndCursor *string + } +} + type ChangesetSpecDescription struct { Typename string `json:"__typename"` - BaseRepository string + BaseRepository Repository ExternalID string BaseRef string - HeadRepository string + HeadRepository Repository HeadRef string Title string diff --git a/enterprise/internal/campaigns/resolvers/campaign_spec.go b/enterprise/internal/campaigns/resolvers/campaign_spec.go index 5044f7899156d..0753adaa0cb99 100644 --- a/enterprise/internal/campaigns/resolvers/campaign_spec.go +++ b/enterprise/internal/campaigns/resolvers/campaign_spec.go @@ -7,6 +7,7 @@ import ( "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" "github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend" + "github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend/graphqlutil" ee "github.com/sourcegraph/sourcegraph/enterprise/internal/campaigns" "github.com/sourcegraph/sourcegraph/internal/campaigns" "github.com/sourcegraph/sourcegraph/internal/errcode" @@ -45,7 +46,7 @@ func (r *campaignSpecResolver) ParsedInput() (graphqlbackend.JSONValue, error) { return graphqlbackend.JSONValue{Value: r.campaignSpec.Spec}, nil } -func (r *campaignSpecResolver) ChangesetSpecs(ctx context.Context) ([]graphqlbackend.ChangesetSpecResolver, error) { +func (r *campaignSpecResolver) ChangesetSpecs(ctx context.Context, args *graphqlbackend.ChangesetSpecsConnectionArgs) (graphqlbackend.ChangesetSpecConnectionResolver, error) { opts := ee.ListChangesetSpecsOpts{Limit: -1, CampaignSpecID: r.campaignSpec.ID} cs, _, err := r.store.ListChangesetSpecs(ctx, opts) if err != nil { @@ -61,7 +62,16 @@ func (r *campaignSpecResolver) ChangesetSpecs(ctx context.Context) ([]graphqlbac }) } - return resolvers, nil + return &changesetSpecConnectionResolver{ + resolvers: resolvers, + }, nil +} + +func (r *campaignSpecResolver) Description() graphqlbackend.CampaignDescriptionResolver { + return &campaignDescriptionResolver{ + name: r.campaignSpec.Spec.Name, + description: r.campaignSpec.Spec.Description, + } } func (r *campaignSpecResolver) Creator(ctx context.Context) (*graphqlbackend.UserResolver, error) { @@ -92,8 +102,8 @@ func (r *campaignSpecResolver) PreviewURL() (string, error) { return "/campaigns/new?spec=" + string(r.ID()), nil } -func (r *campaignSpecResolver) CreatedAt() *graphqlbackend.DateTime { - return &graphqlbackend.DateTime{Time: r.campaignSpec.CreatedAt} +func (r *campaignSpecResolver) CreatedAt() graphqlbackend.DateTime { + return graphqlbackend.DateTime{Time: r.campaignSpec.CreatedAt} } func (r *campaignSpecResolver) ExpiresAt() *graphqlbackend.DateTime { @@ -101,3 +111,39 @@ func (r *campaignSpecResolver) ExpiresAt() *graphqlbackend.DateTime { expiresAt := r.campaignSpec.CreatedAt.Add(2 * time.Hour) return &graphqlbackend.DateTime{Time: expiresAt} } + +func (r *campaignSpecResolver) ViewerCanAdminister() bool { + // TODO: Implement. + return true +} + +type campaignDescriptionResolver struct { + name, description string +} + +func (r *campaignDescriptionResolver) Name() string { + return r.name +} + +func (r *campaignDescriptionResolver) Description() string { + return r.description +} + +type changesetSpecConnectionResolver struct { + resolvers []graphqlbackend.ChangesetSpecResolver +} + +func (r *changesetSpecConnectionResolver) TotalCount(ctx context.Context) (int32, error) { + // TODO: Implement. + return int32(len(r.resolvers)), nil +} + +func (r *changesetSpecConnectionResolver) PageInfo(ctx context.Context) (*graphqlutil.PageInfo, error) { + // TODO: Implement. + return &graphqlutil.PageInfo{}, nil +} + +func (r *changesetSpecConnectionResolver) Nodes(ctx context.Context) ([]graphqlbackend.ChangesetSpecResolver, error) { + // TODO: Implement. + return r.resolvers, nil +} diff --git a/enterprise/internal/campaigns/resolvers/campaign_spec_test.go b/enterprise/internal/campaigns/resolvers/campaign_spec_test.go index 146f7afb0f9e5..7f367da8fe0b9 100644 --- a/enterprise/internal/campaigns/resolvers/campaign_spec_test.go +++ b/enterprise/internal/campaigns/resolvers/campaign_spec_test.go @@ -69,7 +69,7 @@ func TestCampaignSpecResolver(t *testing.T) { PreviewURL: "/campaigns/new?spec=" + apiID, Namespace: apitest.UserOrg{ID: userApiID, DatabaseID: userID}, Creator: apitest.User{ID: userApiID, DatabaseID: userID}, - CreatedAt: &graphqlbackend.DateTime{Time: spec.CreatedAt.Truncate(time.Second)}, + CreatedAt: graphqlbackend.DateTime{Time: spec.CreatedAt.Truncate(time.Second)}, ExpiresAt: &graphqlbackend.DateTime{Time: spec.CreatedAt.Truncate(time.Second).Add(2 * time.Hour)}, } diff --git a/enterprise/internal/campaigns/resolvers/changeset_spec.go b/enterprise/internal/campaigns/resolvers/changeset_spec.go index dbe6124747eae..cb20afe29c35a 100644 --- a/enterprise/internal/campaigns/resolvers/changeset_spec.go +++ b/enterprise/internal/campaigns/resolvers/changeset_spec.go @@ -1,10 +1,12 @@ package resolvers import ( + "context" "time" "github.com/graph-gophers/graphql-go" "github.com/graph-gophers/graphql-go/relay" + "github.com/pkg/errors" "github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend" ee "github.com/sourcegraph/sourcegraph/enterprise/internal/campaigns" "github.com/sourcegraph/sourcegraph/internal/campaigns" @@ -35,8 +37,26 @@ func (r *changesetSpecResolver) ID() graphql.ID { return marshalChangesetSpecRandID(r.changesetSpec.RandID) } -func (r *changesetSpecResolver) Description() graphqlbackend.ChangesetDescription { - return &changesetDescriptionResolver{desc: &r.changesetSpec.Spec} +func (r *changesetSpecResolver) Type() campaigns.ChangesetSpecType { + if r.changesetSpec.Spec.IsExistingChangesetRef() { + return campaigns.ChangesetSpecTypeExisting + } + return campaigns.ChangesetSpecTypeBranch +} + +func (r *changesetSpecResolver) Description(ctx context.Context) (graphqlbackend.ChangesetDescription, error) { + // TODO: Remove n+1 for repository. + repoResolver, err := graphqlbackend.RepositoryByID(ctx, r.changesetSpec.Spec.BaseRepository) + if err != nil { + return nil, err + } + + descriptionResolver := &changesetDescriptionResolver{ + desc: &r.changesetSpec.Spec, + repoResolver: repoResolver, + } + + return descriptionResolver, nil } func (r *changesetSpecResolver) ExpiresAt() *graphqlbackend.DateTime { @@ -45,42 +65,56 @@ func (r *changesetSpecResolver) ExpiresAt() *graphqlbackend.DateTime { return &graphqlbackend.DateTime{Time: expiresAt} } +func (r *changesetSpecResolver) ToHiddenChangesetSpec() (graphqlbackend.HiddenChangesetSpecResolver, bool) { + // TODO: return true when repo is inaccessible. + return nil, false +} +func (r *changesetSpecResolver) ToVisibleChangesetSpec() (graphqlbackend.VisibleChangesetSpecResolver, bool) { + // TODO: return true when repo is accessible. + return r, true +} + var _ graphqlbackend.ChangesetDescription = &changesetDescriptionResolver{} // changesetDescriptionResolver implements both ChangesetDescription // interfaces: ExistingChangesetReferenceResolver and // GitBranchChangesetDescriptionResolver. type changesetDescriptionResolver struct { - desc *campaigns.ChangesetSpecDescription -} - -func (r *changesetDescriptionResolver) isExistingChangesetRef() bool { - return r.desc.ExternalID != "" + repoResolver *graphqlbackend.RepositoryResolver + desc *campaigns.ChangesetSpecDescription } func (r *changesetDescriptionResolver) ToExistingChangesetReference() (graphqlbackend.ExistingChangesetReferenceResolver, bool) { - if r.isExistingChangesetRef() { + if r.desc.IsExistingChangesetRef() { return r, true } return nil, false - } func (r *changesetDescriptionResolver) ToGitBranchChangesetDescription() (graphqlbackend.GitBranchChangesetDescriptionResolver, bool) { - if r.isExistingChangesetRef() { + if r.desc.IsExistingChangesetRef() { return nil, false } return r, true } -func (r *changesetDescriptionResolver) BaseRepository() graphql.ID { return r.desc.BaseRepository } -func (r *changesetDescriptionResolver) ExternalID() string { return r.desc.ExternalID } -func (r *changesetDescriptionResolver) BaseRef() string { return r.desc.BaseRef } -func (r *changesetDescriptionResolver) BaseRev() string { return r.desc.BaseRev } -func (r *changesetDescriptionResolver) HeadRepository() graphql.ID { return r.desc.HeadRepository } -func (r *changesetDescriptionResolver) HeadRef() string { return r.desc.HeadRef } -func (r *changesetDescriptionResolver) Title() string { return r.desc.Title } -func (r *changesetDescriptionResolver) Body() string { return r.desc.Body } -func (r *changesetDescriptionResolver) Published() bool { return r.desc.Published } +func (r *changesetDescriptionResolver) BaseRepository() *graphqlbackend.RepositoryResolver { + return r.repoResolver +} +func (r *changesetDescriptionResolver) ExternalID() string { return r.desc.ExternalID } +func (r *changesetDescriptionResolver) BaseRef() string { return r.desc.BaseRef } +func (r *changesetDescriptionResolver) BaseRev() string { return r.desc.BaseRev } +func (r *changesetDescriptionResolver) HeadRepository() *graphqlbackend.RepositoryResolver { + return r.repoResolver +} +func (r *changesetDescriptionResolver) HeadRef() string { return r.desc.HeadRef } +func (r *changesetDescriptionResolver) Title() string { return r.desc.Title } +func (r *changesetDescriptionResolver) Body() string { return r.desc.Body } +func (r *changesetDescriptionResolver) Published() bool { return r.desc.Published } + +func (r *changesetDescriptionResolver) Diff(ctx context.Context) (*graphqlbackend.RepositoryComparisonResolver, error) { + // TODO: Implement. + return nil, errors.New("not implemented") +} func (r *changesetDescriptionResolver) Commits() []graphqlbackend.GitCommitDescriptionResolver { var resolvers []graphqlbackend.GitCommitDescriptionResolver diff --git a/enterprise/internal/campaigns/resolvers/changeset_spec_test.go b/enterprise/internal/campaigns/resolvers/changeset_spec_test.go index b1603b995ac3b..764ff5c335c97 100644 --- a/enterprise/internal/campaigns/resolvers/changeset_spec_test.go +++ b/enterprise/internal/campaigns/resolvers/changeset_spec_test.go @@ -53,17 +53,21 @@ func TestChangesetSpecResolver(t *testing.T) { rawSpec: ct.NewRawChangesetSpecGitBranch(repoID), want: func(spec *campaigns.ChangesetSpec) apitest.ChangesetSpec { return apitest.ChangesetSpec{ - Typename: "ChangesetSpec", + Typename: "VisibleChangesetSpec", ID: string(marshalChangesetSpecRandID(spec.RandID)), Description: apitest.ChangesetSpecDescription{ - Typename: "GitBranchChangesetDescription", - BaseRepository: string(spec.Spec.BaseRepository), - ExternalID: "", - BaseRef: spec.Spec.BaseRef, - HeadRepository: string(spec.Spec.HeadRepository), - HeadRef: spec.Spec.HeadRef, - Title: spec.Spec.Title, - Body: spec.Spec.Body, + Typename: "GitBranchChangesetDescription", + BaseRepository: apitest.Repository{ + ID: string(spec.Spec.BaseRepository), + }, + ExternalID: "", + BaseRef: spec.Spec.BaseRef, + HeadRepository: apitest.Repository{ + ID: string(spec.Spec.HeadRepository), + }, + HeadRef: spec.Spec.HeadRef, + Title: spec.Spec.Title, + Body: spec.Spec.Body, Commits: []apitest.GitCommitDescription{ {Diff: spec.Spec.Commits[0].Diff, Message: spec.Spec.Commits[0].Message}, }, @@ -80,13 +84,15 @@ func TestChangesetSpecResolver(t *testing.T) { rawSpec: ct.NewRawChangesetSpecExisting(repoID, "9999"), want: func(spec *campaigns.ChangesetSpec) apitest.ChangesetSpec { return apitest.ChangesetSpec{ - Typename: "ChangesetSpec", + Typename: "VisibleChangesetSpec", ID: string(marshalChangesetSpecRandID(spec.RandID)), Description: apitest.ChangesetSpecDescription{ - Typename: "ExistingChangesetReference", - BaseRepository: string(spec.Spec.BaseRepository), - ExternalID: spec.Spec.ExternalID, - Published: false, + Typename: "ExistingChangesetReference", + BaseRepository: apitest.Repository{ + ID: string(spec.Spec.BaseRepository), + }, + ExternalID: spec.Spec.ExternalID, + Published: false, }, ExpiresAt: &graphqlbackend.DateTime{ Time: spec.CreatedAt.Truncate(time.Second).Add(2 * time.Hour), @@ -126,23 +132,29 @@ query($id: ID!) { node(id: $id) { __typename - ... on ChangesetSpec { + ... on VisibleChangesetSpec { id description { __typename ... on ExistingChangesetReference { - baseRepository + baseRepository { + id + } externalID } ... on GitBranchChangesetDescription { - baseRepository + baseRepository { + id + } baseRef baseRev - headRepository + headRepository { + id + } headRef title diff --git a/enterprise/internal/campaigns/resolvers/resolver_test.go b/enterprise/internal/campaigns/resolvers/resolver_test.go index b5c706f858537..f69ffb578e5ff 100644 --- a/enterprise/internal/campaigns/resolvers/resolver_test.go +++ b/enterprise/internal/campaigns/resolvers/resolver_test.go @@ -925,8 +925,10 @@ func TestCreateCampaignSpec(t *testing.T) { PreviewURL: "/campaigns/new?spec=", Namespace: apitest.UserOrg{ID: userApiID, DatabaseID: userID, SiteAdmin: true}, Creator: apitest.User{ID: userApiID, DatabaseID: userID, SiteAdmin: true}, - ChangesetSpecs: []apitest.ChangesetSpec{ - {ID: string(changesetSpecID)}, + ChangesetSpecs: apitest.ChangesetSpecConnection{ + Nodes: []apitest.ChangesetSpec{ + {ID: string(changesetSpecID)}, + }, }, } have := response.CreateCampaignSpec @@ -960,7 +962,11 @@ mutation($namespace: ID!, $campaignSpec: String!, $changesetSpecs: [ID!]!){ previewURL changesetSpecs { - id + nodes { + ... on VisibleChangesetSpec { + id + } + } } createdAt @@ -1006,7 +1012,7 @@ func TestCreateChangesetSpec(t *testing.T) { have := response.CreateChangesetSpec want := apitest.ChangesetSpec{ - Typename: "ChangesetSpec", + Typename: "VisibleChangesetSpec", ID: have.ID, ExpiresAt: have.ExpiresAt, } @@ -1033,9 +1039,11 @@ func TestCreateChangesetSpec(t *testing.T) { const mutationCreateChangesetSpec = ` mutation($changesetSpec: String!){ createChangesetSpec(changesetSpec: $changesetSpec) { - __typename - id - expiresAt + __typename + ... on VisibleChangesetSpec { + id + expiresAt + } } } ` diff --git a/internal/campaigns/types.go b/internal/campaigns/types.go index 55312b36b3b51..3571a4f648f14 100644 --- a/internal/campaigns/types.go +++ b/internal/campaigns/types.go @@ -1726,6 +1726,15 @@ type ChangesetSpec struct { UpdatedAt time.Time } +// ChangesetSpecType tells about the type of the changeset spec without including the description. Useful for HiddenChangesetSpecs in the API. +type ChangesetSpecType string + +// Valid ChangesetEvent kinds +const ( + ChangesetSpecTypeExisting ChangesetSpecType = "EXISTING" + ChangesetSpecTypeBranch ChangesetSpecType = "BRANCH" +) + // Clone returns a clone of a ChangesetSpec. func (cs *ChangesetSpec) Clone() *ChangesetSpec { cc := *cs @@ -1776,6 +1785,11 @@ type ChangesetSpecDescription struct { Published bool `json:"published,omitempty"` } +// IsExistingChangesetRef returns true when the changeset spec is referencing an existing changeset on a codehost. +func (d *ChangesetSpecDescription) IsExistingChangesetRef() bool { + return d.ExternalID != "" +} + type GitCommitDescription struct { Message string `json:"message,omitempty"` Diff string `json:"diff,omitempty"`