diff --git a/app/core/Router.coffee b/app/core/Router.coffee index 17c5a1853..1ad136f59 100644 --- a/app/core/Router.coffee +++ b/app/core/Router.coffee @@ -43,6 +43,12 @@ module.exports = class CocoRouter extends Backbone.Router 'admin/pending-patches': go('admin/PendingPatchesView') 'admin/codelogs': go('admin/CodeLogsView') + 'artisans': go('artisans/ArtisansView') + + 'artisans/level-analytics': go('artisans/LevelAnalyticsView') + 'artisans/level-tasks': go('artisans/LevelTasksView') + 'artisans/thang-tasks': go('artisans/ThangTasksView') + 'beta': go('HomeView') 'careers': => window.location.href = 'https://jobs.lever.co/codecombat' diff --git a/app/styles/artisans/artisansView.sass b/app/styles/artisans/artisansView.sass new file mode 100644 index 000000000..1a56401f8 --- /dev/null +++ b/app/styles/artisans/artisansView.sass @@ -0,0 +1,6 @@ +#artisans-view + text-align: center + a + font-size: xx-large + img + border-radius: 8px \ No newline at end of file diff --git a/app/styles/artisans/levelAnalyticsView.sass b/app/styles/artisans/levelAnalyticsView.sass new file mode 100644 index 000000000..a1327ed63 --- /dev/null +++ b/app/styles/artisans/levelAnalyticsView.sass @@ -0,0 +1 @@ +#level-analytics-view \ No newline at end of file diff --git a/app/styles/artisans/levelTasksView.sass b/app/styles/artisans/levelTasksView.sass new file mode 100644 index 000000000..a5585bb76 --- /dev/null +++ b/app/styles/artisans/levelTasksView.sass @@ -0,0 +1,12 @@ +#level-tasks-view + #levelTable + width: 100% + + .tasksTable + width: 100% + + .tasks + width: 87.5% + + .taskOwner + width: 12.5% \ No newline at end of file diff --git a/app/styles/artisans/thangTasksView.sass b/app/styles/artisans/thangTasksView.sass new file mode 100644 index 000000000..599a6a6ab --- /dev/null +++ b/app/styles/artisans/thangTasksView.sass @@ -0,0 +1,12 @@ +#thang-tasks-view + #thangTable + width: 100% + + .tasksTable + width: 100% + + .tasks + width: 87.5% + + .taskOwner + width: 12.5% \ No newline at end of file diff --git a/app/templates/artisans/artisansView.jade b/app/templates/artisans/artisansView.jade new file mode 100644 index 000000000..0cf81943f --- /dev/null +++ b/app/templates/artisans/artisansView.jade @@ -0,0 +1,10 @@ +extends /templates/base + +block content + img(src='/images/pages/user/artisan.png') + div + a(href='artisans/thang-tasks') + |Thang Tasks + div + a(href="artisans/level-tasks") + |Level Tasks \ No newline at end of file diff --git a/app/templates/artisans/levelAnalyticsView.jade b/app/templates/artisans/levelAnalyticsView.jade new file mode 100644 index 000000000..d733d9169 --- /dev/null +++ b/app/templates/artisans/levelAnalyticsView.jade @@ -0,0 +1,4 @@ +extends /templates/base + +block content + table#levelTable \ No newline at end of file diff --git a/app/templates/artisans/levelTasksView.jade b/app/templates/artisans/levelTasksView.jade new file mode 100644 index 000000000..e44ae7dbd --- /dev/null +++ b/app/templates/artisans/levelTasksView.jade @@ -0,0 +1,34 @@ +extends /templates/base + +block content + #level-tasks-view + div + a(href='./') + | < Artisans Home + input#nameSearch(placeholder='Filter: Level Name') + br + input#descSearch(placeholder='Filter: Task Description') + hr + if view.processedLevels + table.table.table-striped#levelTable + tr + th Level Name + th Task List + for level in view.processedLevels + if view.hasIncompleteTasks(level) + +levelRow(level) + else + div ole? + + + +mixin levelRow(level) + tr + td.taskOwner + a(href= 'level/' + level.slug)= level.name + td.tasks + table.table-striped.table-hover.tasksTable + for task in (level.tasks2 || []) + if !task.complete + tr + td= task.name diff --git a/app/templates/artisans/thangTasksView.jade b/app/templates/artisans/thangTasksView.jade new file mode 100644 index 000000000..dc9c30c08 --- /dev/null +++ b/app/templates/artisans/thangTasksView.jade @@ -0,0 +1,32 @@ +extends /templates/base + +block content + #thang-tasks-view + div + a(href='./') + | < Artisans Home + input#nameSearch(placeholder='Filter: Thang Name') + br + input#descSearch(placeholder='Filter: Task Description') + hr + if view.processedThangs + table.table.table-striped#thangTable + tr + th Thang Name + th Task List + for thang in view.processedThangs + if view.hasIncompleteTasks(thang) + +thangRow(thang) + else + span No view.processedThangs + +mixin thangRow(thang) + tr + td.taskOwner + a(href= 'thang/' + thang.get('slug'))= thang.get('name') + td.tasks + table.table-striped.table-hover.tasksTable + for task in (thang.tasks || []) + if !task.complete + tr + td= task.name diff --git a/app/views/artisans/ArtisansView.coffee b/app/views/artisans/ArtisansView.coffee new file mode 100644 index 000000000..76b4110e9 --- /dev/null +++ b/app/views/artisans/ArtisansView.coffee @@ -0,0 +1,8 @@ +RootView = require 'views/core/RootView' +template = require 'templates/artisans/artisansView' + +module.exports = class ArtisansView extends RootView + template: template + id: 'artisans-view' + constructor: (options) -> + super options \ No newline at end of file diff --git a/app/views/artisans/LevelAnalyticsView.coffee b/app/views/artisans/LevelAnalyticsView.coffee new file mode 100644 index 000000000..4a35daf7f --- /dev/null +++ b/app/views/artisans/LevelAnalyticsView.coffee @@ -0,0 +1,35 @@ +RootView = require 'views/core/RootView' +template = require 'templates/artisans/levelAnalyticsView' +Level = require 'models/Level' +Campaign = require 'models/Campaign' +CocoCollection = require 'collections/CocoCollection' + +module.exports = class LevelAnalyticsView extends RootView + template: template + id: 'level-analytics-view' + constructor: (options) -> + super options + @campaigns = new CocoCollection([], + url: '/db/campaign?project=name,slug,tasks' + model: Campaign + ) + @campaigns.fetch() + @listenTo(@campaigns, 'sync', @onCampaignsLoaded) + @supermodel.loadCollection(@campaigns, 'campaigns') + onCampaignsLoaded: -> + @levels = [] + for campaign in @campaigns.models + continue unless campaign.get('slug') is 'dungeon' + levels = campaign.get('levels') + for key, level of levels + @levels.push level.slug + @stats = new CocoCollection([], + url: '/db/analytics_perday/-/level_completions?slug=dungeons-of-kithgard&startDay=20151022&endDay=20151104' + model: {} + ) + @stats.fetch() + @listenTo(@stats, 'sync', @onStatsLoaded) + @supermodel.loadCollection(@stats, 'stats') + @renderSelectors '#levelTable' + onStatsLoaded: -> + console.log @stats \ No newline at end of file diff --git a/app/views/artisans/LevelTasksView.coffee b/app/views/artisans/LevelTasksView.coffee new file mode 100644 index 000000000..a898e52d4 --- /dev/null +++ b/app/views/artisans/LevelTasksView.coffee @@ -0,0 +1,57 @@ +RootView = require 'views/core/RootView' +template = require 'templates/artisans/levelTasksView' +#ThangType = require 'models/ThangType' +Level = require 'models/Level' +Campaign = require 'models/Campaign' +CocoCollection = require 'collections/CocoCollection' + +module.exports = class LevelTasksView extends RootView + template: template + id: 'level-tasks-view' + events: + 'input input': 'searchUpdate' + 'change input': 'searchUpdate' + excludedCampaigns = [ + "picoctf" + "auditions" + ] + constructor: (options) -> + super options + @campaigns = new CocoCollection([], + url: '/db/campaign?project=name,slug,tasks' + model: Campaign + ) + @campaigns.fetch() + @listenTo(@campaigns, 'sync', @onCampaignsLoaded) + @supermodel.loadCollection(@campaigns, 'campaigns') + + onCampaignsLoaded: -> + @levels = {} + sum = 0 + for campaign in @campaigns.models + continue unless excludedCampaigns.indexOf(campaign.get 'slug') is -1 + levels = campaign.get('levels') + sum += Object.keys(levels).length + for key, level of levels + continue unless ///#{$('#nameSearch')[0].value}///i.test level.name + levelSlug = level.slug + @levels[levelSlug] = level + @processedLevels = @levels + for key, level of @processedLevels + level.tasks2 = _.filter level.tasks, (_elem) -> + return ///#{$('#descSearch')[0].value}///i.test _elem.name + @renderSelectors '#levelTable' + + searchUpdate: -> + if not @lastLoad? or (new Date()).getTime() - @lastLoad > 60 * 1000 * 1 # Update only after a minute from last update. + @campaigns.fetch() + @listenTo(@campaigns, 'sync', @onCampaignsLoaded) + @supermodel.loadCollection(@campaigns, 'campaigns') + @lastLoad = (new Date()).getTime() + else + @onCampaignsLoaded() + + + # Jade helper + hasIncompleteTasks: (level) -> + return level.tasks2 and level.tasks2.filter((_elem) -> return not _elem.complete).length > 0 \ No newline at end of file diff --git a/app/views/artisans/ThangTasksView.coffee b/app/views/artisans/ThangTasksView.coffee new file mode 100644 index 000000000..d17bef021 --- /dev/null +++ b/app/views/artisans/ThangTasksView.coffee @@ -0,0 +1,48 @@ +RootView = require 'views/core/RootView' +template = require 'templates/artisans/thangTasksView' +ThangType = require 'models/ThangType' +CocoCollection = require 'collections/CocoCollection' + +module.exports = class ThangTasksView extends RootView + template: template + id: 'thang-tasks-view' + events: + 'input input': 'searchUpdate' + 'change input': 'searchUpdate' + + constructor: (options) -> + super options + @thangs = new CocoCollection([], + url: '/db/thang.type?project=name,tasks,slug' + model: ThangType + comparator: @sortThangs + ) + @lastLoad = (new Date()).getTime() + @listenTo(@thangs, 'sync', @onThangsLoaded) + @supermodel.loadCollection(@thangs, 'thangs') + + searchUpdate: -> + if not @lastLoad? or (new Date()).getTime() - @lastLoad > 60 * 1000 * 1 # Update only after a minute from last update. + @thangs.fetch() + @listenTo(@thangs, 'sync', @onThangsLoaded) + @supermodel.loadCollection(@thangs, 'thangs') + @lastLoad = (new Date()).getTime() + else + @onThangsLoaded() + + onThangsLoaded: -> + @processedThangs = @thangs.filter (_elem) -> + # Case-insensitive search of input vs name. + return ///#{$('#nameSearch')[0].value}///i.test _elem.get('name') + for thang in @processedThangs + thang.tasks = _.filter thang.attributes.tasks, (_elem) -> + # Similar case-insensitive search of input vs description (name). + return ///#{$('#descSearch')[0].value}///i.test _elem.name + @renderSelectors '#thangTable' + + sortThangs: (a, b) -> + a.get('name').localeCompare(b.get('name')) + + # Jade helper + hasIncompleteTasks: (thang) -> + return thang.tasks and thang.tasks.filter((_elem) -> return not _elem.complete).length > 0 \ No newline at end of file