Git Product home page Git Product logo

mumuki / mumuki-laboratory Goto Github PK

View Code? Open in Web Editor NEW
203.0 14.0 26.0 16.83 MB

:microscope: Where students practice and receive automated and human feedback

Home Page: http://mumuki.io

License: GNU Affero General Public License v3.0

Ruby 51.96% JavaScript 18.92% HTML 21.31% Shell 0.25% SCSS 7.56%
ruby laboratory education assessment code-assesment rails multitenant auth0 saml rspec

mumuki-laboratory's Introduction

Build status Code Climate

Mumuki Laboratory btn_donate_lg

Code assement web application for the Mumuki Platform

About

Laboratory is a multitenant Rails webapp for solving exercises, organized in terms of chapters and guides.

Preparing environment

1. Install essentials and base libraries

First, we need to install some software: PostgreSQL database, RabbitMQ queue, and some common Ruby on Rails native dependencies

sudo apt-get install autoconf curl git build-essential libssl-dev autoconf bison libreadline6 libreadline6-dev zlib1g zlib1g-dev postgresql libpq-dev rabbitmq-server

2. Install rbenv

rbenv is a ruby versions manager, similar to rvm, nvm, and so on.

curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc # or .bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bashrc # or .bash_profile

3. Install ruby

Now we have rbenv installed, we can install ruby and bundler

rbenv install 2.6.3
rbenv global 2.6.3
rbenv rehash
gem install bundler

4. Clone this repository

Because, err... we need to clone this repostory before developing it ๐Ÿ˜›

git clone https://github.com/mumuki/mumuki-laboratory
cd mumuki-laboratory

5. Install and setup database

We need to create a PostgreSQL role - AKA a user - who will be used by Laboratory to create and access the database

# create db user for linux users
sudo -u postgres psql <<EOF
  create role mumuki with createdb login password 'mumuki';
EOF

# create db user for mac users
psql postgres
#once inside postgres server
create role mumuki with createdb login password 'mumuki';

# create schema and initial development data
./devinit

Installing and Running

Quick start

If you want to start the server quickly in developer environment, you can just do the following:

./devstart

This will install your dependencies and boot the server.

Installing the server

If you just want to install dependencies, just do:

bundle install

Running the server

You can boot the server by using the standard rackup command:

# using defaults from config/puma.rb and rackup default port 9292
bundle exec rackup

# changing port
bundle exec rackup -p 8080

# changing threads count
MUMUKI_LABORATORY_THREADS=30 bundle exec rackup

# changing workers count
MUMUKI_LABORATORY_WORKERS=4 bundle exec rackup

Or you can also start it with puma command, which gives you more control:

# using defaults from config/puma.rb
bundle exec puma

# changing ports, workers and threads count, using puma-specific options:
bundle exec puma -w 4 -t 2:30 -p 8080

# changing ports, workers and threads count, using environment variables:
MUMUKI_LABORATORY_WORKERS=4 MUMUKI_LABORATORY_PORT=8080 MUMUKI_LABORATORY_THREADS=30 bundle exec puma

Finally, you can also start your server using rails:

rails s

Running tests

# Run all tests
bundle exec rake

# Run only web tests (i.e. Capybara and Teaspoon)
bundle exec rake spec:web

Running Capybara tests with Selenium

The Capybara config of this project supports running tests on Firefox, Chrome and Safari via Selenium. The webdrivers gem automatically installs (and updates) all the necessary Selenium webdrivers.

By default, Capybara tests will run with the default dummy-driver (Rack test). If you want to run on a real browser, you should set MUMUKI_SELENIUM_DRIVER variable to firefox, chrome or safari. Also, a Rake task to run just the Capybara tests is available.

Some examples:

# Run web tests, using Firefox
MUMUKI_SELENIUM_DRIVER=firefox bundle exec rake spec:web

# Run Capybara tests on Chrome
MUMUKI_SELENIUM_DRIVER=chrome bundle exec rake spec:web:capybara

Running JS tests

The webdrivers gem also works with Teaspoon, no need to install anything manually. By default tests run on Firefox, but this behavior can be changed by setting MUMUKI_SELENIUM_DRIVER (see section above).

bundle exec rake spec:web:teaspoon

Running eslint

yarn run lint

Using a local runner

Sometimes you will need to check laboratory against a local runner. Run the following code in you rails console:

require 'mumuki/domain/seed'

