diff --git a/app/locale/de-CH.coffee b/app/locale/de-CH.coffee index 32c055560..c6a4a9a60 100644 --- a/app/locale/de-CH.coffee +++ b/app/locale/de-CH.coffee @@ -11,7 +11,7 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge for_beginners: "Für Afänger" multiplayer: "Multiplayer" # Not currently shown on home page for_developers: "Für Entwickler" # Not currently shown on home page. -# or_ipad: "Or download for iPad" + or_ipad: "Oder lads fürs iPad abä" nav: play: "Levels" # The top nav bar entry where players choose which levels to play @@ -58,7 +58,7 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge owned: "Scho gkauft" # For items you own locked: "Nonig freischaltbar" # purchasable: "Purchasable" # For a hero you unlocked but haven't purchased -# available: "Available" + available: "vorhandä" # skills_granted: "Skills Granted" # Property documentation details heroes: "Helde" # Tooltip on hero shop button from /play achievements: "Achievements" # Tooltip on achievement list button from /play @@ -93,15 +93,15 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge # campaign_classic_algorithms: "Classic Algorithms" # campaign_classic_algorithms_description: "... in which you learn the most popular algorithms in Computer Science." -# share_progress_modal: -# blurb: "You’re making great progress! Tell someone how much you've learned with CodeCombat." -# email_invalid: "Email address invalid." + share_progress_modal: + blurb: "Du machsch grossi Fortschritts! Verzells öperem wieviel du glernt häsch mit CodeCombat." + email_invalid: "Email Adrässä isch falsch." # form_blurb: "Enter their email below and we’ll show them!" -# form_label: "Email Address" -# placeholder: "email address" + form_label: "Email Adrässä" + placeholder: "Email Adrässä" # title: "Excellent Work, Apprentice" -# tell_friend: "Tell your Friend" -# tell_parent: "Tell your Parent" + tell_friend: "Sägs dim Kolleg oder dinere Kollegin" + tell_parent: "Sägs dinä Elterä" login: sign_up: "Account erstelle" @@ -130,13 +130,13 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge send_password: "Recovery Password sende" # recovery_sent: "Recovery email sent." -# items: + items: # primary: "Primary" # secondary: "Secondary" # armor: "Armor" # accessories: "Accessories" -# misc: "Misc" -# books: "Books" + misc: "Diverses" + books: "Büecher" common: # back: "Back" # When used as an action verb, like "Navigate backward" @@ -154,8 +154,8 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge play: "Spiele" # When used as an action verb, like "Play next level" retry: "nomol versuche" # actions: "Actions" -# info: "Info" -# help: "Help" + info: "Info" + help: "Hilf" # watch: "Watch" # unwatch: "Unwatch" submit_patch: "Patch ireiche" @@ -184,7 +184,7 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge # redo_shortcut: "(Ctrl+Shift+Z)" # play_preview: "Play preview of current level" result: "Resultat" -# results: "Results" + results: "Resultat" description: "Beschriibig" or: "oder" # subject: "Subject" @@ -205,9 +205,9 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge hard: "Schwer" player: "Spieler" player_level: "Stufe" # Like player level 5, not like level: Dungeons of Kithgard -# warrior: "Warrior" + warrior: "Krieger" # ranger: "Ranger" -# wizard: "Wizard" + wizard: "Zauberer" units: second: "Sekunde" @@ -235,15 +235,15 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge restart: "Neu starte" goals: "Ziel" goal: "Goal" -# running: "Running..." + running: "s lauft..." success: "Erfolg!" incomplete: "Unvollständig" timed_out: "Ziit abglaufe" failing: "Fehler" action_timeline: "Aktionsziitleiste" click_to_select: "Klick uf e Einheit zum sie uswähle." -# control_bar_multiplayer: "Multiplayer" -# control_bar_join_game: "Join Game" + control_bar_multiplayer: "Mehrspiiler" + control_bar_join_game: "Mitspiilä" reload: "Neu lade" reload_title: "De ganze Code neu lade?" reload_really: "Bisch sicher du willsch level neu lade bis zrugg zum Afang?" @@ -256,8 +256,8 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge victory_rate_the_level: "Bewerte das Level: " # Only in old-style levels. victory_return_to_ladder: "Zrugg zum letzte Level" victory_play_continue: "Wiiter spile" -# victory_saving_progress: "Saving Progress" -# victory_go_home: "Go Home" # Only in old-style levels. + victory_saving_progress: "Fortschritt abspaicherä" + victory_go_home: "Goon Hai" # Only in old-style levels. victory_review: "Verzell üs meh!" # Only in old-style levels. victory_hour_of_code_done: "Bisch fertig?" victory_hour_of_code_done_yes: "Jo, ich bin fertig mit mim Hour of Code™!" @@ -272,21 +272,21 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge # tome_cast_button_ran: "Ran" # tome_submit_button: "Submit" # tome_reload_method: "Reload original code for this method" # Title text for individual method reload button. -# tome_select_method: "Select a Method" + tome_select_method: "Wähl a Methodä" # tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methdos). tome_select_a_thang: "Wähl öpper us für" tome_available_spells: "Verfüegbari Zaubersprüch" -# tome_your_skills: "Your Skills" + tome_your_skills: "Dini Fähigkaitä" tome_help: "Hilf" -# tome_current_method: "Current Method" -# hud_continue_short: "Continue" + tome_current_method: "Aktuelli Modus" + hud_continue_short: "Wiitermache" code_saved: "Code gpeicheret" skip_tutorial: "Überspringe (esc)" keyboard_shortcuts: "Shortcuts" -# loading_ready: "Ready!" + loading_ready: "Berait!" loading_start: "Level starte" -# problem_alert_title: "Fix Your Code" -# problem_alert_help: "Help" + problem_alert_title: "Reparier diin Code" + problem_alert_help: "Hilf" time_current: "Jetzt:" time_total: "Max:" time_goto: "Goh zu:" @@ -341,18 +341,18 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge game_menu: # inventory_tab: "Inventory" -# save_load_tab: "Save/Load" -# options_tab: "Options" + save_load_tab: "Spaicherä/Ladä" + options_tab: "Optionä" # guide_tab: "Guide" # guide_video_tutorial: "Video Tutorial" -# guide_tips: "Tips" + guide_tips: "Tipps" multiplayer_tab: "Multiplayer" # auth_tab: "Sign Up" # inventory_caption: "Equip your hero" # choose_hero_caption: "Choose hero, language" # save_load_caption: "... and view history" # options_caption: "Configure settings" -# guide_caption: "Docs and tips" + guide_caption: "Doku und Tipps" # multiplayer_caption: "Play with friends!" # auth_caption: "Save your progress." diff --git a/app/locale/es-419.coffee b/app/locale/es-419.coffee index 078d78ab5..39d21a98c 100644 --- a/app/locale/es-419.coffee +++ b/app/locale/es-419.coffee @@ -454,8 +454,8 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip no_users_subscribed: "No se suscribieron usuarios, por favor revisa las direcciones de email." current_recipients: "Recipientes actuales" unsubscribing: "Desuscribiendo..." -# subscribe_prepaid: "Click Subscribe to use prepaid code" -# using_prepaid: "Using prepaid code for monthly subscription" + subscribe_prepaid: "Click en suscribirse para utlizar un código prepago" + using_prepaid: "Usar código prepago para una suscribción mensual" choose_hero: choose_hero: "Elige tu héroe" @@ -572,40 +572,40 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip josh_blurb: "El piso es Lava" jose_title: "Música" jose_blurb: "Despegar" - retrostyle_title: "Ilustracin" + retrostyle_title: "Ilustración" retrostyle_blurb: "Juegos con estilo Retro" teachers: title: "CodeCombat para Profesores" # {change} -# intro_1: "CodeCombat is an online game that teaches programming. Students write code in real programming languages." -# intro_2: "No experience required!" -# free_title: "How much does it cost?" + intro_1: "CodeCombat es un juego online que enseña a programar.Los estudiantes escriben código en idiomas de programación real." + intro_2: "No se necesita experiencia previa!" + free_title: "¿Cuanto cuesta?" # cost_china: "CodeCombat in China is free for the first five levels, after which it costs $9.99 per month for access to our other 120+ levels on our exclusive China servers." # free_1: "CodeCombat Basic is FREE! There are 70+ free levels which cover every concept." # free_2: "A monthly subscription provides access to video tutorials and extra practice levels." -# teacher_subs_title: "Teachers get free subscriptions!" + teacher_subs_title: "¡Los amestros obtienen subscripciones gratuitas!" # teacher_subs_1: "Please contact" -# teacher_subs_2: "to set up a free monthly subscription." -# sub_includes_title: "What is included in the subscription?" -# sub_includes_1: "In additional to the 70+ basic levels, students with a monthly subscription get access to these additional features:" -# sub_includes_2: "40+ practice levels" -# sub_includes_3: "Video tutorials" + teacher_subs_2: "to set up a free monthly subscriptiron." + sub_includes_title: "¿Qué se incluye en la suscripción?" + sub_includes_1: "Adicionalmente a los más de 70 niveles básicos, los estudiantes con una suscripción mensual obtienen acceso a estas características adicionales:" + sub_includes_2: "Más de 40 niveles de práctica" + sub_includes_3: "Video tutoriales" # sub_includes_4: "Premium email support" -# sub_includes_5: "7 new heroes with unique skills to master" -# sub_includes_6: "3500 bonus gems every month" -# who_for_title: "Who is CodeCombat for?" + sub_includes_5: "7 heroes nuevos con habilidades unicas que dominar" + sub_includes_6: "bonificación de 3500 gemas cada mes" + who_for_title: "¿Para quienes es CodeCombat?" # who_for_1: "We recommend CodeCombat for students aged 9 and up. No prior programming experience is needed." # who_for_2: "We've designed CodeCombat to appeal to both boys and girls." # material_title: "How much material is there?" # material_china: "Approximately 22 hours of gameplay spread over 120+ subscriber-only levels so far, with 5 new levels every week." # material_1: "Approximately 8 hours of free content and an additional 14 hours of subscriber content, with 5 new levels every week." # concepts_title: "What concepts are covered?" -# how_much_title: "How much does a monthly subscription cost?" -# how_much_1: "A" -# how_much_2: "monthly subscription" -# how_much_3: "costs $9.99, and can be cancelled anytime." -# how_much_4: "Additionally, we provide discounts for larger groups:" -# group_discounts_1: "We also offer group discounts for bulk subscriptions." + how_much_title: "¿Cuánto cuesta una subscripción mensual?" + how_much_1: "una" + how_much_2: "suscribción mensual" + how_much_3: "Cuesta u$s9.99, y puede ser cancelada en cualquier momento." + how_much_4: "Adicionalmente, nosotros otorgamos descuentos a grupos grandes:" + group_discounts_1: "También ofrecemos descuentes grupales para suscripciones en masa." sys_requirements_title: "Requerimientos del sistema" sys_requirements_1: "Debido que CodeCombat es un juego, es más difícil para las computadoras correrlo en relación a un tutorial escrito o un video. Para que todos puedan jugar, hemos optimizado la web para correr rápidamente en todos los navegadores modernos y en maquinas antiguas. Dicho esto, aquí están nuestras sugerencias para sacar el máximo provecho de su experiencia en la Hora del Código:" # {change} sys_requirements_2: "Usar una versión actualizada del navegador Chrome o Firefox." # {change} @@ -941,7 +941,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip fight: "Pelea!" watch_victory: "Observa tu Victoria" defeat_the: "Derrota a" -# tournament_started: ", started" + tournament_started: ", iniciado" tournament_ends: "Final de Torneo" tournament_ended: "Finalizó el Torneo" tournament_rules: "Reglas del Torneo" diff --git a/app/locale/es-ES.coffee b/app/locale/es-ES.coffee index ededd8888..6a85a0f7f 100644 --- a/app/locale/es-ES.coffee +++ b/app/locale/es-ES.coffee @@ -64,7 +64,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis achievements: "Logros" # Tooltip on achievement list button from /play account: "Cuenta" # Tooltip on account button from /play settings: "Ajustes" # Tooltip on settings button from /play -# poll: "Poll" # Tooltip on poll button from /play + poll: "Encuesta" # Tooltip on poll button from /play next: "Siguiente Heroe" # Go from choose hero to choose inventory before playing a level change_hero: "Seleccionar Heroe" # Go back from choose inventory to choose hero choose_inventory: "Equipar Objetos" @@ -77,7 +77,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis awaiting_levels_adventurer_prefix: "Liberamos cinco niveles cada semana." awaiting_levels_adventurer: "Regístrate como Aventurero" awaiting_levels_adventurer_suffix: "para ser el primero en jugar nuevos niveles." -# adjust_volume: "Adjust volume" + adjust_volume: "Ajustar volúmen" choose_your_level: "Elige tu nivel" # The rest of this section is the old play view at /play-old and isn't very important. adventurer_prefix: "Puedes elegir cualquier pantalla o charlar en " adventurer_forum: "el foro del aventurero " @@ -93,15 +93,15 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis campaign_classic_algorithms: "Algoritmos Clasicos" campaign_classic_algorithms_description: "... donde aprendes los algoritmos mas populares de la informatica." -# share_progress_modal: -# blurb: "You’re making great progress! Tell someone how much you've learned with CodeCombat." -# email_invalid: "Email address invalid." -# form_blurb: "Enter their email below and we’ll show them!" -# form_label: "Email Address" -# placeholder: "email address" -# title: "Excellent Work, Apprentice" -# tell_friend: "Tell your Friend" -# tell_parent: "Tell your Parent" + share_progress_modal: + blurb: "¡Estás teniendo un gran progreso! Cuéntale a alguien que tanto habeis aprendido con CodeCombat." + email_invalid: "La dirección de correo electrónico no es válida." + form_blurb: "¡Introduzca su correo electrónico y nosotros les mostraremos!" + form_label: "Correo Electrónico" + placeholder: "correo electrónico" + title: "Excelente Trabajo Aprendiz" + tell_friend: "Decirle a un amigo" + tell_parent: "Decirle a mis padres" login: sign_up: "Crear una cuenta" @@ -139,8 +139,8 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis books: "Libros" common: -# back: "Back" # When used as an action verb, like "Navigate backward" -# continue: "Continue" # When used as an action verb, like "Continue forward" + back: "Volver" # When used as an action verb, like "Navigate backward" + continue: "Continuar" # When used as an action verb, like "Continue forward" loading: "Cargando..." saving: "Guardando..." sending: "Enviando..." @@ -154,7 +154,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis play: "Jugar" # When used as an action verb, like "Play next level" retry: "Reintentar" actions: "Acciones" -# info: "Info" + info: "Información" help: "Ayuda" watch: "Mirar" unwatch: "Pasar" @@ -172,16 +172,16 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis rejected: "Rechazado" # withdrawn: "Withdrawn" # submitter: "Submitter" -# submitted: "Submitted" + submitted: "Enviado" commit_msg: "Mensaje de Asignación o Commit" # review: "Review" version_history: "Historial de versión" version_history_for: "Historial de las versiones de: " select_changes: "Selecciona dos cambios más abajo para ver la diferencia." undo_prefix: "Deshacer" -# undo_shortcut: "(Ctrl+Z)" + undo_shortcut: "(Ctrl+Z)" redo_prefix: "Rehacer" -# redo_shortcut: "(Ctrl+Shift+Z)" + redo_shortcut: "(Ctrl+Shift+Z)" #tal vez sea mejor usar el común Control+Y play_preview: "Reproducir una vista previa del nivel actual" result: "Resultado" results: "Resultados" @@ -205,8 +205,8 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis hard: "Difícil" player: "Jugador" player_level: "Nivel" # Like player level 5, not like level: Dungeons of Kithgard -# warrior: "Warrior" -# ranger: "Ranger" + warrior: "Guerrero" +# ranger: "Ranger" #guardabosques? wizard: "Mago" units: @@ -326,18 +326,18 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis tip_superpower: "Programar es lo más parecido que tenemos a un superpoder." # tip_control_destiny: "In real open source, you have the right to control your own destiny. - Linus Torvalds" # tip_no_code: "No code is faster than no code." -# tip_code_never_lies: "Code never lies, comments sometimes do. — Ron Jeffries" + tip_code_never_lies: "El código nunca os miente, los comentarios algunas veces. — Ron Jeffries" # tip_reusable_software: "Before software can be reusable it first has to be usable." # tip_optimization_operator: "Every language has an optimization operator. In most languages that operator is ‘//’" # tip_lines_of_code: "Measuring programming progress by lines of code is like measuring aircraft building progress by weight. — Bill Gates" # tip_source_code: "I want to change the world but they would not give me the source code." # tip_javascript_java: "Java is to JavaScript what Car is to Carpet. - Chris Heilmann" # tip_move_forward: "Whatever you do, keep moving forward. - Martin Luther King Jr." -# tip_google: "Have a problem you can't solve? Google it!" + tip_google: "¿Teneis un problema que no podeis resolver? ¡Googleadlo!" # tip_adding_evil: "Adding a pinch of evil." # tip_hate_computers: "That's the thing about people who think they hate computers. What they really hate is lousy programmers. - Larry Niven" # tip_open_source_contribute: "You can help CodeCombat improve!" -# tip_recurse: "To iterate is human, to recurse divine. - L. Peter Deutsch" + tip_recurse: "Iterar es humano, recursar es divino. - L. Peter Deutsch" game_menu: inventory_tab: "Inventario" @@ -356,18 +356,18 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis multiplayer_caption: "Juega con amigos!" auth_caption: "Salvar tu progreso." -# leaderboard: + leaderboard: # leaderboard: "Leaderboard" # view_other_solutions: "View Other Solutions" -# scores: "Scores" + scores: "Puntuaciones" # top_players: "Top Players by" -# day: "Today" -# week: "This Week" + day: "Hoy" + week: "Esta semana" # all: "All-Time" # time: "Time" # damage_taken: "Damage Taken" # damage_dealt: "Damage Dealt" -# difficulty: "Difficulty" + difficulty: "Difficultad" # gold_collected: "Gold Collected" inventory: @@ -402,16 +402,16 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis # feature2: "7 powerful new heroes with unique skills!" # feature3: "30+ bonus levels" # feature4: "3500 bonus gems every month!" -# feature5: "Video tutorials" + feature5: "Vídeo tutoriales" # feature6: "Premium email support" -# free: "Free" -# month: "month" + free: "Gratis" + month: "mes" subscribe_title: "Suscríbete" # unsubscribe: "Unsubscribe" # confirm_unsubscribe: "Confirm Unsubscribe" # never_mind: "Never Mind, I Still Love You" # thank_you_months_prefix: "Thank you for supporting us these last" -# thank_you_months_suffix: "months." + thank_you_months_suffix: "meses." # thank_you: "Thank you for supporting CodeCombat." # sorry_to_see_you_go: "Sorry to see you go! Please let us know what we could have done better." # unsubscribe_feedback_placeholder: "O, what have we done?" @@ -470,7 +470,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis lua_blurb: "Lenguaje Script para Juegos." io_blurb: "Simple pero oscuro." status: "Estado" -# hero_type: "Type" + hero_type: "Tipo" weapons: "Armas" weapons_warrior: "Espadas - Corto Alcance, Sin Magia" weapons_ranger: "Ballestas, Pistolas - Largo Alcance, Sin Magia" @@ -570,12 +570,12 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis # cat_blurb: "Airbender" # josh_title: "Game Designer" # josh_blurb: "Floor Is Lava" -# jose_title: "Music" + jose_title: "Música" # jose_blurb: "Taking Off" # retrostyle_title: "Illustration" # retrostyle_blurb: "RetroStyle Games" -# teachers: + teachers: # title: "CodeCombat: Info for Teachers" # intro_1: "CodeCombat is an online game that teaches programming. Students write code in real programming languages." # intro_2: "No experience required!" @@ -602,7 +602,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis # concepts_title: "What concepts are covered?" # how_much_title: "How much does a monthly subscription cost?" # how_much_1: "A" -# how_much_2: "monthly subscription" + how_much_2: "suscripción mensual" # how_much_3: "costs $9.99, and can be cancelled anytime." # how_much_4: "Additionally, we provide discounts for larger groups:" # group_discounts_1: "We also offer group discounts for bulk subscriptions." @@ -751,7 +751,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis pick_a_terrain: "Escoge un Terreno" # dungeon: "Dungeon" # indoor: "Indoor" -# desert: "Desert" + desert: "Desierto" #desert like take a desert in desert? :P grassy: "Cubierto de hierba" small: "Pequeño" # large: "Large" @@ -818,7 +818,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis # achievement_query_goals: "Key achievement off of level goals" level_completion: "Porcentaje de Nivel Completado" pop_i18n: "Poblar I18N" -# tasks: "Tasks" + tasks: "Tareas" # clear_storage: "Clear your local changes" article: @@ -998,11 +998,11 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis service: "Servicio" price: "Precio" gems: "Joyas" -# active: "Active" + active: "Activo" # subscribed: "Subscribed" # unsubscribed: "Unsubscribed" # active_until: "Active Until" -# cost: "Cost" + cost: "Costo" next_payment: "Siguiente Pago" card: "Tarjeta" status_unsubscribed_active: "No estás suscrito y no seras facturado, pero tu cuenta sigue activa por ahora." @@ -1078,7 +1078,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis feedback: "Apoyo" payment_info: "Información de Pago" # campaigns: "Campaigns" -# poll: "Poll" + poll: "Encuesta" # user_polls_record: "Poll Voting History" delta: diff --git a/app/views/admin/AnalyticsSubscriptionsView.coffee b/app/views/admin/AnalyticsSubscriptionsView.coffee index b1f60ae65..6a9208258 100644 --- a/app/views/admin/AnalyticsSubscriptionsView.coffee +++ b/app/views/admin/AnalyticsSubscriptionsView.coffee @@ -2,8 +2,8 @@ RootView = require 'views/core/RootView' template = require 'templates/admin/analytics-subscriptions' RealTimeCollection = require 'collections/RealTimeCollection' +# TODO: Add last N subscribers table # TODO: Add revenue line -# TODO: Add LTV line # TODO: Graphing code copied/mangled from campaign editor level view. OMG, DRY. require 'vendor/d3' @@ -14,14 +14,15 @@ module.exports = class AnalyticsSubscriptionsView extends RootView constructor: (options) -> super options + @resetData() if me.isAdmin() @refreshData() _.delay (=> @refreshData()), 30 * 60 * 1000 getRenderData: -> context = super() - context.analytics = @analytics - context.subs = @subs ? [] + context.analytics = @analytics ? graphs: [] + context.subs = _.cloneDeep(@subs ? []).reverse() context.total = @total ? 0 context.cancelled = @cancelled ? 0 context.monthlyChurn = @monthlyChurn ? 0.0 @@ -31,14 +32,26 @@ module.exports = class AnalyticsSubscriptionsView extends RootView super() @updateAnalyticsGraphs() - refreshData: -> - return unless me.isAdmin() + resetData: -> @analytics = graphs: [] @subs = [] @total = 0 @cancelled = 0 @monthlyChurn = 0.0 - onSuccess = (subs) => + + refreshData: -> + return unless me.isAdmin() + @resetData() + + options = + url: '/db/subscription/-/subscriptions' + method: 'GET' + options.error = (model, response, options) => + return if @destroyed + console.error 'Failed to get subscriptions', response + options.success = (subs, response, options) => + return if @destroyed + @resetData() subDayMap = {} for sub in subs startDay = sub.start.substring(0, 10) @@ -63,15 +76,9 @@ module.exports = class AnalyticsSubscriptionsView extends RootView sub.total = @total startedLastMonth += sub.started if @subs.length - i < 31 @monthlyChurn = @cancelled / startedLastMonth * 100.0 - @updateAnalyticsGraphData() - @render() - @supermodel.addRequestResource('subscriptions', { - url: '/db/subscription/-/subscriptions' - method: 'GET' - success: onSuccess - }, 0).load() - + @render?() + @supermodel.addRequestResource('get_subscriptions', options, 0).load() updateAnalyticsGraphData: -> # console.log 'updateAnalyticsGraphData' @@ -79,6 +86,8 @@ module.exports = class AnalyticsSubscriptionsView extends RootView # Currently only one graph @analytics.graphs = [graphID: 'total-subs', lines: []] + timeframeDays = 60 + return unless @subs?.length > 0 # TODO: Where should this metadata live? @@ -86,16 +95,24 @@ module.exports = class AnalyticsSubscriptionsView extends RootView totalSubsID = 'total-subs' startedSubsID = 'started-subs' cancelledSubsID = 'cancelled-subs' + netSubsID = 'net-subs' lineMetadata = {} lineMetadata[totalSubsID] = description: 'Total Active Subscriptions' color: 'green' + strokeWidth: 1 lineMetadata[startedSubsID] = description: 'New Subscriptions' color: 'blue' + strokeWidth: 1 lineMetadata[cancelledSubsID] = description: 'Cancelled Subscriptions' color: 'red' + strokeWidth: 1 + lineMetadata[netSubsID] = + description: '7-day Average Net Subscriptions' + color: 'black' + strokeWidth: 4 days = (sub.day for sub in @subs) if days.length > 0 @@ -132,7 +149,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView levelPoints[i].x = i levelPoints[i].pointID = "#{totalSubsID}#{i}" - levelPoints.splice(0, levelPoints.length - 60) if levelPoints.length > 60 + levelPoints.splice(0, levelPoints.length - timeframeDays) if levelPoints.length > timeframeDays @analytics.graphs[0].lines.push lineID: totalSubsID @@ -166,7 +183,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView levelPoints[i].x = i levelPoints[i].pointID = "#{startedSubsID}#{i}" - levelPoints.splice(0, levelPoints.length - 60) if levelPoints.length > 60 + levelPoints.splice(0, levelPoints.length - timeframeDays) if levelPoints.length > timeframeDays @analytics.graphs[0].lines.push lineID: startedSubsID @@ -178,16 +195,21 @@ module.exports = class AnalyticsSubscriptionsView extends RootView max: d3.max(@subs, (d) -> d.started) ## Cancelled + averageCancelled = 0 # Build line data levelPoints = [] + cancelled = [] for sub, i in @subs + if i >= @subs.length - 30 + cancelled.push sub.cancelled levelPoints.push x: i y: sub.cancelled day: sub.day pointID: "#{cancelledSubsID}#{i}" values: [] + averageCancelled = cancelled.reduce((a, b) -> a + b) / cancelled.length # Ensure points for each day for day, i in days @@ -200,7 +222,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView levelPoints[i].x = i levelPoints[i].pointID = "#{cancelledSubsID}#{i}" - levelPoints.splice(0, levelPoints.length - 60) if levelPoints.length > 60 + levelPoints.splice(0, levelPoints.length - timeframeDays) if levelPoints.length > timeframeDays @analytics.graphs[0].lines.push lineID: cancelledSubsID @@ -211,6 +233,52 @@ module.exports = class AnalyticsSubscriptionsView extends RootView min: 0 max: d3.max(@subs, (d) -> d.started) + ## 7-Day Net Subs + + # Build line data + levelPoints = [] + sevenNets = [] + for sub, i in @subs + net = 0 + if i >= @subs.length - 30 + sevenNets.push sub.started - sub.cancelled + else + sevenNets.push sub.started - averageCancelled + if sevenNets.length > 7 + sevenNets.shift() + if sevenNets.length is 7 + net = sevenNets.reduce((a, b) -> a + b) / 7 + levelPoints.push + x: i + y: net + day: sub.day + pointID: "#{netSubsID}#{i}" + values: [] + + # Ensure points for each day + for day, i in days + if levelPoints.length <= i or levelPoints[i].day isnt day + prevY = if i > 0 then levelPoints[i - 1].y else 0.0 + levelPoints.splice i, 0, + y: prevY + day: day + values: [] + levelPoints[i].x = i + levelPoints[i].pointID = "#{netSubsID}#{i}" + + levelPoints.splice(0, levelPoints.length - timeframeDays) if levelPoints.length > timeframeDays + + @analytics.graphs[0].lines.push + lineID: netSubsID + enabled: true + points: levelPoints + description: lineMetadata[netSubsID].description + lineColor: lineMetadata[netSubsID].color + strokeWidth: lineMetadata[netSubsID].strokeWidth + min: 0 + max: d3.max(@subs, (d) -> d.started) + + updateAnalyticsGraphs: -> # Build d3 graphs return unless @analytics?.graphs?.length > 0 @@ -229,7 +297,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView svg = d3.select(containerSelector).append("svg") .attr("width", containerWidth) .attr("height", containerHeight) - width = containerWidth - margin * 2 - yAxisWidth * graphLineCount + width = containerWidth - margin * 2 - yAxisWidth * 2 height = containerHeight - margin * 2 - xAxisHeight - keyHeight * graphLineCount currentLine = 0 for line in graph.lines @@ -251,36 +319,39 @@ module.exports = class AnalyticsSubscriptionsView extends RootView .call(xAxis) .selectAll("text") .attr("dy", ".35em") - .attr("transform", "translate(" + (margin + yAxisWidth * (graphLineCount - 1)) + "," + (height + margin) + ")") + .attr("transform", "translate(" + (margin + yAxisWidth) + "," + (height + margin) + ")") .style("text-anchor", "start") + if line.lineID is 'started-subs' # Horizontal guidelines - # svg.selectAll(".line") - # .data([10, 30, 50, 70, 90]) - # .enter() - # .append("line") - # .attr("x1", margin + yAxisWidth * graphLineCount) - # .attr("y1", (d) -> margin + yRange(d)) - # .attr("x2", margin + yAxisWidth * graphLineCount + width) - # .attr("y2", (d) -> margin + yRange(d)) - # .attr("stroke", line.lineColor) - # .style("opacity", "0.5") + marks = (Math.round(i * line.max / 5) for i in [1...5]) + svg.selectAll(".line") + .data(marks) + .enter() + .append("line") + .attr("x1", margin + yAxisWidth * 2) + .attr("y1", (d) -> margin + yRange(d)) + .attr("x2", margin + yAxisWidth * 2 + width) + .attr("y2", (d) -> margin + yRange(d)) + .attr("stroke", line.lineColor) + .style("opacity", "0.5") - # y-Axis - yAxisRange = d3.scale.linear().range([height, 0]).domain([line.min, line.max]) - yAxis = d3.svg.axis() - .scale(yRange) - .orient("left") - svg.append("g") - .attr("class", "y axis") - .attr("transform", "translate(" + (margin + yAxisWidth * currentLine) + "," + margin + ")") - .style("color", line.lineColor) - .call(yAxis) - .selectAll("text") - .attr("y", 0) - .attr("x", 0) - .attr("fill", line.lineColor) - .style("text-anchor", "start") + if currentLine < 2 + # y-Axis + yAxisRange = d3.scale.linear().range([height, 0]).domain([line.min, line.max]) + yAxis = d3.svg.axis() + .scale(yRange) + .orient("left") + svg.append("g") + .attr("class", "y axis") + .attr("transform", "translate(" + (margin + yAxisWidth * currentLine) + "," + margin + ")") + .style("color", line.lineColor) + .call(yAxis) + .selectAll("text") + .attr("y", 0) + .attr("x", 0) + .attr("fill", line.lineColor) + .style("text-anchor", "start") # Key svg.append("line") @@ -302,7 +373,7 @@ module.exports = class AnalyticsSubscriptionsView extends RootView .data(line.points) .enter() .append("circle") - .attr("transform", "translate(" + (margin + yAxisWidth * graphLineCount) + "," + margin + ")") + .attr("transform", "translate(" + (margin + yAxisWidth * 2) + "," + margin + ")") .attr("cx", (d) -> xRange(d.x)) .attr("cy", (d) -> yRange(d.y)) .attr("r", 2) @@ -316,8 +387,8 @@ module.exports = class AnalyticsSubscriptionsView extends RootView .interpolate("linear") svg.append("path") .attr("d", d3line(line.points)) - .attr("transform", "translate(" + (margin + yAxisWidth * graphLineCount) + "," + margin + ")") - .style("stroke-width", 1) + .attr("transform", "translate(" + (margin + yAxisWidth * 2) + "," + margin + ")") + .style("stroke-width", line.strokeWidth) .style("stroke", line.lineColor) .style("fill", "none") currentLine++ diff --git a/scripts/analytics/mongodb/queries/campaignLevelCounts.js b/scripts/analytics/mongodb/queries/campaignLevelCounts.js new file mode 100644 index 000000000..d66ab55a8 --- /dev/null +++ b/scripts/analytics/mongodb/queries/campaignLevelCounts.js @@ -0,0 +1,27 @@ +// Print out campaign level counts + +// Usage: +// mongo
:/