repos store

This commit is contained in:
Radek Stepan 2018-06-29 11:21:17 -04:00
parent 516c2af417
commit cb5d6ea712
3 changed files with 128 additions and 199 deletions

View File

@ -56,45 +56,6 @@ export default {
request(data, cb);
},
// Get all open milestones.
allMilestones: (user, { owner, name }, cb) => {
let token = (user && user.credential != null) ? user.credential.accessToken : null;
let data = _.defaults({
'path': `/repos/${owner}/${name}/milestones`,
'query': { 'state': 'open', 'sort': 'due_date', 'direction': 'asc' },
'headers': headers(token)
}, defaults.github);
request(data, cb);
},
// Get one open milestone.
oneMilestone: (user, { owner, name, milestone }, cb) => {
let token = (user && user.credential != null) ? user.credential.accessToken : null;
let data = _.defaults({
'path': `/repos/${owner}/${name}/milestones/${milestone}`,
'query': { 'state': 'open', 'sort': 'due_date', 'direction': 'asc' },
'headers': headers(token)
}, defaults.github);
request(data, function(err, data) {
console.log('oneMilestone', data);
cb(err, data);
});
},
// Get all issues for a state..
allIssues: (user, { owner, name, milestone }, query, cb) => {
let token = (user && user.credential != null) ? user.credential.accessToken : null;
let data = _.defaults({
'path': `/repos/${owner}/${name}/issues`,
'query': _.extend(query, { milestone, 'per_page': '100' }),
'headers': headers(token)
}, defaults.github);
return request(data, cb);
},
allProjects: (user, { owner, name }, cb) => {
let token = (user && user.credential != null) ? user.credential.accessToken : null;
@ -117,10 +78,8 @@ export default {
},
method: 'POST',
}, defaults.github);
request(data, (err, result) => {
console.log('graphql response!', err, result);
cb(err, result);
});
request(data, cb);
},
oneProject: (user, { owner, name, project }, cb) => {
@ -147,11 +106,8 @@ export default {
},
method: 'POST',
}, defaults.github);
request(data, (err, result) => {
console.log('graphql response!', err, result);
// Filter down to cards that are issues (as opposed to notes) here?
cb(err, result);
});
request(data, cb);
}
};
@ -246,6 +202,3 @@ let error = (err) => {
return text;
};
// oneMilestone:
// - returns lists of open and closed issues.

View File

@ -1,8 +1,8 @@
// The app store needs to go last because it loads user.
import projectsStore from './projectsStore.js';
import reposStore from './reposStore.js';
import appStore from './appStore.js';
export default {
'app': appStore,
'projects': projectsStore
'repos': reposStore
};

View File