# import a new language
Mumuki::Domain::Seed.languages_syncer.locate_and_import!  Language, 'http://localhost:9292'

# update an existing language object
Mumuki::Domain::Seed.languages_syncer.import!  Mumukit::Sync.key(Language, 'http://localhost:9292'), language

Using a remote content

Likewise, you will sometimes require a guide that is not locally available. Run the following code in rails console:

require 'mumuki/domain/seed'

# import a new guide
Mumuki::Domain::Seed.contents_syncer.locate_and_import! Guide, slug

# update an existing guide object
Mumuki::Domain::Seed.contents_syncer.import!  Mumukit::Sync.key(Guide, slug), guide

After that you will probably to add it somewhere. The easiest way is to create a complement of central:

o = Organization.central
o.book.complements << Guide.locate!(slug).as_complement_of(o.book)
o.reindex_usages!

Now you will be able to visit that guide at http://localhost:3000/central/guides/#{slug}

Debugging email sender

The development environment is configured to "send" emails via mailcatcher, a mock server, if it is available. Run these commands to install and run it - and do it before the emails are sent, so it can actually catch them:

gem install mailcatcher
mailcatcher

Once up and running, go to http://localhost:1080/ to see which emails have been sent. Unfortunately, the developers recommend not to install it via Bundler, so it has to be done this way. ๐Ÿคทโ€โ™€๏ธ

JavaScript API Docs

In order to be customized by runners, Laboratory exposes the following selectors and methods which are granted to be safe and stable.

Public Selectors

  • .mu-final-state
  • .mu-initial-state-header
  • .mu-initial-state
  • .mu-kids-blocks
  • .mu-kids-context
  • .mu-kids-exercise-description
  • .mu-kids-exercise
  • .mu-kids-reset-button
  • .mu-kids-results-aborted
  • .mu-kids-results
  • .mu-kids-state-image
  • .mu-kids-state
  • .mu-kids-states
  • .mu-kids-submit-button
  • .mu-multiple-scenarios
  • .mu-scenarios
  • .mu-submit-button
  • #mu-actual-state-text
  • #mu-${languageName}-custom-editor
  • #mu-custom-editor-default-value
  • #mu-custom-editor-extra
  • #mu-custom-editor-test
  • #mu-custom-editor-value
  • #mu-initial-state-text

Deprecated Selectors

  • .mu-kids-gbs-board-initial: Use .mu-initial-state instead
  • .mu-state-final: Use .mu-final-state instead
  • .mu-state-initial: Use .mu-initial-state instead
  • #kids-results-aborted: Use .mu-kids-results-aborted instead
  • #kids-results: Use .mu-kids-results instead

Methods

  • mumuki.bridge.Laboratory
    • .runTests
  • mumuki.CustomEditor
    • addSource
  • mumuki.editor
    • formatContent
    • reset
    • toggleFullscreen
  • mumuki.elipsis
    • replaceHtml
  • mumuki.kids
    • registerBlocksAreaScaler
    • registerStateScaler
    • restart
    • scaleBlocksArea
    • scaleState
    • showResult
    • showContext
  • mumuki.renderers
    • SpeechBubbleRenderer
    • renderSpeechBubbleResultItem
  • mumuki.locale
  • mumuki.exercise
    • id: the id of the currently loaded exercise, if any
    • layout: the layout of the currently loaded exercise, if any
  • mumuki.incognitoUser: whether the current user is an incognito user
  • mumuki.MultipleScenarios
    • scenarios
    • currentScenarioIndex
    • resetIndicators
    • updateIndicators
  • mumuki.multipleFileEditor
    • setUpAddFile
    • setUpDeleteFiles
    • setUpDeleteFile
    • updateButtonsVisibility
  • mumuki.submission
    • processSolution
    • sendSolution
    • registerContentSyncer
  • mumuki.version

Bridge Response Format

{
  "status": "passed|passed_with_warnings|failed",
  "guide_finished_by_solution": "boolean",
  "html": "string",
  "remaining_attempts_html": "string",
  "current_exp": "integer",
  "title_html": "string",                         // kids-only
  "button_html": "string",                        // kids-only
  "expectations": [                               // kids-only
    {
      "status": "passed|failed",
      "explanation": "string"
    }
  ],
  "tips": [ "string" ],                           // kids-only
  "test_results": [                               // kids-only
    {
      "title": "string",
      "status": "passed|failed",
      "result": "string",
      "summary": "string"
    }
  ]
}

