code.gitea.io/gitea@v1.22.3/services/convert/issue.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package convert
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/url"
    10  	"strings"
    11  
    12  	issues_model "code.gitea.io/gitea/models/issues"
    13  	repo_model "code.gitea.io/gitea/models/repo"
    14  	user_model "code.gitea.io/gitea/models/user"
    15  	"code.gitea.io/gitea/modules/label"
    16  	"code.gitea.io/gitea/modules/log"
    17  	"code.gitea.io/gitea/modules/setting"
    18  	api "code.gitea.io/gitea/modules/structs"
    19  )
    20  
    21  func ToIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
    22  	return toIssue(ctx, doer, issue, WebAssetDownloadURL)
    23  }
    24  
    25  // ToAPIIssue converts an Issue to API format
    26  // it assumes some fields assigned with values:
    27  // Required - Poster, Labels,
    28  // Optional - Milestone, Assignee, PullRequest
    29  func ToAPIIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
    30  	return toIssue(ctx, doer, issue, APIAssetDownloadURL)
    31  }
    32  
    33  func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Issue {
    34  	if err := issue.LoadLabels(ctx); err != nil {
    35  		return &api.Issue{}
    36  	}
    37  	if err := issue.LoadPoster(ctx); err != nil {
    38  		return &api.Issue{}
    39  	}
    40  	if err := issue.LoadRepo(ctx); err != nil {
    41  		return &api.Issue{}
    42  	}
    43  
    44  	apiIssue := &api.Issue{
    45  		ID:          issue.ID,
    46  		Index:       issue.Index,
    47  		Poster:      ToUser(ctx, issue.Poster, doer),
    48  		Title:       issue.Title,
    49  		Body:        issue.Content,
    50  		Attachments: toAttachments(issue.Repo, issue.Attachments, getDownloadURL),
    51  		Ref:         issue.Ref,
    52  		State:       issue.State(),
    53  		IsLocked:    issue.IsLocked,
    54  		Comments:    issue.NumComments,
    55  		Created:     issue.CreatedUnix.AsTime(),
    56  		Updated:     issue.UpdatedUnix.AsTime(),
    57  		PinOrder:    issue.PinOrder,
    58  	}
    59  
    60  	if issue.Repo != nil {
    61  		if err := issue.Repo.LoadOwner(ctx); err != nil {
    62  			return &api.Issue{}
    63  		}
    64  		apiIssue.URL = issue.APIURL(ctx)
    65  		apiIssue.HTMLURL = issue.HTMLURL()
    66  		apiIssue.Labels = ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner)
    67  		apiIssue.Repo = &api.RepositoryMeta{
    68  			ID:       issue.Repo.ID,
    69  			Name:     issue.Repo.Name,
    70  			Owner:    issue.Repo.OwnerName,
    71  			FullName: issue.Repo.FullName(),
    72  		}
    73  	}
    74  
    75  	if issue.ClosedUnix != 0 {
    76  		apiIssue.Closed = issue.ClosedUnix.AsTimePtr()
    77  	}
    78  
    79  	if err := issue.LoadMilestone(ctx); err != nil {
    80  		return &api.Issue{}
    81  	}
    82  	if issue.Milestone != nil {
    83  		apiIssue.Milestone = ToAPIMilestone(issue.Milestone)
    84  	}
    85  
    86  	if err := issue.LoadAssignees(ctx); err != nil {
    87  		return &api.Issue{}
    88  	}
    89  	if len(issue.Assignees) > 0 {
    90  		for _, assignee := range issue.Assignees {
    91  			apiIssue.Assignees = append(apiIssue.Assignees, ToUser(ctx, assignee, nil))
    92  		}
    93  		apiIssue.Assignee = ToUser(ctx, issue.Assignees[0], nil) // For compatibility, we're keeping the first assignee as `apiIssue.Assignee`
    94  	}
    95  	if issue.IsPull {
    96  		if err := issue.LoadPullRequest(ctx); err != nil {
    97  			return &api.Issue{}
    98  		}
    99  		if issue.PullRequest != nil {
   100  			apiIssue.PullRequest = &api.PullRequestMeta{
   101  				HasMerged:        issue.PullRequest.HasMerged,
   102  				IsWorkInProgress: issue.PullRequest.IsWorkInProgress(ctx),
   103  			}
   104  			if issue.PullRequest.HasMerged {
   105  				apiIssue.PullRequest.Merged = issue.PullRequest.MergedUnix.AsTimePtr()
   106  			}
   107  			// Add pr's html url
   108  			apiIssue.PullRequest.HTMLURL = issue.HTMLURL()
   109  		}
   110  	}
   111  	if issue.DeadlineUnix != 0 {
   112  		apiIssue.Deadline = issue.DeadlineUnix.AsTimePtr()
   113  	}
   114  
   115  	return apiIssue
   116  }
   117  
   118  // ToIssueList converts an IssueList to API format
   119  func ToIssueList(ctx context.Context, doer *user_model.User, il issues_model.IssueList) []*api.Issue {
   120  	result := make([]*api.Issue, len(il))
   121  	for i := range il {
   122  		result[i] = ToIssue(ctx, doer, il[i])
   123  	}
   124  	return result
   125  }
   126  
   127  // ToAPIIssueList converts an IssueList to API format
   128  func ToAPIIssueList(ctx context.Context, doer *user_model.User, il issues_model.IssueList) []*api.Issue {
   129  	result := make([]*api.Issue, len(il))
   130  	for i := range il {
   131  		result[i] = ToAPIIssue(ctx, doer, il[i])
   132  	}
   133  	return result
   134  }
   135  
   136  // ToTrackedTime converts TrackedTime to API format
   137  func ToTrackedTime(ctx context.Context, doer *user_model.User, t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
   138  	apiT = &api.TrackedTime{
   139  		ID:      t.ID,
   140  		IssueID: t.IssueID,
   141  		UserID:  t.UserID,
   142  		Time:    t.Time,
   143  		Created: t.Created,
   144  	}
   145  	if t.Issue != nil {
   146  		apiT.Issue = ToAPIIssue(ctx, doer, t.Issue)
   147  	}
   148  	if t.User != nil {
   149  		apiT.UserName = t.User.Name
   150  	}
   151  	return apiT
   152  }
   153  
   154  // ToStopWatches convert Stopwatch list to api.StopWatches
   155  func ToStopWatches(ctx context.Context, sws []*issues_model.Stopwatch) (api.StopWatches, error) {
   156  	result := api.StopWatches(make([]api.StopWatch, 0, len(sws)))
   157  
   158  	issueCache := make(map[int64]*issues_model.Issue)
   159  	repoCache := make(map[int64]*repo_model.Repository)
   160  	var (
   161  		issue *issues_model.Issue
   162  		repo  *repo_model.Repository
   163  		ok    bool
   164  		err   error
   165  	)
   166  
   167  	for _, sw := range sws {
   168  		issue, ok = issueCache[sw.IssueID]
   169  		if !ok {
   170  			issue, err = issues_model.GetIssueByID(ctx, sw.IssueID)
   171  			if err != nil {
   172  				return nil, err
   173  			}
   174  		}
   175  		repo, ok = repoCache[issue.RepoID]
   176  		if !ok {
   177  			repo, err = repo_model.GetRepositoryByID(ctx, issue.RepoID)
   178  			if err != nil {
   179  				return nil, err
   180  			}
   181  		}
   182  
   183  		result = append(result, api.StopWatch{
   184  			Created:       sw.CreatedUnix.AsTime(),
   185  			Seconds:       sw.Seconds(),
   186  			Duration:      sw.Duration(),
   187  			IssueIndex:    issue.Index,
   188  			IssueTitle:    issue.Title,
   189  			RepoOwnerName: repo.OwnerName,
   190  			RepoName:      repo.Name,
   191  		})
   192  	}
   193  	return result, nil
   194  }
   195  
   196  // ToTrackedTimeList converts TrackedTimeList to API format
   197  func ToTrackedTimeList(ctx context.Context, doer *user_model.User, tl issues_model.TrackedTimeList) api.TrackedTimeList {
   198  	result := make([]*api.TrackedTime, 0, len(tl))
   199  	for _, t := range tl {
   200  		result = append(result, ToTrackedTime(ctx, doer, t))
   201  	}
   202  	return result
   203  }
   204  
   205  // ToLabel converts Label to API format
   206  func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_model.User) *api.Label {
   207  	result := &api.Label{
   208  		ID:          label.ID,
   209  		Name:        label.Name,
   210  		Exclusive:   label.Exclusive,
   211  		Color:       strings.TrimLeft(label.Color, "#"),
   212  		Description: label.Description,
   213  		IsArchived:  label.IsArchived(),
   214  	}
   215  
   216  	// calculate URL
   217  	if label.BelongsToRepo() && repo != nil {
   218  		if repo != nil {
   219  			result.URL = fmt.Sprintf("%s/labels/%d", repo.APIURL(), label.ID)
   220  		} else {
   221  			log.Error("ToLabel did not get repo to calculate url for label with id '%d'", label.ID)
   222  		}
   223  	} else { // BelongsToOrg
   224  		if org != nil {
   225  			result.URL = fmt.Sprintf("%sapi/v1/orgs/%s/labels/%d", setting.AppURL, url.PathEscape(org.Name), label.ID)
   226  		} else {
   227  			log.Error("ToLabel did not get org to calculate url for label with id '%d'", label.ID)
   228  		}
   229  	}
   230  
   231  	return result
   232  }
   233  
   234  // ToLabelList converts list of Label to API format
   235  func ToLabelList(labels []*issues_model.Label, repo *repo_model.Repository, org *user_model.User) []*api.Label {
   236  	result := make([]*api.Label, len(labels))
   237  	for i := range labels {
   238  		result[i] = ToLabel(labels[i], repo, org)
   239  	}
   240  	return result
   241  }
   242  
   243  // ToAPIMilestone converts Milestone into API Format
   244  func ToAPIMilestone(m *issues_model.Milestone) *api.Milestone {
   245  	apiMilestone := &api.Milestone{
   246  		ID:           m.ID,
   247  		State:        m.State(),
   248  		Title:        m.Name,
   249  		Description:  m.Content,
   250  		OpenIssues:   m.NumOpenIssues,
   251  		ClosedIssues: m.NumClosedIssues,
   252  		Created:      m.CreatedUnix.AsTime(),
   253  		Updated:      m.UpdatedUnix.AsTimePtr(),
   254  	}
   255  	if m.IsClosed {
   256  		apiMilestone.Closed = m.ClosedDateUnix.AsTimePtr()
   257  	}
   258  	if m.DeadlineUnix.Year() < 9999 {
   259  		apiMilestone.Deadline = m.DeadlineUnix.AsTimePtr()
   260  	}
   261  	return apiMilestone
   262  }
   263  
   264  // ToLabelTemplate converts Label to API format
   265  func ToLabelTemplate(label *label.Label) *api.LabelTemplate {
   266  	result := &api.LabelTemplate{
   267  		Name:        label.Name,
   268  		Exclusive:   label.Exclusive,
   269  		Color:       strings.TrimLeft(label.Color, "#"),
   270  		Description: label.Description,
   271  	}
   272  
   273  	return result
   274  }
   275  
   276  // ToLabelTemplateList converts list of Label to API format
   277  func ToLabelTemplateList(labels []*label.Label) []*api.LabelTemplate {
   278  	result := make([]*api.LabelTemplate, len(labels))
   279  	for i := range labels {
   280  		result[i] = ToLabelTemplate(labels[i])
   281  	}
   282  	return result
   283  }