Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Commit

Permalink
Implement moveCampaign mutation (#12049)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrnugget committed Jul 20, 2020
1 parent 5b2b4fb commit dcdc465
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 13 deletions.
4 changes: 2 additions & 2 deletions cmd/frontend/graphqlbackend/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ type OrgResolver struct {

func NewOrg(org *types.Org) *OrgResolver { return &OrgResolver{org: org} }

func (o *OrgResolver) ID() graphql.ID { return marshalOrgID(o.org.ID) }
func (o *OrgResolver) ID() graphql.ID { return MarshalOrgID(o.org.ID) }

func marshalOrgID(id int32) graphql.ID { return relay.MarshalID("Org", id) }
func MarshalOrgID(id int32) graphql.ID { return relay.MarshalID("Org", id) }

func UnmarshalOrgID(id graphql.ID) (orgID int32, err error) {
err = relay.UnmarshalSpec(id, &orgID)
Expand Down
2 changes: 1 addition & 1 deletion cmd/frontend/graphqlbackend/saved_searches.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (r savedSearchResolver) Query() string { return r.s.Query }

func (r savedSearchResolver) Namespace(ctx context.Context) (*NamespaceResolver, error) {
if r.s.OrgID != nil {
n, err := NamespaceByID(ctx, marshalOrgID(*r.s.OrgID))
n, err := NamespaceByID(ctx, MarshalOrgID(*r.s.OrgID))
if err != nil {
return nil, err
}
Expand Down
64 changes: 54 additions & 10 deletions enterprise/internal/campaigns/resolvers/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func (r *Resolver) ChangesetSpecByID(ctx context.Context, id graphql.ID) (graphq

func (r *Resolver) CreateCampaign(ctx context.Context, args *graphqlbackend.CreateCampaignArgs) (graphqlbackend.CampaignResolver, error) {
var err error
tr, ctx := trace.New(ctx, "Resolver.CreateCampaign", fmt.Sprintf("CampaignSpec %s", args.CampaignSpec))
tr, _ := trace.New(ctx, "Resolver.CreateCampaign", fmt.Sprintf("CampaignSpec %s", args.CampaignSpec))
defer func() {
tr.SetError(err)
tr.Finish()
Expand All @@ -194,14 +194,9 @@ func (r *Resolver) ApplyCampaign(ctx context.Context, args *graphqlbackend.Apply
tr.Finish()
}()

user, err := db.Users.GetByCurrentAuthUser(ctx)
if err != nil {
return nil, errors.Wrapf(err, "%v", backend.ErrNotAuthenticated)
}

// 🚨 SECURITY: Only site admins may create a campaign for now.
if !user.SiteAdmin {
return nil, backend.ErrMustBeSiteAdmin
// 🚨 SECURITY: Only site admins may apply campaigns for now.
if err := backend.CheckCurrentUserIsSiteAdmin(ctx); err != nil {
return nil, err
}

opts := ee.ApplyCampaignOpts{}
Expand All @@ -211,6 +206,10 @@ func (r *Resolver) ApplyCampaign(ctx context.Context, args *graphqlbackend.Apply
return nil, err
}

if opts.CampaignSpecRandID == "" {
return nil, ErrIDIsZero
}

if args.EnsureCampaign != nil {
opts.EnsureCampaignID, err = campaigns.UnmarshalCampaignID(*args.EnsureCampaign)
if err != nil {
Expand Down Expand Up @@ -324,7 +323,52 @@ func (r *Resolver) CreateChangesetSpec(ctx context.Context, args *graphqlbackend
}

func (r *Resolver) MoveCampaign(ctx context.Context, args *graphqlbackend.MoveCampaignArgs) (graphqlbackend.CampaignResolver, error) {
return nil, errors.New("TODO: not implemented")
var err error
tr, ctx := trace.New(ctx, "Resolver.MoveCampaign", fmt.Sprintf("Campaign %s", args.Campaign))
defer func() {
tr.SetError(err)
tr.Finish()
}()

campaignID, err := campaigns.UnmarshalCampaignID(args.Campaign)
if err != nil {
return nil, err
}

if campaignID == 0 {
return nil, ErrIDIsZero
}

var opts ee.MoveCampaignOpts

if args.NewName != nil {
opts.NewName = *args.NewName
}

if args.NewNamespace != nil {
newNamespace := *args.NewNamespace
switch relay.UnmarshalKind(newNamespace) {
case "User":
err = relay.UnmarshalSpec(newNamespace, &opts.NewNamespaceUserID)
case "Org":
err = relay.UnmarshalSpec(newNamespace, &opts.NewNamespaceOrgID)
default:
err = errors.Errorf("Invalid namespace %q", newNamespace)
}

if err != nil {
return nil, err
}
}

svc := ee.NewService(r.store, r.httpFactory)
// 🚨 SECURITY: MoveCampaign checks whether the current user is authorized.
campaign, err := svc.MoveCampaign(ctx, opts)
if err != nil {
return nil, err
}

return &campaignResolver{store: r.store, httpFactory: r.httpFactory, Campaign: campaign}, nil
}

func (r *Resolver) DeleteCampaign(ctx context.Context, args *graphqlbackend.DeleteCampaignArgs) (_ *graphqlbackend.EmptyResponse, err error) {
Expand Down
93 changes: 93 additions & 0 deletions enterprise/internal/campaigns/resolvers/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/graph-gophers/graphql-go"
"github.com/sourcegraph/sourcegraph/cmd/frontend/backend"
"github.com/sourcegraph/sourcegraph/cmd/frontend/db"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend"
"github.com/sourcegraph/sourcegraph/cmd/repo-updater/repos"
ee "github.com/sourcegraph/sourcegraph/enterprise/internal/campaigns"
Expand Down Expand Up @@ -806,6 +807,7 @@ func TestNullIDResilience(t *testing.T) {
campaigns.MarshalCampaignID(0),
marshalExternalChangesetID(0),
marshalCampaignSpecRandID(""),
marshalChangesetSpecRandID(""),
}

for _, id := range ids {
Expand All @@ -823,6 +825,8 @@ func TestNullIDResilience(t *testing.T) {
fmt.Sprintf(`mutation { closeCampaign(campaign: %q) { id } }`, campaigns.MarshalCampaignID(0)),
fmt.Sprintf(`mutation { deleteCampaign(campaign: %q) { alwaysNil } }`, campaigns.MarshalCampaignID(0)),
fmt.Sprintf(`mutation { syncChangeset(changeset: %q) { alwaysNil } }`, marshalExternalChangesetID(0)),
fmt.Sprintf(`mutation { applyCampaign(campaignSpec: %q) { id } }`, marshalCampaignSpecRandID("")),
fmt.Sprintf(`mutation { moveCampaign(campaign: %q, newName: "foobar") { id } }`, campaigns.MarshalCampaignID(0)),
}

for _, m := range mutations {
Expand Down Expand Up @@ -1173,3 +1177,92 @@ mutation($campaignSpec: ID!, $ensureCampaign: ID){
}
}
`

func TestMoveCampaign(t *testing.T) {
if testing.Short() {
t.Skip()
}

ctx := context.Background()
dbtesting.SetupGlobalTestDB(t)

userID := insertTestUser(t, dbconn.Global, "move-campaign1", true)

org, err := db.Orgs.Create(ctx, "org", nil)
if err != nil {
t.Fatal(err)
}

store := ee.NewStore(dbconn.Global)

campaignSpec := &campaigns.CampaignSpec{
RawSpec: ct.TestRawCampaignSpec,
UserID: userID,
NamespaceUserID: userID,
}
if err := store.CreateCampaignSpec(ctx, campaignSpec); err != nil {
t.Fatal(err)
}

campaign := &campaigns.Campaign{
CampaignSpecID: campaignSpec.ID,
Name: "old-name",
AuthorID: userID,
NamespaceUserID: campaignSpec.UserID,
}
if err := store.CreateCampaign(ctx, campaign); err != nil {
t.Fatal(err)
}

r := &Resolver{store: store}
s, err := graphqlbackend.NewSchema(r, nil, nil)
if err != nil {
t.Fatal(err)
}

// Move to a new name
input := map[string]interface{}{
"campaign": string(campaigns.MarshalCampaignID(campaign.ID)),
"newName": "new-name",
}

var response struct{ MoveCampaign apitest.Campaign }
actorCtx := actor.WithActor(ctx, actor.FromUser(userID))
apitest.MustExec(actorCtx, t, s, input, &response, mutationMoveCampaign)

haveCampaign := response.MoveCampaign
fmt.Printf("haveCampaign=%+v\n", haveCampaign)
if diff := cmp.Diff(input["newName"], haveCampaign.Name); diff != "" {
t.Fatalf("unexpected name (-want +got):\n%s", diff)
}

// Move to a new namespace
orgApiID := graphqlbackend.MarshalOrgID(org.ID)
input = map[string]interface{}{
"campaign": string(campaigns.MarshalCampaignID(campaign.ID)),
"newNamespace": orgApiID,
}

apitest.MustExec(actorCtx, t, s, input, &response, mutationMoveCampaign)

haveCampaign = response.MoveCampaign
if diff := cmp.Diff(string(orgApiID), haveCampaign.Namespace.ID); diff != "" {
t.Fatalf("unexpected namespace (-want +got):\n%s", diff)
}
}

const mutationMoveCampaign = `
fragment u on User { id, databaseID, siteAdmin }
fragment o on Org { id, name }
mutation($campaign: ID!, $newName: String, $newNamespace: ID){
moveCampaign(campaign: $campaign, newName: $newName, newNamespace: $newNamespace) {
id, name, description, branch
author { ...u }
namespace {
... on User { ...u }
... on Org { ...o }
}
}
}
`
54 changes: 54 additions & 0 deletions enterprise/internal/campaigns/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,60 @@ func (s *Service) ApplyCampaign(ctx context.Context, opts ApplyCampaignOpts) (ca
return campaign, tx.UpdateCampaign(ctx, campaign)
}

type MoveCampaignOpts struct {
CampaignID int64

NewName string

NewNamespaceUserID int32
NewNamespaceOrgID int32
}

func (o MoveCampaignOpts) String() string {
return fmt.Sprintf(
"CampaignID %d, NewName %q, NewNamespaceUserID %d, NewNamespaceOrgID %d",
o.CampaignID,
o.NewName,
o.NewNamespaceUserID,
o.NewNamespaceOrgID,
)
}

// MoveCampaign moves the campaign from one namespace to another and/or renames
// the campaign.
func (s *Service) MoveCampaign(ctx context.Context, opts MoveCampaignOpts) (campaign *campaigns.Campaign, err error) {
tr, ctx := trace.New(ctx, "Service.MoveCampaign", opts.String())
defer func() {
tr.SetError(err)
tr.Finish()
}()

tx, err := s.store.Transact(ctx)
if err != nil {
return nil, err
}
defer tx.Done(&err)

campaign, err = tx.GetCampaign(ctx, GetCampaignOpts{ID: opts.CampaignID})
if err != nil {
return nil, err
}

if opts.NewName != "" {
campaign.Name = opts.NewName
}

if opts.NewNamespaceOrgID != 0 {
campaign.NamespaceOrgID = opts.NewNamespaceOrgID
campaign.NamespaceUserID = 0
} else if opts.NewNamespaceUserID != 0 {
campaign.NamespaceUserID = opts.NewNamespaceUserID
campaign.NamespaceOrgID = 0
}

return campaign, tx.UpdateCampaign(ctx, campaign)
}

// ErrEnsureCampaignFailed is returned by ApplyCampaign when a ensureCampaignID
// is provided but a campaign with the name specified the campaignSpec exists
// in the given namespace but has a different ID.
Expand Down
78 changes: 78 additions & 0 deletions enterprise/internal/campaigns/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,84 @@ func TestService(t *testing.T) {
})
})
})

t.Run("MoveCampaign", func(t *testing.T) {
svc := NewServiceWithClock(store, cf, clock)

createCampaign := func(t *testing.T, name string, authorID, userID, orgID int32) *campaigns.Campaign {
t.Helper()

c := &campaigns.Campaign{
AuthorID: authorID,
NamespaceUserID: userID,
NamespaceOrgID: orgID,
Name: name,
}

if err := store.CreateCampaign(ctx, c); err != nil {
t.Fatal(err)
}

return c
}

t.Run("new name", func(t *testing.T) {
campaign := createCampaign(t, "old-name", user.ID, user.ID, 0)

opts := MoveCampaignOpts{CampaignID: campaign.ID, NewName: "new-name"}
moved, err := svc.MoveCampaign(ctx, opts)
if err != nil {
t.Fatal(err)
}

if have, want := moved.Name, opts.NewName; have != want {
t.Fatalf("wrong name. want=%q, have=%q", want, have)
}
})

t.Run("new user namespace", func(t *testing.T) {
campaign := createCampaign(t, "old-name", user.ID, user.ID, 0)

user2 := createTestUser(ctx, t)

opts := MoveCampaignOpts{CampaignID: campaign.ID, NewNamespaceUserID: user2.ID}
moved, err := svc.MoveCampaign(ctx, opts)
if err != nil {
t.Fatal(err)
}

if have, want := moved.NamespaceUserID, opts.NewNamespaceUserID; have != want {
t.Fatalf("wrong NamespaceUserID. want=%d, have=%d", want, have)
}

if have, want := moved.NamespaceOrgID, opts.NewNamespaceOrgID; have != want {
t.Fatalf("wrong NamespaceOrgID. want=%d, have=%d", want, have)
}
})

t.Run("new org namespace", func(t *testing.T) {
campaign := createCampaign(t, "old-name", user.ID, user.ID, 0)

org, err := db.Orgs.Create(ctx, "org", nil)
if err != nil {
t.Fatal(err)
}

opts := MoveCampaignOpts{CampaignID: campaign.ID, NewNamespaceOrgID: org.ID}
moved, err := svc.MoveCampaign(ctx, opts)
if err != nil {
t.Fatal(err)
}

if have, want := moved.NamespaceUserID, opts.NewNamespaceUserID; have != want {
t.Fatalf("wrong NamespaceUserID. want=%d, have=%d", want, have)
}

if have, want := moved.NamespaceOrgID, opts.NewNamespaceOrgID; have != want {
t.Fatalf("wrong NamespaceOrgID. want=%d, have=%d", want, have)
}
})
})
}

var testUser = db.NewUser{
Expand Down

0 comments on commit dcdc465

Please sign in to comment.