Kids Call order

  1. Laboratory Kids API Initialization
  2. Runner Editor JS
  3. Laboratory Kids Layout Initialization
  4. Runner Editor HTML

Generic event system

Laboartory provides the mumuki.events object, which acts as minimal, generic event system, which is mostly designed for third party components built on top of laboratory and runners. It does nothing by default.

This API has two parts: consumers API and producers API.

// ======
// producer
// ======

// you need to call this method in order to enable registration of event handlers
// otherwise, it will  be ignored
mumuki.events.enable('myEvent');

// fire the event, with an optional event object as payload
mumuki.events.fire('myEvent', aPlainOldObject);

// clear all the registered event handlers
mumuki.events.clear('myEvent');

// ========
// consumer
// ========

// register an event handler
mumuki.events.on('myEvent', (anEventObject) => {
  // do stuff
});

Custom editors

Mumuki provides several editor types: code editors, multiple choice, file upload, and so on. However, some runners will require custom editors in order to provide better ways of entering solutions.

The process to do so is not difficult, but tricky, since there are a few hooks you need to implement. Let's look at them:

1. Before state: adding layout assets

If you need to provide a custom editor, chances are that you also need to provide assets to augment the layout, e.g. providing ways to render some custom components on descriptions or corollaries. That code will be included first.

In order to do that, add to your runner the layout html, css and js code. Layout code has no further requirements. It can customize any public selector previously.

Although it is not required, it is recommended that your layout code works with any of the mumuki layouts:

  • input_right
  • input_bottom
  • input_primary
  • input_kindergarten

โš ๏ธ Not all the selectors will be available to all layouts.

Then expose code in the MetadataHook:

class ... < Mumukit::Hook
  def metadata
    {
      layout_assets_urls: {
        js: [
          'assets/....'
        ],
        css: [
          'assets/....'
        ],
        html: [
          'assets/....'
        ]
      }
    }
  end
end

Finally, it is recommended that you layout code calls mumuki.assetsLoadedFor('layout') when fully loaded.

That's it!

2. Adding custom editor assets

The process for registering custom editors is more involving.

2.1 Add your assets and expose them

Add your js, css and html assets to your runner, and expose them in MetadataHook:

class ... < Mumukit::Hook
  def metadata
    {
      editor_assets_urls: {
        js: [
          'assets/....'
        ],
        css: [
          'assets/....'
        ],
        html: [
          'assets/....'
        ]
      }
    }
  end
end

These assets will only be loaded when the editor custom is used.

2.2 Add your components to the custom editor

Using JavaScript, append your components the custom-editor root, which can be found using the following selectors:

  • mu-${languageName}-custom-editor
  • #mu-${languageName}-custom-editor
  • .mu-${languageName}-custom-editor
$('#mu-mylang-custom-editor').append(/* ... */)

2.3 Extract the test

If necessary, read the test definition from #mu-custom-editor-test, and plump into your custom components

const test = $('#mu-custom-editor-test').val()
//...use test...

2.4 Exposing your content

Before sending a submission, mumuki needs to be able to your read you editor components contents. There are two different approaches:

  • Register a syncer that writes #mu-custom-editor-value or any other custom editor selectors
  • Add one or more content sources
// simplest method - you can register just one
mumuki.editors.registerContentSyncer(() => {
  // ... write here your custom component content...
  $('#mu-custom-editor-value').val(/* ... */);
});

// alternate method
// you can register many sources
mumuki.editors.addCustomSource({
  getContent() {
    return { name: "solution[content]", value: /* ... */ } ;
  }
});

2.5 Optional: Triggering submission processing programmatically

Your solution will be automatically sent to the client and processed when the submit button is pressed. However, if you need to trigger the whole submission process programmatically, call mumuki.submission.processSolution:

mumuki.submission.processSolution({solution: {content: /* ... */}});

2.6 Optional: Sending your solution to the server programmatically

Your solution will be automatically sent to the client when the submit button is pressed, as part of the solution processing. However, if you just need to send your submission to the server programmatically, call mumuki.submission.sendSolution:

mumuki.submission.sendSolution({solution: {content: /* ... */}});

2.7 Optional: customizing your submit button

You can alternatively override the default submit button UI and behaviour, by replacing it with a custom component. In order to do that, override the .mu-submit-button or the kids-specific .mu-kids-submit-button:

 $(".mu-submit-button").html(/* ... */);

