Add waiting room to create programming groups (#1919)
Co-authored-by: Sebastian Serth <Sebastian.Serth@hpi.de>
This commit is contained in:
@ -3,10 +3,9 @@ $(document).on('turbolinks:load', function () {
|
||||
if ($.isController('programming_groups') && window.location.pathname.includes('programming_groups/new')) {
|
||||
const matching_page = $('#matching');
|
||||
const exercise_id = matching_page.data('exercise-id');
|
||||
const specific_channel = { channel: "PgMatchingChannel", exercise_id: exercise_id};
|
||||
|
||||
App.pg_matching = App.cable.subscriptions.create({
|
||||
channel: "PgMatchingChannel", exercise_id: exercise_id
|
||||
}, {
|
||||
App.pg_matching = App.cable.subscriptions.create(specific_channel, {
|
||||
connected() {
|
||||
// Called when the subscription is ready for use on the server
|
||||
},
|
||||
@ -23,8 +22,17 @@ $(document).on('turbolinks:load', function () {
|
||||
window.location.reload();
|
||||
}
|
||||
break;
|
||||
case 'joined_pg':
|
||||
if (ProgrammingGroups.contains_own_user(data.users)) {
|
||||
window.location.reload();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
waiting_for_match() {
|
||||
this.perform('waiting_for_match');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -20,6 +20,10 @@ var ProgrammingGroups = {
|
||||
is_other_session: function (other_session_id) {
|
||||
return this.session_id !== other_session_id;
|
||||
},
|
||||
|
||||
contains_own_user: function (users) {
|
||||
return users.find(e => ProgrammingGroups.is_other_user(e) === false) !== undefined
|
||||
}
|
||||
};
|
||||
|
||||
$(document).on('turbolinks:load', function () {
|
||||
@ -35,4 +39,12 @@ $(document).on('turbolinks:load', function () {
|
||||
new bootstrap.Modal(modal).show();
|
||||
}
|
||||
}
|
||||
|
||||
const join_pair_button = $('.join_programming_pair');
|
||||
if (join_pair_button.isPresent()) {
|
||||
join_pair_button.on('click', function() {
|
||||
App.pg_matching?.waiting_for_match();
|
||||
CodeOceanEditor.showSpinner(join_pair_button);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -8,6 +8,8 @@ class PgMatchingChannel < ApplicationCable::Channel
|
||||
|
||||
def unsubscribed
|
||||
# Any cleanup needed when channel is unsubscribed
|
||||
@current_waiting_user.status_disconnected! if @current_waiting_user&.reload&.status_waiting?
|
||||
|
||||
stop_all_streams
|
||||
end
|
||||
|
||||
@ -15,8 +17,29 @@ class PgMatchingChannel < ApplicationCable::Channel
|
||||
"pg_matching_channel_exercise_#{@exercise.id}"
|
||||
end
|
||||
|
||||
def waiting_for_match
|
||||
@current_waiting_user = PairProgrammingWaitingUser.find_or_initialize_by(user: current_user, exercise: @exercise)
|
||||
@current_waiting_user.status_waiting!
|
||||
|
||||
match_waiting_users
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def match_waiting_users
|
||||
# Check if there is another waiting user for this exercise
|
||||
waiting_user = PairProgrammingWaitingUser.where(exercise: @exercise, status: :waiting).where.not(user: current_user).order(created_at: :asc).first
|
||||
return if waiting_user.blank?
|
||||
|
||||
# If there is another waiting user, create a programming group with both users
|
||||
match = [waiting_user, @current_waiting_user]
|
||||
# Create the programming group. Note that an unhandled exception will be raised if the programming group
|
||||
# is not valid (i.e., if one of the users already joined a programming group for this exercise).
|
||||
pg = ProgrammingGroup.create!(exercise: @exercise, users: match.map(&:user))
|
||||
match.each {|wu| wu.update(status: :created_pg, programming_group: pg) }
|
||||
ActionCable.server.broadcast(specific_channel, {action: 'joined_pg', users: pg.users.map(&:to_page_context)})
|
||||
end
|
||||
|
||||
def set_and_authorize_exercise
|
||||
@exercise = Exercise.find(params[:exercise_id])
|
||||
reject unless ExercisePolicy.new(current_user, @exercise).implement?
|
||||
|
@ -299,7 +299,7 @@ class ExercisesController < ApplicationController
|
||||
|
||||
private :update_exercise_tips
|
||||
|
||||
def implement
|
||||
def implement # rubocop:disable Metrics/CyclomaticComplexity
|
||||
if session[:pg_id] && current_contributor.exercise != @exercise
|
||||
# we are acting on behalf of a programming group
|
||||
if current_user.admin?
|
||||
@ -315,8 +315,9 @@ class ExercisesController < ApplicationController
|
||||
# we are just acting on behalf of a single user who has already worked on this exercise as part of a programming group **in the context of the current study group**
|
||||
session[:pg_id] = pg.id
|
||||
@current_contributor = pg
|
||||
elsif PairProgramming23Study.participate?(current_user, @exercise) && current_user.submissions.where(study_group_id: current_user.current_study_group_id, exercise: @exercise).any?
|
||||
elsif PairProgramming23Study.participate?(current_user, @exercise) && current_user.submissions.where(study_group_id: current_user.current_study_group_id, exercise: @exercise).none?
|
||||
Event.create(category: 'pp_work_alone', user: current_user, exercise: @exercise, data: nil, file_id: nil)
|
||||
current_user.pair_programming_waiting_users&.find_by(exercise: @exercise)&.update(status: :worked_alone)
|
||||
end
|
||||
|
||||
user_solved_exercise = @exercise.solved_by?(current_contributor)
|
||||
|
@ -53,6 +53,9 @@ class ProgrammingGroupsController < ApplicationController
|
||||
ActionCable.server.broadcast("pg_matching_channel_exercise_#{@exercise.id}", message)
|
||||
end
|
||||
|
||||
# Check if the user was waiting for a programming group match and update the status
|
||||
current_user.pair_programming_waiting_users&.find_by(exercise: @exercise)&.update(status: :created_pg, programming_group: @programming_group)
|
||||
|
||||
# Just set the programming group id in the session for the creator of the group, so that the user can be redirected.
|
||||
session[:pg_id] = @programming_group.id
|
||||
|
||||
|
@ -32,6 +32,7 @@ class Exercise < ApplicationRecord
|
||||
has_many :external_users, source: :contributor, source_type: 'ExternalUser', through: :submissions
|
||||
has_many :internal_users, source: :contributor, source_type: 'InternalUser', through: :submissions
|
||||
has_many :programming_groups
|
||||
has_many :pair_programming_waiting_users
|
||||
|
||||
scope :with_submissions, -> { where('id IN (SELECT exercise_id FROM submissions)') }
|
||||
|
||||
|
25
app/models/pair_programming_waiting_user.rb
Normal file
25
app/models/pair_programming_waiting_user.rb
Normal file
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PairProgrammingWaitingUser < ApplicationRecord
|
||||
include Creation
|
||||
|
||||
belongs_to :exercise
|
||||
belongs_to :programming_group, optional: true
|
||||
|
||||
enum status: {
|
||||
waiting: 0,
|
||||
joined_pg: 1,
|
||||
disconnected: 2,
|
||||
worked_alone: 3,
|
||||
created_pg: 4,
|
||||
}, _prefix: true
|
||||
|
||||
validates :user_id, uniqueness: {scope: %i[exercise_id user_type]}
|
||||
validates :programming_group_id, presence: true, if: -> { status_joined_pg? || status_created_pg? }
|
||||
|
||||
after_save :capture_event
|
||||
|
||||
def capture_event
|
||||
Event.create(category: 'pp_matching', user:, exercise:, data: status.to_s)
|
||||
end
|
||||
end
|
@ -12,6 +12,7 @@ class ProgrammingGroup < ApplicationRecord
|
||||
has_many :events
|
||||
has_many :events_synchronized_editor, class_name: 'Event::SynchronizedEditor'
|
||||
has_many :pair_programming_exercise_feedbacks
|
||||
has_many :pair_programming_waiting_users
|
||||
belongs_to :exercise
|
||||
|
||||
validate :min_group_size
|
||||
|
@ -25,6 +25,7 @@ class User < ApplicationRecord
|
||||
has_many :events
|
||||
has_many :events_synchronized_editor, class_name: 'Event::SynchronizedEditor'
|
||||
has_many :pair_programming_exercise_feedbacks
|
||||
has_many :pair_programming_waiting_users
|
||||
has_one :codeharbor_link, dependent: :destroy
|
||||
accepts_nested_attributes_for :user_proxy_exercise_exercises
|
||||
|
||||
|
@ -1,8 +1,16 @@
|
||||
= form_for(@programming_group, url: exercise_programming_groups_path) do |f|
|
||||
= render('shared/form_errors', object: @programming_group)
|
||||
.mb-3
|
||||
= f.label(:programming_partner_ids, class: 'form-label')
|
||||
= f.text_field(:programming_partner_ids, class: 'form-control', required: true, value: (@programming_group.programming_partner_ids - [current_user.id_with_type]).join(', '))
|
||||
/.help-block.form-text = t('.hints.programming_partner_ids')
|
||||
.actions.mb-0
|
||||
= render('shared/submit_button', f: f, object: @programming_group)
|
||||
.form-group
|
||||
.row
|
||||
.col-md-6
|
||||
= f.label(:programming_partner_ids, class: 'form-label')
|
||||
.row
|
||||
.col-md-6
|
||||
.input-group.mb-3
|
||||
= f.text_field(:programming_partner_ids, class: 'form-control', required: true, value: (@programming_group.programming_partner_ids - [current_user.id_with_type]).join(', '))
|
||||
= render('shared/submit_button', f: f, object: @programming_group)
|
||||
/.help-block.form-text = t('.hints.programming_partner_ids')
|
||||
.col-md-6
|
||||
.join_programming_pair.button.btn.btn-primary.d-none.d-md-block
|
||||
i.fa-solid.fa-circle-notch.fa-spin.d-none
|
||||
= t('programming_groups.new.find_programming_partner')
|
||||
|
@ -1,30 +1,38 @@
|
||||
h1 = t('programming_groups.new.create_programming_pair')
|
||||
#matching.row data-exercise-id=@exercise.id
|
||||
.col-md-6
|
||||
p
|
||||
h1.d-inline-block = t('programming_groups.new.create_programming_pair')
|
||||
.btn.btn-success.float-end data-bs-toggle= 'modal' data-bs-target= '#modal-info-pair-programming'
|
||||
i.fa-solid.fa-circle-info
|
||||
= t('programming_groups.new.pair_programming_info')
|
||||
#matching data-exercise-id=@exercise.id.to_s
|
||||
.row
|
||||
.col-12.mt-2.mb-4
|
||||
p = t('programming_groups.new.info_work_together', exercise_title: @exercise.title)
|
||||
.text-body-tertiary == t('exercises.implement.pair_programming_feedback', url: "https://etherpad.xopic.de/p/openHPI_PairProgrammingFeedback?userName=#{CGI.escape(current_user.displayname)}")
|
||||
|
||||
.row
|
||||
.col-md-6
|
||||
h5 = t('programming_groups.new.work_with_a_friend')
|
||||
p = t('programming_groups.new.enter_partner_id', exercise_title: @exercise.title)
|
||||
|
||||
|
||||
=> t('programming_groups.new.own_user_id')
|
||||
b
|
||||
= current_user.id_with_type
|
||||
b = current_user.id_with_type
|
||||
.d-md-none
|
||||
= render('form')
|
||||
|
||||
button.btn.btn-success data-bs-toggle= 'modal' data-bs-target= '#modal-info-pair-programming'
|
||||
i.fa-solid.fa-circle-info
|
||||
= t('programming_groups.new.pair_programming_info')
|
||||
.col-md-6
|
||||
h5 = t('programming_groups.new.find_partner_title')
|
||||
p = t('programming_groups.new.find_partner_description')
|
||||
|
||||
p.mt-4
|
||||
= t('programming_groups.new.enter_partner_id', exercise_title: @exercise.title)
|
||||
= render('form')
|
||||
|
||||
div.mt-4
|
||||
== t('programming_groups.new.work_alone', path: implement_exercise_path(@exercise))
|
||||
|
||||
.col-md-6
|
||||
h5 = t('programming_groups.new.find_partner_title')
|
||||
p
|
||||
= t('programming_groups.new.find_partner_description')
|
||||
- if !browser.safari?
|
||||
iframe name="embed_readwrite" src="https://etherpad.xopic.de/p/find_programming_group_for_exercise_#{@exercise.id}?userName=#{CGI.escape(current_user.displayname)}&showControls=false&showChat=false&showLineNumbers=true&useMonospaceFont=false" width="100%" height="300" style='border: 1px solid black;'
|
||||
- else
|
||||
== t('programming_groups.new.safari_not_supported', url: "https://etherpad.xopic.de/p/find_programming_group_for_exercise_#{@exercise.id}?userName=#{CGI.escape(current_user.displayname)}&showControls=false&showChat=false&showLineNumbers=true&useMonospaceFont=false")
|
||||
.join_programming_pair.button.btn.btn-primary.d-md-none.mb-3
|
||||
i.fa-solid.fa-circle-notch.fa-spin.d-none
|
||||
= t('programming_groups.new.find_programming_partner')
|
||||
.row
|
||||
.col-12.d-none.d-md-block
|
||||
= render('form')
|
||||
|
||||
.row
|
||||
.col-12
|
||||
h5 = t('programming_groups.new.work_alone')
|
||||
== t('programming_groups.new.work_alone_description', path: implement_exercise_path(@exercise))
|
||||
|
||||
= render('shared/modal', classes: 'modal-lg', id: 'modal-info-pair-programming', template: 'programming_groups/_info_pair_programming', title: t('programming_groups.new.pair_programming_info'))
|
||||
|
@ -233,8 +233,8 @@ de:
|
||||
one: Feedback
|
||||
other: Feedbacks
|
||||
programming_group:
|
||||
one: Programmiergruppe
|
||||
other: Programmiergruppen
|
||||
one: Programmierpaar
|
||||
other: Programmierpaare
|
||||
programming_group_membership:
|
||||
one: Programmiergruppenmitgliedschaft
|
||||
other: Programmiergruppenmitgliedschaften
|
||||
@ -598,15 +598,18 @@ de:
|
||||
close: Schließen
|
||||
create_programming_pair: Programmierpaar erstellen
|
||||
dont_show_modal_again: "Auf diesem Gerät nicht mehr anzeigen"
|
||||
enter_partner_id: "Bitte gib hier die Nutzer-ID der Person ein, mit der du zusammen die Aufgabe '%{exercise_title}' lösen möchtest. Beachte jedoch, dass anschließend keiner die Zusammenarbeit beenden kann. Dein:e Teampartner:in kann sehen, was du in dieser Aufgabe schreibst und umgekehrt. Für die nächste Aufgabe kannst du dich erneuert entscheiden, ob und mit wem du zusammen arbeiten möchtest."
|
||||
enter_partner_id: "Kennst du eine Person in dem Kurs, mit der du gemeinsam die Aufgabe lösen möchtest? Dann gib hier die Nutzer-ID dieser Person ein."
|
||||
find_partner_title: "Finde eine:n Programmierpartner:in für die Aufgabe"
|
||||
find_partner_description: "Kopiere eine andere Nutzer-ID aus der Liste unten und lösche sie anschließend. Wenn noch keine Nutzer-IDs in der Liste vorhanden sind, füge deine Nutzer-ID hinzu und warte, bis ein andere:r Nutzer:in ein Programmierpaar mit dir erstellt. Sobald dich eine Partner:in zu einer Gruppe eingeladen hast, wirst Du automatisch weitergeleitet, um gemeinsam an dieser Aufgabe arbeiten zu können. Wenn du allein arbeiten möchtest, lösche bitte deine Nutzer-ID aus der Liste, falls du sie hinzugefügt hast."
|
||||
find_partner_description: "Wenn du keine Person aus dem Kurs kennst, hast du die Möglichkeit mit einer anderen Person gepaart zu werden. Du wirst dann zur Aufgabe weitergeleitet, sobald eine andere Person ebenfalls diese Aufgabe im Team lösen möchte."
|
||||
info_pair_programming: "Pair Programming (Programmieren in Paaren) ist eine Methode, bei der zwei Personen gemeinsam programmieren. Dabei übernehmen sie <i>abwechselnd</i> zwei verschiedene Rollen: Den <i>Driver</i>, der den Code schreibt und sich auf die Details fokussiert und den <i>Navigator</i>, der Tippfehler korrigiert, die Aufgabenstellung im Blick behält und Verbesserungsideen vorschlägt. Kommunikation miteinander ist von entscheidender Bedeutung für erfolgreiches Pair Programming."
|
||||
info_study: "Im Rahmen meiner Masterarbeit möchte ich das Anwenden von Pair Programming in diesem MOOC analysieren. Ich würde mich daher sehr darüber freuen, wenn ihr gemeinsam mit einer anderen Person die Aufgaben bearbeitet und mir anschließend euer Feedback in den Umfragen mitteilt. Bitte beachtet, dass es sich bei dieser Funktion um eine Beta-Version handelt, die bisher noch nicht für alle Nutzer:innen freigeschaltet wurde."
|
||||
info_work_together: "Du hast die Möglichkeit, die Aufgabe '%{exercise_title}' zusammen mit einer anderen Person zu lösen. Dein:e Teampartner:in kann sehen, was du in dieser Aufgabe schreibst und umgekehrt. Beachte dabei, dass anschließend keiner die Zusammenarbeit beenden kann. Für die nächste Aufgabe kannst du dich erneuert entscheiden, ob und mit wem du zusammen arbeiten möchtest."
|
||||
find_programming_partner: Programmierpartner:in finden
|
||||
own_user_id: "Deine Nutzer-ID:"
|
||||
pair_programming_info: Pair Programming Info
|
||||
safari_not_supported: "Safari unterstützt nicht das Einbinden der Seite mit den Nutzer-IDs. <a target='_blank', style='display: contents;' href=%{url}>Bitte klicke hier, um die Liste der Nutzer-IDs zu öffnen.</a>"
|
||||
work_alone: "Du kannst dich einmalig dafür entscheiden, die Aufgabe alleine zu bearbeiten. Anschließend kannst du jedoch nicht mehr in die Partnerarbeit für diese Aufgabe wechseln. <a href=%{path}>Klicke hier, um die Aufgabe im Einzelmodus zu starten.</a>"
|
||||
work_alone: "Alleine arbeiten"
|
||||
work_alone_description: "Du kannst dich einmalig dafür entscheiden, die Aufgabe alleine zu bearbeiten. Anschließend kannst du jedoch nicht mehr in die Partnerarbeit für diese Aufgabe wechseln. <br> <a href=%{path}>Klicke hier, um die Aufgabe im Einzelmodus zu starten.</a>"
|
||||
work_with_a_friend: "Mit einem/einer Freund:in zusammenarbeiten"
|
||||
implement:
|
||||
info_disconnected: Ihre Verbindung zum Server wurde unterbrochen. Bitte überprüfen Sie Ihre Internetverbindung und laden Sie die Seite erneut.
|
||||
external_users:
|
||||
|
@ -233,8 +233,8 @@ en:
|
||||
one: Feedback
|
||||
other: Feedbacks
|
||||
programming_group:
|
||||
one: Programming Group
|
||||
other: Programming Groups
|
||||
one: Programming Pair
|
||||
other: Programming Pairs
|
||||
programming_group_membership:
|
||||
one: Programming Group Membership
|
||||
other: Programming Group Memberships
|
||||
@ -598,15 +598,18 @@ en:
|
||||
close: Close
|
||||
create_programming_pair: Create Programming Pair
|
||||
dont_show_modal_again: "Don't display on this device anymore"
|
||||
enter_partner_id: "Please enter the user ID from the practice partner with whom you want to solve the exercise '%{exercise_title}'. However, note that no one can leave the pair afterward. Hence, your team partner can see what you write in this exercise and vice versa. For the next exercise, you can decide again whether and with whom you want to work together."
|
||||
enter_partner_id: "Do you know a person in the course with whom you would like to solve the task together? Then enter that person's user ID here."
|
||||
find_partner_title: "Find a programming partner for the exercise"
|
||||
find_partner_description: "Copy another user ID from the list below and delete it afterward. If there are no user IDs on the list yet, add your user ID and wait for another user to create a programming group with you. Once a partner invited you to a group, you'll be redirected automatically to start working on this exercise collaboratively. If you decide to work alone, please delete your user ID from the list if you added it before."
|
||||
find_partner_description: "If you don't know a person from the course, you have the possibility to be paired with another person. Then, you will be redirected to the task as soon as another person also wants to solve this task in a team."
|
||||
info_pair_programming: "Pair Programming is a method where two people program together. They alternate between two distinct roles: the <i>Driver</i>, responsible for writing the code and focusing on the details, and the <i>Navigator</i>, tasked with correcting typos, overseeing the task's progress, and offering suggestions for improvement. Effective communication in the pair is crucial for the success of pair programming."
|
||||
info_study: "As part of my master's thesis, I would like to analyze the use of pair programming in this MOOC. Therefore, I would be very happy if you work on the exercises together with another person and then give me your feedback in the surveys. Please note that this feature is a beta version that has not yet been activated for all users."
|
||||
info_work_together: "You have the possibility to solve the task '%{exercise_title}' together with another person. Your team partner can see what you write in this task and vice versa. Note that no one can stop the collaboration afterwards. For the next task you can decide again if and with whom you want to work together."
|
||||
find_programming_partner: Find Programming Partner
|
||||
own_user_id: "Your user ID:"
|
||||
pair_programming_info: Pair Programming Info
|
||||
safari_not_supported: "Safari does not support embedding the user IDs page. <a target='_blank', style='display: contents;' href=%{url}>Please click here to open the list of user IDs.</a>"
|
||||
work_alone: "You can choose once to work on the exercise alone. Afterward, however, you will not be able to switch to work in a pair for this exercise. <a href=%{path}>Click here to get to the exercise in single mode.</a>"
|
||||
work_alone: Work Alone
|
||||
work_alone_description: "You can choose once to work on the exercise alone. Afterward, however, you will not be able to switch to work in a pair for this exercise. <br> <a href=%{path}>Click here to get to the exercise in single mode.</a>"
|
||||
work_with_a_friend: "Work with a friend"
|
||||
implement:
|
||||
info_disconnected: You are disconnected from the server. Please check your internet connection and reload the page.
|
||||
external_users:
|
||||
|
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreatePairProgrammingWaitingUsers < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :pair_programming_waiting_users, id: :uuid do |t|
|
||||
t.references :user, index: true, null: false, polymorphic: true
|
||||
t.references :exercise, index: true, null: false, foreign_key: true
|
||||
t.references :programming_group, index: true, null: true, foreign_key: true
|
||||
t.integer :status, limit: 1, null: false, comment: 'Used as enum in Rails'
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
17
db/schema.rb
17
db/schema.rb
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_09_12_162208) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_09_20_094122) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_trgm"
|
||||
enable_extension "pgcrypto"
|
||||
@ -401,6 +401,19 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_12_162208) do
|
||||
t.index ["user_type", "user_id"], name: "index_pair_programming_exercise_feedbacks_on_user"
|
||||
end
|
||||
|
||||
create_table "pair_programming_waiting_users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.string "user_type", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.bigint "exercise_id", null: false
|
||||
t.bigint "programming_group_id"
|
||||
t.integer "status", limit: 2, null: false, comment: "Used as enum in Rails"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["exercise_id"], name: "index_pair_programming_waiting_users_on_exercise_id"
|
||||
t.index ["programming_group_id"], name: "index_pair_programming_waiting_users_on_programming_group_id"
|
||||
t.index ["user_type", "user_id"], name: "index_pair_programming_waiting_users_on_user"
|
||||
end
|
||||
|
||||
create_table "programming_group_memberships", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.bigint "programming_group_id", null: false
|
||||
t.string "user_type", null: false
|
||||
@ -666,6 +679,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_12_162208) do
|
||||
add_foreign_key "pair_programming_exercise_feedbacks", "programming_groups"
|
||||
add_foreign_key "pair_programming_exercise_feedbacks", "study_groups"
|
||||
add_foreign_key "pair_programming_exercise_feedbacks", "submissions"
|
||||
add_foreign_key "pair_programming_waiting_users", "exercises"
|
||||
add_foreign_key "pair_programming_waiting_users", "programming_groups"
|
||||
add_foreign_key "programming_group_memberships", "programming_groups"
|
||||
add_foreign_key "programming_groups", "exercises"
|
||||
add_foreign_key "remote_evaluation_mappings", "study_groups"
|
||||
|
Reference in New Issue
Block a user