diff --git a/app/controllers/api/projects_controller.rb b/app/controllers/api/projects_controller.rb new file mode 100644 index 00000000..189eb15f --- /dev/null +++ b/app/controllers/api/projects_controller.rb @@ -0,0 +1,39 @@ +module Api + class ProjectsController < ApiController + + def index + path_prefix = "./repos/#{params[:user]}/#{params[:repo]}/projects" + projects = Project.new(gh.connection, path_prefix).all do |request| + request.headers["Accept"] = "application/vnd.github.inertia-preview.full+json" + end + render json: projects + end + + def show + path_prefix = "./repos/#{params[:user]}/#{params[:repo]}/projects/#{params[:id]}" + project = Project.new(gh.connection, path_prefix).all do |request| + request.headers["Accept"] = "application/vnd.github.inertia-preview.full+json" + end + + path_prefix = "./repos/#{params[:user]}/#{params[:repo]}/projects/#{params[:id]}/columns" + columns = Project.new(gh.connection, path_prefix).all do |request| + request.headers["Accept"] = "application/vnd.github.inertia-preview.full+json" + end + + columns.each do |column| + path_prefix = "./repos/#{params[:user]}/#{params[:repo]}/projects/columns/#{column['id']}/cards" + column["cards"] = Project.new(gh.connection, path_prefix).all do |request| + request.headers["Accept"] = "application/vnd.github.inertia-preview.full+json" + end + end + + render json: { project: project, columns: columns } + end + + class Project < Ghee::ResourceProxy + accept_header "application/vnd.github.inertia-preview.full+json" + end + + end +end + diff --git a/config/routes.rb b/config/routes.rb index 51117bbd..977b7217 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -66,6 +66,7 @@ resources :integrations, only: [:index, :create, :destroy] resources :milestones, only: [:create, :update] resources :links, only: [:index, :create] + resources :projects, only: [:index, :show] delete 'links' => 'links#destroy' post 'links/validate' => 'links#validate' put 'links/update' => 'links#update' diff --git a/ember-app/app/components/columns/hb-project.js b/ember-app/app/components/columns/hb-project.js new file mode 100644 index 00000000..c55ef155 --- /dev/null +++ b/ember-app/app/components/columns/hb-project.js @@ -0,0 +1,69 @@ +import Ember from "ember"; +import HbColumn from "../columns/hb-column"; +import Messaging from 'app/mixins/messaging'; + +var HbProjectComponent = HbColumn.extend( + Messaging, { + classNames: ["column"], + isTaskColumn: true, + + //Scrolling Columns Tolerance + _toleranceDown: 59, + _toleranceUp: 70, + + sortedIssues: function () { + var cards = this.get('model.cards'); + var issues = this.get("issues") + .filter(function(issue){ + return cards.find(function(c){ + return Ember.get(c, 'content_url') == Ember.get(issue, 'url'); + }) != null; + }) + return issues || []; + }.property("issues.[]"), + sortStrategy: function(a,b){ + }, + moveIssue: function(issue, order, cancelMove){ + // no-op + }, + isCreateVisible: false, + topOrderNumber: function(){ + var issues = this.get("issues") + .filter(function(i) { return !i.get("isArchived");}) + .sort(this.sortStrategy); + var first = this.get("issues") + .filter(function(i) { return !i.get("isArchived");}) + .sort(function (a, b){ + return a.data._data.order - b.data._data.order; + }).get("firstObject"); + if(issues.length){ + var milestone_order = this.cardMover.moveToTop(issues.get("firstObject.data")); + var order = { milestone_order: milestone_order}; + if(first){ + order.order = this.cardMover.moveToTop(first.data, 'order'); + } + return order; + } else { + if(first){ + return { order: this.cardMover.moveToTop(first.data, 'order') }; + } + return {}; + } + }.property("sortedIssues.[]"), + isCollapsed: Ember.computed({ + get: function(){ + return this.get("settings.projectColumn" + this.get("model.number") + "Collapsed"); + }, + set: function(key, value){ + this.set("settings.projectColumn" + this.get("model.number") + "Collapsed", value); + return value; + } + }).property(), + actions: { + toggleCollapsed(){ + this.toggleProperty('isCollapsed'); + } + } +}); + +export default HbProjectComponent; diff --git a/ember-app/app/controllers/projects.js b/ember-app/app/controllers/projects.js new file mode 100644 index 00000000..1b4abe2c --- /dev/null +++ b/ember-app/app/controllers/projects.js @@ -0,0 +1,29 @@ +import Ember from 'ember'; + +var ProjectsController = Ember.Controller.extend({ + application: Ember.inject.controller(), + + qps: Ember.inject.service("query-params"), + queryParams: [ + {"qps.searchParams": "search"}, + {"qps.repoParams": "repo"}, + {"qps.assigneeParams": "assignee"}, + {"qps.milestoneParams": "milestone"}, + {"qps.labelParams": "label"}, + {"qps.cardParams": "card"} + ], + + filters: Ember.inject.service(), + filtersActive: Ember.computed.alias("filters.active"), + + isCollaborator: function(){ + return this.get("model.repo.isCollaborator"); + }.property('model.repo.isCollaborator'), + + isSidebarOpen: Ember.computed.alias("application.isSidebarOpen"), + + actions: { + } +}); + +export default ProjectsController; diff --git a/ember-app/app/controllers/projects/project.js b/ember-app/app/controllers/projects/project.js new file mode 100644 index 00000000..17d7fe23 --- /dev/null +++ b/ember-app/app/controllers/projects/project.js @@ -0,0 +1,18 @@ +import Ember from 'ember'; +var ProjectController = Ember.Controller.extend({ + application: Ember.inject.controller(), + registeredColumns: Ember.A(), + actions: { + registerColumn: function(column_component){ + this.get("registeredColumns").pushObject(column_component); + }, + unregisterColumn: function(column_component){ + this.get("registeredColumns").removeObject(column_component); + }, + openFullscreenIssue(issue){ + this.get("target").send("openFullscreenIssue", issue); + } + } +}); + +export default ProjectController; diff --git a/ember-app/app/models/new/board.js b/ember-app/app/models/new/board.js index b08d4a26..1c2c89ab 100644 --- a/ember-app/app/models/new/board.js +++ b/ember-app/app/models/new/board.js @@ -133,6 +133,13 @@ var Board = Model.extend({ return Ember.RSVP.all(promises).then((issues)=>{ return _.flatten(issues); }); + }, + fetchProjects: function(repo) { + var board = this; + return repo.fetchProjects().then(function(projects){ + repo.set('projects', projects); + return board; + }); } }); diff --git a/ember-app/app/models/new/project-column.js b/ember-app/app/models/new/project-column.js new file mode 100644 index 00000000..f3cf781f --- /dev/null +++ b/ember-app/app/models/new/project-column.js @@ -0,0 +1,32 @@ +import Ember from 'ember'; +import Model from '../model'; + +var ProjectColumn = Model.extend({ + issueNumberRegex: /\d+$/, + isLastColumn: Ember.computed('project.columns.[]', { + get() { + return this.get('project.columns.lastObject.data.id') === this.get('data.id'); + } + }), + sortedIssues: function(){ + var issues = this.get('project.repo.issues'); + return this.get("cards").map((card)=>{ + if(card.content_url){ + var match = card.content_url.match(this.get('issueNumberRegex')); + if(match){ + var issue = issues.findBy('number', parseInt(match[0])); + if(issue){ + Ember.set(card, 'issue', issue); + } else { + Ember.set(card, 'note', `Issue #${match[0]} has been archived`); + } + } + } + return card; + }).filter((card) => { + return Ember.get(card, 'note') || !Ember.get(card, 'issue.isArchived'); + }); + }.property("data.cards.[]", 'project.repo.issues.[]'), +}); + +export default ProjectColumn; diff --git a/ember-app/app/models/new/project.js b/ember-app/app/models/new/project.js new file mode 100644 index 00000000..59f18ad8 --- /dev/null +++ b/ember-app/app/models/new/project.js @@ -0,0 +1,16 @@ +import Ember from 'ember'; +import Model from '../model'; +import ProjectColumn from './project-column'; + +var Project = Model.extend({ + columns: Ember.computed('data.columns', { + get: function(){ + var project = this; + return this.get('data.columns').map((c) =>{ + return ProjectColumn.create({ data: c, project: this }) + }); + } + }) +}); + +export default Project; diff --git a/ember-app/app/models/new/repo.js b/ember-app/app/models/new/repo.js index 857d9e9c..b1c4b18a 100644 --- a/ember-app/app/models/new/repo.js +++ b/ember-app/app/models/new/repo.js @@ -2,6 +2,7 @@ import Ember from 'ember'; import Model from '../model'; import Board from './board'; import Issue from './issue'; +import Project from './project'; import Milestone from './milestone'; import Integration from 'app/models/integration'; import Health from 'app/models/health'; @@ -230,6 +231,17 @@ var Repo = Model.extend({ fetchIssues: function(options){ var url = `/api/${this.get('data.repo.full_name')}/issues`; return Ember.$.getJSON(url,{ options: options }); + }, + fetchProjects: function(){ + var url = `/api/${this.get('data.repo.full_name')}/projects`; + return Ember.$.getJSON(url); + }, + fetchProject: function(number){ + var repo = this; + var url = `/api/${this.get('data.repo.full_name')}/projects/${number}`; + return Ember.$.getJSON(url).then((response) => { + return Project.create({data: response, repo: repo}); + }); } }); diff --git a/ember-app/app/router.js b/ember-app/app/router.js index fcf73c3c..89ddc6ce 100644 --- a/ember-app/app/router.js +++ b/ember-app/app/router.js @@ -15,6 +15,12 @@ Router.map(function() { }); this.route("milestones.missing"); + this.resource("projects", function(){ + this.resource("projects.project", {path:"/:project_id"}, function(){ + this.resource("projects.project.issue", {path:"/issues/:issue_id"}); + }); + }); + this.resource("settings", function(){ this.resource('settings.integrations', {path: '/integrations'}, function(){ diff --git a/ember-app/app/routes/projects.js b/ember-app/app/routes/projects.js new file mode 100644 index 00000000..74dc3aa8 --- /dev/null +++ b/ember-app/app/routes/projects.js @@ -0,0 +1,44 @@ +import Board from 'app/models/new/board'; +import Ember from 'ember'; +import CreateIssue from 'app/models/forms/create-issue'; +import animateModalClose from 'app/config/animate-modal-close'; + +var ProjectsRoute = Ember.Route.extend({ + qps: Ember.inject.service("query-params"), + + model: function(){ + var repo = this.modelFor("application"); + return Board.fetch(repo).then(function(board){ + return board.fetchProjects(repo); + }); + }, + afterModel: function (model){ + if (model.get("isLoaded")) { + return; + } + }, + renderTemplate: function() { + this._super.apply(this, arguments); + + var assignee = this.controllerFor("assignee"); + assignee.set("model", this.currentModel); + this.render('assignee', { + into: 'projects', + outlet: 'sidebarTop', + controller: assignee + }); + + this.render('filters', {into: 'projects', outlet: 'sidebarMiddle'}); + }, + setupController: function(controller, model){ + this._super(controller, model); + this.get("qps").applyFilterBuffer(); + this.get("qps").applySearchBuffer(); + }, + + actions : { + } +}); + +export default ProjectsRoute; + diff --git a/ember-app/app/routes/projects/project.js b/ember-app/app/routes/projects/project.js new file mode 100644 index 00000000..d110fd23 --- /dev/null +++ b/ember-app/app/routes/projects/project.js @@ -0,0 +1,16 @@ +import Ember from 'ember'; + +var ProjectRoute = Ember.Route.extend({ + model: function(params) { + var repo = this.modelFor("application"); + var project = repo.get('projects').findBy('number', parseInt(params.project_id)); + return repo.fetchProject(project.number); + }, + actions: { + openFullscreenIssue: function(model) { + this.transitionTo("projects.project.issue", this.currentModel, model); + }, + } +}); + +export default ProjectRoute; diff --git a/ember-app/app/routes/projects/project/issue.js b/ember-app/app/routes/projects/project/issue.js new file mode 100644 index 00000000..e00002dd --- /dev/null +++ b/ember-app/app/routes/projects/project/issue.js @@ -0,0 +1,24 @@ +import Route from 'app/routes/issue'; + + +var ProjectIssueRoute = Route.extend({ + model : function (params, transition){ + // hacks! + var issue = this.modelFor("application") + .get("board.issues") + .findBy('id', parseInt(params.issue_id)); + if(issue) { return issue; } + + transition.abort(); + this.transitionTo("projects.project", params.project_id); + }, + actions: { + closeModal: function () { + this.transitionTo("projects.project"); + return true; + } + } +}); + +export default ProjectIssueRoute; + diff --git a/ember-app/app/templates/application.hbs b/ember-app/app/templates/application.hbs index 727a8fe5..3537e9c5 100644 --- a/ember-app/app/templates/application.hbs +++ b/ember-app/app/templates/application.hbs @@ -11,6 +11,11 @@ {{flash/hb-flash-message}}