However, doing this is tricky, since you will need to manually update the UI and connecting to the server. See:

  • mumuki.kids.showResult
  • mumuki.bridge.Laboratory.runTests
  • mumuki.updateProgressBarAndShowModal

2.8 Register kids scalers

Kids layouts have some special areas:

  • state area: its display initial and/or final states of the exercise
  • blocks area: a workspace that contains the building blocks of the solution - which are not necessary programming or blockly blocks, actually

If you want to support kids layouts, you need to register scalers that will be called when device is resized. Skip this step otherwise.

mumuki.kids.registerStateScaler(($state, fullMargin, preferredWidth, preferredHeight) => {
  // ... resize your components ...
});

mumuki.kids.registerBlocksAreaScaler(($blocks) => {
  // ... resize your components ...
});

2.9 Notify when your assets have been loaded

In order to remove loading spinners, you will need to call mumuki.assetsLoadedFor when your code is ready.

mumuki.assetsLoadedFor('editor');

Transparent Navigation API Docs

In order to be able to link content, laboratory exposes slug-based routes that will redirect to the actual content URL in the current organization transparently:

  • GET <organization-url>/topics/<organization>/<repository>
  • GET <organization-url>/guides/<organization>/<repository>
  • GET <organization-url>/exercises/<organization>/<repository>/<bibliotheca-id>

REST API Docs

Before using the API, you must create an ApiClient using rails c, which will generate a private JWT. Use it to authenticate API calls in any Platform application within a Authorizaion: Bearer <TOKEN>.

Before using the API, take a look to the roles hierarchy:

roles hierarchy.

Permissions are bound to a scope, that states in which context the operation can be performed. Scopes are simply two-level contexts, expressed as slugss <first>/<second>, without any explicit semantic. They exact meaning depends on the role:

  • ex_student: organization/course
  • student: organization/course
  • teacher and headmaster: organization/course
  • writer and editor: organization/content
  • janitor: organization/_
  • moderator and forum_supervisor: organization/_
  • admin: _/_
  • owner: _/_

Users

Create single user

This is a generic user creation request.

Minimal permission: janitor

POST /users

Sample request body:

{
  "first_name": "Marรญa",
  "last_name": "Casas",
  "email": "[email protected]",
  "permissions": {
     "student": "cpt/*:rte/*",
     "teacher": "ppp/2016-2q"
  }
}

Update single user

This is a way of updating user basic data. Permissions are ignored.

Minimal permission: janitor

PUT /users/:uid

Sample request body:

{
  "first_name": "Marรญa",
  "last_name": "Casas",
  "email": "[email protected]",
  "uid": "[email protected]"
}

Add student to course

Creates the student if necessary, and updates her permissions.

Minimal permission: janitor

POST /courses/:organization/:course/students
{
  "first_name": "Marรญa",
  "last_name": "Casas",
  "email": "[email protected]",
  "uid": "[email protected]"
}

Response

{
  "uid": "[email protected]",
  "first_name": "Marรญa",
  "last_name": "Casas",
  "email": "[email protected]"
}

Forbidden Response

{
  "status": 403,
  "error": "Exception"
}

Detach student from course

Remove student permissions from a course.

Minimal permission: janitor

POST /courses/:organization/:course/students/:uid/detach

Response: status code: 200

Not Found Response

{
  "status": 404,
  "error": "Couldn't find User"
}

Attach student to course

Add student permissions to a course.

Minimal permission: janitor

POST /courses/:organization/:course/students/:uid/attach

Response: status code: 200

Not Found Response

{
  "status": 404,
  "error": "Couldn't find User"
}

Add teacher to course

Creates the teacher if necessary, and updates her permissions.

Minimal permission: headmaster, janitor

POST /course/:id/teachers
{
  "first_name": "Erica",
  "last_name": "Gonzalez",
  "email": "[email protected]",
  "uid": "[email protected]"
}

Add a batch of users to a course

Creates every user if necesssary, an updates permissions.

Minimal permission: janitor

POST /course/:id/batches
{
  "students": [
    {
      "first_name": "Tupac",
      "last_name": "Lincoln",
      "email": "[email protected]",
      "uid": "[email protected]"
    }
  ],
  "teachers": [
    {
      "first_name": "Erica",
      "last_name": "Gonzalez",
      "email": "[email protected]",
      "uid": "[email protected]"
    }
  ]
}