@ -12,17 +12,17 @@ import stats from '../modules/stats.js';
import request from '../modules/github/request.js';
import issues from '../modules/github/issues.js';
class ProjectsStore extends Store {
class ReposStore extends Store {
// Initial payload.
constructor() {
// Init the projects from local storage.
let list = lscache.get('projects') || [];
// Init the repos from local storage.
const list = lscache.get('repos') || [];
super({
// A stack of projects.
list: list,
// A sorted projects and milestones index.
// A stack of repos.
list,
// A sorted repos and projects index.
'index': [],
// The default sort order.
'sortBy': 'priority',
@ -30,20 +30,20 @@ class ProjectsStore extends Store {
'sortFns': [ 'progress', 'priority', 'name' ]
});
// Listen to only projects actions.
actions.on('projects.*', (obj, event) => {
// Listen to only repos actions.
actions.on('repos.*', (obj, event) => {
let fn = `on.${event}`.replace(/[.]+(\w|$)/g, (m, p) => p.toUpperCase());
// Run?
(fn in this) && this[fn](obj);
});
// Listen to when user is ready and save info on us.
actions.on('user.ready', (user) => this.set('user', user));
actions.on('user.ready', user => this.set('user', user));
// Persist projects in local storage (sans milestones and issues).
// Persist repos in local storage (sans projects and issues).
this.on('list.*', () => {
if (process.browser) {
lscache.set('projects', _.pluckMany(this.get('list'), [ 'owner', 'name' ]));
lscache.set('repos', _.pluckMany(this.get('list'), [ 'owner', 'name' ]));
}
});
@ -56,56 +56,54 @@ class ProjectsStore extends Store {
// Debounce.
if (process.browser) { // easier to test
this.onProjectsSearch = _.debounce(this.onProjectsSearch, 500);
this.onReposSearch = _.debounce(this.onReposSearch, 500);
}
}
// Fetch milestone(s) and issues for a project(s).
onProjectsLoad(args) {
let projects = this.get('list');
// Fetch issues for a repo(s).
onReposLoad(args) {
let repos = this.get('list');
// Reset first.
projects = _.each(projects, (p) => delete p.errors);
repos = _.each(repos, r => delete r.errors);
// Wait for the user to get resolved.
this.get('user', this.cb((user) => { // async
this.get('user', this.cb(user => { // async
if (args) {
if ('milestone' in args) {
// For a single milestone.
this.getMilestone(user, {
if ('project' in args) {
// For a single project.
this.getProject(user, {
'owner': args.owner,
'name': args.name
}, args.milestone, true); // notify as well
} else if ('project' in args) {
// ...
}, args.project, true); // notify as well
} else {
// For a single project.
_.find(this.get('list'), (obj) => {
// For a single repo.
_.find(this.get('list'), obj => {
if (args.owner == obj.owner && args.name == obj.name) {
args = obj; // expand by saved properties
return true;
};
return false;
});
this.getProject(user, args);
this.getRepo(user, args);
}
} else {
// For all projects.
_.each(projects, project => this.getProject(user, project));
// For all repos.
_.each(repos, r => this.getRepo(user, r));
}
}));
}
// Push to the stack unless it exists already.
onProjectsAdd(project) {
if (!_.find(this.get('list'), project)) {
this.push('list', project);
onRepoAdd(repo) {
if (!_.find(this.get('list'), repo)) {
this.push('list', repo);
}
}
// Cycle through projects sort order.
onProjectsSort() {
let { sortBy, sortFns } = this.get();
// Cycle through repos sort order.
onReposSort() {
const { sortBy, sortFns } = this.get();
let idx = 1 + sortFns.indexOf(sortBy);
if (idx === sortFns.length) idx = 0;
@ -113,8 +111,8 @@ class ProjectsStore extends Store {
this.set('sortBy', sortFns[idx]);
}
// Demonstration projects.
onProjectsDemo() {
// Demonstration repos.
onReposDemo() {
this.set({
'list': [
{ 'owner': 'd3', 'name': 'd3' },
@ -125,8 +123,8 @@ class ProjectsStore extends Store {
});
}
// Search for projects.
onProjectsSearch(text) {
// Search for repos.
onRepoSearch(text) {
if (!text || !text.length) return;
// Wait for the user to get resolved.
@ -169,10 +167,10 @@ class ProjectsStore extends Store {
}));
}
// Delete a project.
onProjectsDelete(project) {
let i = this.findIndex(project);
// Delete the project.
// Delete a repo.
onRepoDelete(repo) {
const i = this.findIndex(repo);
// Delete the repo.
this.del(`list.${i}`);
// And the index, sorting again.
this.set('index', []);
@ -183,10 +181,10 @@ class ProjectsStore extends Store {
comparator() {
let { list, sortBy } = this.get();
// Convert existing index into actual project milestone.
// Convert existing index into actual repo project.
let deIdx = (fn) => {
return ([ i, j ], ...rest) => {
return fn.apply(this, [ [ list[i], list[i].milestones[j] ] ].concat(rest));
return fn.apply(this, [ [ list[i], list[i].projects[j] ] ].concat(rest));
};
};
@ -205,43 +203,43 @@ class ProjectsStore extends Store {
switch (sortBy) {
// From highest progress points.
case 'progress':
return deIdx(([ , aM ], [ , bM ]) => {
defaults([ aM, bM ], { 'stats.progress.points': 0 });
return deIdx(([ , aP ], [ , bP ]) => {
defaults([ aP, bP ], { 'stats.progress.points': 0 });
// Simple points difference.
return aM.stats.progress.points - bM.stats.progress.points;
return aP.stats.progress.points - bP.stats.progress.points;
});
// From most delayed in days.
case 'priority':
return deIdx(([ , aM ], [ , bM ]) => {
// Milestones with no deadline are always at the "beginning".
defaults([ aM, bM ], { 'stats.progress.time': 0, 'stats.days': 1e3 });
return deIdx(([ , aP ], [ , bP ]) => {
// Projects with no deadline are always at the "beginning".
defaults([ aP, bP ], { 'stats.progress.time': 0, 'stats.days': 1e3 });
// % difference in progress times the number of days ahead or behind.
let [ $a, $b ] = _.map([ aM, bM ], ({ stats }) => {
let [ $a, $b ] = _.map([ aP, bP ], ({ stats }) => {
return (stats.progress.points - stats.progress.time) * stats.days;
});
return $b - $a;
});
// Based on project then milestone name including semver.
// Based on repo then project name including semver.
case 'name':
return deIdx(([ aP, aM ], [ bP, bM ]) => {
return deIdx(([ aR, aP ], [ bR, bP ]) => {
let owner, name;
if (owner = bP.owner.localeCompare(aP.owner)) {
if (owner = bR.owner.localeCompare(aR.owner)) {
return owner;
}
if (name = bP.name.localeCompare(aP.name)) {
if (name = bR.name.localeCompare(aR.name)) {
return name;
}
// Try semver.
if (semver.valid(bM.title) && semver.valid(aM.title)) {
return semver.gt(bM.title, aM.title);
if (semver.valid(bP.title) && semver.valid(aP.title)) {
return semver.gt(bP.title, aP.title);
// Back to string compare.
} else {
return bM.title.localeCompare(aM.title);
return bP.title.localeCompare(aP.title);
}
});
@ -251,61 +249,39 @@ class ProjectsStore extends Store {
}
}
// Fetch milestones and issues for a project.
getProject(user, p) {
// Fetch their milestones.
request.allMilestones(user, p, this.cb((err, milestones) => { // async
// Save the error if project does not exist.
if (err) return this.saveError(p, err);
// Now add in the issues.
milestones.forEach((milestone) => {
// Do we have this milestone already? Skip fetching issues then.
if (!_.find(p.milestones, ({ number }) => {
return milestone.number === number;
})) {
// Fetch all the issues for this milestone.
this.getIssues(user, p, milestone);
}
// Fetch projects in a repo.
getProjects(user, r) {
request.allProjects(user, r, this.cb((err, projects) => { // async
// Save the error if repo does not exist.
if (err) return this.saveError(r, err);
projects.forEach(obj => {
// Save the projet.
// TODO do something with obj and projects
this.addProject(r, obj, say);
});
}));
}
// Fetch a single milestone.
getMilestone(user, p, m, say) {
// Fetch the single milestone.
request.oneMilestone(user, {
'owner': p.owner,
'name': p.name,
'milestone': m
}, this.cb((err, milestone) => { // async
// Save the error if project does not exist.
if (err) return this.saveError(p, err, say);
// Now add in the issues.
this.getIssues(user, p, milestone, say);
}));
}
// Fetch all issues for a milestone.
getIssues(user, p, m, say) {
issues.fetchAll(user, {
'owner': p.owner,
'name': p.name,
'milestone': m.number
// Fetch a single project.
getProject(user, r, p, say) {
request.oneProject(user, {
'owner': r.owner,
'name': r.name,
'project': p
}, this.cb((err, obj) => { // async
// Save any errors on the project.
if (err) return this.saveError(p, err, say);
// Add in the issues to the milestone.
_.extend(m, { 'issues': obj });
// Save the milestone.
this.addMilestone(p, m, say);
// Save the error if repo does not exist.
if (err) return this.saveError(r, err, say);
// Save the projet.
// TODO do something with obj and p
this.addProject(r, obj, say);
}));
}
// Talk about the stats of a milestone.
notify(milestone) {
if (milestone.stats.isEmpty) {
// Talk about the stats of a project.
notify(project) {
if (project.stats.isEmpty) {
let left;
if (left = milestone.issues.open.size) {
if (left = project.issues.open.size) {
return actions.emit('system.notify', {
'text': `No progress has been made, ${left} point${(left > 1) ? 's' : ''} left`,
'system': true,
@ -313,7 +289,7 @@ class ProjectsStore extends Store {
});
} else {
return actions.emit('system.notify', {
'text': 'This milestone has no issues',
'text': 'This project has no issues',
'type': 'warn',
'system': true,
'ttl': null
@ -321,66 +297,66 @@ class ProjectsStore extends Store {
}
}
if (milestone.stats.isDone) {
if (project.stats.isDone) {
actions.emit('system.notify', {
'text': 'This milestone is complete',
'text': 'This project is complete',
'type': 'success'
});
}
if (milestone.stats.isOverdue) {
if (project.stats.isOverdue) {
actions.emit('system.notify', {
'text': 'This milestone is overdue',
'text': 'This project is overdue',
'type': 'warn'
});
}
}
// Add a milestone for a project.
addMilestone(project, milestone, say) {
// Add a project for a repo.
addProject(repo, project, say) {
// Add in the stats.
let i, j;
_.extend(milestone, { 'stats': stats(milestone) });
_.extend(project, { 'stats': stats(project) });
// Notify?
if (say) this.notify(milestone);
if (say) this.notify(project);
// If project hasn't been found, add it behind the scenes.
if ((i = this.findIndex(project)) < 0) {
i = this.push('list', project);
// If repo hasn't been found, add it behind the scenes.
if ((i = this.findIndex(repo)) < 0) {
i = this.push('list', repo);
}
// Does the milestone exist already?
let milestones;
if (milestones = this.get(`list.${i}.milestones`)) {
j = _.findIndex(milestones, { 'number': milestone.number });
// Does the project exist already?
let projects;
if (projects = this.get(`list.${i}.projects`)) {
j = _.findIndex(projects, { 'number': project.number });
// Just make an update then.
if (j != -1) {
return this.set(`list.${i}.milestones.${j}`, milestone);
return this.set(`list.${i}.projects.${j}`, project);
}
}
// Push the milestone and return the index.
j = this.push(`list.${i}.milestones`, milestone);
// Push the project and return the index.
j = this.push(`list.${i}.projects`, project);
// Now index this milestone.
this.sort([ i, j ], [ project, milestone ]);
// Now index this project.
this.sort([ i, j ], [ repo, project ]);
}
// Find index of a project.
// Find index of a repo.
findIndex({ owner, name }) {
return _.findIndex(this.get('list'), { owner, name });
}
// Save an error from loading milestones or issues.
// TODO: clear these when we fetch all projects anew.
saveError(project, err, say=false) {
// Save an error from loading projects.
// TODO: clear these when we fetch all repos anew.
saveError(repo, err, say=false) {
var idx;
if ((idx = this.findIndex(project)) > -1) {
if ((idx = this.findIndex(repo)) > -1) {
this.push(`list.${idx}.errors`, err);
} else {
// Create the stub project behind the scenes.
this.push('list', _.extend({}, project, { 'errors': [ err ] }));
// Create the stub repo behind the scenes.
this.push('list', _.extend({}, repo, { 'errors': [ err ] }));
}
// Notify?
@ -393,29 +369,29 @@ class ProjectsStore extends Store {
});
}
// Sort projects (update the index). Can pass reference to the
// project and milestone index in the stack.
// Sort repos (update the index). Can pass reference to the
// repo and project index in the stack.
sort(ref, data) {
let idx;
// Get the existing index.
let index = this.get('index');
const index = this.get('index');
// Index one milestone in an already sorted index.
// Index one project in an already sorted index.
if (ref) {
idx = sortedIndex(index, data, this.comparator());
index.splice(idx, 0, ref);
// Sort them all.
} else {
let list = this.get('list');
const list = this.get('list');
for (let i = 0; i < list.length; i++) {
let p = list[i];
// TODO: need to show projects that failed too...
if (p.milestones == null) continue;
// Walk the milestones.
for (let j = 0; j < p.milestones.length; j++) {
let m = p.milestones[j];
const r = list[i];
// TODO: need to show repos that failed too...
if (r.projects == null) continue;
// Walk the projects.
for (let j = 0; j < r.projects.length; j++) {
const p = r.projects[j];
// Run a comparator here inserting into index.
idx = sortedIndex(index, [ p, m ], this.comparator());
idx = sortedIndex(index, [ r, p ], this.comparator());
index.splice(idx, 0, [ i, j ]);
}
}
@ -424,7 +400,7 @@ class ProjectsStore extends Store {
this.set('index', index);
}
// Do we have this project? Case-insensitive.
// Do we have this repo? Case-insensitive.
has(o, n) {
o = o.toUpperCase() ; n = n.toUpperCase();
return !!_.find(this.get('list'), ({ owner, name }) => {
@ -434,4 +410,4 @@ class ProjectsStore extends Store {
}
export default new ProjectsStore();
export default new ReposStore();