diff --git a/models/project/column.go b/models/project/column.go index 9c9abb4599..6f4452984e 100644 --- a/models/project/column.go +++ b/models/project/column.go @@ -337,20 +337,6 @@ func SetDefaultColumn(ctx context.Context, projectID, columnID int64) error { }) } -func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (ColumnList, error) { - columns := make([]*Column, 0, 5) - if len(columnsIDs) == 0 { - return columns, nil - } - if err := db.GetEngine(ctx). - Where("project_id =?", projectID). - In("id", columnsIDs). - OrderBy("sorting").Find(&columns); err != nil { - return nil, err - } - return columns, nil -} - // MoveColumnsOnProject sorts columns in a project func MoveColumnsOnProject(ctx context.Context, project *Project, sortedColumnIDs map[int64]int64) error { return db.WithTx(ctx, func(ctx context.Context) error { diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go index 1ba67b1ff3..acd514deab 100644 --- a/routers/api/v1/repo/project.go +++ b/routers/api/v1/repo/project.go @@ -689,11 +689,10 @@ func ListProjectColumnIssues(ctx *context.APIContext) { listOptions := utils.GetListOptions(ctx) issuesOpts := &issues_model.IssuesOptions{ - Paginator: &listOptions, - RepoIDs: []int64{ctx.Repo.Repository.ID}, - ProjectID: column.ProjectID, - ProjectColumnID: column.ID, - SortType: issues_model.SortTypeProjectColumnSorting, + Paginator: &listOptions, + RepoIDs: []int64{ctx.Repo.Repository.ID}, + ProjectIDs: []int64{column.ProjectID}, + SortType: issues_model.SortTypeProjectColumnSorting, } count, err := issues_model.CountIssues(ctx, issuesOpts) @@ -853,7 +852,7 @@ func assignIssueToProjectColumn(ctx *context.APIContext, add bool) { } projectID = 0 } - if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, projectID, column.ID); err != nil { + if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, []int64{projectID}); err != nil { ctx.APIErrorInternal(err) return } diff --git a/routers/web/repo/issue_page_meta.go b/routers/web/repo/issue_page_meta.go index 635ae8ef9c..428171dd0e 100644 --- a/routers/web/repo/issue_page_meta.go +++ b/routers/web/repo/issue_page_meta.go @@ -33,12 +33,15 @@ type issueSidebarAssigneesData struct { CandidateAssignees []*user_model.User } -type issueSidebarProjectsData struct { - SelectedProjectIDs []int64 // TODO: support multiple projects in the future +type issueSidebarProjectCardData struct { + Project *project_model.Project + Columns []*project_model.Column + SelectedColumn *project_model.Column +} - // the "selected" fields are only valid when len(SelectedProjectIDs)==1 - SelectedProjectColumns []*project_model.Column - SelectedProjectColumn *project_model.Column +type issueSidebarProjectsData struct { + SelectedProjectIDs []int64 + ProjectCards []*issueSidebarProjectCardData OpenProjects []*project_model.Project ClosedProjects []*project_model.Project @@ -107,7 +110,7 @@ func retrieveRepoIssueMetaData(ctx *context.Context, repo *repo_model.Repository // A reader(creator) could update some meta (eg: target branch), but can't change assignees anymore. // For non-creator users, only writers could update some meta (eg: assignees, milestone, project) // Need to clarify the logic and add some tests in the future - data.CanModifyIssueOrPull = ctx.Repo.CanWriteIssuesOrPulls(isPull) && !ctx.Repo.Repository.IsArchived + data.CanModifyIssueOrPull = ctx.Repo.Permission.CanWriteIssuesOrPulls(isPull) && !ctx.Repo.Repository.IsArchived if !data.CanModifyIssueOrPull { return data } @@ -168,34 +171,80 @@ func (d *IssuePageMetaData) retrieveAssigneesData(ctx *context.Context) { ctx.Data["Assignees"] = d.AssigneesData.CandidateAssignees } -func (d *IssuePageMetaData) retrieveProjectData(ctx *context.Context) { - if d.Issue == nil || d.Issue.Project == nil { +func (d *IssuePageMetaData) retrieveProjectCardsForExistingIssue(ctx *context.Context) { + if err := d.Issue.LoadProjects(ctx); err != nil { + ctx.ServerError("LoadProjects", err) return } - d.ProjectsData.SelectedProjectIDs = []int64{d.Issue.Project.ID} - columns, err := project_model.GetProjectColumns(ctx, d.Issue.Project.ID, db.ListOptionsAll) + + // Load column mappings for all projects + projectColumnMap, err := d.Issue.ProjectColumnMap(ctx) if err != nil { - ctx.ServerError("GetProjectColumns", err) + ctx.ServerError("ProjectColumnMap", err) return } - d.ProjectsData.SelectedProjectColumns = columns - columnID, err := d.Issue.ProjectColumnID(ctx) - if err != nil { - ctx.ServerError("ProjectColumnID", err) - return - } - for _, col := range columns { - if col.ID == columnID { - d.ProjectsData.SelectedProjectColumn = col - break + + // Build project cards for each project + d.ProjectsData.ProjectCards = make([]*issueSidebarProjectCardData, 0, len(d.Issue.Projects)) + for _, project := range d.Issue.Projects { + columns, err := project.GetColumns(ctx) + if err != nil { + ctx.ServerError("GetProjectColumns", err) + return } + + var selectedColumn *project_model.Column + columnID := projectColumnMap[project.ID] + for _, col := range columns { + if col.ID == columnID { + selectedColumn = col + break + } + } + + if selectedColumn == nil { + selectedColumn, err = project.MustDefaultColumn(ctx) + if err != nil { + ctx.ServerError("MustDefaultColumn", err) + return + } + } + d.ProjectsData.ProjectCards = append(d.ProjectsData.ProjectCards, &issueSidebarProjectCardData{ + Project: project, + Columns: columns, + SelectedColumn: selectedColumn, + }) + } + d.ProjectsData.SelectedProjectIDs = make([]int64, 0, len(d.ProjectsData.ProjectCards)) + for _, card := range d.ProjectsData.ProjectCards { + d.ProjectsData.SelectedProjectIDs = append(d.ProjectsData.SelectedProjectIDs, card.Project.ID) } } -func (d *IssuePageMetaData) retrieveProjectsDataForIssueWriter(ctx *context.Context) { - if d.Issue != nil && d.Issue.Project != nil { - d.ProjectsData.SelectedProjectIDs = []int64{d.Issue.Project.ID} +func (d *IssuePageMetaData) retrieveProjectData(ctx *context.Context) { + if d.Issue == nil { + return } + d.retrieveProjectCardsForExistingIssue(ctx) +} + +func (d *IssuePageMetaData) SetSelectedProjectIDs(ids []int64) { + allProjects := map[int64]*project_model.Project{} + for _, p := range d.ProjectsData.OpenProjects { + allProjects[p.ID] = p + } + for _, p := range d.ProjectsData.ClosedProjects { + allProjects[p.ID] = p + } + for _, id := range ids { + if project, ok := allProjects[id]; ok { + d.ProjectsData.ProjectCards = append(d.ProjectsData.ProjectCards, &issueSidebarProjectCardData{Project: project}) + } + } + d.ProjectsData.SelectedProjectIDs = ids +} + +func (d *IssuePageMetaData) retrieveProjectsDataForIssueWriter(ctx *context.Context) { d.ProjectsData.OpenProjects, d.ProjectsData.ClosedProjects = retrieveProjectsInternal(ctx, ctx.Repo.Repository) } diff --git a/services/convert/issue.go b/services/convert/issue.go index 8e3adaa82d..08cd9d71c8 100644 --- a/services/convert/issue.go +++ b/services/convert/issue.go @@ -99,7 +99,7 @@ func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Iss return &api.Issue{} } if len(issue.Projects) > 0 { - apiIssue.Projects = ToAPIProjectList(issue.Projects) + apiIssue.Projects = ToProjectList(ctx, issue.Projects, doer) } if err := issue.LoadAssignees(ctx); err != nil { diff --git a/services/projects/issue.go b/services/projects/issue.go index b8e4390fef..01d067508f 100644 --- a/services/projects/issue.go +++ b/services/projects/issue.go @@ -63,10 +63,11 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum continue } - projectColumnID, err := curIssue.ProjectColumnID(ctx) + projectColumnMap, err := curIssue.ProjectColumnMap(ctx) if err != nil { return err } + projectColumnID := projectColumnMap[column.ProjectID] if projectColumnID != column.ID { // add timeline to issue @@ -121,7 +122,7 @@ func LoadIssuesAssigneesForProject(ctx context.Context, issuesMap map[int64]issu // LoadIssuesFromProject load issues assigned to each project column inside the given project func LoadIssuesFromProject(ctx context.Context, project *project_model.Project, opts *issues_model.IssuesOptions) (results map[int64]issues_model.IssueList, _ error) { issueList, err := issues_model.Issues(ctx, opts.Copy(func(o *issues_model.IssuesOptions) { - o.ProjectID = project.ID + o.ProjectIDs = []int64{project.ID} o.SortType = issues_model.SortTypeProjectColumnSorting })) if err != nil { @@ -215,7 +216,7 @@ func LoadIssueNumbersForProject(ctx context.Context, project *project_model.Proj // for user or org projects, we need to check access permissions opts := issues_model.IssuesOptions{ - ProjectID: project.ID, + ProjectIDs: []int64{project.ID}, Doer: doer, AllPublic: doer == nil, Owner: project.Owner,