Detach student from course

Minimal permission: janitor

DELETE /course/:id/students/:uid

Detach teacher from course

Minimal permission: janitor

DELETE /course/:id/teachers/:uid

Destroy single user

Minimal permission: admin

DELETE /users/:uid

Courses

Create single course

Minimal permission: janitor

POST /organization/:id/courses/
{
   "name": "....",
}

Archive single course

Minimal permission: janitor

DELETE /organization/:id/courses/:id

Destroy single course

Minimal permission: admin

DELETE /courses/:id

Organizations

Model

Mandatory fields

{
  "name": "academy",
  "contact_email": "[email protected]",
  "books": [
    "MumukiProject/mumuki-libro-metaprogramacion"
  ],
  "locale": "es-AR"
}

Optional fields

{
  "public": false,
  "description": "...",
  "login_methods": [
    "facebook", "twitter", "google"
  ],
  "logo_url": "http://mumuki.io/logo-alt-large.png",
  "terms_of_service": "Al usar Mumuki aceptรกs que las soluciones de tus ejercicios sean registradas para ser corregidas por tu/s docente/s...",
  "theme_stylesheet": ".theme { color: red }",
  "extension_javascript": "doSomething = function() { }"
}
  • If you set null to public, login_methods, the values will be false and `["user_pass"].
  • If you set null to description, the value will be null.
  • If you set null to the others, it will be inherited from an organization called "base" every time you query the API.

Generated fields

{
  "theme_stylesheet_url": "stylesheets/academy-asjdf92j1jd8.css",
  "extension_javascript_url": "javascripts/academy-jd912j8jdj19.js"
}

List all organizations

get /organizations

Sample response body:

{
  "organizations": [
    { "name": "academy", "contact_email": "[email protected]", "locale": "es-AR", "login_methods": ["facebook"], "books": ["libro"], "public": true, "logo_url": "http://..." },
    { "name": "alcal", "contact_email": "[email protected]", "locale": "en-US", "login_methods": ["facebook", "github"], "books": ["book"], "public": false }
  ]
}

Minimal permission: None for public organizations, janitor for user's private organizations.

Get single organization by name

get /organizations/:name

Sample response body:

{ "name": "academy", "contact_email": "[email protected]", "locale": "es-AR", "login_methods": ["facebook"], "books": ["libro"], "public": true, "logo_url": "http://..." }

Minimal permission: janitor of the organization.

Create organization

post /organizations

... with at least the required fields.

Minimal permission: admin of that organization

Update organization

put /organizations/:name

... with a partial update.

Minimal permission: admin of :name

Authentication Powered by Auth0

JWT Auth for open source projects

mumuki-laboratory's People

Contributors

afska avatar aguspina avatar askl56 avatar bossiernesto avatar charlyzzz avatar dependabot[bot] avatar dggluz avatar faloi avatar fedescarpa avatar felipecalvo avatar flbulgarelli avatar gastonprieto avatar gcrespi avatar giuliano64 avatar gustrucco avatar iluetich avatar javi avatar julian-berbel avatar jvillarejo avatar leonardoalifraco avatar luchotc avatar manugb avatar mumukibot avatar nadiafinzi avatar palumbon avatar rolodato avatar waffle-iron avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mumuki-laboratory's Issues

Gamification on Exercise Quality

In order to encourage people to create "good" exercises, add the concept of user's points or rank, similar to http://stackoverflow.com/, so that a user who creates exercises with more submissions, or with a higher submission rate - submissions per exercise average - gets more points.

That points could be then used to search or order exercies or guides.

Exchange tokens between atheneum and services

We need this improvement so that services and atheneum know were the request come from, in order to prevent some kind of DoS and man-in-the-middle's.

This applies to:

  • test runners
  • event subscribers
  • guides/submissions API

I18n error messages

The error message in the red box should be in Spanish just as the content

screen shot 2015-01-07 at 12 06 27 pm

Add hints to exercise

Be able to add zero or more hints to exercise definition.

A hint may be just an explanation, a link to some related material, or a piece of code.

Hints should be hidden by default to submitter, but she should be able to reveal them whenever she wants.

Migrate to true microservices for test runner

Refactor Mumuki to replace language plugins by test runner servers and endpoints.

Test runners must be clever, so that they are responsible of compiling the test template, tests and submission.

Exercise edition permissions

Let the creator of the exercise to give permissions to other users to edit them. Or add the notion of organizations, like Github's.

It would be nice to investigate about Github's API permissions and get the most of them. Coupling to GH should not be consider an issue, but a design decision.

Empty listing messages

Display user friendly messages when a listing is empty. This applies to:

  • exercise search
  • guides listing
  • exercise guides listing
  • exercise submissions
  • user guides
  • user exercises
  • user submissions

Add backoffice

We need an admin user, who can remove or edit exercises and submissions

Group exercises

We need a way of grouping exercises, so that we can distribute them to students easily.

That could be achieved by adding the concept of "Exercise Sets"- each exercise should belong to a set - or just - hash? - tagging them. Then we could search exercises by tags like "polymorhpism" "higher order functions" and so on.

Landing page

The current homepage is just a proof of concept. It must the splitted into several pages. A first step is to create a true landing page.

Details

We need a landing page that explains what Mumuki is, and guides the user to the most common flows:

  • Search exercises and submit solutions - "practice your skills"
  • Create new exercises and guides - "share your exercises" .
  • Develop and create new language plugins - "extend mumuki"

Add dependencies through guides

Guides could have dependencies on other guides. A dependency would mean that a user should do a guide before another.

This dependens on issue #39

Code highlight is broken

Mumuki is using highlight.js for highlighting test results and exercises tests; however highlight is broken in master.

Too long descriptions cause mumiki K-BOOM :P

PG::StringDataRightTruncation: ERROR: value too long for type character varying(255) : UPDATE "exercises" SET "description" = $1, "updated_at" = $2 WHERE "exercises"."id" = 10

Extracted source (around line #30):
  def update
    if @exercise.update(exercise_params)
      redirect_to @exercise, notice: 'Exercise was successfully updated.'
    else
      render :edit
Rails.root: /app

Application Trace | Framework Trace | Full Trace
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql_adapter.rb:834:in `get_last_result'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql_adapter.rb:834:in `block in exec_cache'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract_adapter.rb:373:in `block in log'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract_adapter.rb:367:in `log'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql_adapter.rb:831:in `exec_cache'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql/database_statements.rb:155:in `exec_delete'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract/database_statements.rb:101:in `update'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract/query_cache.rb:14:in `update'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/relation.rb:77:in `_update_record'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/persistence.rb:493:in `_update_record'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/locking/optimistic.rb:70:in `_update_record'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/attribute_methods/dirty.rb:83:in `_update_record'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/callbacks.rb:310:in `block in _update_record'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:113:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:113:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `block in halting_and_conditional'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `block in halting_and_conditional'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `block in halting_and_conditional'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `block in halting_and_conditional'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `block in halting_and_conditional'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:86:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:86:in `run_callbacks'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/callbacks.rb:310:in `_update_record'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/timestamp.rb:70:in `_update_record'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/persistence.rb:482:in `create_or_update'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/callbacks.rb:302:in `block in create_or_update'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:113:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:113:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:166:in `block in halting'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:166:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:166:in `block in halting'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:166:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:166:in `block in halting'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `block in halting_and_conditional'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:215:in `block in halting_and_conditional'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:86:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:86:in `run_callbacks'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/callbacks.rb:302:in `create_or_update'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/persistence.rb:103:in `save'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/validations.rb:51:in `save'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/attribute_methods/dirty.rb:21:in `save'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/transactions.rb:268:in `block (2 levels) in save'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/transactions.rb:329:in `block in with_transaction_returning_status'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract/database_statements.rb:199:in `transaction'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/transactions.rb:208:in `transaction'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/transactions.rb:326:in `with_transaction_returning_status'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/transactions.rb:268:in `block in save'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/transactions.rb:283:in `rollback_active_record_state!'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/transactions.rb:267:in `save'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/persistence.rb:232:in `block in update'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/transactions.rb:329:in `block in with_transaction_returning_status'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract/database_statements.rb:201:in `block in transaction'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract/database_statements.rb:209:in `within_new_transaction'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract/database_statements.rb:201:in `transaction'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/transactions.rb:208:in `transaction'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/transactions.rb:326:in `with_transaction_returning_status'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/persistence.rb:230:in `update'
app/controllers/exercises_controller.rb:30:in `update'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_controller/metal/implicit_render.rb:4:in `send_action'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/abstract_controller/base.rb:189:in `process_action'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_controller/metal/rendering.rb:10:in `process_action'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/abstract_controller/callbacks.rb:20:in `block in process_action'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:113:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:113:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:149:in `block in halting_and_conditional'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:149:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:149:in `block in halting_and_conditional'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:229:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:229:in `block in halting'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:166:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:166:in `block in halting'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:166:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:166:in `block in halting'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:86:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:86:in `run_callbacks'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/abstract_controller/callbacks.rb:19:in `process_action'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_controller/metal/rescue.rb:29:in `process_action'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_controller/metal/instrumentation.rb:31:in `block in process_action'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/notifications.rb:159:in `block in instrument'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/notifications.rb:159:in `instrument'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_controller/metal/instrumentation.rb:30:in `process_action'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/railties/controller_runtime.rb:18:in `process_action'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/abstract_controller/base.rb:136:in `process'
vendor/bundle/ruby/2.0.0/gems/actionview-4.1.4/lib/action_view/rendering.rb:30:in `process'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_controller/metal.rb:196:in `dispatch'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_controller/metal/rack_delegation.rb:13:in `dispatch'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_controller/metal.rb:232:in `block in action'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/routing/route_set.rb:82:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/routing/route_set.rb:82:in `dispatch'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/routing/route_set.rb:50:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/journey/router.rb:71:in `block in call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/journey/router.rb:59:in `each'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/journey/router.rb:59:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/routing/route_set.rb:678:in `call'
vendor/bundle/ruby/2.0.0/gems/omniauth-1.2.2/lib/omniauth/strategy.rb:186:in `call!'
vendor/bundle/ruby/2.0.0/gems/omniauth-1.2.2/lib/omniauth/strategy.rb:164:in `call'
vendor/bundle/ruby/2.0.0/gems/omniauth-1.2.2/lib/omniauth/builder.rb:59:in `call'
vendor/bundle/ruby/2.0.0/gems/rack-1.5.2/lib/rack/etag.rb:23:in `call'
vendor/bundle/ruby/2.0.0/gems/rack-1.5.2/lib/rack/conditionalget.rb:35:in `call'
vendor/bundle/ruby/2.0.0/gems/rack-1.5.2/lib/rack/head.rb:11:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/middleware/params_parser.rb:27:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/middleware/flash.rb:254:in `call'
vendor/bundle/ruby/2.0.0/gems/rack-1.5.2/lib/rack/session/abstract/id.rb:225:in `context'
vendor/bundle/ruby/2.0.0/gems/rack-1.5.2/lib/rack/session/abstract/id.rb:220:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/middleware/cookies.rb:560:in `call'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/query_cache.rb:36:in `call'
vendor/bundle/ruby/2.0.0/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:621:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:82:in `run_callbacks'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/middleware/callbacks.rb:27:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/middleware/remote_ip.rb:76:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
vendor/bundle/ruby/2.0.0/gems/railties-4.1.4/lib/rails/rack/logger.rb:38:in `call_app'
vendor/bundle/ruby/2.0.0/gems/railties-4.1.4/lib/rails/rack/logger.rb:20:in `block in call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/tagged_logging.rb:68:in `block in tagged'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/tagged_logging.rb:26:in `tagged'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/tagged_logging.rb:68:in `tagged'
vendor/bundle/ruby/2.0.0/gems/railties-4.1.4/lib/rails/rack/logger.rb:20:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/middleware/request_id.rb:21:in `call'
vendor/bundle/ruby/2.0.0/gems/rack-1.5.2/lib/rack/methodoverride.rb:21:in `call'
vendor/bundle/ruby/2.0.0/gems/rack-1.5.2/lib/rack/runtime.rb:17:in `call'
vendor/bundle/ruby/2.0.0/gems/activesupport-4.1.4/lib/active_support/cache/strategy/local_cache_middleware.rb:26:in `call'
vendor/bundle/ruby/2.0.0/gems/actionpack-4.1.4/lib/action_dispatch/middleware/static.rb:64:in `call'
vendor/bundle/ruby/2.0.0/gems/rack-1.5.2/lib/rack/sendfile.rb:112:in `call'
vendor/bundle/ruby/2.0.0/gems/railties-4.1.4/lib/rails/engine.rb:514:in `call'
vendor/bundle/ruby/2.0.0/gems/railties-4.1.4/lib/rails/application.rb:144:in `call'
vendor/bundle/ruby/2.0.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:576:in `process_client'
vendor/bundle/ruby/2.0.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:670:in `worker_loop'
vendor/bundle/ruby/2.0.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:525:in `spawn_missing_workers'
vendor/bundle/ruby/2.0.0/gems/unicorn-4.8.3/lib/unicorn/http_server.rb:140:in `start'
vendor/bundle/ruby/2.0.0/gems/unicorn-4.8.3/bin/unicorn:126:in `<top (required)>'
vendor/bundle/ruby/2.0.0/bin/unicorn:23:in `load'
vendor/bundle/ruby/2.0.0/bin/unicorn:23:in `<main>'
Request

Parameters:

{"utf8"=>"โœ“",
 "_method"=>"patch",
 "authenticity_token"=>"/bEkfutn2eQUo/D7qUhE9CibwU9u2p6R0kJHcXD3gSY=",
 "exercise"=>{"title"=>"Los simpsons",
 "language"=>"prolog",
 "tag_list"=>"prolog",
 "description"=>"Teniendo la siguiente base de conocimiento\r\n\r\n```\r\npadreDe(homero,
 bart).\r\npadreDe(homero,
 lisa).\r\npadreDe(homero,
 maggie).\r\n```\r\n\r\nHacer hermanoDe/2,
 que se verifica cuando 2 personas distintas tienen el mismo padre. dfafadfsdfdafsdfadfsdafadfdfadfadfadfadf",
 "test"=>"padreDe(homero,
 bart).\r\npadreDe(homero,
 lisa).\r\npadreDe(homero,
 maggie).\r\npadreDe(abe,
 homero).\r\npadreDe(abe,
 hebert).\r\n\r\ntest(test_bart_es_hermano_de_lisa) :-\r\n    hermanoDe(bart,
 lisa)."},
 "commit"=>"Update Exercise",
 "locale"=>"en",
 "id"=>"10"}
Toggle session dump
_csrf_token: "/bEkfutn2eQUo/D7qUhE9CibwU9u2p6R0kJHcXD3gSY="
session_id: "3d888e17b20c79d47cec87345af43b09"
user_id: 5
Toggle env dump
HTTP_ACCEPT: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
HTTP_ACCEPT_ENCODING: "gzip, deflate"
HTTP_ACCEPT_LANGUAGE: "en-US,en;q=0.8,es-419;q=0.6,es;q=0.4"
HTTP_CACHE_CONTROL: "max-age=0"
REMOTE_ADDR: "10.4.38.156"
SERVER_NAME: "mumuki.herokuapp.com"
SERVER_PROTOCOL: "HTTP/1.1"
Response
Headers:

None

Avoid processing of redundant submissions

When a submission is enqueued for processing, if there is already one for the same exercise and user in pending state, replace it with the latest one.

That would reduce network traffic and test-servers load.

When user is logged, display if exercise has been resolved by user

An exercise should be consider resolved by a user, when he/she has at least a submission that passed for that exercise.

This should be indicated in every listing of exercises. We shoul use an icon or a specific visual format, in order to avoid page polution.

This might be a cornerstone for more advanced - not yet planned - features like telling a user which exercises from a guide she has not resolved yet, or for which guides a user has submitions.

Use label's whenever possible

Whenever possible, we should use label :model, :attribute in all views. This makes I18N easier, since we only need to define the standard active record entries in the translation file.

Add locale to exercise

Exercise's should have a locale attribute which indicates if the exercise is spelled in the user's language.

That information should be also listed in the metadata when exercises are imported from git.

I am proposing calling it locale instead of language because we already have a language property for the programming language.

Add FB Integrations

We need to have the classical FB like buttons and page, in order to improve visibilty of the project.

I18N UI

Support UI both in English and Spanish

Full text search for exercises

Instead of having "search by tag", "search by title", etc, options, we should have a simple "search function", that looks for exercises based on the following elements:

  • title
  • tags
  • username
  • language
  • description

We can use Postgre Full Text search support, which is simple and requires nearly no extra configuration.

Give more relavance to guides

Currently guides are just a way of uploading a group of exercises. We should give more relevance to them, making them publicly visible, and listable, so that an instructor could give a link to a guide to their students.

Guide's author

Currently a guide author is the user that registered the hook. However, it should be the owner of the repository, and all the users that have access to the repo should have edition access to the guide.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.